处理交互式 Vega 传说中的事件 - 竞争条件
Handling events in interactive Vega legends - race condtion
linked chart仅包含一个图例,图例的作用如下:
- 单击水果名称可将其打开和关闭
- 按住 Shift 键并单击一个水果名称会将其打开并关闭所有其他水果名称
图例显示由两个实体控制:
- 数据集 SELECTED 记住所选项目
- 信号 FILTERMODE 在 include 和 exclude
之间切换过滤器类型
目前,如果只有一个水果名称打开,则单击它会将其关闭(因此所有水果名称都会关闭)。
我想修改此行为,以便单击最后启用的水果名称将打开所有内容。
(换句话说 - 不可能取消选择所有内容。)
为了打开一切,我只需要将信号 FILTERMODE 的值更改为 exclude。这是我遇到障碍的地方。
我在信号定义中尝试了以下内容:
"update": "event.shiftKey? 'include' : (length(data('selected'))? filtermode : 'exclude')",
这不起作用。我相当确定这是由于竞争条件而发生的。
当我检查数据的长度('source')时,它仍然是非空的。
所以事件的顺序如下:
- 点击
- 更新信号 FILTERMODE(检查数据集 SELECTED 是否为空 - 它不是)
- 更新数据集SELECTED(只是现在它已经变空了)
最优雅的解决方法是什么?
您检查的是正确数组的长度吗?很难准确理解所需的行为是什么,但如果我添加代码(取决于过滤模式是包含还是排除)
length(data('selected')) == 6
或
length(data('selected')) == 0
然后它似乎工作。
Editor
试试这个。它与您的代码相同,但也会检查您的单行当前不执行的数组长度。
现在可以shift点击甜瓜,然后正常点击,滤镜模式切换。
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "A scatter plot example with interactive legend and x-axis.",
"width": 200,
"height": 200,
"padding": 5,
"autosize": "pad",
"signals": [
{
"name": "shift",
"value": false,
"on": [
{
"events": "@legendSymbol:click, @legendLabel:click",
"update": "event.shiftKey",
"force": true
}
]
},
{
"name": "clicked",
"value": null,
"on": [
{
"events": "@legendSymbol:click, @legendLabel:click",
"update": "{value: datum.value}",
"force": true
}
]
},
{
"name": "filtermode",
"value": "exclude",
"on": [
{
"events": "@legendSymbol:click, @legendLabel:click",
"update": "event.shiftKey? 'include' : (length(data('selected') == 0)? filtermode : 'exclude')",
"force": true
}
]
}
],
"data": [
{
"name": "source",
"values": [
{"fruit": "apple"},
{"fruit": "plum"},
{"fruit": "pear"},
{"fruit": "melon"},
{"fruit": "grape"},
{"fruit": "strawberry"}
]
},
{
"name": "selected",
"on": [
{"trigger": "clicked && (event.shiftKey)", "remove": true},
{"trigger": "clicked && (event.shiftKey)", "insert": "clicked"},
{"trigger": "clicked && (!event.shiftKey)", "toggle": "clicked"}
]
}
],
"scales": [
{
"name": "color",
"type": "ordinal",
"range": {"scheme": "category10"},
"domain": {"data": "source", "field": "fruit"}
}
],
"legends": [
{
"stroke": "color",
"title": "Fruit",
"encode": {
"symbols": {
"name": "legendSymbol",
"interactive": true,
"update": {
"fill": {"value": "transparent"},
"strokeWidth": {"value": 2},
"opacity": [
{
"test": "filtermode == 'exclude' && !indata('selected', 'value', datum.value)",
"value": 1
},
{
"test": "filtermode == 'include' && indata('selected', 'value', datum.value)",
"value": 1
},
{"value": 0.15}
],
"size": {"value": 64}
}
},
"labels": {
"name": "legendLabel",
"interactive": true,
"update": {
"opacity": [
{
"test": "filtermode == 'exclude' && !indata('selected', 'value', datum.value)",
"value": 1
},
{
"test": "filtermode == 'include' && indata('selected', 'value', datum.value)",
"value": 1
},
{"value": 0.25}
]
}
}
}
}
]
}
linked chart仅包含一个图例,图例的作用如下:
- 单击水果名称可将其打开和关闭
- 按住 Shift 键并单击一个水果名称会将其打开并关闭所有其他水果名称
图例显示由两个实体控制:
- 数据集 SELECTED 记住所选项目
- 信号 FILTERMODE 在 include 和 exclude 之间切换过滤器类型
目前,如果只有一个水果名称打开,则单击它会将其关闭(因此所有水果名称都会关闭)。 我想修改此行为,以便单击最后启用的水果名称将打开所有内容。 (换句话说 - 不可能取消选择所有内容。)
为了打开一切,我只需要将信号 FILTERMODE 的值更改为 exclude。这是我遇到障碍的地方。 我在信号定义中尝试了以下内容:
"update": "event.shiftKey? 'include' : (length(data('selected'))? filtermode : 'exclude')",
这不起作用。我相当确定这是由于竞争条件而发生的。 当我检查数据的长度('source')时,它仍然是非空的。
所以事件的顺序如下:
- 点击
- 更新信号 FILTERMODE(检查数据集 SELECTED 是否为空 - 它不是)
- 更新数据集SELECTED(只是现在它已经变空了)
最优雅的解决方法是什么?
您检查的是正确数组的长度吗?很难准确理解所需的行为是什么,但如果我添加代码(取决于过滤模式是包含还是排除)
length(data('selected')) == 6
或
length(data('selected')) == 0
然后它似乎工作。 Editor
试试这个。它与您的代码相同,但也会检查您的单行当前不执行的数组长度。
现在可以shift点击甜瓜,然后正常点击,滤镜模式切换。
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "A scatter plot example with interactive legend and x-axis.",
"width": 200,
"height": 200,
"padding": 5,
"autosize": "pad",
"signals": [
{
"name": "shift",
"value": false,
"on": [
{
"events": "@legendSymbol:click, @legendLabel:click",
"update": "event.shiftKey",
"force": true
}
]
},
{
"name": "clicked",
"value": null,
"on": [
{
"events": "@legendSymbol:click, @legendLabel:click",
"update": "{value: datum.value}",
"force": true
}
]
},
{
"name": "filtermode",
"value": "exclude",
"on": [
{
"events": "@legendSymbol:click, @legendLabel:click",
"update": "event.shiftKey? 'include' : (length(data('selected') == 0)? filtermode : 'exclude')",
"force": true
}
]
}
],
"data": [
{
"name": "source",
"values": [
{"fruit": "apple"},
{"fruit": "plum"},
{"fruit": "pear"},
{"fruit": "melon"},
{"fruit": "grape"},
{"fruit": "strawberry"}
]
},
{
"name": "selected",
"on": [
{"trigger": "clicked && (event.shiftKey)", "remove": true},
{"trigger": "clicked && (event.shiftKey)", "insert": "clicked"},
{"trigger": "clicked && (!event.shiftKey)", "toggle": "clicked"}
]
}
],
"scales": [
{
"name": "color",
"type": "ordinal",
"range": {"scheme": "category10"},
"domain": {"data": "source", "field": "fruit"}
}
],
"legends": [
{
"stroke": "color",
"title": "Fruit",
"encode": {
"symbols": {
"name": "legendSymbol",
"interactive": true,
"update": {
"fill": {"value": "transparent"},
"strokeWidth": {"value": 2},
"opacity": [
{
"test": "filtermode == 'exclude' && !indata('selected', 'value', datum.value)",
"value": 1
},
{
"test": "filtermode == 'include' && indata('selected', 'value', datum.value)",
"value": 1
},
{"value": 0.15}
],
"size": {"value": 64}
}
},
"labels": {
"name": "legendLabel",
"interactive": true,
"update": {
"opacity": [
{
"test": "filtermode == 'exclude' && !indata('selected', 'value', datum.value)",
"value": 1
},
{
"test": "filtermode == 'include' && indata('selected', 'value', datum.value)",
"value": 1
},
{"value": 0.25}
]
}
}
}
}
]
}