Spaces:
Building
Building
import json | |
from typing import Optional, Dict | |
import jinja2 | |
from altair.utils._importers import import_vl_convert, vl_version_for_vl_convert | |
HTML_TEMPLATE = jinja2.Template( | |
""" | |
{%- if fullhtml -%} | |
<!DOCTYPE html> | |
<html> | |
<head> | |
{%- endif %} | |
<style> | |
#{{ output_div }}.vega-embed { | |
width: 100%; | |
display: flex; | |
} | |
#{{ output_div }}.vega-embed details, | |
#{{ output_div }}.vega-embed details summary { | |
position: relative; | |
} | |
</style> | |
{%- if not requirejs %} | |
<script type="text/javascript" src="{{ base_url }}/vega@{{ vega_version }}"></script> | |
{%- if mode == 'vega-lite' %} | |
<script type="text/javascript" src="{{ base_url }}/vega-lite@{{ vegalite_version }}"></script> | |
{%- endif %} | |
<script type="text/javascript" src="{{ base_url }}/vega-embed@{{ vegaembed_version }}"></script> | |
{%- endif %} | |
{%- if fullhtml %} | |
{%- if requirejs %} | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script> | |
<script> | |
requirejs.config({ | |
"paths": { | |
"vega": "{{ base_url }}/vega@{{ vega_version }}?noext", | |
"vega-lib": "{{ base_url }}/vega-lib?noext", | |
"vega-lite": "{{ base_url }}/vega-lite@{{ vegalite_version }}?noext", | |
"vega-embed": "{{ base_url }}/vega-embed@{{ vegaembed_version }}?noext", | |
} | |
}); | |
</script> | |
{%- endif %} | |
</head> | |
<body> | |
{%- endif %} | |
<div id="{{ output_div }}"></div> | |
<script> | |
{%- if requirejs and not fullhtml %} | |
requirejs.config({ | |
"paths": { | |
"vega": "{{ base_url }}/vega@{{ vega_version }}?noext", | |
"vega-lib": "{{ base_url }}/vega-lib?noext", | |
"vega-lite": "{{ base_url }}/vega-lite@{{ vegalite_version }}?noext", | |
"vega-embed": "{{ base_url }}/vega-embed@{{ vegaembed_version }}?noext", | |
} | |
}); | |
{% endif %} | |
{% if requirejs -%} | |
require(['vega-embed'], | |
{%- else -%} | |
( | |
{%- endif -%} | |
function(vegaEmbed) { | |
var spec = {{ spec }}; | |
var embedOpt = {{ embed_options }}; | |
function showError(el, error){ | |
el.innerHTML = ('<div style="color:red;">' | |
+ '<p>JavaScript Error: ' + error.message + '</p>' | |
+ "<p>This usually means there's a typo in your chart specification. " | |
+ "See the javascript console for the full traceback.</p>" | |
+ '</div>'); | |
throw error; | |
} | |
const el = document.getElementById('{{ output_div }}'); | |
vegaEmbed("#{{ output_div }}", spec, embedOpt) | |
.catch(error => showError(el, error)); | |
}){% if not requirejs %}(vegaEmbed){% endif %}; | |
</script> | |
{%- if fullhtml %} | |
</body> | |
</html> | |
{%- endif %} | |
""" | |
) | |
HTML_TEMPLATE_UNIVERSAL = jinja2.Template( | |
""" | |
<style> | |
#{{ output_div }}.vega-embed { | |
width: 100%; | |
display: flex; | |
} | |
#{{ output_div }}.vega-embed details, | |
#{{ output_div }}.vega-embed details summary { | |
position: relative; | |
} | |
</style> | |
<div id="{{ output_div }}"></div> | |
<script type="text/javascript"> | |
var VEGA_DEBUG = (typeof VEGA_DEBUG == "undefined") ? {} : VEGA_DEBUG; | |
(function(spec, embedOpt){ | |
let outputDiv = document.currentScript.previousElementSibling; | |
if (outputDiv.id !== "{{ output_div }}") { | |
outputDiv = document.getElementById("{{ output_div }}"); | |
} | |
const paths = { | |
"vega": "{{ base_url }}/vega@{{ vega_version }}?noext", | |
"vega-lib": "{{ base_url }}/vega-lib?noext", | |
"vega-lite": "{{ base_url }}/vega-lite@{{ vegalite_version }}?noext", | |
"vega-embed": "{{ base_url }}/vega-embed@{{ vegaembed_version }}?noext", | |
}; | |
function maybeLoadScript(lib, version) { | |
var key = `${lib.replace("-", "")}_version`; | |
return (VEGA_DEBUG[key] == version) ? | |
Promise.resolve(paths[lib]) : | |
new Promise(function(resolve, reject) { | |
var s = document.createElement('script'); | |
document.getElementsByTagName("head")[0].appendChild(s); | |
s.async = true; | |
s.onload = () => { | |
VEGA_DEBUG[key] = version; | |
return resolve(paths[lib]); | |
}; | |
s.onerror = () => reject(`Error loading script: ${paths[lib]}`); | |
s.src = paths[lib]; | |
}); | |
} | |
function showError(err) { | |
outputDiv.innerHTML = `<div class="error" style="color:red;">${err}</div>`; | |
throw err; | |
} | |
function displayChart(vegaEmbed) { | |
vegaEmbed(outputDiv, spec, embedOpt) | |
.catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`)); | |
} | |
if(typeof define === "function" && define.amd) { | |
requirejs.config({paths}); | |
require(["vega-embed"], displayChart, err => showError(`Error loading script: ${err.message}`)); | |
} else { | |
maybeLoadScript("vega", "{{vega_version}}") | |
.then(() => maybeLoadScript("vega-lite", "{{vegalite_version}}")) | |
.then(() => maybeLoadScript("vega-embed", "{{vegaembed_version}}")) | |
.catch(showError) | |
.then(() => displayChart(vegaEmbed)); | |
} | |
})({{ spec }}, {{ embed_options }}); | |
</script> | |
""" | |
) | |
# 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> | |
<style> | |
#{{ output_div }}.vega-embed { | |
width: 100%; | |
display: flex; | |
} | |
#{{ output_div }}.vega-embed details, | |
#{{ output_div }}.vega-embed details summary { | |
position: relative; | |
} | |
</style> | |
<script type="text/javascript"> | |
// vega-embed.js bundle with Vega-Lite version v{{ vegalite_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: Dict[str, jinja2.Template] = { | |
"standard": HTML_TEMPLATE, | |
"universal": HTML_TEMPLATE_UNIVERSAL, | |
"inline": INLINE_HTML_TEMPLATE, | |
} | |
def spec_to_html( | |
spec: dict, | |
mode: str, | |
vega_version: Optional[str], | |
vegaembed_version: Optional[str], | |
vegalite_version: Optional[str] = None, | |
base_url: str = "https://cdn.jsdelivr.net/npm", | |
output_div: str = "vis", | |
embed_options: Optional[dict] = None, | |
json_kwds: Optional[dict] = None, | |
fullhtml: bool = True, | |
requirejs: bool = False, | |
template: str = "standard", | |
) -> str: | |
"""Embed a Vega/Vega-Lite spec into an HTML page | |
Parameters | |
---------- | |
spec : dict | |
a dictionary representing a vega-lite plot spec. | |
mode : string {'vega' | 'vega-lite'} | |
The rendering mode. This value is overridden by embed_options['mode'], | |
if it is present. | |
vega_version : string | |
For html output, the version of vega.js to use. | |
vegalite_version : string | |
For html output, the version of vegalite.js to use. | |
vegaembed_version : string | |
For html output, the version of vegaembed.js to use. | |
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 | |
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', 'inline'}. Otherwise, it | |
can be a jinja2.Template object containing a custom template. | |
Returns | |
------- | |
output : string | |
an HTML string for rendering the chart. | |
""" | |
embed_options = embed_options or {} | |
json_kwds = json_kwds or {} | |
mode = embed_options.setdefault("mode", mode) | |
if mode not in ["vega", "vega-lite"]: | |
raise ValueError("mode must be either 'vega' or 'vega-lite'") | |
if vega_version is None: | |
raise ValueError("must specify vega_version") | |
if vegaembed_version is None: | |
raise ValueError("must specify vegaembed_version") | |
if mode == "vega-lite" and vegalite_version is None: | |
raise ValueError("must specify vega-lite version for mode='vega-lite'") | |
render_kwargs = {} | |
if template == "inline": | |
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"): | |
raise ValueError("Invalid template: {0}".format(jinja_template)) | |
return jinja_template.render( | |
spec=json.dumps(spec, **json_kwds), | |
embed_options=json.dumps(embed_options), | |
mode=mode, | |
vega_version=vega_version, | |
vegalite_version=vegalite_version, | |
vegaembed_version=vegaembed_version, | |
base_url=base_url, | |
output_div=output_div, | |
fullhtml=fullhtml, | |
requirejs=requirejs, | |
**render_kwargs, | |
) | |