Skip to content

feat: add Matplotlib SVG output support#8312

Open
daizutabi wants to merge 3 commits intomarimo-team:mainfrom
daizutabi:matplotlib-svg-output
Open

feat: add Matplotlib SVG output support#8312
daizutabi wants to merge 3 commits intomarimo-team:mainfrom
daizutabi:matplotlib-svg-output

Conversation

@daizutabi
Copy link
Contributor

@daizutabi daizutabi commented Feb 13, 2026

📝 Summary

This PR introduces support for rendering Matplotlib figures as SVG output within marimo notebooks. Previously, Matplotlib figures were primarily rendered as PNGs, even when users might prefer SVG for its scalability.

The core change involves enhancing marimo's Matplotlib backend to respect user preferences for rcParams["savefig.format"].

Closes #6287

🔍 Description of Changes

  • SVG Output Logic: Modified marimo/_output/mpl.py to add logic that allows Matplotlib figures to be rendered as SVG. When matplotlib.rcParams["savefig.format"] is set to "svg", the _render_figure_mimebundle function now directly returns the figure as a SVG string. The existing PNG rendering path is used as a fallback.
  • Docstring Update: The docstring for _render_figure_mimebundle in marimo/_output/mpl.py has been updated to accurately reflect the dual output capabilities (SVG or PNG) and their respective return types.
  • Test Coverage: A new unit test, test_matplotlib_svg_rendering, has been added to tests/_output/formatters/test_matplotlib.py. This test verifies that Matplotlib figures are correctly output as SVG when savefig.format is configured appropriately.

Limitation

This PR enhances Matplotlib SVG output handling by ensuring the MIME type is correctly set to image/svg+xml. This is the expected standard for standalone SVG content, improving compatibility, particularly for ipynb exports.

However, a limitation has been identified regarding the display of text (such as ticks and labels) in SVGs generated with matplotlib.rcParams["svg.fonttype"] = "path" (Matplotlib's default, which uses <defs> and <use> elements for text glyphs). The frontend currently skips or fails to render these <use> elements.

Workaround

Users can mitigate this by setting matplotlib.rcParams["svg.fonttype"] = "none" in their Python code. This instructs Matplotlib to embed text directly as <text> elements rather than <use> references to paths, which the frontend can then process correctly.

Screenshots

PNG

a-1

SVG ('svg.fonttype' = 'none')

a-2

SVG ('svg.fonttype' = 'path')

a-3

📋 Checklist

  • I have read the contributor guidelines.
  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Tests have been added for the changes made.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Pull request title is a good summary of the changes - it will be used in the release notes.

@vercel
Copy link

vercel bot commented Feb 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Feb 15, 2026 4:03am

Request Review

@dmadisetti dmadisetti added the enhancement New feature or request label Feb 13, 2026
@daizutabi daizutabi marked this pull request as draft February 14, 2026 00:16
Copy link
Contributor

@akshayka akshayka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default I don't get text elements, at least on my machine.

I saw the noted limitation in the PR description. Do you have an idea of what it would take to support text? We will certainly be asked to support it if this is merged.

Image

@daizutabi
Copy link
Contributor Author

When Matplotlib figures are rendered as SVGs, some elements (texts, scatter points) do not appear in the rendered output within the marimo frontend.

Root Cause

The marimo frontend performs sanitization of SVG content to mitigate XSS vulnerabilities. This sanitization process removes certain SVG elements that are essential for displaying these graphical components. For instance, Matplotlib generates SVGs where text glyphs are defined once in a <defs> section using paths and then referenced multiple times throughout the SVG using <use> tags. The removal of these <use> tags by the sanitizer prevents text from being displayed. It is likely that plt.scatter points also rely on <use> elements that are removed by this sanitization. This issue is tracked in #8316.

Proposed Solutions:

  1. Disable Sanitization for image/svg+xml in Frontend (Simplest approach)

    • Description: Directly modify frontend/src/components/editor/Output.tsx to set alwaysSanitizeHtml: false for image/svg+xml. This would effectively disable all sanitization for any SVG content displayed. The text/html output type is also handled without sanitization in a similar manner.
    • Pros:
      • The easiest implementation, requiring minimal code changes.
      • Directly solves the problem by preventing the removal of SVG elements.
    • Cons:
      • Security Concern: Disabling sanitization for all image/svg+xml outputs could potentially allow arbitrary JavaScript execution or the injection of malicious content from untrusted SVG sources. I cannot definitively determine the full security implications of this approach.
  2. Allow specific SVG tags/attributes in DOMPurify (Intermediate approach)

    • Description: Modify frontend/src/plugins/core/sanitize.ts to explicitly allow <use> tags and xlink:href/href attributes within the DOMPurify configuration (ADD_TAGS and ADD_ATTR). This approach specifically targets the missing elements for Matplotlib SVGs without completely disabling sanitization.
    • Pros:
      • More secure than completely disabling sanitization for all SVGs, as it only allows specific, necessary elements and attributes.
      • Relatively simple to implement, involving a small change to the DOMPurify configuration.
      • Likely resolves the issue for Matplotlib SVGs.
    • Cons:
      • Still involves modifying the sanitization rules, which requires careful security review to ensure no new vulnerabilities are introduced.
      • May need to be extended if other Matplotlib (or general SVG) features also rely on currently forbidden tags/attributes.
  3. Conditional Frontend Sanitization Bypass (using metadata)

    • Description: The backend (marimo/_output/mpl.py) would be modified to include a {"trusted": True} flag within the mimebundle's metadata for image/svg+xml outputs generated by Matplotlib. The frontend (frontend/src/components/editor/Output.tsx) would then check for this trusted flag and, if present, would skip the sanitization specifically for that SVG.
    • Pros:
      • Keeps the general SVG sanitization strict, maintaining security for untrusted SVG sources.
      • Leverages the existing mimebundle metadata mechanism for communication between backend and frontend.
      • Relatively straightforward to implement, mainly involving passing a flag.
    • Cons:
      • Introduces a concept of "trusted" content which, while localized to backend-generated Matplotlib SVGs, still requires careful consideration of the trust boundary.

Which solution would you prefer to proceed with?

@daizutabi
Copy link
Contributor Author

Proposed Solutions (Addendum):

  1. Base64 Encoded SVG Data URI via ImageOutput

    • Description: The backend (marimo/_output/mpl.py) would convert the generated SVG into a Base64 encoded data URI (e.g., data:image/svg+xml;base64,...). The frontend (frontend/src/components/editor/Output.tsx) would then render this data URI directly using the existing ImageOutput component, treating the SVG as a standard image.

    • Pros:

      • Leverages existing image rendering infrastructure (ImageOutput).
      • Completely bypasses DOMPurify's SVG sanitization on the frontend, as it's treated as an <img> source.
      • Allow drag-and-drop from browser (like PNGs).
    • Cons:

      • Interactivity & Styling Limitations: SVGs rendered via <img> tags are generally static. They lose interactive capabilities.

Personal Opinion: Based on the balance of simplicity, direct problem solving, and preserving user experience (especially the drag-and-drop feature), Solution 4 (Base64 Encoded SVG Data URI via ImageOutput) seems to be the most appealing option.

@daizutabi
Copy link
Contributor Author

Update: I implemented "Solution 4".

Changes made:

  • marimo/_output/mpl.py and related tests: Changed Matplotlib's SVG output to Base64 data URI format.
  • marimo/_convert/ipynb/from_ir.py and related tests: Added logic to decode Base64 SVG data URIs back to plain SVG strings during notebook export.
  • frontend/src/components/editor/Output.tsx: Modified image/svg+xml MIME type handling so that Base64 data URIs are rendered with ImageOutput. Regular SVG strings continue to be rendered via renderHTML.
  • frontend/src/components/editor/__tests__/Output.test.tsx: Added tests to verify the above changes.
svg

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Matplotlib SVG output

3 participants