Skip to content

Commit eee6efc

Browse files
authored
Merge pull request #5784 from breken-ai/fix/write-images-layout-size
Fix pio.write_images ignoring layout width and height
2 parents ad0d0bb + 67b1dd8 commit eee6efc

3 files changed

Lines changed: 64 additions & 31 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2222
- Fix `px.sunburst`, `px.treemap` and `px.icicle` listing sectors in a different order on every run when `path` is used with a Polars DataFrame; sectors now follow their order of first appearance for all dataframe backends [[#5766](https://github.com/plotly/plotly.py/pull/5766)], with thanks to @Irahan2 for the contribution!
2323
- Fix `mpl_to_plotly` conversion of matplotlib contour lines and line collections: close contour rings ending with `Z` codes, support dash styles, convert date x-axes, separate disjoint subpaths with `None` separators, hide line collection traces from the legend by default, and group consecutive same-style lines into single traces [[#5770](https://github.com/plotly/plotly.py/pull/5770)], with thanks to @robertoffmoura for the contribution!
2424
- Fix serialization of box/lasso select data for figures with multiple subplots [[#5710](https://github.com/plotly/plotly.py/pull/5710)], with thanks to @flpi-sun for the contribution!
25+
- Fix `plotly.io.write_images` ignoring `layout.width`/`layout.height` (and the template's) and exporting every figure at the default 700x500 size; it now uses the same width/height priority as `write_image` and `to_image` [[#5784](https://github.com/plotly/plotly.py/pull/5784)], with thanks to @breken-ai for the contribution!
2526

2627
## [7.1.0] - 2026-09-15
2728

‎plotly/io/_kaleido.py‎

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,31 @@ def infer_format(path: Union[Path, None], format: Union[str, None]) -> Union[str
9090
return format
9191

9292

93+
def _resolve_width_height(
94+
fig_dict: dict, width: Union[int, None], height: Union[int, None]
95+
) -> tuple:
96+
"""
97+
Return the image width and height to request from Kaleido, in priority order:
98+
the `width`/`height` arguments, then `layout.width`/`layout.height`, then the
99+
template's `layout.width`/`layout.height`, then the plotly.io defaults.
100+
"""
101+
layout = fig_dict.get("layout", {})
102+
template_layout = layout.get("template", {}).get("layout", {})
103+
width = (
104+
width
105+
or layout.get("width")
106+
or template_layout.get("width")
107+
or defaults.default_width
108+
)
109+
height = (
110+
height
111+
or layout.get("height")
112+
or template_layout.get("height")
113+
or defaults.default_height
114+
)
115+
return width, height
116+
117+
93118
def to_image(
94119
fig: Union[dict, plotly.graph_objects.Figure],
95120
format: Union[str, None] = None,
@@ -163,24 +188,7 @@ def to_image(
163188
if defaults.headers:
164189
kopts["headers"] = defaults.headers
165190

166-
width = (
167-
width
168-
or fig_dict.get("layout", {}).get("width")
169-
or fig_dict.get("layout", {})
170-
.get("template", {})
171-
.get("layout", {})
172-
.get("width")
173-
or defaults.default_width
174-
)
175-
height = (
176-
height
177-
or fig_dict.get("layout", {}).get("height")
178-
or fig_dict.get("layout", {})
179-
.get("template", {})
180-
.get("layout", {})
181-
.get("height")
182-
or defaults.default_height
183-
)
191+
width, height = _resolve_width_height(fig_dict, width, height)
184192

185193
img_bytes = kaleido.calc_fig_sync(
186194
fig_dict,
@@ -405,20 +413,23 @@ def write_images(
405413
# We call infer_format() here rather than above so that the `file` argument
406414
# has already been cast to a Path object.
407415
# Also insert defaults for any missing arguments as needed
408-
kaleido_specs = [
409-
dict(
410-
fig=d["fig"],
411-
path=d["file"],
412-
opts=dict(
413-
format=infer_format(d["file"], d["format"]) or defaults.default_format,
414-
width=d["width"] or defaults.default_width,
415-
height=d["height"] or defaults.default_height,
416-
scale=d["scale"] or defaults.default_scale,
417-
),
418-
topojson=defaults.topojson,
416+
kaleido_specs = []
417+
for d in arg_dicts:
418+
width, height = _resolve_width_height(d["fig"], d["width"], d["height"])
419+
kaleido_specs.append(
420+
dict(
421+
fig=d["fig"],
422+
path=d["file"],
423+
opts=dict(
424+
format=infer_format(d["file"], d["format"])
425+
or defaults.default_format,
426+
width=width,
427+
height=height,
428+
scale=d["scale"] or defaults.default_scale,
429+
),
430+
topojson=defaults.topojson,
431+
)
419432
)
420-
for d in arg_dicts
421-
]
422433

423434
from kaleido.errors import ChromeNotFoundError
424435

‎tests/test_optional/test_kaleido/test_kaleido.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,3 +335,24 @@ def test_width_height_priority():
335335
assert height == pio.defaults.default_height, (
336336
"Default height should be used when no layout or argument"
337337
)
338+
339+
340+
def test_write_images_width_height_priority(tmp_path):
341+
"""write_images uses the same width/height priority as to_image:
342+
arguments > layout.width/height > defaults."""
343+
paths = [tmp_path / f"fig{i}.svg" for i in range(3)]
344+
pio.write_images(
345+
[
346+
create_figure(width=800, height=600),
347+
create_figure(width=800, height=600),
348+
create_figure(),
349+
],
350+
paths,
351+
width=[None, 1200, None],
352+
)
353+
dimensions = [parse_svg_dimensions(path.read_bytes()) for path in paths]
354+
assert dimensions[0] == (800, 600), "Layout dimensions should be used"
355+
assert dimensions[1] == (1200, 600), (
356+
"Width argument should override layout, height should use layout"
357+
)
358+
assert dimensions[2] == (pio.defaults.default_width, pio.defaults.default_height)

0 commit comments

Comments
 (0)