Skip to content

Commit 1e1152c

Browse files
committed
feat(altair): rename inline to raw_svg and add external resource warning for SVG
1 parent e3d488a commit 1e1152c

3 files changed

Lines changed: 42 additions & 12 deletions

File tree

marimo/_output/formatters/altair_formatters.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from __future__ import annotations
33

44
import json
5+
import re
56
from typing import Any
67
from urllib.request import urlopen
78

@@ -82,8 +83,9 @@ def _show_chart(chart: AltairChartType) -> tuple[KnownMimeType, str]:
8283
if isinstance(mime_response, str):
8384
if (
8485
mime_type == "image/svg+xml"
85-
and not altair.renderers.options.get("inline")
86+
and not altair.renderers.options.get("raw_svg")
8687
):
88+
_maybe_warn_external_resources(mime_response)
8789
svg_bytes = mime_response.encode()
8890
data_url = io_to_data_url(svg_bytes, mime_type)
8991
return (mime_type, data_url or "")
@@ -135,6 +137,27 @@ def _format_png_mimebundle(
135137
return "application/vnd.marimo+mimebundle", json.dumps(mimebundle)
136138

137139

140+
# Check if the SVG contains external resources that may not render
141+
# correctly when encoded as a Data URL.
142+
# https://github.com/marimo-team/marimo/pull/9104
143+
def _maybe_warn_external_resources(svg: str) -> None:
144+
# Strictly detecting external resource usage in SVG is difficult;
145+
# as a heuristic, we check for 'href' or 'xlink:href' attributes
146+
# that point to external resources (ignoring internal '#' or 'data:' URLs).
147+
if re.search(
148+
r'<[^>]*\b(?:xlink:)?href\s*=\s*["\'](?!\s*(?:#|data:))[^"\']+', svg
149+
):
150+
msg = "".join(
151+
[
152+
"This SVG contains external resources (href/xlink:href) ",
153+
"that may not render correctly when encoded as a Data URL. ",
154+
"If images are missing, try enabling raw SVG rendering with: ",
155+
"altair.renderers.enable('svg', raw_svg=True).",
156+
]
157+
)
158+
LOGGER.warning(msg)
159+
160+
138161
# This is only needed since it seems that altair does not
139162
# handle this internally.
140163
# https://github.com/marimo-team/marimo/issues/2302

marimo/_smoke_tests/altair_examples/altair_svg_rendering.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@
1212

1313
import marimo
1414

15-
__generated_with = "0.22.0"
15+
__generated_with = "0.23.1"
1616
app = marimo.App(width="medium")
1717

1818
with app.setup:
1919
import marimo as mo
2020
import altair as alt
2121
import pandas as pd
2222

23+
# Verify vl-convert-python is installed; it's required for the SVG renderer.
24+
import vl_convert as vlc
25+
2326

2427
@app.cell
2528
def _():
@@ -29,7 +32,6 @@ def _():
2932

3033
@app.cell
3134
def _(data):
32-
# Issue #9015: SVG charts in layouts render raw base64 string
3335
alt.renderers.enable("svg")
3436

3537
chart = alt.Chart(data).mark_point().encode(x="x", y="y")
@@ -45,8 +47,10 @@ def _(chart):
4547

4648
@app.cell
4749
def _(chart):
48-
# This renders the raw base64-encoded string (issue #9015)
49-
mo.vstack([chart])
50+
# SVG outputs should be correctly rendered in vstack or hstack
51+
# (reported in Issue #9015 and fixed in PR #9043).
52+
# For vstack, align="start" is needed to preserve the image size
53+
mo.vstack([chart], align="start")
5054
return
5155

5256

@@ -78,17 +82,20 @@ def _(chart_with_images):
7882

7983
@app.cell
8084
def _(chart_with_images):
81-
# Image marks should not be broken
85+
# Image marks are broken.
86+
# The root cause is the browser's security restriction.
87+
# When marimo detects an SVG with external resources (e.g., image URLs),
88+
# it warns the user to enable 'raw_svg=True' for correct rendering.
8289
alt.renderers.enable("svg")
8390
chart_with_images
8491
return
8592

8693

8794
@app.cell
8895
def _(chart_with_images):
89-
# Image marks should not be broken
90-
alt.renderers.enable("svg")
91-
mo.hstack([chart_with_images])
96+
# Image marks are correctly rendered when 'raw_svg=True' is enabled.
97+
alt.renderers.enable("svg", raw_svg=True)
98+
chart_with_images
9299
return
93100

94101

tests/_output/formatters/test_altair_formatters.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,21 +158,21 @@ def test_altair_formatter_mimebundle():
158158

159159
@pytest.mark.skipif(not HAS_DEPS, reason="altair not installed")
160160
@pytest.mark.parametrize(
161-
("inline", "expected"),
161+
("raw_svg", "expected"),
162162
[
163163
(True, "<svg></svg>"),
164164
(False, "data:image/svg+xml;base64,PHN2Zz48L3N2Zz4="),
165165
],
166166
)
167-
def test_altair_formatter_svg(inline: bool, expected: str):
167+
def test_altair_formatter_svg(raw_svg: bool, expected: str):
168168
AltairFormatter().register()
169169

170170
import altair as alt
171171

172172
# Create a mock chart with a _repr_mimebundle_ method that returns SVG
173173
mock_chart = alt.Chart(get_data()).mark_point()
174174
with (
175-
patch.dict(alt.renderers.options, {"inline": inline}),
175+
patch.dict(alt.renderers.options, {"raw_svg": raw_svg}),
176176
patch.object(
177177
alt.Chart,
178178
"_repr_mimebundle_",

0 commit comments

Comments
 (0)