Vega: width and height signals not reactive?

1k Views Asked by At

I'm trying to create a minimal Vega spec that resizes both its width and its height in response to the window changing dimensions.

This works: https://vega.github.io/editor/#/url/vega/N4IgJAzgxgFgpgWwIYgFwhgF0wBwqgegIDc4BzJAOjIEtMYBXAI0poHsDp5kTykSArJQBWENgDsQAGhBQJAMxpk0oMgCc2DHCpARMGgNZw0IJgBsGxgL5WZEJeKRmIaANqhHCY+hxIAJn404soyQXQmcuKYSEFwagDKNABecAAUAJSuAAwAugAEAFR5WZQAjABMAtIgEm6gWn5ImN6yEtGxCclpmbmFxWWV1XCkUS7oAO5BfmzjqGpw9ikgVjm2HkheJpN+9NVhmCapke3icYkpGdn5ALR55QW+AUFk6QUlAJzVtajuIA1NLSObRip06Fx6NzuD38gWCrw+QxGmDGIEm4mms3mi2sqyk6026HgSiwe3E4XQQKiILOXUupUh90esJeb0onxk31+-2ah2O1LB3Vc9Lyt0ZMOe8LZiLgoy2UxmcwWXWWq1WQA

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "config": {"group": {"stroke": "blue"}},
  "signals": [
    {
      "name": "padding",
      "init": "containerSize()[0] * 0.125",
      "on": [
        {"update": "containerSize()[0] * 0.125", "events": "window:resize"}
      ]
    },
    {
      "name": "width",
      "init": "(containerSize()[0] - 2*padding)*0.9",
      "on": [
        {
          "update": "(containerSize()[0] - 2*padding)*0.9",
          "events": "window:resize"
        }
      ]
    },
    {
      "name": "height",
      "init": "(containerSize()[1] - 2*padding)*0.9",
      "on": [
        {
          "update": "(containerSize()[1] - 2*padding)*0.9",
          "events": "window:resize"
        }
      ]
    }
  ]
}

To cut down on the horrible code redundancy in the above example, I tried introducing other signals. According to the docs, using an update property for the signal should be OK ("This expression may include other signals, in which case the signal will automatically update in response to upstream signal changes"). But it doesn't seem to be OK in the case of the height and width signals: https://vega.github.io/editor/#/url/vega/N4IgJAzgxgFgpgWwIYgFwhgF0wBwqgegIDc4BzJAOjIEtMYBXAI0poHsDp5kTykSArJQBWENgDsQAGhBQJAMxpk0oMgCc2DHCpARMGgNZw0IJgBsGxgL5WZEJeKRmIaANqhHCY+jnjMSGnE4NQB9ewAvYxlAuhNff0DggGUaSIAKAEppEAk3UC0AEyRMb1kJBKC1FPSsmThSPxd0AHdAgrZm1DU4COsAXVsPJC848oDKkPglLGzC4tL48eCw1LhXAEY+kEGQTwWxxNDWgvpZnCKS0b8l0N7XAAYtnb2THCQCgsDlGTnLnwOJlMyFgAAQAKhB90o6wATAJtlIhiMWjQTjAzhdSmlFocQsd6CCALQgmFgt4fL4ZMFQgCcCKRpSBMx+53mJmxAOWTMwRJJZPen3EZCptO2AyAA

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "config": {"group": {"stroke": "blue"}},
  "signals": [
    {
      "name": "container_size",
      "init": "containerSize()",
      "on": [{"update": "containerSize()", "events": "window:resize"}]
    },
    {"name": "container_height", "update": "container_size[1]"},
    {"name": "container_width", "update": "container_size[0]"},
    {"name": "padding", "update": "container_height * 0.125"},
    {"name": "width", "update": "(container_width - 2*padding)*0.9"},
    {"name": "height", "update": "(container_height - 2*padding)*0.9"}
  ]
}

With the above spec the result is tiny, just a few pixels long in either dimension, and it's also not responsive.

This is an attempt at working around the width and height not being responsive, basically I kept the multiple signals for eliminating redundancy in the spec, but added on properties for leaf signals with window:resize events: https://vega.github.io/editor/#/url/vega/N4IgJAzgxgFgpgWwIYgFwhgF0wBwqgegIDc4BzJAOjIEtMYBXAI0poHsDp5kTykSArJQBWENgDsQAGhBQJAMxpk0oMgCc2DHCpARMGgNZw0IJgBsGxgL5WZSBpjYQaAL2PpxE4zOdlxSMwg0AG1QfwR3WQlMJBpxODUAfWc3aRA4uhM5cRi4hIBlVzgACgBKNIkQ0C0AEyRMSOzc+LVCtzK0uFIcoPQAdziatj7UNTgU6wBdWzCkCKzo2JbE+CUsNNr6xsW8pInggEZJkBmQcO2cpYTEgZr6DZw6hoXL3eSi4IAGY9PzkxwkDUanEyIkAB4PJ4XZrXVZkLAAAgAVAjPpQDgAmAQnKSzeb9Gh3GDgyFbEzFJpXJK3egIgC0CIxSIBQJB4NKSLRAE4cXjInCsCSZJtnugKTtlgLMPTGczAcDxKCwRzubyznNIiyFcoZMQApY0AdPjJKqhQiARZr5WyITIunAeiYBuIhiMxhMTtNcer8Rg4GtMGk9RZ3EbjSBTebLSYpUKQPbHQSXcNRuMip7fhqnYT7rr9aHPuHI9VHmSCUS4wnML0QM7XamPVZptMgA

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "config": {"group": {"stroke": "blue"}},
  "autosize": "none",
  "signals": [
    {
      "name": "container_size",
      "init": "containerSize()",
      "on": [{"update": "containerSize()", "events": "window:resize"}]
    },
    {"name": "container_height", "update": "container_size[1]"},
    {"name": "container_width", "update": "container_size[0]"},
    {"name": "padding_x", "update": "container_height * 0.125"},
    {"name": "width_x", "update": "(container_width - 2*padding_x)*0.9"},
    {"name": "height_x", "update": "(container_height - 2*padding_x)*0.9"},
    {
      "name": "padding",
      "value": 10,
      "on": [{"update": "padding_x", "events": "window:resize"}]
    },
    {
      "name": "height",
      "value": 100,
      "on": [{"update": "height_x", "events": "window:resize"}]
    },
    {
      "name": "width",
      "value": 100,
      "on": [{"update": "width_x", "events": "window:resize"}]
    }
  ]
}

All of these specs seem to behave weirdly, and have different behavior in the Vega Editor compared to just including the JSON in the HTML with Vega Embed.

Also, although width and height are symmetrically specified in my specs, they behave differently in the resulting visualizations, so it seems they are handled differently by some Vega component.

So I'd say there are multiple bugs here in various Vega components.

2

There are 2 best solutions below

0
user2373145 On

If using Vega Embed, getting rid of the "actions" fixes this. This is done using the actions: false option.

Full example:

<!DOCTYPE html>
<html style="height: 100%; width: 100%;">

<head>
  <title>Microbenchmarking visualizations</title>
  <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
  <script src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
</head>

<body style="height: 100%; width: 100%; margin: 0;">
  <div id="view" style="height: 100%; width: 100%;"></div>

  <script type="text/javascript">
    vegaEmbed(
      '#view',
      {
        "$schema": "https://vega.github.io/schema/vega/v5.json",
        "config": { "group": { "stroke": "blue" } },
        "autosize": { "type": "none", "resize": true },
        "signals": [
          {
            "name": "container_size",
            "init": "containerSize()",
            "on": [{ "update": "containerSize()", "events": "window:resize" }]
          },
          { "name": "container_height", "update": "container_size[1]" },
          { "name": "container_width", "update": "container_size[0]" },
          { "name": "padding", "update": "container_width * 0.125" },
          { "name": "width", "update": "(container_width - 2*padding)*0.9" },
          { "name": "height", "update": "(container_height - 2*padding)*0.9" }
        ]
      }
      ,
      {
        renderer: 'svg',
        logLevel: vega.Info,
        actions: false
      });
  </script>
</body>

</html>

Not really a complete answer, but better than nothing.

1
Roy Ing On

For the chart to resize dynamically (i.e., "reactive") you can use the Vega top-level property "autosize": "fit"

Here are modified versions of the Vega example bar chart to show how the chart's "width" and "height" can be controlled by signals (named "width" and "height").

Example 1. Resize Vega chart using input

View in Vega's on-line editor

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "description": "A basic bar chart example, with value labels shown upon mouse hover.",
  "width": 400,
  "height": 200,
  "padding": 5,

  "autosize": {
    "type": "fit"
  },  

  "signals": [
    
    { "name": "width", "value": 400,
      "bind": {"input": "range", "min": 200, "max": 600, "step": 10} },

    { "name": "height", "value": 300,
      "bind": {"input": "range", "min": 200, "max": 400, "step": 10} },

enter image description here

Example 2. Vega chart dynamically resizes when browser window is resized

View in Vega's on-line editor

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "description": "A basic bar chart example, with value labels shown upon mouse hover.",
  "width": 400,
  "height": 200,
  "padding": 5,

  "autosize": {
    "type": "fit"
  },

 "signals": [
    { "name": "width",
      "value": 400,
      "on": [
        {
          "events": "window:resize",
          "update": "windowSize()[0] * 0.4"
        }
      ]
    },
    { "name": "height", 
      "value": 200,
      "on": [
        {
          "events": "window:resize",
          "update": "windowSize()[1] * 0.4"
        }
      ]
    },

enter image description here

About Vega expression "containerSize()": This function gets the size of the chart's "container". The "container" is the DOM element (e.g. <div id="vis">) that is specified when Vega is run.

Note that the Vega chart size in the online editor does not change if the "splitters" for the window panes are moved. This is becasue the Vega online editor has not bound the chart container to the right upper pane.

In your own application, you can set CSS of the Vega chart container (e.g. <div id="vis">) to be 100% of it's parent container, then the Vega chart will be resized automatically when the parent container is resized and "containerSize()" will get the new current size of Vega chart.