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 inline argument to chart.save() for html export #2807

Merged
merged 6 commits into from
Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions altair/utils/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,42 @@
)


# This is like the HTML_TEMPLATE template, but includes vega javascript inline
# so that the resulting file is not dependent on external resources. This was
# ported over from altair_saver.
#
# implies requirejs=False and full_html=True
INLINE_HTML_TEMPLATE = jinja2.Template(
"""\
<!DOCTYPE html>
<html>
<head>
<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 }}
{{ vegaembed_script }}
</script>
</head>
<body>
<div class="vega-visualization" id="{{ output_div }}"></div>
<script type="text/javascript">
const spec = {{ spec }};
const embedOpt = {{ embed_options }};
vegaEmbed('#{{ output_div }}', spec, embedOpt).catch(console.error);
</script>
</body>
</html>
"""
)


TEMPLATES = {
"standard": HTML_TEMPLATE,
"universal": HTML_TEMPLATE_UNIVERSAL,
"inline": INLINE_HTML_TEMPLATE,
}


Expand Down Expand Up @@ -218,6 +251,22 @@ def spec_to_html(
if mode == "vega-lite" and vegalite_version is None:
raise ValueError("must specify vega-lite version for mode='vega-lite'")

render_kwargs = dict()
if template == "inline":
try:
from altair_viewer import get_bundled_script
except ImportError:
raise ImportError(
"The altair_viewer package is required to convert to HTML with inline=True"
)
Comment on lines +255 to +261
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this approach versus adding altair_viewer as a required dependency for altair

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
)

template = TEMPLATES.get(template, template)
if not hasattr(template, "render"):
raise ValueError("Invalid template: {0}".format(template))
Expand All @@ -233,4 +282,5 @@ def spec_to_html(
output_div=output_div,
fullhtml=fullhtml,
requirejs=requirejs,
**render_kwargs,
)
17 changes: 15 additions & 2 deletions altair/utils/save.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import pathlib
import warnings

from .mimebundle import spec_to_mimebundle

Expand Down Expand Up @@ -27,11 +28,12 @@ def save(
webdriver=None,
scale_factor=1,
engine=None,
inline=False,
**kwargs,
):
"""Save a chart to file in a variety of formats
joelostblom marked this conversation as resolved.
Show resolved Hide resolved

Supported formats are [json, html, png, svg]
Supported formats are [json, html, png, svg, pdf]

Parameters
----------
Expand All @@ -40,7 +42,7 @@ def save(
fp : string filename, pathlib.Path or file-like object
file to which to write the chart.
format : string (optional)
the format to write: one of ['json', 'html', 'png', 'svg'].
the format to write: one of ['json', 'html', 'png', 'svg', 'pdf'].
If not specified, the format will be determined from the filename.
mode : string (optional)
Either 'vega' or 'vegalite'. If not specified, then infer the mode from
Expand All @@ -64,6 +66,12 @@ def save(
scale_factor to use to change size/resolution of png or svg output
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 altair_viewer package is required if True.
**kwargs :
additional kwargs passed to spec_to_mimebundle.
"""
Expand Down Expand Up @@ -99,10 +107,15 @@ def save(
if mode == "vega-lite" and vegalite_version is None:
raise ValueError("must specify vega-lite version")

if format != "html" and inline:
warnings.warn("inline argument ignored for non HTML formats.")

if format == "json":
json_spec = json.dumps(spec, **json_kwds)
write_file_or_filename(fp, json_spec, mode="w")
elif format == "html":
if inline:
kwargs["template"] = "inline"
mimebundle = spec_to_mimebundle(
spec=spec,
format=format,
Expand Down
17 changes: 17 additions & 0 deletions tests/vegalite/v5/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,23 @@ def test_save(format, engine, basic_chart):
os.remove(fp)


@pytest.mark.parametrize("inline", [False, True])
def test_save_html(basic_chart, inline):
out = io.StringIO()
basic_chart.save(out, format="html", inline=inline)
out.seek(0)
content = out.read()

assert content.startswith("<!DOCTYPE html>")

if inline:
assert '<script type="text/javascript">' in content
else:
assert 'src="https://cdn.jsdelivr.net/npm/vega@5' in content
assert 'src="https://cdn.jsdelivr.net/npm/vega-lite@5' in content
assert 'src="https://cdn.jsdelivr.net/npm/vega-embed@6' in content


def test_facet_basic():
# wrapped facet
chart1 = (
Expand Down