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

Saving HTML charts with offline rendereing and interactivity by inlining external resources #33

Closed
joelostblom opened this issue Dec 19, 2022 · 6 comments · Fixed by #118
Milestone

Comments

@joelostblom
Copy link

joelostblom commented Dec 19, 2022

I just noticed that this was a functionality of Altair saver as mentioned here https://stackoverflow.com/a/63103365/2166823. Is this something that would fit into vl-convert as well? I'ts more like a small convenience, but curious if this would be an option. Some of this functionality might already exist here https://github.com/altair-viz/altair_viewer

@jonmmease
Copy link
Collaborator

I'd like to limit vl-convert's scope to include only the functionality where we need to interact with the Deno runtime using Rust. It sound to me like the functionality of generating standalone HTML files is mostly a matter of html string manipulation (not requiring a JavaScript runtime) that would be fine to do in Python.

Does Altair saver rely on the browser for doing this? If not, I'd personally recommend we move that functionality directly into Altair.

@joelostblom
Copy link
Author

Good point, it looks like you are correct regarding string manipulation being the only thing needed so I opened vega/altair#2765 to explore if it makes sense to move this functionality into Altair core. Closing this for now at least

@jonmmease
Copy link
Collaborator

Thinking about this some more, and I do think this is in scope for VlConvert. It already bundles the Vega/Vega-Lite javascript libraries, so it would be great to be able to take advantage of this for generating HTML files that render charts.

An approach that I think is promising is to use the deno_emit Rust crate. In particular, the bundle function. We could bundle a small script that imports vega-embed and then renders the chart to a div on the page. One caveat is that it doesn't look like deno_emit does whitespace minification.

Then we could add vega_to_html/vegalite_to_html functions that include an option of whether to embed the JS dependencies inline or load them from a CDN.

@jonmmease jonmmease reopened this Sep 30, 2023
@joelostblom
Copy link
Author

joelostblom commented Oct 1, 2023

I like this direction! I'm curious if this means that vl-convert would be able to also replace altair_viewer and allow for rendering of charts offline from an active Python environment? It sounds like we could maybe use the same mechanism with bundling/embedding the JS dependencies and it would even have the advantage of not needing an active Python kernel (like altair viewer currently does)? It would save us from keeping altair_viewer up to date as a separate package, which is currently an issue both in terms of release rights and timeliness altair-viz/altair_viewer#59 (but only if it does not increase the maintenance burden significantly in vl-convert of course).

And maybe there is even something from the altair_viewer side of things that could be borrowed and help the implementation in vl-convert? I remember you said there is about 1 MB of relevant code/bundles in altair_viewer. Also related to vega/altair#2807 and vega/altair#2797.

@jonmmease
Copy link
Collaborator

I'm curious if this means that vl-convert would be able to also replace altair_viewer and allow for rendering of charts offline from an active Python environment?

After looking some at altair_viewer, it looks like one of the main things it does is provide an altair renderer that pushes displayed charts through a websocket connection to a browser. This limits you to only viewing a single chart at a time, so it's not a workflow that resonates with me personally. I'd be interested to hear feedback from folks who would like to use Altair outside of a notebook context on what kind of workflow they would like. As an alternative, for plotly, we added a "browser" renderer that opens a chart in a new browser tab each time a chart is rendered.

Once we decide on the workflow, I think vl-convert could probably help with the implementation.

@joelostblom
Copy link
Author

This limits you to only viewing a single chart at a time, so it's not a workflow that resonates with me personally.

I agree with that.

I'd be interested to hear feedback from folks who would like to use Altair outside of a notebook context on what kind of workflow they would like.

I'm unsure how common this workflow is and where we can find these people =)

Once we decide on the workflow, I think vl-convert could probably help with the implementation.

Maybe there will be two workflows to support: 1. Offline inline inside a notebook. 2. Offline in a separate browser tab/window (with a preference for being able to render more than a single chart at a time, unless that is notably more complex to implement).

Maybe this is a good discussion to have during our call as well?

@jonmmease jonmmease added this to the 1.0 milestone Oct 4, 2023
jonmmease added a commit that referenced this issue Oct 5, 2023
Closes #33, cc @joelostblom

This PR adds support for converting Vega and Vega-Lite charts to live HTML documents. There is a "bundle" option that controls whether the JavaScript dependencies should be loaded from a CDN, or whether they should be inlined into the resulting HTML file. 

### bundle=False
When bundle is False, this follows the [Vega Embed directions](https://vega.github.io/vega-lite/usage/embed.html#start-using-vega-lite-with-vega-embed) to load vega, vega-lite, and vega-embed from jsdelivr 

### bundle=True
When bundle is True, things are a bit more involved. We already inline Vega and several versions of Vega-Lite into the VlConvert executables, so I wanted to avoid including additional copies for the purpose of HTML export.  But in order to use the JS deps inlined into VlConvert, they need to be bundled.  I found that the [deno_emit](https://github.com/denoland/deno_emit) project provides a Rust crate that uses SWC to bundle JavaScript / TypeScript dependencies, and this ended up working well.

One note, the bundled code isn't fully minimized yet, but I have an open PR that will expose SWC's minify option. See denoland/deno_emit#141. Currently, the resulting HTML files start at ~1.6MB, but this will drop to ~1MB when minification is enabled.

The bundling process takes about 1.5s on my machine. Because this is pretty slow, I decided to cache the bundle results, so that subsequent HTML exports that use the same Vega-Lite version will be fast (10-20ms).

### Custom JavaScript bundles
Additionally, a `javascipt_bundle` Python function is added that can be used to create bundles with custom JavaScript logic that references Vega Embed, Vega, and Vega-Lite.  The idea is that Vega-based systems like Altair can use this to build additional integrations.

### Integrations
The most immediate application of the HTML export is to remove the altair_viewer dependency in Altair's html export when `inline=True`.
 
We could also use this to add an "html-offline" Altair renderer, though this could result in large notebooks as every individual Chart would be over 1MB.

Another use-case I have in mind is to use the `javascript_bundle` function to create offline bundles for Altair's JupyterChart. This is why I added support for the lodash debounce function as well, since this is the only import, in addition to vegaEmbed, that JupyterChart's JavaScript logic uses.  The cool thing about this approach is that we can build the offline bundle on the fly (in under 2s) without an internet connection required.
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 a pull request may close this issue.

2 participants