Skip to content

Honor width/height/scale when saving plots (#91) - #4

Open
PabloBotin wants to merge 1 commit into
mainfrom
issue-91-save-interface
Open

Honor width/height/scale when saving plots (#91)#4
PabloBotin wants to merge 1 commit into
mainfrom
issue-91-save-interface

Conversation

@PabloBotin

Copy link
Copy Markdown
Owner

Closes Sienna-Platform#91.

Addresses kdayday's comment on the issue: "width/height/scale not available for resizing PNG/PDF/SVG at save time."

Three defects on main

  1. CairoMakie's save_plot accepted kwargs... and discarded all of them — the body was CairoMakie.save(filename, plot.figure). No size, no DPI control.
  2. The docstring advertised width, height, scale, but SUPPORTED_PLOTLY_SAVE_KWARGS allowlisted default_width/default_height/autoplay/post_script/full_html/animation_opts. All three documented names were silently filtered out — a live documentation lie.
  3. A latent crash: show(io, MIME("text/html"), plot; save_kwargs...). PlotlyLight's show accepts no keyword arguments, so any allowlisted name raised a MethodError. Latent only because nothing ever passed one.

Changes

  • CairoMakiewidth/height set the saved scene size; scale multiplies px_per_unit (png) and pt_per_unit (pdf/svg). Only one supplied dimension is completed from the figure's current size. Validation via multiple dispatch; bad input raises ArgumentError.
  • PlotlyLightwidth/height set the Plotly layout; scale warns and is ignored, since HTML has no rendering scale. SUPPORTED_PLOTLY_SAVE_KWARGS deleted and show called with no keywords, fixing defect 3. The .html extension check is now case-insensitive, matching the CairoMakie side.
  • src/call_plots.jl — docstring corrected to describe what the code does, including that width/height are Makie scene units on CairoMakie (so width = 800 writes a 1600 px PNG at the default px_per_unit = 2).

scale = 1 reproduces the previous output byte-for-byte, and the no-keyword path is unchanged.

The trap this had to avoid

Makie.save(...; size = …) calls resize!(scene, size) and never restores it — and it resizes before its own try, then rethrows without unwinding. Passing a size straight through would have permanently resized the caller's live Figure; every later display and every later save would inherit the new size. Both backends therefore snapshot and restore in a finally, so the plot object is unchanged even when the write throws.

Reading a PlotlyLight layout key also had to be done carefully: EasyConfig's getproperty auto-vivifies on read, so plot.layout.width on an unset layout inserts an empty Config() that serializes as "width":{}. Every read is haskey-guarded, and keys absent beforehand are deleted on restore.

Verification

Full suite 65/65, zero Error log events; formatter clean.

Directly exercised against the real API — 21/21:

check result
default PNG (1280×720 figure, px_per_unit=2) 2560×1440
width=800, height=600 1600×1200
+ scale=2 3200×2400
width=800 only 1600×1440 (height from figure)
figure scene size after sized save unchanged
figure scene size after save that throws restored
plotly layout after save / after throwing save restored, no "width":{} artifact
full_html = true no longer raises MethodError
.html on CairoMakie / width=0 / scale=-1 ArgumentError
plot-time size passed to save_plot inert (reserved for Sienna-Platform#77)

scale was confirmed to reach vector formats too: SVG width="400"width="1200", PDF MediaBox 300pt900pt at scale=3.

Known limitations

  • scale overrides a custom Makie theme. The 2.0/0.75 multipliers are Makie's documented defaults, hardcoded rather than read from CURRENT_DEFAULT_THEME (an undocumented internal). With CairoMakie.activate!(px_per_unit = 1.0), the no-scale path respects the theme but passing scale re-imposes the default. Documented in the docstring rather than handled.
  • width = true is accepted (Bool <: Integer) and writes a 2 px image. Absurd input, harmless outcome.
  • Vector-format scale is only smoke-tested in CI (filesize > 0); the dimension change was verified manually, not pinned.
  • Verified on macOS only.
  • PlotlyLight HTML output remains a <div> fragment rather than a standalone page (pre-existing). Consequently full_html = true now silently does nothing instead of crashing — strictly better, but the keyword was never functional.

Adjacent, not fixed here

_plot_demand! reads :save without popping it and forwards the full kwargs onward, so plot_demand(...; save = dir) appears to write the same file twice. Pre-existing and unrelated; the restore logic makes it harmless.

…rm#91)

The docstring advertised `width`, `height`, and `scale` as `save_plot`
keywords, but neither backend implemented them. CairoMakie's `save_plot`
accepted `kwargs...` and discarded every one; PlotlyLight filtered them
against an allowlist of PlotlyBase names it could not forward anyway.

Implement all three. On CairoMakie `width`/`height` set the saved scene size
and `scale` multiplies `px_per_unit` (png) and `pt_per_unit` (pdf/svg), so
`scale = 1` reproduces the previous output exactly. On PlotlyLight
`width`/`height` set the Plotly layout and `scale` warns and is ignored,
since HTML has no rendering scale.

Both backends restore the plot object afterwards, including when the write
throws: `Makie.save` resizes the scene and never restores it, so passing a
size through would otherwise permanently resize the caller's figure.

Also drop `SUPPORTED_PLOTLY_SAVE_KWARGS` and stop forwarding keywords to
`show(io, MIME("text/html"), plot)`, which accepts none — any allowlisted
keyword raised a `MethodError`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve interface for saving plots

1 participant