From 862bb4c817d3f1eda1ab14152e411af49414a42b Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Sat, 4 Nov 2023 16:08:10 -0400 Subject: [PATCH 1/5] Use vl-convert for offline HTML support --- altair/utils/_importers.py | 10 +++++++++- altair/utils/html.py | 25 ++++++------------------- altair/utils/mimebundle.py | 8 ++------ doc/user_guide/saving_charts.rst | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/altair/utils/_importers.py b/altair/utils/_importers.py index 388d379f5..faf347243 100644 --- a/altair/utils/_importers.py +++ b/altair/utils/_importers.py @@ -42,7 +42,7 @@ def import_vl_convert() -> ModuleType: return vlc except ImportError as err: raise ImportError( - f"The vl-convert Vega-Lite compiler and image export feature requires\n" + f"The vl-convert Vega-Lite compiler and file export feature requires\n" f"version {min_version} or greater of the 'vl-convert-python' package. \n" f"This can be installed with pip using:\n" f' pip install "vl-convert-python>={min_version}"\n' @@ -52,6 +52,14 @@ def import_vl_convert() -> ModuleType: ) from err +def vl_version_for_vl_convert() -> str: + from ..vegalite import SCHEMA_VERSION + + # Compute VlConvert's vl_version string (of the form 'v5_2') + # from SCHEMA_VERSION (of the form 'v5.2.0') + return "_".join(SCHEMA_VERSION.split(".")[:2]) + + def import_pyarrow_interchange() -> ModuleType: min_version = "11.0.0" try: diff --git a/altair/utils/html.py b/altair/utils/html.py index c1084aeec..ce9af4668 100644 --- a/altair/utils/html.py +++ b/altair/utils/html.py @@ -3,6 +3,7 @@ import jinja2 +from altair.utils._importers import import_vl_convert, vl_version_for_vl_convert HTML_TEMPLATE = jinja2.Template( """ @@ -182,11 +183,7 @@ } @@ -256,7 +253,7 @@ def spec_to_html( tags. If True, then load libraries using requirejs template : jinja2.Template or string (optional) Specify the template to use (default = 'standard'). If template is a - string, it must be one of {'universal', 'standard'}. Otherwise, it + string, it must be one of {'universal', 'standard', 'inline'}. Otherwise, it can be a jinja2.Template object containing a custom template. Returns @@ -283,19 +280,9 @@ def spec_to_html( render_kwargs = {} if template == "inline": - try: - from altair_viewer import get_bundled_script - except ImportError as err: - raise ImportError( - "The altair_viewer package is required to convert to HTML with inline=True" - ) from err - render_kwargs["vega_script"] = get_bundled_script("vega", vega_version) - render_kwargs["vegalite_script"] = get_bundled_script( - "vega-lite", vegalite_version - ) - render_kwargs["vegaembed_script"] = get_bundled_script( - "vega-embed", vegaembed_version - ) + vlc = import_vl_convert() + vl_version = vl_version_for_vl_convert() + render_kwargs["vegaembed_script"] = vlc.javascript_bundle(vl_version=vl_version) jinja_template = TEMPLATES.get(template, template) if not hasattr(jinja_template, "render"): diff --git a/altair/utils/mimebundle.py b/altair/utils/mimebundle.py index fa36ed4b8..021a1fb0f 100644 --- a/altair/utils/mimebundle.py +++ b/altair/utils/mimebundle.py @@ -1,6 +1,6 @@ from .deprecation import AltairDeprecationWarning from .html import spec_to_html -from ._importers import import_vl_convert +from ._importers import import_vl_convert, vl_version_for_vl_convert import struct import warnings @@ -107,11 +107,7 @@ def _spec_to_mimebundle_with_engine(spec, format, mode, **kwargs): if normalized_engine == "vlconvert": vlc = import_vl_convert() - from ..vegalite import SCHEMA_VERSION - - # Compute VlConvert's vl_version string (of the form 'v5_2') - # from SCHEMA_VERSION (of the form 'v5.2.0') - vl_version = "_".join(SCHEMA_VERSION.split(".")[:2]) + vl_version = vl_version_for_vl_convert() if format == "vega": if mode == "vega": vg = spec diff --git a/doc/user_guide/saving_charts.rst b/doc/user_guide/saving_charts.rst index e265c85d0..9f01709ef 100644 --- a/doc/user_guide/saving_charts.rst +++ b/doc/user_guide/saving_charts.rst @@ -144,6 +144,22 @@ change to ``svg`` rendering, use the ``embed_options`` as such: This is not the same as ``alt.renderers.enable('svg')``, what renders the chart as a static ``svg`` image within a Jupyter notebook. + +Offline HTML support +^^^^^^^^^^^^^^^^^^^^ +By default, an HTML file generated by ``chart.save('chart.html')`` loads the necessary JavaScript dependencies from an online CDN location. This results in a small HTML file, but it means that an active internet connection is required in order to display the chart. + +As an alternative, the ``inline=True`` keyword argument may be provided to ``chart.save`` to generate an HTML file that includes all necessary JavaScript dependencies inline. This results in a larger file size, but HTML files generated this way do not require an active internet connection to display. + +.. code-block:: python + + chart.save('chart.html', inline=True) + +.. note:: + + Calling ``chart.save`` with ``inline=True`` requires the :ref:`install-vl-convert` package. + + .. _saving-png: PNG, SVG, and PDF format @@ -161,6 +177,8 @@ javascript code necessary to interpret the Vega-Lite specification and output it in the form of an image. There are two packages that can be used to enable image export: vl-convert_ or altair_saver_. +.. _install-vl-convert: + vl-convert ^^^^^^^^^^ The vl-convert_ package can be installed with:: From c338c336bfef6dcd89db83cfdbc14accd28666c5 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Sun, 5 Nov 2023 04:27:56 -0500 Subject: [PATCH 2/5] vl-convert-python 1.0.1 --- altair/utils/_importers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/altair/utils/_importers.py b/altair/utils/_importers.py index faf347243..bb539fb73 100644 --- a/altair/utils/_importers.py +++ b/altair/utils/_importers.py @@ -29,7 +29,7 @@ def import_vegafusion() -> ModuleType: def import_vl_convert() -> ModuleType: - min_version = "1.0.0" + min_version = "1.0.1" try: version = importlib_version("vl-convert-python") if Version(version) < Version(min_version): From ba9d9ab942e200b6efce12263fbfd453042dfe1c Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Sun, 5 Nov 2023 04:54:02 -0500 Subject: [PATCH 3/5] vl-convert-python 1.0.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8f86b8353..7d93ff8e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ dev = [ "pytest-cov", "m2r", "vega_datasets", - "vl-convert-python>=1.0.0", + "vl-convert-python>=1.0.1", "mypy", "pandas-stubs", "types-jsonschema", From 62097ce54f9ae47e0b6ee2fb60dc65d9a9341ef9 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Sun, 5 Nov 2023 06:22:41 -0500 Subject: [PATCH 4/5] Add changelog --- doc/releases/changes.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/releases/changes.rst b/doc/releases/changes.rst index db4ad5c82..cbfe14de5 100644 --- a/doc/releases/changes.rst +++ b/doc/releases/changes.rst @@ -8,6 +8,7 @@ Version 5.2.0 (unreleased month date, year) Enhancements ~~~~~~~~~~~~ +- Support offline HTML export using vl-convert (#3251) Bug Fixes ~~~~~~~~~ From 89a86e8e3f82b9ee43e7b9030198e9fefb27e25d Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Thu, 9 Nov 2023 09:45:34 -0500 Subject: [PATCH 5/5] Update docstrings and signatures --- altair/utils/save.py | 4 +- altair/vegalite/v5/api.py | 83 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/altair/utils/save.py b/altair/utils/save.py index 797210b3f..6ee5c6619 100644 --- a/altair/utils/save.py +++ b/altair/utils/save.py @@ -101,7 +101,7 @@ def save( Additional keyword arguments are passed to the output method associated with the specified format. webdriver : string {'chrome' | 'firefox'} (optional) - Webdriver to use for png or svg output + Webdriver to use for png, svg, or pdf output when using altair_saver engine scale_factor : float (optional) scale_factor to use to change size/resolution of png or svg output engine: string {'vl-convert', 'altair_saver'} @@ -111,7 +111,7 @@ def save( from a CDN location in the resulting html file. If True, the required JavaScript libraries are inlined into the resulting html file so that it will work without an internet connection. - The altair_viewer package is required if True. + The vl-convert-python package is required if True. **kwargs : additional kwargs passed to spec_to_mimebundle. """ diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 259cce0e1..44629d4d4 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -1040,7 +1040,43 @@ def to_html( json_kwds: Optional[dict] = None, fullhtml: bool = True, requirejs: bool = False, + inline: bool = False, + **kwargs, ) -> str: + """Embed a Vega/Vega-Lite spec into an HTML page + + Parameters + ---------- + base_url : string (optional) + The base url from which to load the javascript libraries. + output_div : string (optional) + The id of the div element where the plot will be shown. + embed_options : dict (optional) + Dictionary of options to pass to the vega-embed script. Default + entry is {'mode': mode}. + json_kwds : dict (optional) + Dictionary of keywords to pass to json.dumps(). + fullhtml : boolean (optional) + If True (default) then return a full html page. If False, then return + an HTML snippet that can be embedded into an HTML page. + requirejs : boolean (optional) + If False (default) then load libraries from base_url using