vlSelectionResolve is not being called on specific scenario

Hello everyone, I'm struggling with a bug I found when implementing a custom visualization using Vega. I'm reposting this from Stackoverflow (Where the Vega folks suggest posting) because nobody is awnsering, so I'm trying my luck here.

I've been working on a line chart with some functionality that pushed me to switch from vega-lite to vega, more especificaly I needed to bind two signals (which, as far as I know is not possible yet in vega-lite) in order to reset the legend values when I change the group.

There's this specific scenario where if you have legend values selected, move around the chart and then change the group value, the legendSelection signal is not cleared, so the lines in the new group are not visible (Because the signal is still holding the previous group values)

Why would the legendSelection (and all other related signals) be affected by the xZoom and yZoom signals?

Steps to recreate:

  1. Select a single or multiple values in the legend
  2. Pan the chart around
  3. Change the group value

image

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "description": "Google's stock price over time.",
  "background": "white",
  "padding": 5,
  "width": 200,
  "height": 200,
  "style": "cell",
  "data": [
    {"name": "xZoom_store"},
    {"name": "yZoom_store"},
    {"name": "legendSelection_store"},
    {
      "name": "source_0",
      "values": [
        {
          "dimention1": 1,
          "dimention2": "C",
          "data": [
            {"x": 1, "y": 1},
            {"x": 2, "y": 1},
            {"x": 3, "y": 1},
            {"x": 4, "y": 1},
            {"x": 5, "y": 1}
          ]
        },
        {
          "dimention1": 2,
          "dimention2": "B",
          "data": [
            {"x": 1, "y": 2},
            {"x": 2, "y": 2},
            {"x": 3, "y": 2},
            {"x": 4, "y": 2},
            {"x": 5, "y": 2}
          ]
        },
        {
          "dimention1": 3,
          "dimention2": "A",
          "data": [
            {"x": 1, "y": 3},
            {"x": 2, "y": 3},
            {"x": 3, "y": 3},
            {"x": 4, "y": 3},
            {"x": 5, "y": 3}
          ]
        }
      ]
    },
    {
      "name": "data_0",
      "source": "source_0",
      "transform": [
        {"type": "flatten", "fields": ["data"], "as": ["data"]},
        {
          "type": "formula",
          "expr": "datum[\"data\"] && datum[\"data\"][\"x\"]",
          "as": "data.x"
        },
        {
          "type": "formula",
          "expr": "datum[\"data\"] && datum[\"data\"][\"y\"]",
          "as": "data.y"
        },
        {
          "type": "fold",
          "fields": ["dimention1", "dimention2"],
          "as": ["key", "value"]
        },
        {"type": "filter", "expr": "group == datum.key"}
      ]
    }
  ],
  "signals": [
    {
      "name": "unit",
      "value": {},
      "on": [
        {"events": "mousemove", "update": "isTuple(group()) ? group() : unit"}
      ]
    },
    {
      "name": "legendSelection_value_legend",
      "value": null,
      "on": [
        {
          "events": [
            {
              "source": "view",
              "type": "click",
              "markname": "value_legend_symbols"
            },
            {
              "source": "view",
              "type": "click",
              "markname": "value_legend_labels"
            },
            {
              "source": "view",
              "type": "click",
              "markname": "value_legend_entries"
            }
          ],
          "update": "isDefined(datum.value) ? datum.value : item().items[0].items[0].datum.value",
          "force": true
        },
        {
          "events": [{"source": "view", "type": "click"}],
          "update": "!event.item || !datum ? null : legendSelection_value_legend",
          "force": true
        },
        {
          "events": [
            {"signal": "group"},
            {"source": "view", "type": "dblclick"}
          ],
          "update": "null",
          "force": true
        }
      ]
    },
    {
      "name": "xZoom",
      "update": "vlSelectionResolve(\"xZoom_store\", \"union\")"
    },
    {
      "name": "yZoom",
      "update": "vlSelectionResolve(\"yZoom_store\", \"union\")"
    },
    {
      "name": "legendSelection",
      "update": "vlSelectionResolve(\"legendSelection_store\", \"union\", true, true)"
    },
    {
      "name": "group",
      "value": "dimention1",
      "bind": {"input": "select", "options": ["dimention1", "dimention2"]}
    },
    {
      "name": "xZoom_data_x",
      "on": [
        {"events": [{"source": "view", "type": "dblclick"}], "update": "null"},
        {
          "events": {"signal": "xZoom_translate_delta"},
          "update": "panLinear(xZoom_translate_anchor.extent_x, -xZoom_translate_delta.x / width)"
        },
        {
          "events": {"signal": "xZoom_zoom_delta"},
          "update": "zoomLinear(domain(\"x\"), xZoom_zoom_anchor.x, xZoom_zoom_delta)"
        }
      ]
    },
    {
      "name": "xZoom_tuple",
      "on": [
        {
          "events": [{"signal": "xZoom_data_x"}],
          "update": "xZoom_data_x ? {unit: \"\", fields: xZoom_tuple_fields, values: [xZoom_data_x]} : null"
        }
      ]
    },
    {
      "name": "xZoom_tuple_fields",
      "value": [{"field": "data\\.x", "channel": "x", "type": "R"}]
    },
    {
      "name": "xZoom_translate_anchor",
      "value": {},
      "on": [
        {
          "events": [{"source": "scope", "type": "mousedown"}],
          "update": "{x: x(unit), y: y(unit), extent_x: domain(\"x\")}"
        }
      ]
    },
    {
      "name": "xZoom_translate_delta",
      "value": {},
      "on": [
        {
          "events": [
            {
              "source": "window",
              "type": "mousemove",
              "consume": true,
              "between": [
                {"source": "scope", "type": "mousedown"},
                {"source": "window", "type": "mouseup"}
              ]
            }
          ],
          "update": "{x: xZoom_translate_anchor.x - x(unit), y: xZoom_translate_anchor.y - y(unit)}"
        }
      ]
    },
    {
      "name": "xZoom_zoom_anchor",
      "on": [
        {
          "events": [
            {
              "source": "scope",
              "type": "wheel",
              "consume": true,
              "filter": ["event.shiftKey"]
            }
          ],
          "update": "{x: invert(\"x\", x(unit)), y: invert(\"y\", y(unit))}"
        }
      ]
    },
    {
      "name": "xZoom_zoom_delta",
      "on": [
        {
          "events": [
            {
              "source": "scope",
              "type": "wheel",
              "consume": true,
              "filter": ["event.shiftKey"]
            }
          ],
          "force": true,
          "update": "pow(1.001, event.deltaY * pow(16, event.deltaMode))"
        }
      ]
    },
    {
      "name": "xZoom_modify",
      "on": [
        {
          "events": {"signal": "xZoom_tuple"},
          "update": "modify(\"xZoom_store\", xZoom_tuple, true)"
        }
      ]
    },
    {
      "name": "yZoom_data_y",
      "on": [
        {"events": [{"source": "view", "type": "dblclick"}], "update": "null"},
        {
          "events": {"signal": "yZoom_translate_delta"},
          "update": "panLinear(yZoom_translate_anchor.extent_y, yZoom_translate_delta.y / height)"
        },
        {
          "events": {"signal": "yZoom_zoom_delta"},
          "update": "zoomLinear(domain(\"y\"), yZoom_zoom_anchor.y, yZoom_zoom_delta)"
        }
      ]
    },
    {
      "name": "yZoom_tuple",
      "on": [
        {
          "events": [{"signal": "yZoom_data_y"}],
          "update": "yZoom_data_y ? {unit: \"\", fields: yZoom_tuple_fields, values: [yZoom_data_y]} : null"
        }
      ]
    },
    {
      "name": "yZoom_tuple_fields",
      "value": [{"field": "data\\.y", "channel": "y", "type": "R"}]
    },
    {
      "name": "yZoom_translate_anchor",
      "value": {},
      "on": [
        {
          "events": [{"source": "scope", "type": "mousedown"}],
          "update": "{x: x(unit), y: y(unit), extent_y: domain(\"y\")}"
        }
      ]
    },
    {
      "name": "yZoom_translate_delta",
      "value": {},
      "on": [
        {
          "events": [
            {
              "source": "window",
              "type": "mousemove",
              "consume": true,
              "between": [
                {"source": "scope", "type": "mousedown"},
                {"source": "window", "type": "mouseup"}
              ]
            }
          ],
          "update": "{x: yZoom_translate_anchor.x - x(unit), y: yZoom_translate_anchor.y - y(unit)}"
        }
      ]
    },
    {
      "name": "yZoom_zoom_anchor",
      "on": [
        {
          "events": [
            {
              "source": "scope",
              "type": "wheel",
              "consume": true,
              "filter": ["!event.shiftKey"]
            }
          ],
          "update": "{x: invert(\"x\", x(unit)), y: invert(\"y\", y(unit))}"
        }
      ]
    },
    {
      "name": "yZoom_zoom_delta",
      "on": [
        {
          "events": [
            {
              "source": "scope",
              "type": "wheel",
              "consume": true,
              "filter": ["!event.shiftKey"]
            }
          ],
          "force": true,
          "update": "pow(1.001, event.deltaY * pow(16, event.deltaMode))"
        }
      ]
    },
    {
      "name": "yZoom_modify",
      "on": [
        {
          "events": {"signal": "yZoom_tuple"},
          "update": "modify(\"yZoom_store\", yZoom_tuple, true)"
        }
      ]
    },
    {
      "name": "legendSelection_tuple",
      "update": "legendSelection_value_legend !== null ? {fields: legendSelection_tuple_fields, values: [legendSelection_value_legend]} : null"
    },
    {
      "name": "legendSelection_tuple_fields",
      "value": [{"type": "E", "field": "value"}]
    },
    {
      "name": "legendSelection_toggle",
      "value": false,
      "on": [
        {
          "events": {"merge": [{"source": "view", "type": "click"}]},
          "update": "true"
        },
        {
          "events": [
            {"signal": "group"},
            {"source": "view", "type": "dblclick"}
          ],
          "update": "null",
          "force": true
        }
      ]
    },
    {
      "name": "legendSelection_modify",
      "on": [
        {
          "events": {"signal": "legendSelection_tuple"},
          "update": "modify(\"legendSelection_store\", legendSelection_toggle ? null : legendSelection_tuple, legendSelection_toggle ? null : true, legendSelection_toggle ? legendSelection_tuple : null)"
        }
      ]
    }
  ],
  "marks": [
    {
      "name": "pathgroup",
      "type": "group",
      "from": {
        "facet": {
          "name": "faceted_path_main",
          "data": "data_0",
          "groupby": ["value"]
        }
      },
      "encode": {
        "update": {
          "width": {"field": {"group": "width"}},
          "height": {"field": {"group": "height"}}
        }
      },
      "marks": [
        {
          "name": "marks",
          "type": "line",
          "clip": true,
          "style": ["line"],
          "sort": {"field": "datum[\"data.x\"]"},
          "interactive": true,
          "from": {"data": "faceted_path_main"},
          "encode": {
            "update": {
              "stroke": {"scale": "color", "field": "value"},
              "opacity": [
                {
                  "test": "!length(data(\"legendSelection_store\")) || vlSelectionTest(\"legendSelection_store\", datum)",
                  "value": 1
                },
                {"value": 0}
              ],
              "description": {
                "signal": "\"data.x: \" + (format(datum[\"data.x\"], \"\")) + \"; data.y: \" + (format(datum[\"data.y\"], \"\")) + \"; value: \" + (isValid(datum[\"value\"]) ? datum[\"value\"] : \"\"+datum[\"value\"])"
              },
              "x": {"scale": "x", "field": "data\\.x"},
              "y": {"scale": "y", "field": "data\\.y"},
              "defined": {
                "signal": "isValid(datum[\"data.x\"]) && isFinite(+datum[\"data.x\"]) && isValid(datum[\"data.y\"]) && isFinite(+datum[\"data.y\"])"
              }
            }
          }
        }
      ]
    }
  ],
  "scales": [
    {
      "name": "x",
      "type": "linear",
      "domain": {"data": "data_0", "field": "data\\.x"},
      "domainRaw": {"signal": "xZoom[\"data\\\\.x\"]"},
      "range": [0, {"signal": "width"}],
      "nice": true,
      "zero": false
    },
    {
      "name": "y",
      "type": "linear",
      "domain": {"data": "data_0", "field": "data\\.y"},
      "domainRaw": {"signal": "yZoom[\"data\\\\.y\"]"},
      "range": [{"signal": "height"}, 0],
      "nice": true,
      "zero": true
    },
    {
      "name": "color",
      "type": "ordinal",
      "domain": {"data": "data_0", "field": "value", "sort": true},
      "range": "category"
    }
  ],
  "axes": [
    {
      "scale": "x",
      "orient": "bottom",
      "gridScale": "y",
      "grid": true,
      "tickCount": {"signal": "ceil(width/40)"},
      "domain": false,
      "labels": false,
      "aria": false,
      "maxExtent": 0,
      "minExtent": 0,
      "ticks": false,
      "zindex": 0
    },
    {
      "scale": "y",
      "orient": "left",
      "gridScale": "x",
      "grid": true,
      "tickCount": {"signal": "ceil(height/40)"},
      "domain": false,
      "labels": false,
      "aria": false,
      "maxExtent": 0,
      "minExtent": 0,
      "ticks": false,
      "zindex": 0
    },
    {
      "scale": "x",
      "orient": "bottom",
      "grid": false,
      "title": "data.x",
      "labelFlush": true,
      "labelOverlap": true,
      "tickCount": {"signal": "ceil(width/40)"},
      "zindex": 0
    },
    {
      "scale": "y",
      "orient": "left",
      "grid": false,
      "title": "data.y",
      "labelOverlap": true,
      "tickCount": {"signal": "ceil(height/40)"},
      "zindex": 0
    }
  ],
  "legends": [
    {
      "stroke": "color",
      "symbolType": "stroke",
      "title": "value",
      "encode": {
        "labels": {
          "name": "value_legend_labels",
          "interactive": true,
          "update": {
            "opacity": [
              {
                "test": "(!length(data(\"legendSelection_store\")) || (legendSelection[\"value\"] && indexof(legendSelection[\"value\"], datum.value) >= 0))",
                "value": 1
              },
              {"value": 0.35}
            ]
          }
        },
        "symbols": {
          "name": "value_legend_symbols",
          "interactive": true,
          "update": {
            "opacity": [
              {
                "test": "(!length(data(\"legendSelection_store\")) || (legendSelection[\"value\"] && indexof(legendSelection[\"value\"], datum.value) >= 0))",
                "value": 1
              },
              {"value": 0.35}
            ]
          }
        },
        "entries": {
          "name": "value_legend_entries",
          "interactive": true,
          "update": {"fill": {"value": "transparent"}}
        }
      }
    }
  ]
}

Using the Record signal changes in the Online Vega Editor I can see that the legendSelection signal is cleared in every other scenario except this one.

The legendSelection signal is updated using the vlSelectionResolve function, which is not called in this particular case.

Is this a known bug? Am I missing something?

I would really appreciate your help

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.