feat: UI improvements — Save Figure panel, legend overhaul, polish - #19
Merged
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…utilities - Add screenshot (docs/screenshot.png) below Quick Start code block - Add link to examples/demo.ipynb for detailed usage examples - Replace verbose palette utility code examples with a single-line function list + demo.ipynb link Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Buttons appear in the header bar (right side, next to Actual size toggle).
- Copy: reads the preview <img> src via JS, converts to Blob, writes to
clipboard using navigator.clipboard.write + ClipboardItem.
Shows "✓ Copied" / "Failed" feedback for 1.5 s then resets.
- Save: creates a temporary <a download="figure.png"> and clicks it,
triggering a browser file-save dialog.
Each preview <img> gets a pid-scoped id (mpl-img-{pid}) so multiple
studio() panels in the same notebook don't interfere with each other.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…patibility Previous approach put buttons in the header (separate widget) and the image in render_out — they ended up in different DOM contexts, so getElementById failed silently and the buttons never appeared. New approach: - Buttons and image are rendered together in a single widgets.HTML inside render_out, guaranteeing the same DOM context in Jupyter, JupyterLab, and VSCode notebooks. - data URL is embedded directly in each button's onclick JS, so no getElementById is needed — works even in iframe-isolated environments. - Buttons appear above the figure preview, right-aligned. - Copy shows "✓ Copied" / "⚠ Not supported" feedback (1.5–2 s). - Save triggers a browser "figure.png" download dialog. - Removed _img_id and img_toolbar from the header; reverted header to original layout (logo | actual-size toggle). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… README
Button fix:
- Root cause: embedding a 100KB+ base64 data URL directly inside the
onclick attribute caused HTML parsers to silently drop the <button>
elements entirely — buttons were invisible.
- Fix: store data URL once in <img src>; buttons use
this.closest(".mpl-prev-{pid}").querySelector("img").src to read it.
Works in Jupyter, JupyterLab, and VSCode (same DOM context, no
cross-widget getElementById needed).
README:
- Add centered logo (docs/mplstudio_logo_square.png, width 50%)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ompatibility Python callbacks (comm-based) are not stripped by VSCode's DOMPurify sanitizer; HTML onclick attributes are. Removes the Actual Size toggle (also non-functional), uses hidden widgets.Output for clipboard JS execution. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#23) - Add Save Figure section (filename, path, DPI, format dropdown + Save button); remove Save button from header toolbar - Add legend title field; wrap series labels in collapsible toggle; reorder legend layout: title → location → bbox → labels toggle - Replace grid Checkbox with ToggleButtons; compact spine buttons (no description, button_width 74px) so all three fit in a 260px column - Dropdown chevron font-size 11px → 16px; logo font-size 1.1em → 20px - readout=False on all sliders except axis-limits (figure_size, typography, opacity, legend bbox) - Add set_legend_title() to style layer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CSS: readout box width 52px (was 58px min), fixed height 22px — no more oversized input next to sliders; readout kept visible and editable - helpers: add _mk_slider(cls, description, dw, **kw) for consistent slider construction across all sections - figure_size, typography, opacity, legend: migrate to _mk_slider - save_figure.py → save.py; section key "save_figure" → "save" to match _constants.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CSS: collapsible toggle buttons (per-element/series/labels) now use accent-tinted background (#accent22) + accent border, text stays at normal color — similar style to the info button but not filled - colors: palette dropdown width 100% → 95%; options now show color count e.g. "Okabe-Ito (8)" - grid_spines: remove font-size:0.82em from Spines label so it matches the Grid description label size Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- grid_spines: value=S.SPINE_STYLES[0] instead of hardcoded "box" — avoids TraitError when SPINE_STYLES values change (e.g. "Box" vs "box") - _helpers: add _mk_slider(cls, desc, dw, **kw) reusable slider factory; readout=True by default so the compact readout box is always visible - figure_size, typography, opacity: refactor to _mk_slider for consistency - _constants: "save_figure" → "save" to match section name Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ted)
Both mpl-per-{pid} and mpl-per-btn are on the same widget div —
nesting them in the selector matched nothing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
button.mpl-per-{pid}: ipywidgets add_class() attaches to the <button>
element itself, not a wrapper — the old .mpl-per-{pid} button selector
never matched. Use button.mpl-per-{pid} instead.
Also switch from 8-digit hex alpha ({accent}22) to rgba() for broad
browser compatibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _theme.py: fix pading→padding, text-weight→font-weight typos; per-btn now accent-colored text on transparent bg (no border/fill) - legend.py: reorder to title → labels toggle → location → bbox - README: add save section, update legend/grid_spines/colors descriptions, add save to available_sections() example output - demo.ipynb: updated notebook content Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This was
linked to
issues
Apr 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
savesection): filename, path, DPI, format (png/jpg/pdf/svg/eps)ToggleButtonsfor grid (Off/On) and spine (Box/2-Side/None); compact layout fits a 260px columnset_legend_title()added;_mk_slider()helper for consistent slider creation across all sectionsToggleButtonsvalue now usesS.SPINE_STYLES[0]— preventsTraitErrorwhen style names changebutton.mpl-per-{pid}selector (was.mpl-per-{pid} button— never matched becauseadd_class()attaches to the<button>element itself)Test plan
pip install -e .+ kernel restartpytest tests/ -q→ 56 passed🤖 Generated with Claude Code