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:
- Select a single or multiple values in the legend
- Pan the chart around
- Change the
group
value
{
"$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