Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mimetype renderer #195

Merged
merged 27 commits into from
Dec 27, 2022
Merged

Add mimetype renderer #195

merged 27 commits into from
Dec 27, 2022

Conversation

jonmmease
Copy link
Collaborator

@jonmmease jonmmease commented Dec 26, 2022

Overview

This PR introduces a new Altair data transformer and renderer for displaying pre-transformed Vega specifications using standard Jupyter renderers (vega, html, svg, and png).

This renderer provides most of the benefits of VegaFusion without requiring the custom VegaFusionWidget renderer.

Usage

The new data transformer and renderer are activated using the vegafusion.enable_mime() function.

import altair as alt
from vega_datasets import data

source = data.movies()

chart = alt.Chart(source).mark_bar().encode(
    alt.X("IMDB_Rating:Q", bin=True),
    y='count()',
)

# Display chart with default Altair renderer
chart

visualization

# Display chart with vegafusion mimetype renderer
import vegafusion as vf
vf.enable_mime(mimetype="html")
chart

visualization

Other supported mimetype values are vega, svg, and png

How it works

The mimetype renderer uses an approach that is very different than the existing Jupyter Widget-based renderer.

Here is an outline of the process

1. vegafusion-inline data transformer

Inline DataFrames are extracted into a global dict by the new vegafusion-inline data transformer. The DataFrames are replaced by VegaFusion inline dataset URLs (e.g. vegafusion+dataset://table-{uuid}). This prevents DataFrames from being serialized directly to JSON.

2. compile Vega-Lite to Vega

The vegafusion-mime renderer inputs a Vega-Lite spec that has already gone through step 1. In compiles the Vega-Lite to a Vega spec using the active plugin in the new vf.vegalite_compilers plugin registry. The default plugin uses vl-convert to perform the conversion, but it's possible to register custom compilation functions as well.

3. Pre-transform Vega spec

The Vega spec that results from step 2 is processed using the vegafusion.runtime.pre_transform_spec function. This function inputs a Vega spec, and DataFrames extracted in step 1, extracts and evaluates as many transforms as possible, and then produces a new Vega spec with the transformed data inlined.

Unlike the VegaFusionWidget renderer, the resulting Vega spec does not require communication with the server to maintain interactivity. All of the data required for interactivity is inlined in the pre-transformed Vega spec. The benefit is that this pre-transformed spec can be renderered just like a normal Vega spec.

4. Produce mime bundle

The renderer uses this pre-transformed Vega spec to generate mimetype bundles for the various mime types. The default mime type is "text/html", which works in the classic Jupyter Notebook and JupyterLab. The "application/vnd.vega.v5+json" mime type works in JupyterLab and other environments that provide a Vega mimetype renderer. The "image/svg+xml" mimetype uses vl-convert to convert the pre-transformed Vega spec to an SVG image and similarly the "image/png" mimetype uses vl-convert to create a PNG image.

What's the benefit?

Here are three examples that demonstrate the benefit of using the VegaFusion mime renderer compared to the default Altair renderer. The specs themselves are below, but I thought it would be easier to walk through the benefits in a screencast.

https://www.loom.com/share/094b3958ffad49bdaa3a7af19f26370b

Example 1: Aggregated chart

import altair as alt
from vega_datasets import data

source = data.movies()

agg_chart = alt.Chart(source).mark_bar().encode(
    alt.X('IMDB_Rating:Q', bin=True),
    alt.Y('count()')
)
agg_chart

visualization (1)

Example 2: Non-aggregated scatter chart

import altair as alt
from vega_datasets import data

source = data.movies()

scatter_chart = alt.Chart(source).mark_circle().encode(
    alt.X('IMDB_Rating:Q'),
    alt.Y('Rotten_Tomatoes_Rating:Q'),
    alt.Tooltip(["IMDB_Rating", "Rotten_Tomatoes_Rating", "MPAA_Rating", "Worldwide_Gross"])
)
scatter_chart

visualization (2)

Example 3: Interactive Chart

import altair as alt
from vega_datasets import data

source = data.seattle_weather()
brush = alt.selection(type='interval', encodings=['x'])

bars = alt.Chart().mark_bar().encode(
    x='month(date):O',
    y='mean(precipitation):Q',
    opacity=alt.condition(brush, alt.OpacityValue(1), alt.OpacityValue(0.7)),
).add_selection(
    brush
)

line = alt.Chart().mark_rule(color='firebrick').encode(
    y='mean(precipitation):Q',
    size=alt.SizeValue(3)
).transform_filter(
    brush
)

interactive_chart = alt.layer(bars, line, data=source)
interactive_chart

visualization (3)

Comparison to the widget renderer.

The existing VegaFusion renderer can be enabled using vegfusion.jupyter.enable() or (now in this PR) vegafusion.enable_widget(). Here are some details on how the two renderers compar.

  • The widget renderer writes DataFrames to arrow files on disk in a _vegafusion_data directory while the mime renderer handles everything in memory without temporary files
  • The widget renderer requires the VegaFusionWidget be installed in the front end (e.g. the Jupyter Notebook), and interactive charts require communication with a running Python kernel. The mime renderer requires no custom client logic as charts are displayed using the regular Vega embed renderer.
  • The widget renderer transmits transformed data from the server to the client in Arrow IPC format while the mime renderer serializes transformed data as JSON to inline in the resulting Vega specification. Neither renderer serializes the input DataFrame to JSON as is required by the default Altair renderer.

@jonmmease jonmmease merged commit 3a10ebc into main Dec 27, 2022
@mattijn
Copy link
Contributor

mattijn commented Dec 29, 2022

Looks great! Question, will the following also work?:

with vf.enable_mime(mimetype="html"):  # or similar
    chart

@jonmmease
Copy link
Collaborator Author

No, but that's a good idea! #200

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants