Skip to content

Commit

Permalink
Use vl-convert for offline html export (#3251)
Browse files Browse the repository at this point in the history
* Use vl-convert for offline HTML support

* vl-convert-python 1.0.1

* vl-convert-python 1.0.1

* Add changelog

* Update docstrings and signatures
  • Loading branch information
jonmmease authored Nov 9, 2023
1 parent dbe799e commit aa77828
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 36 deletions.
12 changes: 10 additions & 2 deletions altair/utils/_importers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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'
Expand All @@ -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:
Expand Down
25 changes: 6 additions & 19 deletions altair/utils/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import jinja2

from altair.utils._importers import import_vl_convert, vl_version_for_vl_convert

HTML_TEMPLATE = jinja2.Template(
"""
Expand Down Expand Up @@ -182,11 +183,7 @@
}
</style>
<script type="text/javascript">
// vega.js v{{ vega_version }}
{{ vega_script }}
// vega-lite.js v{{ vegalite_version }}
{{ vegalite_script }}
// vega-embed.js v{{ vegaembed_version }}
// vega-embed.js bundle with Vega-Lite version v{{ vegalite_version }}
{{ vegaembed_script }}
</script>
</head>
Expand Down Expand Up @@ -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
Expand All @@ -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"):
Expand Down
8 changes: 2 additions & 6 deletions altair/utils/mimebundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

Expand Down Expand Up @@ -116,11 +116,7 @@ def _spec_to_mimebundle_with_engine(

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
Expand Down
4 changes: 2 additions & 2 deletions altair/utils/save.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,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'}
Expand All @@ -124,7 +124,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.
"""
Expand Down
83 changes: 77 additions & 6 deletions altair/vegalite/v5/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <script>
tags. If True, then load libraries using requirejs
inline: bool (optional)
If False (default), the required JavaScript libraries are loaded
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 vl-convert-python package is required if True.
**kwargs :
additional kwargs passed to spec_to_html.
Returns
-------
output : string
an HTML string for rendering the chart.
"""
if inline:
kwargs["template"] = "inline"
return utils.spec_to_html(
self.to_dict(),
mode="vega-lite",
Expand All @@ -1053,6 +1089,7 @@ def to_html(
json_kwds=json_kwds,
fullhtml=fullhtml,
requirejs=requirejs,
**kwargs,
)

def to_url(self, *, fullscreen: bool = False) -> str:
Expand Down Expand Up @@ -1080,9 +1117,15 @@ def save(
format: Optional[Literal["json", "html", "png", "svg", "pdf"]] = None,
override_data_transformer: bool = True,
scale_factor: float = 1.0,
mode: Optional[str] = None,
vegalite_version: str = VEGALITE_VERSION,
vega_version: str = VEGA_VERSION,
vegaembed_version: str = VEGAEMBED_VERSION,
embed_options: Optional[dict] = None,
json_kwds: Optional[dict] = None,
webdriver: Optional[str] = None,
engine: Optional[str] = None,
inline=False,
**kwargs,
) -> None:
"""Save a chart to file in a variety of formats
Expand All @@ -1101,14 +1144,36 @@ def save(
If True (default), then the save action will be done with
the MaxRowsError disabled. If False, then do not change the data
transformer.
scale_factor : float
For svg or png formats, scale the image by this factor when saving.
This can be used to control the size or resolution of the output.
Default is 1.0
**kwargs :
scale_factor : float (optional)
scale_factor to use to change size/resolution of png or svg output
mode : string (optional)
Must be 'vega-lite'. If not specified, then infer the mode from
the '$schema' property of the spec, or the ``opt`` dictionary.
If it's not specified in either of those places, then use 'vega-lite'.
vegalite_version : string (optional)
For html output, the version of vegalite.js to use
vega_version : string (optional)
For html output, the version of vega.js to use
vegaembed_version : string (optional)
For html output, the version of vegaembed.js to use
embed_options : dict (optional)
The vegaEmbed options dictionary. Default is {}
(See https://github.com/vega/vega-embed for details)
json_kwds : dict (optional)
Additional keyword arguments are passed to the output method
associated with the specified format.
webdriver : string {'chrome' | 'firefox'} (optional)
Webdriver to use for png, svg, or pdf output when using altair_saver engine
engine: string {'vl-convert', 'altair_saver'}
the conversion engine to use for 'png', 'svg', and 'pdf' formats
inline: bool (optional)
If False (default), the required JavaScript libraries are loaded
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 vl-convert-python package is required if True.
**kwargs :
additional kwargs passed to spec_to_mimebundle.
"""
from ...utils.save import save

Expand All @@ -1117,9 +1182,15 @@ def save(
fp=fp,
format=format,
scale_factor=scale_factor,
mode=mode,
vegalite_version=vegalite_version,
vega_version=vega_version,
vegaembed_version=vegaembed_version,
embed_options=embed_options,
json_kwds=json_kwds,
webdriver=webdriver,
engine=engine,
inline=inline,
**kwargs,
)

Expand Down
1 change: 1 addition & 0 deletions doc/releases/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Version 5.2.0 (unreleased month date, year)

Enhancements
~~~~~~~~~~~~
- Support offline HTML export using vl-convert (#3251)
- Support saving charts as PDF files using the vl-convert export engine (#3244)
- Support converting charts to sharable Vega editor URLs with ``chart.to_url()`` (#3252)

Expand Down
18 changes: 18 additions & 0 deletions doc/user_guide/saving_charts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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::
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit aa77828

Please sign in to comment.