Skip to content

Commit c6686a4

Browse files
Merge branch 'main' into axis-match-bug
2 parents 7ac1e27 + b5b1062 commit c6686a4

6 files changed

Lines changed: 103 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ This project adheres to [Semantic Versioning](http://semver.org/).
99
- Change: change the keyword argument for the trace, so that when the graph is initialized, it uses the correct axis instead of the autogenerated one
1010
- Note: The program generates a unique axis label for each subgraph, and then overwrites the label (under this fix)
1111

12+
### Added
13+
- Add optional `font` parameter for `make_subplots` [[#5393](https://github.com/plotly/plotly.py/pull/5393)]
14+
1215
### Fixed
1316
- Fix issue where user-specified `color_continuous_scale` was ignored when template had `autocolorscale=True` [[#5439](https://github.com/plotly/plotly.py/pull/5439)], with thanks to @antonymilne for the contribution!
17+
- Use presence of `COLAB_NOTEBOOK_ID` env var to enable Colab renderer instead of testing import of `google.colab` [[#5473](https://github.com/plotly/plotly.py/pull/5473)], with thanks to @kevineger for the contribution!
1418
- Update tests to be compatible with numpy 2.4 [[#5522](https://github.com/plotly/plotly.py/pull/5522)], with thanks to @thunze for the contribution!
1519

1620
### Updated

doc/python/templates.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ for template in ["plotly", "plotly_white", "plotly_dark", "ggplot2", "seaborn",
9393
fig.show()
9494
```
9595

96-
#### Specifying a default themes
96+
#### Specifying a default theme
9797

9898
If a theme is not provided to a Plotly Express function or to a graph object figure, then the default theme is used. The default theme starts out as `"plotly"`, but it can be changed by setting the `plotly.io.templates.default` property to the name of a registered theme.
9999

@@ -475,4 +475,4 @@ It may be useful to examine the contents and structure of the built-in templates
475475
import plotly.io as pio
476476
plotly_template = pio.templates["plotly"]
477477
plotly_template.layout
478-
```
478+
```

plotly/_subplots.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def make_subplots(
7474
x_title=None,
7575
y_title=None,
7676
figure=None,
77+
font=None,
7778
**kwargs,
7879
):
7980
"""
@@ -108,7 +109,7 @@ def make_subplots(
108109
109110
- 'top-left': Subplots are numbered with (1, 1) in the top
110111
left corner
111-
- 'bottom-left': Subplots are numbererd with (1, 1) in the bottom
112+
- 'bottom-left': Subplots are numbered with (1, 1) in the bottom
112113
left corner
113114
114115
print_grid: boolean (default True):
@@ -182,7 +183,7 @@ def make_subplots(
182183
for this subplot to span.
183184
* l (float, default 0.0): padding left of cell
184185
* r (float, default 0.0): padding right of cell
185-
* t (float, default 0.0): padding right of cell
186+
* t (float, default 0.0): padding top of cell
186187
* b (float, default 0.0): padding bottom of cell
187188
188189
- Note: Use 'horizontal_spacing' and 'vertical_spacing' to adjust
@@ -251,6 +252,9 @@ def make_subplots(
251252
layout of this figure and this figure will be returned. If the figure
252253
already contains axes, they will be overwritten.
253254
255+
font: dict (default None)
256+
Font used by any title of the subplots.
257+
254258
Examples
255259
--------
256260
@@ -821,7 +825,7 @@ def _check_hv_spacing(dimsize, spacing, name, dimvarname, dimname):
821825

822826
# Add subplot titles
823827
plot_title_annotations = _build_subplot_title_annotations(
824-
subplot_titles, list_of_domains
828+
subplot_titles, list_of_domains, font=font
825829
)
826830

827831
layout["annotations"] = plot_title_annotations
@@ -842,7 +846,7 @@ def _check_hv_spacing(dimsize, spacing, name, dimvarname, dimname):
842846

843847
# Add subplot titles
844848
column_title_annotations = _build_subplot_title_annotations(
845-
column_titles, domains_list
849+
column_titles, domains_list, font=font
846850
)
847851

848852
layout["annotations"] += tuple(column_title_annotations)
@@ -856,7 +860,7 @@ def _check_hv_spacing(dimsize, spacing, name, dimvarname, dimname):
856860

857861
# Add subplot titles
858862
column_title_annotations = _build_subplot_title_annotations(
859-
row_titles, domains_list, title_edge="right"
863+
row_titles, domains_list, title_edge="right", font=font
860864
)
861865

862866
layout["annotations"] += tuple(column_title_annotations)
@@ -866,7 +870,7 @@ def _check_hv_spacing(dimsize, spacing, name, dimvarname, dimname):
866870

867871
# Add subplot titles
868872
column_title_annotations = _build_subplot_title_annotations(
869-
[x_title], domains_list, title_edge="bottom", offset=30
873+
[x_title], domains_list, title_edge="bottom", offset=30, font=font
870874
)
871875

872876
layout["annotations"] += tuple(column_title_annotations)
@@ -876,7 +880,7 @@ def _check_hv_spacing(dimsize, spacing, name, dimvarname, dimname):
876880

877881
# Add subplot titles
878882
column_title_annotations = _build_subplot_title_annotations(
879-
[y_title], domains_list, title_edge="left", offset=40
883+
[y_title], domains_list, title_edge="left", offset=40, font=font
880884
)
881885

882886
layout["annotations"] += tuple(column_title_annotations)
@@ -1266,7 +1270,7 @@ def _get_cartesian_label(x_or_y, r, c, cnt):
12661270

12671271

12681272
def _build_subplot_title_annotations(
1269-
subplot_titles, list_of_domains, title_edge="top", offset=0
1273+
subplot_titles, list_of_domains, title_edge="top", offset=0, font=None
12701274
):
12711275
# If shared_axes is False (default) use list_of_domains
12721276
# This is used for insets and irregular layouts
@@ -1276,6 +1280,10 @@ def _build_subplot_title_annotations(
12761280
subtitle_pos_x = []
12771281
subtitle_pos_y = []
12781282

1283+
# If no font size is provided, use this fallback
1284+
if font is None:
1285+
font = dict(size=16)
1286+
12791287
if title_edge == "top":
12801288
text_angle = 0
12811289
xanchor = "center"
@@ -1339,7 +1347,7 @@ def _build_subplot_title_annotations(
13391347
"yref": "paper",
13401348
"text": subplot_titles[index],
13411349
"showarrow": False,
1342-
"font": dict(size=16),
1350+
"font": font,
13431351
"xanchor": xanchor,
13441352
"yanchor": yanchor,
13451353
}

plotly/io/_renderers.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -488,13 +488,10 @@ def show(fig, renderer=None, validate=True, **kwargs):
488488
elif ipython and ipython.get_ipython():
489489
# Try to detect environment so that we can enable a useful
490490
# default renderer
491-
if not default_renderer:
492-
try:
493-
import google.colab # noqa: F401
494491

495-
default_renderer = "colab"
496-
except ImportError:
497-
pass
492+
# Check if we're running in a Colab web notebook
493+
if not default_renderer and "COLAB_NOTEBOOK_ID" in os.environ:
494+
default_renderer = "colab"
498495

499496
# Check if we're running in a Kaggle notebook
500497
if not default_renderer and os.path.exists("/kaggle/input"):

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ include = [
120120
"js/install.json", # used by Jupyter extension
121121
]
122122

123+
[tool.hatch.build.targets.wheel]
124+
# Prevent js/ directory from being installed as top-level package
125+
exclude = ["js"]
126+
123127
[tool.hatch.build.targets.wheel.shared-data]
124128
# Specify files from this package which will be copied to the user's system on install
125129
# This is how the jupyterlab extension gets installed

tests/test_io/test_renderers.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import os
23
import threading
34
import time
45

@@ -418,3 +419,75 @@ def test_missing_webbrowser_methods(fig1):
418419
finally:
419420
# restore everything after this test
420421
webbrowser.get = removed_webbrowser_get_method
422+
423+
424+
def test_colab_renderer_when_env_var_is_set():
425+
"""
426+
When COLAB_NOTEBOOK_ID is present the default renderer should be 'colab'.
427+
"""
428+
import importlib
429+
import plotly.io._renderers as _renderers_mod
430+
from plotly import optional_imports
431+
432+
fake_ipython = MagicMock()
433+
original_get_module = optional_imports.get_module
434+
435+
def patched_get_module(name, *args, **kwargs):
436+
if name in ("IPython", "IPython.display"):
437+
return fake_ipython
438+
return original_get_module(name, *args, **kwargs)
439+
440+
original_default = pio.renderers.default
441+
try:
442+
with mock.patch.dict(os.environ, {"COLAB_NOTEBOOK_ID": "fake-id"}, clear=True):
443+
with mock.patch.object(
444+
optional_imports, "get_module", side_effect=patched_get_module
445+
):
446+
importlib.reload(_renderers_mod)
447+
assert _renderers_mod.renderers.default == "colab"
448+
finally:
449+
importlib.reload(_renderers_mod)
450+
pio.renderers.default = original_default
451+
452+
453+
def test_colab_renderer_when_env_var_is_absent():
454+
"""
455+
Without COLAB_NOTEBOOK_ID the default renderer must not be 'colab',
456+
even when ``google.colab`` is importable.
457+
458+
Regression test for https://github.com/plotly/plotly.py/pull/5473.
459+
"""
460+
import sys
461+
import types
462+
import importlib
463+
import plotly.io._renderers as _renderers_mod
464+
from plotly import optional_imports
465+
466+
fake_ipython = MagicMock()
467+
original_get_module = optional_imports.get_module
468+
469+
def patched_get_module(name, *args, **kwargs):
470+
if name in ("IPython", "IPython.display"):
471+
return fake_ipython
472+
return original_get_module(name, *args, **kwargs)
473+
474+
# Make google.colab importable so the old ``import google.colab``
475+
# approach would have chosen the colab renderer here.
476+
fake_google = types.ModuleType("google")
477+
fake_google_colab = types.ModuleType("google.colab")
478+
479+
original_default = pio.renderers.default
480+
try:
481+
with mock.patch.dict(os.environ, {}, clear=True):
482+
with mock.patch.dict(
483+
sys.modules,
484+
{"google": fake_google, "google.colab": fake_google_colab},
485+
):
486+
with mock.patch.object(
487+
optional_imports, "get_module", side_effect=patched_get_module
488+
):
489+
importlib.reload(_renderers_mod)
490+
assert _renderers_mod.renderers.default != "colab"
491+
finally:
492+
importlib.reload(_renderers_mod)
493+
pio.renderers.default = original_default

0 commit comments

Comments
 (0)