marimo
marimo copied to clipboard
Altair reactive plots do not work if vegafusion is enabled
Describe the bug
In this example from the documentation,
import marimo as mo
import altair as alt
import vega_datasets
# Load some data
cars = vega_datasets.data.cars()
# Create an Altair chart
chart = alt.Chart(cars).mark_point().encode(
x='Horsepower', # Encoding along the x-axis
y='Miles_per_Gallon', # Encoding along the y-axis
color='Origin', # Category encoding by color
)
# Make it reactive ⚡
chart = mo.ui.altair_chart(chart)
if you add the line
alt.data_transformers.enable("vegafusion")
the plot stops being reactive and it is not possible to select data on it.
Environment
{
"marimo": "0.13.0",
"OS": "Darwin",
"OS Version": "24.1.0",
"Processor": "arm",
"Python Version": "3.12.9",
"Binaries": {
"Browser": "135.0.7049.96",
"Node": "v23.11.0"
},
"Dependencies": {
"click": "8.1.8",
"docutils": "0.21.2",
"itsdangerous": "2.2.0",
"jedi": "0.19.2",
"markdown": "3.8",
"narwhals": "1.35.0",
"packaging": "24.2",
"psutil": "7.0.0",
"pygments": "2.19.1",
"pymdown-extensions": "10.14.3",
"pyyaml": "6.0.2",
"ruff": "0.11.5",
"starlette": "0.46.2",
"tomlkit": "0.13.2",
"typing-extensions": "4.13.2",
"uvicorn": "0.34.1",
"websockets": "15.0.1"
},
"Optional Dependencies": {},
"Experimental Flags": {
"lsp": true,
"table_charts": false,
"inline_ai_tooltip": false
}
}
Code to reproduce
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "altair==5.5.0",
# "marimo==0.13.0",
# "pyarrow==19.0.1",
# "vega-datasets==0.9.0",
# "vegafusion==2.0.2",
# "vl-convert-python==1.7.0",
# ]
# ///
import marimo
__generated_with = "0.13.0"
app = marimo.App(width="medium")
@app.cell
def _():
import marimo as mo
import altair as alt
import vega_datasets
alt.data_transformers.enable("vegafusion")
return alt, mo, vega_datasets
@app.cell
def _(alt, mo, vega_datasets):
# Load some data
cars = vega_datasets.data.cars()
# Create an Altair chart
chart = alt.Chart(cars).mark_point().encode(
x='Horsepower', # Encoding along the x-axis
y='Miles_per_Gallon', # Encoding along the y-axis
color='Origin', # Category encoding by color
)
# Make it reactive ⚡
chart = mo.ui.altair_chart(chart)
return cars, chart
@app.cell
def _(chart, mo):
mo.vstack([chart, chart.value.head()])
return
if __name__ == "__main__":
app.run()
This is not yet supported. vegafusion compiles down to a vega spec, but our selection logic works on a vega-lite spec. We can look into adding support for this still
This is especially useful when working with larger datasets. Altair recommends using vegafusion for any dataset with more than 5000 rows, as performance starts to drop otherwise; who are we kidding though, most datasets nowadays are larger than that :p
I'm currently using alt.data_transformers.disable_max_rows() as a temporary workaround, but it does start to chug when the datasets get too large, so it would be really nice if vegafusion was supported.
Another option that I currently use is to precompute the values in numpy or polars and then feed them to altair for much larger datasets.
I often hit this limitation too! I'd be keen to work on a pair if I can pair with one the contributors and get some guidance!! @mscolnick
You can still make reactive Altair plots with vegafusion by passing an explicit selection param. This is hand rolling the selection yourself (something that mo.ui.altair_chart otherwise does under the hood for non-vegafusion)
# /// script
# requires-python = ">=3.13"
# dependencies = [
# "altair>=5.5.0",
# "marimo",
# "vega-datasets>=0.9.0",
# "vegafusion[embed]>=2.0.1",
# "vl-convert-python>=1.7.0",
# ]
# ///
import marimo
__generated_with = "0.17.2"
app = marimo.App(width="medium")
@app.cell
def _():
import altair as alt
import anywidget
from vega_datasets import data
import marimo as mo
alt.data_transformers.enable("vegafusion")
# Load some data
cars = data.cars()
# Create an Altair chart
_chart = (
alt.Chart(cars)
.mark_point()
.encode(
x="Horsepower",
y="Miles_per_Gallon",
color="Origin",
)
.properties(height=300)
.add_params(alt.selection_interval(name="interval"))
)
# Make it reactive ⚡
chart = mo.ui.anywidget(alt.JupyterChart(_chart))
return (chart,)
@app.cell
def _(chart):
chart
return
@app.cell
def _(chart):
list(chart.selections.interval.value.items())
return