altair icon indicating copy to clipboard operation
altair copied to clipboard

JavaScriptError - Multi Series Line Chart with Tooltip from Vega-Lite examples page

Open tpellet opened this issue 9 months ago • 2 comments

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:

image

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>

tpellet avatar Sep 21 '23 19:09 tpellet

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.

joelostblom avatar Sep 21 '23 20:09 joelostblom

Thank you for your response @joelostblom. It is a neat way of seeing what is going on!

tpellet avatar Sep 21 '23 20:09 tpellet