Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c460b14
Initial plan
Copilot Nov 7, 2025
74d3ae4
Add subplot_titles, use_existing_titles, and title parameters to make…
Copilot Nov 7, 2025
8adf354
Apply black formatting to subplots code and tests
Copilot Nov 7, 2025
c339107
Apply blacken-docs formatting to sub-plots.md documentation
Copilot Nov 7, 2025
6292cc4
Fix row calculation for subplot titles to account for reversed grid
Copilot Nov 7, 2025
287266f
Address code review feedback: improve None handling and add title tru…
Copilot Nov 7, 2025
7a5f519
Refactor annotation logic: extract helper function and improve empty …
Copilot Nov 7, 2025
5e6d609
Simplify title check by removing redundant isinstance
Copilot Nov 7, 2025
eaed59c
Refactor subplot titles implementation based on PR feedback
Copilot Nov 7, 2025
10b6052
Address PR feedback: simplify parameter names and remove padding logic
Copilot Nov 7, 2025
a070a6b
tweaks and testing
jnumainville Jan 26, 2026
a3fb367
Merge remote-tracking branch 'origin/main' into 20808_subplot_titles
jnumainville Jan 26, 2026
5010206
add pytz workaround
jnumainville Jan 29, 2026
a361305
attempt build/test fixes
jnumainville Feb 3, 2026
70b5bb4
snapshots update
jnumainville Feb 3, 2026
40d4511
review comments
jnumainville Feb 3, 2026
94c19f2
subplot titles default
jnumainville Feb 9, 2026
790994b
fix internal logic
jnumainville Feb 9, 2026
19583a8
Merge remote-tracking branch 'origin/main' into 20808_subplot_titles
jnumainville Mar 2, 2026
a8ef937
some comments
jnumainville Mar 2, 2026
a720014
comments
jnumainville Mar 2, 2026
dd69a6b
review comments
jnumainville Mar 5, 2026
97398b5
trigger rerun
jnumainville Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

This file was deleted.

Large diffs are not rendered by default.

135 changes: 106 additions & 29 deletions plugins/plotly-express/docs/sub-plots.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,93 @@ Create a series of plots as subplots, all providing unique perspectives on the d

```python order=tipping_plots,tips
import deephaven.plot.express as dx
tips = dx.data.tips() # import a ticking version of the Tips dataset

tips = dx.data.tips() # import a ticking version of the Tips dataset

# create 4 plots from within make_subplots
tipping_plots = dx.make_subplots(
dx.scatter(tips, x="TotalBill", y="Tip", by="Sex",
title="Tip amount by total bill"),
dx.violin(tips, y="TotalBill", by="Day",
title="Total bill distribution by day"),
dx.scatter(
tips, x="TotalBill", y="Tip", by="Sex", title="Tip amount by total bill"
),
dx.violin(tips, y="TotalBill", by="Day", title="Total bill distribution by day"),
dx.pie(
tips
.count_by("Count", by=["Sex", "Smoker"])
tips.count_by("Count", by=["Sex", "Smoker"])
.update_view("SmokerStatus = Smoker == `No` ? `non-smoker` : `smoker`")
.update_view("SmokerLabel = Sex + ` ` + SmokerStatus"),
names="SmokerLabel", values="Count",
title="Total bill by sex and smoking status"),
dx.bar(tips
.view(["TotalBill", "Tip", "Day"])
.avg_by("Day"),
x="Day", y=["TotalBill", "Tip"],
title="Average tip as a fraction of total bill"),
rows=2, cols=2, shared_xaxes=False, shared_yaxes=False
names="SmokerLabel",
values="Count",
title="Total bill by sex and smoking status",
),
dx.bar(
tips.view(["TotalBill", "Tip", "Day"]).avg_by("Day"),
x="Day",
y=["TotalBill", "Tip"],
title="Average tip as a fraction of total bill",
),
rows=2,
cols=2,
shared_xaxes=False,
shared_yaxes=False,
)
```

### Existing Titles as Subplot Titles

By default, titles from the original figures are converted to subplot titles if they exist.

```python order=tipping_plots,lunch_tips,dinner_tips
import deephaven.plot.express as dx

tips = dx.data.tips()

lunch_tips = tips.where("Time = `Lunch`")
dinner_tips = tips.where("Time = `Dinner`")

# Figures with titles
lunch_chart = dx.scatter(lunch_tips, x="TotalBill", y="Tip", title="Lunch Tips")
dinner_chart = dx.scatter(dinner_tips, x="TotalBill", y="Tip", title="Dinner Tips")

# Use existing titles as subplot titles
tipping_plots = dx.make_subplots(
lunch_chart, dinner_chart, cols=2
)
```

### Adding New Subplot Titles

Add titles to individual subplots using the `subplot_titles` parameter. Provide a list or tuple of titles, ordered from left to right, top to bottom.

```python order=tipping_plots,lunch_tips,dinner_tips
import deephaven.plot.express as dx

tips = dx.data.tips()

lunch_tips = tips.where("Time = `Lunch`")
dinner_tips = tips.where("Time = `Dinner`")

# Add titles to subplots
tipping_plots = dx.make_subplots(
dx.scatter(lunch_tips, x="TotalBill", y="Tip"),
dx.scatter(dinner_tips, x="TotalBill", y="Tip"),
cols=2,
subplot_titles=["Lunch Tips", "Dinner Tips"],
)
```

### Adding a Title

Add a title to the combined subplot figure using the `title` parameter.

```python order=tipping_plots,tips
import deephaven.plot.express as dx

tips = dx.data.tips()

tipping_plots = dx.make_subplots(
dx.scatter(tips, x="TotalBill", y="Tip", by="Day"),
dx.histogram(tips, x="TotalBill"),
cols=2,
title="Tipping Analysis",
)
```

Expand All @@ -45,7 +111,8 @@ When one axis is adjusted, all axes are adjusted to match.

```python order=tipping_plots,lunch_tips,dinner_tips
import deephaven.plot.express as dx
tips = dx.data.tips() # import a ticking version of the Tips dataset

tips = dx.data.tips() # import a ticking version of the Tips dataset

# filter the tips dataset for separate lunch and dinner charts
lunch_tips = tips.where("Time = `Lunch`")
Expand All @@ -55,7 +122,9 @@ dinner_tips = tips.where("Time = `Dinner`")
tipping_plots = dx.make_subplots(
dx.scatter(lunch_tips, x="TotalBill", y="Tip", labels={"Tip": "Lunch Tips"}),
dx.scatter(dinner_tips, x="TotalBill", y="Tip", labels={"Tip": "Dinner Tips"}),
rows=2, shared_yaxes="all", shared_xaxes="all"
rows=2,
shared_yaxes="all",
shared_xaxes="all",
)
```

Expand All @@ -66,7 +135,8 @@ When one y-axis is adjusted, all axes along the same row are adjusted to match.

```python order=tipping_plots,lunch_tips,dinner_tips
import deephaven.plot.express as dx
tips = dx.data.tips() # import a ticking version of the Tips dataset

tips = dx.data.tips() # import a ticking version of the Tips dataset

# filter the tips dataset for separate lunch and dinner charts
lunch_tips = tips.where("Time = `Lunch`")
Expand All @@ -75,16 +145,18 @@ dinner_tips = tips.where("Time = `Dinner`")
# create chart that shares y axes along the row
tipping_plots = dx.make_subplots(
dx.scatter(lunch_tips, x="TotalBill", y="Tip", labels={"Tip": "Lunch Tips"}),
dx.scatter(dinner_tips, x="TotalBill", y="Tip", labels={"Tip": "Dinner Tips"}),
cols=2, shared_yaxes=True
dx.scatter(dinner_tips, x="TotalBill", y="Tip", labels={"Tip": "Dinner Tips"}),
cols=2,
shared_yaxes=True,
)
```

To share the y axes along the same column, set `shared_yaxes` to `"columns"`.

```python order=tipping_plots,lunch_tips,dinner_tips
import deephaven.plot.express as dx
tips = dx.data.tips() # import a ticking version of the Tips dataset

tips = dx.data.tips() # import a ticking version of the Tips dataset

# filter the tips dataset for separate lunch and dinner charts
lunch_tips = tips.where("Time = `Lunch`")
Expand All @@ -93,8 +165,9 @@ dinner_tips = tips.where("Time = `Dinner`")
# create chart that shares y axes along the column
tipping_plots = dx.make_subplots(
dx.scatter(lunch_tips, x="TotalBill", y="Tip", labels={"Tip": "Lunch Tips"}),
dx.scatter(dinner_tips, x="TotalBill", y="Tip", labels={"Tip": "Dinner Tips"}),
rows=2, shared_yaxes="columns"
dx.scatter(dinner_tips, x="TotalBill", y="Tip", labels={"Tip": "Dinner Tips"}),
rows=2,
shared_yaxes="columns",
)
```

Expand All @@ -105,7 +178,8 @@ When one x-axis is adjusted, all axes along the same column are adjusted to matc

```python order=tipping_plots,lunch_tips,dinner_tips
import deephaven.plot.express as dx
tips = dx.data.tips() # import a ticking version of the Tips dataset

tips = dx.data.tips() # import a ticking version of the Tips dataset

# filter the tips dataset for separate lunch and dinner charts
lunch_tips = tips.where("Time = `Lunch`")
Expand All @@ -114,16 +188,18 @@ dinner_tips = tips.where("Time = `Dinner`")
# create chart that shares x axes along the column
tipping_plots = dx.make_subplots(
dx.scatter(lunch_tips, x="TotalBill", y="Tip", labels={"Tip": "Lunch Tips"}),
dx.scatter(dinner_tips, x="TotalBill", y="Tip", labels={"Tip": "Dinner Tips"}),
rows=2, shared_xaxes=True
dx.scatter(dinner_tips, x="TotalBill", y="Tip", labels={"Tip": "Dinner Tips"}),
rows=2,
shared_xaxes=True,
)
```

To share the x axes along the same column, set `shared_yaxes` to `"columns"`.

```python order=tipping_plots,lunch_tips,dinner_tips
import deephaven.plot.express as dx
tips = dx.data.tips() # import a ticking version of the Tips dataset

tips = dx.data.tips() # import a ticking version of the Tips dataset

# filter the tips dataset for separate lunch and dinner charts
lunch_tips = tips.where("Time = `Lunch`")
Expand All @@ -132,8 +208,9 @@ dinner_tips = tips.where("Time = `Dinner`")
# create chart that shares x axes along the row
tipping_plots = dx.make_subplots(
dx.scatter(lunch_tips, x="TotalBill", y="Tip", labels={"Tip": "Lunch Tips"}),
dx.scatter(dinner_tips, x="TotalBill", y="Tip", labels={"Tip": "Dinner Tips"}),
cols=2, shared_xaxes="rows"
dx.scatter(dinner_tips, x="TotalBill", y="Tip", labels={"Tip": "Dinner Tips"}),
cols=2,
shared_xaxes="rows",
)
```

Expand Down
25 changes: 25 additions & 0 deletions plugins/plotly-express/src/deephaven/plot/express/plots/_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,8 @@ def atomic_layer(
specs: list[LayerSpecDict] | None = None,
unsafe_update_figure: Callable = default_callback,
remove_legend_title: bool = False,
subplot_annotations: list[dict] | None = None,
title: str | None = None,
) -> DeephavenFigure:
"""
Layers the provided figures. This is an atomic version of layer, so the
Expand All @@ -460,6 +462,11 @@ def atomic_layer(
should be kept, but is necessary for other layering and subplotting as
they may not use the same plot by (and similar) columns, so the legend
title would be incorrect.
subplot_annotations:
List of annotation dictionaries to add to the layout for subplot titles.
title:
Title to set for the figure. If an empty string, no overall title is shown.
If None, leaves the title as is.
Comment thread
mattrunyon marked this conversation as resolved.

Returns:
The layered chart
Expand Down Expand Up @@ -527,6 +534,19 @@ def atomic_layer(
if remove_legend_title:
new_fig.update_layout(legend_title_text=None)

# Add subplot annotations if provided
if subplot_annotations:
existing_annotations = (
list(new_fig.layout.annotations) if new_fig.layout.annotations else []
)
new_fig.update_layout(annotations=existing_annotations + subplot_annotations)

# Add overall title if provided
if isinstance(title, str) and title.strip() == "":
new_fig.update_layout(title=None)
elif isinstance(title, str):
new_fig.update_layout(title=title)

update_wrapper = partial(unsafe_figure_update_wrapper, unsafe_update_figure)

return update_wrapper(
Expand All @@ -546,6 +566,7 @@ def layer(
which_layout: int | None = None,
specs: list[LayerSpecDict] | None = None,
unsafe_update_figure: Callable = default_callback,
title: str | None = None,
) -> DeephavenFigure:
"""Layers the provided figures. Be default, the layouts are sequentially
applied, so the layouts of later figures will override the layouts of early
Expand All @@ -571,6 +592,9 @@ def layer(
Used to add any custom changes to the underlying plotly figure. Note that
the existing data traces should not be removed. This may lead to unexpected
behavior if traces are modified in a way that break data mappings.
title:
Overall title to set for the figure.
If an empty string, no overall title is shown.

Returns:
The layered chart
Expand All @@ -588,6 +612,7 @@ def layer(
# remove the legend title as it is likely incorrect
remove_legend_title=True,
unsafe_update_figure=unsafe_update_figure,
title=title,
)

exec_ctx = make_user_exec_ctx()
Expand Down
Loading
Loading