Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/src/plugins/impl/vega/vega.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.vega-embed {
width: 100%;
flex: 1;
display: inline-block;

@media (min-width: 500px) {
min-width: 300px;
Expand Down
19 changes: 18 additions & 1 deletion marimo/_plugins/ui/_impl/altair_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,9 @@ def __init__(
chart = maybe_make_full_width(chart)

# Fix the sizing for vconcat charts
if isinstance(chart, alt.VConcatChart):
if isinstance(chart, alt.VConcatChart) and _has_no_nested_hconcat(
chart
):
chart = _update_vconcat_width(chart)

# without autosize, vconcat will overflow
Expand Down Expand Up @@ -699,3 +701,18 @@ def _update_vconcat_width(chart: AltairChartType) -> AltairChartType:

# Not handled
return chart


def _has_no_nested_hconcat(chart: AltairChartType) -> bool:
import altair as alt

if isinstance(chart, alt.HConcatChart):
return False
if isinstance(chart, alt.VConcatChart):
return all(
_has_no_nested_hconcat(subchart) for subchart in chart.vconcat
)
if isinstance(chart, alt.LayerChart):
return all(_has_no_nested_hconcat(layer) for layer in chart.layer)

return True
225 changes: 225 additions & 0 deletions marimo/_smoke_tests/altair_examples/hconcat_vconcat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import marimo

__generated_with = "0.16.2"
app = marimo.App(width="full")


@app.cell
def _():
from vega_datasets import data
import altair as alt
import marimo as mo

cars = data.cars()

_chart = (
alt.Chart(cars)
.mark_point()
.encode(
x="Horsepower",
y="Miles_per_Gallon",
color="Origin",
)
.properties(height=100)
)
return alt, cars, mo


@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## vconcat with hconcat inside""")
return


@app.cell
def _(alt, cars):
_chart = (
alt.Chart(cars)
.mark_point()
.encode(
x="Horsepower",
y="Miles_per_Gallon",
color="Origin",
)
.properties(height=100)
)

stacked_chart = alt.vconcat(
alt.hconcat(
_chart,
_chart,
),
_chart,
)
return (stacked_chart,)


@app.cell
def _(stacked_chart):
stacked_chart
return


@app.cell
def _(mo, stacked_chart):
mo.ui.altair_chart(stacked_chart.copy())
return


@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## Nested vconcat""")
return


@app.cell
def _(alt, cars):
_chart = (
alt.Chart(cars)
.mark_point()
.encode(
x="Horsepower",
y="Miles_per_Gallon",
color="Origin",
)
.properties(height=100)
)

stacked_chart1 = alt.vconcat(
_chart,
alt.vconcat(
_chart,
_chart,
),
)

stacked_chart1
return (stacked_chart1,)


@app.cell
def _(mo, stacked_chart1):
mo.ui.altair_chart(stacked_chart1.copy())
return


@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## hconcat with vconcat inside""")
return


@app.cell
def _(alt, cars):
_chart = (
alt.Chart(cars)
.mark_point()
.encode(
x="Horsepower",
y="Miles_per_Gallon",
color="Origin",
)
.properties(height=100)
)

h_v_chart = alt.hconcat(
alt.vconcat(
_chart,
_chart,
),
_chart,
)

h_v_chart
return (h_v_chart,)


@app.cell
def _(h_v_chart, mo):
mo.ui.altair_chart(h_v_chart.copy())
return


@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## Nested hconcat""")
return


@app.cell
def _(alt, cars):
_chart = (
alt.Chart(cars)
.mark_point()
.encode(
x="Horsepower",
y="Miles_per_Gallon",
color="Origin",
)
.properties(height=100)
)

nested_h_chart = alt.hconcat(
_chart,
alt.hconcat(
_chart,
_chart,
),
)

nested_h_chart
return (nested_h_chart,)


@app.cell
def _(mo, nested_h_chart):
mo.ui.altair_chart(nested_h_chart.copy())
return


@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## Complex nested combination""")
return


@app.cell
def _(alt, cars):
_chart = (
alt.Chart(cars)
.mark_point()
.encode(
x="Horsepower",
y="Miles_per_Gallon",
color="Origin",
)
.properties(height=100)
)

# Complex: vconcat(hconcat(vconcat(...), ...), ...)
complex_chart = alt.vconcat(
alt.hconcat(
alt.vconcat(
_chart,
_chart,
),
_chart,
),
alt.hconcat(
_chart,
_chart,
),
)

complex_chart
return (complex_chart,)


@app.cell
def _(complex_chart, mo):
mo.ui.altair_chart(complex_chart.copy())
return


if __name__ == "__main__":
app.run()
87 changes: 87 additions & 0 deletions tests/_plugins/ui/_impl/test_altair_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
_has_binning,
_has_geoshape,
_has_legend_param,
_has_no_nested_hconcat,
_has_selection_param,
_parse_spec,
_update_vconcat_width,
Expand Down Expand Up @@ -1101,3 +1102,89 @@ def test_chart_with_column_encoding_not_full_width() -> None:
)
result_without_column = maybe_make_full_width(chart_without_column)
assert result_without_column.width == "container"


@pytest.mark.skipif(not HAS_DEPS, reason="optional dependencies not installed")
def test_has_no_nested_hconcat() -> None:
import altair as alt

# Create simple charts
chart1 = alt.Chart(pd.DataFrame({"x": [1, 2], "y": [3, 4]})).mark_point()
chart2 = alt.Chart(pd.DataFrame({"x": [1, 2], "y": [3, 4]})).mark_line()

# Simple chart has no hconcat
assert _has_no_nested_hconcat(chart1) is True

# HConcatChart should return False
hconcat_chart = alt.hconcat(chart1, chart2)
assert _has_no_nested_hconcat(hconcat_chart) is False

# VConcatChart with no nested hconcat should return True
vconcat_chart = alt.vconcat(chart1, chart2)
assert _has_no_nested_hconcat(vconcat_chart) is True

# LayerChart with no nested hconcat should return True
layer_chart = alt.layer(chart1, chart2)
assert _has_no_nested_hconcat(layer_chart) is True

# VConcatChart with nested HConcatChart should return False
nested_vconcat_with_hconcat = alt.vconcat(hconcat_chart, chart1)
assert _has_no_nested_hconcat(nested_vconcat_with_hconcat) is False

# VConcatChart with nested VConcatChart (no hconcat) should return True
nested_vconcat = alt.vconcat(
alt.vconcat(chart1, chart2), alt.vconcat(chart1, chart2)
)
assert _has_no_nested_hconcat(nested_vconcat) is True

# LayerChart with simple charts (no hconcat) should return True
layer_simple = alt.layer(chart1, chart2)
assert _has_no_nested_hconcat(layer_simple) is True

# VConcatChart with nested layers (no hconcat) should return True
vconcat_with_layer = alt.vconcat(chart1, alt.layer(chart1, chart2))
assert _has_no_nested_hconcat(vconcat_with_layer) is True

# Deeply nested VConcat with HConcat should return False
deeply_nested = alt.vconcat(alt.vconcat(chart1, hconcat_chart), chart2)
assert _has_no_nested_hconcat(deeply_nested) is False


@pytest.mark.skipif(not HAS_DEPS, reason="optional dependencies not installed")
def test_autosize_not_applied_with_nested_hconcat() -> None:
import altair as alt

# Create simple charts
data = pd.DataFrame({"x": [1, 2], "y": [3, 4]})
chart1 = alt.Chart(data).mark_point().encode(x="x", y="y")
chart2 = alt.Chart(data).mark_line().encode(x="x", y="y")

# Test 1: VConcatChart with nested HConcatChart should NOT have autosize applied
hconcat_chart = alt.hconcat(chart1, chart2)
vconcat_with_hconcat = alt.vconcat(hconcat_chart, chart1)

marimo_chart = altair_chart(vconcat_with_hconcat)
# The autosize should remain Undefined (not set to "fit-x")
assert marimo_chart._chart.autosize is alt.Undefined

# Test 2: Simple VConcatChart (no nested hconcat) SHOULD have autosize applied
simple_vconcat = alt.vconcat(chart1, chart2)
marimo_chart_simple = altair_chart(simple_vconcat)
# The autosize should be set to "fit-x"
assert marimo_chart_simple._chart.autosize == "fit-x"

# Test 3: VConcatChart with nested vconcat containing hconcat should NOT have autosize
nested_with_hconcat = alt.vconcat(
alt.vconcat(chart1, hconcat_chart), chart2
)

marimo_chart_complex = altair_chart(nested_with_hconcat)
assert marimo_chart_complex._chart.autosize is alt.Undefined

# Test 4: VConcatChart with explicit autosize should not be overridden
vconcat_with_autosize = alt.vconcat(chart1, chart2).properties(
autosize="none"
)
marimo_chart_explicit = altair_chart(vconcat_with_autosize)
# Should keep the explicit autosize value
assert marimo_chart_explicit._chart.autosize == "none"
Loading