altair
altair copied to clipboard
JavaScriptError - Multi Series Line Chart with Tooltip from Vega-Lite examples page
Hi all!
I have been using Altair to turn Vega-Lite specifications into html charts. I have an issue with some interactive chart templates from the Vega-Lite example library. Specifically, specifications that render proper html files in the Vega-Lite editor cannot be saved properly to html using altair.chart.save(). The issue looks similar to vega/vega-lite#7854.
For example, here is an interactive graph that I have been trying to save as html using Altair.
When I use exactly the same dictionary specification in Altair (up to boolean caps due to Python) and try:
chart = alt.Chart.from_dict(specification)
chart.save("chart.html")
the resulting html file cannot be read and I get the following error message:
For reference, the html file saved using Altair looks like this:
<!DOCTYPE html>
<html>
<head>
<style>
#vis.vega-embed {
width: 100%;
display: flex;
}
#vis.vega-embed details,
#vis.vega-embed details summary {
position: relative;
}
</style>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vega@5"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
</head>
<body>
<div id="vis"></div>
<script>
(function(vegaEmbed) {
var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300}}, "layer": [{"layer": [{"mark": "line"}, {"mark": "point", "transform": [{"filter": {"param": "hover", "empty": false}}]}], "encoding": {"color": {"field": "symbol", "type": "nominal"}, "y": {"field": "price", "type": "quantitative"}}}, {"mark": "rule", "encoding": {"opacity": {"condition": {"param": "hover", "value": 0.3, "empty": false}, "value": 0}, "tooltip": [{"field": "GOOGL", "type": "quantitative"}, {"field": "AAPL", "type": "quantitative"}]}, "transform": [{"pivot": "symbol", "value": "price", "groupby": ["date"]}]}], "data": {"name": "values"}, "datasets": {"values": [{"date": "2023-09-14T00:00:00+00:00", "price": 138.1, "symbol": "GOOGL"}, {"date": "2023-09-15T00:00:00+00:00", "price": 137.4, "symbol": "GOOGL"}, {"date": "2023-09-18T00:00:00+00:00", "price": 138.21, "symbol": "GOOGL"}, {"date": "2023-09-19T00:00:00+00:00", "price": 138.04, "symbol": "GOOGL"}, {"date": "2023-09-20T00:00:00+00:00", "price": 133.74, "symbol": "GOOGL"}, {"date": "2023-09-21T00:00:00+00:00", "price": 130.89999389648438, "symbol": "GOOGL"}, {"date": "2023-09-14T00:00:00+00:00", "price": 175.74, "symbol": "AAPL"}, {"date": "2023-09-15T00:00:00+00:00", "price": 175.01, "symbol": "AAPL"}, {"date": "2023-09-18T00:00:00+00:00", "price": 177.97, "symbol": "AAPL"}, {"date": "2023-09-19T00:00:00+00:00", "price": 179.07, "symbol": "AAPL"}, {"date": "2023-09-20T00:00:00+00:00", "price": 175.49, "symbol": "AAPL"}, {"date": "2023-09-21T00:00:00+00:00", "price": 174.6428985595703, "symbol": "AAPL"}]}, "encoding": {"x": {"field": "date", "type": "temporal"}}, "height": 300, "params": [{"name": "hover", "select": {"type": "point", "clear": "mouseout", "fields": ["date"], "nearest": true, "on": "mouseover"}, "views": []}], "width": 400, "$schema": "https://vega.github.io/schema/vega-lite/v5.json"};
var embedOpt = {"mode": "vega-lite"};
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('vis');
vegaEmbed("#vis", spec, embedOpt)
.catch(error => showError(el, error));
})(vegaEmbed);
</script>
</body>
</html>
Thanks for the report; I think you are correct that it is related to the parameters being lifted to top level. You can run the following to see which spec altair creates:
print(
alt.Chart.from_json(
'''{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {"name": "values"},
"width": 400,
"height": 300,
"encoding": {"x": {"field": "date", "type": "temporal"}},
"layer": [
{
"encoding": {
"color": {"field": "symbol", "type": "nominal"},
"y": {"field": "price", "type": "quantitative"}
},
"layer": [
{"mark": "line"},
{
"transform": [{"filter": {"param": "hover", "empty": false}}],
"mark": "point"
}
]
},
{
"transform": [{"pivot": "symbol", "value": "price", "groupby": ["date"]}],
"mark": "rule",
"encoding": {
"opacity": {
"condition": {"value": 0.3, "param": "hover", "empty": false},
"value": 0
},
"tooltip": [
{"field": "GOOGL", "type": "quantitative"},
{"field": "AAPL", "type": "quantitative"}
]
},
"params": [
{
"name": "hover",
"select": {
"type": "point",
"fields": ["date"],
"nearest": true,
"on": "mouseover",
"clear": "mouseout"
}
}
]
}
],
"datasets": {
"values": [
{"date": "2023-09-14T00:00:00+00:00", "price": 138.1, "symbol": "GOOGL"},
{"date": "2023-09-15T00:00:00+00:00", "price": 137.4, "symbol": "GOOGL"},
{"date": "2023-09-18T00:00:00+00:00", "price": 138.21, "symbol": "GOOGL"},
{"date": "2023-09-19T00:00:00+00:00", "price": 138.04, "symbol": "GOOGL"},
{"date": "2023-09-20T00:00:00+00:00", "price": 133.74, "symbol": "GOOGL"},
{
"date": "2023-09-21T00:00:00+00:00",
"price": 130.89999389648438,
"symbol": "GOOGL"
},
{"date": "2023-09-14T00:00:00+00:00", "price": 175.74, "symbol": "AAPL"},
{"date": "2023-09-15T00:00:00+00:00", "price": 175.01, "symbol": "AAPL"},
{"date": "2023-09-18T00:00:00+00:00", "price": 177.97, "symbol": "AAPL"},
{"date": "2023-09-19T00:00:00+00:00", "price": 179.07, "symbol": "AAPL"},
{"date": "2023-09-20T00:00:00+00:00", "price": 175.49, "symbol": "AAPL"},
{
"date": "2023-09-21T00:00:00+00:00",
"price": 174.6428985595703,
"symbol": "AAPL"
}
]
}
}'''
).to_json()
)
output:
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"config": {
"view": {
"continuousHeight": 300,
"continuousWidth": 300
}
},
"data": {
"name": "values"
},
"datasets": {
"values": [
{
"date": "2023-09-14T00:00:00+00:00",
"price": 138.1,
"symbol": "GOOGL"
},
{
"date": "2023-09-15T00:00:00+00:00",
"price": 137.4,
"symbol": "GOOGL"
},
{
"date": "2023-09-18T00:00:00+00:00",
"price": 138.21,
"symbol": "GOOGL"
},
{
"date": "2023-09-19T00:00:00+00:00",
"price": 138.04,
"symbol": "GOOGL"
},
{
"date": "2023-09-20T00:00:00+00:00",
"price": 133.74,
"symbol": "GOOGL"
},
{
"date": "2023-09-21T00:00:00+00:00",
"price": 130.89999389648438,
"symbol": "GOOGL"
},
{
"date": "2023-09-14T00:00:00+00:00",
"price": 175.74,
"symbol": "AAPL"
},
{
"date": "2023-09-15T00:00:00+00:00",
"price": 175.01,
"symbol": "AAPL"
},
{
"date": "2023-09-18T00:00:00+00:00",
"price": 177.97,
"symbol": "AAPL"
},
{
"date": "2023-09-19T00:00:00+00:00",
"price": 179.07,
"symbol": "AAPL"
},
{
"date": "2023-09-20T00:00:00+00:00",
"price": 175.49,
"symbol": "AAPL"
},
{
"date": "2023-09-21T00:00:00+00:00",
"price": 174.6428985595703,
"symbol": "AAPL"
}
]
},
"encoding": {
"x": {
"field": "date",
"type": "temporal"
}
},
"height": 300,
"layer": [
{
"encoding": {
"color": {
"field": "symbol",
"type": "nominal"
},
"y": {
"field": "price",
"type": "quantitative"
}
},
"layer": [
{
"mark": "line"
},
{
"mark": "point",
"transform": [
{
"filter": {
"empty": false,
"param": "hover"
}
}
]
}
]
},
{
"encoding": {
"opacity": {
"condition": {
"empty": false,
"param": "hover",
"value": 0.3
},
"value": 0
},
"tooltip": [
{
"field": "GOOGL",
"type": "quantitative"
},
{
"field": "AAPL",
"type": "quantitative"
}
]
},
"mark": "rule",
"transform": [
{
"groupby": [
"date"
],
"pivot": "symbol",
"value": "price"
}
]
}
],
"params": [
{
"name": "hover",
"select": {
"clear": "mouseout",
"fields": [
"date"
],
"nearest": true,
"on": "mouseover",
"type": "point"
},
"views": []
}
],
"width": 400
}
The params section looks the same in the output, but it is is lifted out from the layer to the top level. I am not sure what an action here would be but @ChristopherDavisUCI or @mattijn might have an idea.
Thank you for your response @joelostblom. It is a neat way of seeing what is going on!