From 14565eed0b8268d27e56ff74f762b158fa5ebd6d Mon Sep 17 00:00:00 2001 From: Arvind Satyanarayan Date: Thu, 7 Apr 2022 18:07:49 -0400 Subject: [PATCH] feat: optimize ID-driven selections (#7933) Co-authored-by: GitHub Actions Bot --- examples/compiled/concat_hover.vg.json | 21 ++++------- examples/compiled/concat_hover_filter.vg.json | 19 ++++------ .../interactive_bar_select_highlight.vg.json | 28 +++++++------- .../interactive_multi_line_tooltip.vg.json | 13 +++---- .../compiled/interactive_paintbrush.vg.json | 13 +++---- .../interactive_paintbrush_color.vg.json | 13 +++---- ...teractive_paintbrush_color_nearest.vg.json | 13 +++---- ...nteractive_paintbrush_simple_false.vg.json | 13 +++---- ...interactive_paintbrush_simple_true.vg.json | 13 +++---- examples/compiled/selection_heatmap.vg.json | 17 ++++----- examples/compiled/selection_insert.vg.json | 13 +++---- .../selection_multi_condition.vg.json | 13 +++---- .../compiled/selection_project_multi.vg.json | 12 +++--- .../compiled/selection_project_single.vg.json | 12 +++--- .../compiled/selection_toggle_altKey.vg.json | 13 +++---- .../selection_toggle_altKey_shiftKey.vg.json | 13 +++---- .../selection_toggle_shiftKey.vg.json | 13 +++---- .../compiled/selection_type_point.vg.json | 10 +++-- .../selection_type_single_dblclick.vg.json | 10 +++-- .../selection_type_single_mouseover.vg.json | 10 +++-- package.json | 1 + site/_includes/docs_toc.md | 2 - src/compile/selection/assemble.ts | 22 +++++++---- src/compile/selection/index.ts | 5 +-- src/compile/selection/parse.ts | 6 +-- src/compile/selection/point.ts | 37 +++++++++++-------- src/compile/selection/project.ts | 8 +++- test-runtime/discrete.test.ts | 5 +-- test/compile/selection/clear.test.ts | 6 +-- test/compile/selection/point.test.ts | 27 ++++++++++---- test/compile/selection/predicate.test.ts | 26 +++++++------ 31 files changed, 220 insertions(+), 207 deletions(-) diff --git a/examples/compiled/concat_hover.vg.json b/examples/compiled/concat_hover.vg.json index cc80790be5a..891fdd35306 100644 --- a/examples/compiled/concat_hover.vg.json +++ b/examples/compiled/concat_hover.vg.json @@ -5,7 +5,10 @@ "padding": 5, "height": 200, "data": [ - {"name": "hover_store"}, + { + "name": "hover_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -65,7 +68,7 @@ "on": [ { "events": [{"source": "scope", "type": "mouseover"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"concat_0\", fields: hover_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"concat_0\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, { @@ -74,10 +77,6 @@ } ] }, - { - "name": "hover_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "hover_toggle", "value": false, @@ -115,7 +114,7 @@ "fill": {"value": "transparent"}, "stroke": [ { - "test": "length(data(\"hover_store\")) && vlSelectionTest(\"hover_store\", datum)", + "test": "length(data(\"hover_store\")) && vlSelectionIdTest(\"hover_store\", datum)", "scale": "color", "field": "Cylinders" }, @@ -197,7 +196,7 @@ "on": [ { "events": [{"source": "scope", "type": "mouseover"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"concat_1\", fields: hover_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"concat_1\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, { @@ -206,10 +205,6 @@ } ] }, - { - "name": "hover_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "hover_toggle", "value": false, @@ -247,7 +242,7 @@ "fill": {"value": "transparent"}, "stroke": [ { - "test": "length(data(\"hover_store\")) && vlSelectionTest(\"hover_store\", datum)", + "test": "length(data(\"hover_store\")) && vlSelectionIdTest(\"hover_store\", datum)", "scale": "color", "field": "Cylinders" }, diff --git a/examples/compiled/concat_hover_filter.vg.json b/examples/compiled/concat_hover_filter.vg.json index a3991765ab3..1407fe2aab5 100644 --- a/examples/compiled/concat_hover_filter.vg.json +++ b/examples/compiled/concat_hover_filter.vg.json @@ -5,7 +5,10 @@ "padding": 5, "height": 200, "data": [ - {"name": "hover_store"}, + { + "name": "hover_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -28,7 +31,7 @@ "transform": [ { "type": "filter", - "expr": "length(data(\"hover_store\")) && vlSelectionTest(\"hover_store\", datum)" + "expr": "length(data(\"hover_store\")) && vlSelectionIdTest(\"hover_store\", datum)" } ] }, @@ -95,7 +98,7 @@ "on": [ { "events": [{"source": "scope", "type": "mouseover"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"concat_0_layer_0\", fields: hover_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"concat_0_layer_0\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, { @@ -104,10 +107,6 @@ } ] }, - { - "name": "hover_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "hover_toggle", "value": false, @@ -240,7 +239,7 @@ "on": [ { "events": [{"source": "scope", "type": "mouseover"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"concat_1_layer_0\", fields: hover_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"concat_1_layer_0\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, { @@ -249,10 +248,6 @@ } ] }, - { - "name": "hover_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "hover_toggle", "value": false, diff --git a/examples/compiled/interactive_bar_select_highlight.vg.json b/examples/compiled/interactive_bar_select_highlight.vg.json index 341082f21d2..ce0b0aa6477 100644 --- a/examples/compiled/interactive_bar_select_highlight.vg.json +++ b/examples/compiled/interactive_bar_select_highlight.vg.json @@ -6,8 +6,14 @@ "height": 200, "style": "cell", "data": [ - {"name": "highlight_store"}, - {"name": "select_store"}, + { + "name": "highlight_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, + { + "name": "select_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "values": [ @@ -68,16 +74,12 @@ "on": [ { "events": [{"source": "scope", "type": "mouseover"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: highlight_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "highlight_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "highlight_toggle", "value": false, @@ -103,16 +105,12 @@ "on": [ { "events": [{"source": "scope", "type": "click"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: select_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "select_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "select_toggle", "value": false, @@ -148,18 +146,18 @@ "cursor": {"value": "pointer"}, "fillOpacity": [ { - "test": "!length(data(\"select_store\")) || vlSelectionTest(\"select_store\", datum)", + "test": "!length(data(\"select_store\")) || vlSelectionIdTest(\"select_store\", datum)", "value": 1 }, {"value": 0.3} ], "strokeWidth": [ { - "test": "length(data(\"select_store\")) && vlSelectionTest(\"select_store\", datum)", + "test": "length(data(\"select_store\")) && vlSelectionIdTest(\"select_store\", datum)", "value": 2 }, { - "test": "length(data(\"highlight_store\")) && vlSelectionTest(\"highlight_store\", datum)", + "test": "length(data(\"highlight_store\")) && vlSelectionIdTest(\"highlight_store\", datum)", "value": 1 }, {"value": 0} diff --git a/examples/compiled/interactive_multi_line_tooltip.vg.json b/examples/compiled/interactive_multi_line_tooltip.vg.json index f7bc24edf2d..fbd9e7b8b50 100644 --- a/examples/compiled/interactive_multi_line_tooltip.vg.json +++ b/examples/compiled/interactive_multi_line_tooltip.vg.json @@ -6,7 +6,10 @@ "height": 200, "style": "cell", "data": [ - {"name": "hover_store"}, + { + "name": "hover_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/seattle-weather.csv", @@ -49,16 +52,12 @@ "on": [ { "events": [{"source": "scope", "type": "mouseover"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"layer_2\", fields: hover_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"layer_2\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "hover_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "hover_toggle", "value": false, @@ -139,7 +138,7 @@ "update": { "stroke": [ { - "test": "length(data(\"hover_store\")) && vlSelectionTest(\"hover_store\", datum)", + "test": "length(data(\"hover_store\")) && vlSelectionIdTest(\"hover_store\", datum)", "value": "black" }, {"value": "transparent"} diff --git a/examples/compiled/interactive_paintbrush.vg.json b/examples/compiled/interactive_paintbrush.vg.json index d66ffbba694..f1d71d5cdbc 100644 --- a/examples/compiled/interactive_paintbrush.vg.json +++ b/examples/compiled/interactive_paintbrush.vg.json @@ -7,7 +7,10 @@ "height": 200, "style": "cell", "data": [ - {"name": "paintbrush_store"}, + { + "name": "paintbrush_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -40,16 +43,12 @@ "events": [ {"source": "scope", "type": "mouseover", "markname": "voronoi"} ], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: paintbrush_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "paintbrush_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "paintbrush_toggle", "value": false, @@ -93,7 +92,7 @@ "y": {"scale": "y", "field": "Miles_per_Gallon"}, "size": [ { - "test": "!length(data(\"paintbrush_store\")) || vlSelectionTest(\"paintbrush_store\", datum)", + "test": "!length(data(\"paintbrush_store\")) || vlSelectionIdTest(\"paintbrush_store\", datum)", "value": 300 }, {"value": 50} diff --git a/examples/compiled/interactive_paintbrush_color.vg.json b/examples/compiled/interactive_paintbrush_color.vg.json index 913a4f1e073..c8eea9f2d72 100644 --- a/examples/compiled/interactive_paintbrush_color.vg.json +++ b/examples/compiled/interactive_paintbrush_color.vg.json @@ -7,7 +7,10 @@ "height": 200, "style": "cell", "data": [ - {"name": "paintbrush_store"}, + { + "name": "paintbrush_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -38,16 +41,12 @@ "on": [ { "events": [{"source": "scope", "type": "mouseover"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: paintbrush_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "paintbrush_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "paintbrush_toggle", "value": false, @@ -81,7 +80,7 @@ "opacity": {"value": 0.7}, "fill": [ { - "test": "!length(data(\"paintbrush_store\")) || vlSelectionTest(\"paintbrush_store\", datum)", + "test": "!length(data(\"paintbrush_store\")) || vlSelectionIdTest(\"paintbrush_store\", datum)", "scale": "color", "field": "Cylinders" }, diff --git a/examples/compiled/interactive_paintbrush_color_nearest.vg.json b/examples/compiled/interactive_paintbrush_color_nearest.vg.json index db0b103e362..02c98ed76e3 100644 --- a/examples/compiled/interactive_paintbrush_color_nearest.vg.json +++ b/examples/compiled/interactive_paintbrush_color_nearest.vg.json @@ -7,7 +7,10 @@ "height": 200, "style": "cell", "data": [ - {"name": "paintbrush_store"}, + { + "name": "paintbrush_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -40,16 +43,12 @@ "events": [ {"source": "scope", "type": "mouseover", "markname": "voronoi"} ], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: paintbrush_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "paintbrush_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "paintbrush_toggle", "value": false, @@ -85,7 +84,7 @@ "opacity": {"value": 0.7}, "fill": [ { - "test": "!length(data(\"paintbrush_store\")) || vlSelectionTest(\"paintbrush_store\", datum)", + "test": "!length(data(\"paintbrush_store\")) || vlSelectionIdTest(\"paintbrush_store\", datum)", "scale": "color", "field": "Cylinders" }, diff --git a/examples/compiled/interactive_paintbrush_simple_false.vg.json b/examples/compiled/interactive_paintbrush_simple_false.vg.json index 7a89c340b85..1ff770e2202 100644 --- a/examples/compiled/interactive_paintbrush_simple_false.vg.json +++ b/examples/compiled/interactive_paintbrush_simple_false.vg.json @@ -6,7 +6,10 @@ "height": 200, "style": "cell", "data": [ - {"name": "paintbrush_store"}, + { + "name": "paintbrush_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -38,16 +41,12 @@ "on": [ { "events": [{"source": "scope", "type": "mouseover"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: paintbrush_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "paintbrush_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "paintbrush_toggle", "value": false, @@ -92,7 +91,7 @@ "y": {"scale": "y", "field": "Miles_per_Gallon"}, "size": [ { - "test": "length(data(\"paintbrush_store\")) && vlSelectionTest(\"paintbrush_store\", datum)", + "test": "length(data(\"paintbrush_store\")) && vlSelectionIdTest(\"paintbrush_store\", datum)", "value": 300 }, {"value": 50} diff --git a/examples/compiled/interactive_paintbrush_simple_true.vg.json b/examples/compiled/interactive_paintbrush_simple_true.vg.json index f039fd7d290..6456a391a38 100644 --- a/examples/compiled/interactive_paintbrush_simple_true.vg.json +++ b/examples/compiled/interactive_paintbrush_simple_true.vg.json @@ -6,7 +6,10 @@ "height": 200, "style": "cell", "data": [ - {"name": "paintbrush_store"}, + { + "name": "paintbrush_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -38,16 +41,12 @@ "on": [ { "events": [{"source": "scope", "type": "mouseover"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: paintbrush_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "paintbrush_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "paintbrush_toggle", "value": false, @@ -92,7 +91,7 @@ "y": {"scale": "y", "field": "Miles_per_Gallon"}, "size": [ { - "test": "!length(data(\"paintbrush_store\")) || vlSelectionTest(\"paintbrush_store\", datum)", + "test": "!length(data(\"paintbrush_store\")) || vlSelectionIdTest(\"paintbrush_store\", datum)", "value": 300 }, {"value": 50} diff --git a/examples/compiled/selection_heatmap.vg.json b/examples/compiled/selection_heatmap.vg.json index 6dcbcf9d26f..0871bb40c23 100644 --- a/examples/compiled/selection_heatmap.vg.json +++ b/examples/compiled/selection_heatmap.vg.json @@ -4,7 +4,10 @@ "padding": 5, "style": "cell", "data": [ - {"name": "highlight_store"}, + { + "name": "highlight_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "values": [ @@ -55,16 +58,12 @@ "on": [ { "events": [{"source": "scope", "type": "click"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: highlight_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "highlight_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "highlight_toggle", "value": false, @@ -99,21 +98,21 @@ "fill": {"scale": "fill", "field": "count"}, "stroke": [ { - "test": "length(data(\"highlight_store\")) && vlSelectionTest(\"highlight_store\", datum)", + "test": "length(data(\"highlight_store\")) && vlSelectionIdTest(\"highlight_store\", datum)", "value": "black" }, {"value": null} ], "opacity": [ { - "test": "!length(data(\"highlight_store\")) || vlSelectionTest(\"highlight_store\", datum)", + "test": "!length(data(\"highlight_store\")) || vlSelectionIdTest(\"highlight_store\", datum)", "value": 1 }, {"value": 0.5} ], "zindex": [ { - "test": "!length(data(\"highlight_store\")) || vlSelectionTest(\"highlight_store\", datum)", + "test": "!length(data(\"highlight_store\")) || vlSelectionIdTest(\"highlight_store\", datum)", "value": 1 }, {"value": 0} diff --git a/examples/compiled/selection_insert.vg.json b/examples/compiled/selection_insert.vg.json index a8ab7d3ec1d..575aaf76e3a 100644 --- a/examples/compiled/selection_insert.vg.json +++ b/examples/compiled/selection_insert.vg.json @@ -6,7 +6,10 @@ "height": 200, "style": "cell", "data": [ - {"name": "paintbrush_store"}, + { + "name": "paintbrush_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -37,16 +40,12 @@ "on": [ { "events": [{"source": "scope", "type": "click"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: paintbrush_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "paintbrush_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "paintbrush_modify", "on": [ @@ -69,7 +68,7 @@ "opacity": {"value": 0.7}, "fill": [ { - "test": "!length(data(\"paintbrush_store\")) || vlSelectionTest(\"paintbrush_store\", datum)", + "test": "!length(data(\"paintbrush_store\")) || vlSelectionIdTest(\"paintbrush_store\", datum)", "scale": "color", "field": "Cylinders" }, diff --git a/examples/compiled/selection_multi_condition.vg.json b/examples/compiled/selection_multi_condition.vg.json index 7a3315fe41e..3925ac4a3e1 100644 --- a/examples/compiled/selection_multi_condition.vg.json +++ b/examples/compiled/selection_multi_condition.vg.json @@ -8,7 +8,10 @@ "style": "cell", "data": [ {"name": "brush_store"}, - {"name": "hoverbrush_store"}, + { + "name": "hoverbrush_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -267,16 +270,12 @@ "events": [ {"source": "scope", "type": "mouseover", "markname": "voronoi"} ], - "update": "datum && item().mark.marktype !== 'group' && indexof(item().mark.name, 'brush_brush') < 0 ? {unit: \"\", fields: hoverbrush_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' && indexof(item().mark.name, 'brush_brush') < 0 ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "hoverbrush_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "hoverbrush_toggle", "value": false, @@ -351,7 +350,7 @@ "fill": {"value": "transparent"}, "stroke": [ { - "test": "!length(data(\"hoverbrush_store\")) || vlSelectionTest(\"hoverbrush_store\", datum)", + "test": "!length(data(\"hoverbrush_store\")) || vlSelectionIdTest(\"hoverbrush_store\", datum)", "value": "teal" }, { diff --git a/examples/compiled/selection_project_multi.vg.json b/examples/compiled/selection_project_multi.vg.json index 4bb92d02104..deda91826ca 100644 --- a/examples/compiled/selection_project_multi.vg.json +++ b/examples/compiled/selection_project_multi.vg.json @@ -6,7 +6,10 @@ "height": 200, "style": "cell", "data": [ - {"name": "pts_store"}, + { + "name": "pts_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -37,13 +40,12 @@ "on": [ { "events": [{"source": "scope", "type": "click"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: pts_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - {"name": "pts_tuple_fields", "value": [{"type": "E", "field": "_vgsid_"}]}, { "name": "pts_toggle", "value": false, @@ -78,7 +80,7 @@ "fill": {"value": "transparent"}, "stroke": [ { - "test": "!length(data(\"pts_store\")) || vlSelectionTest(\"pts_store\", datum)", + "test": "!length(data(\"pts_store\")) || vlSelectionIdTest(\"pts_store\", datum)", "scale": "color", "field": "Cylinders" }, @@ -92,7 +94,7 @@ "y": {"scale": "y", "field": "Miles_per_Gallon"}, "size": [ { - "test": "length(data(\"pts_store\")) && vlSelectionTest(\"pts_store\", datum)", + "test": "length(data(\"pts_store\")) && vlSelectionIdTest(\"pts_store\", datum)", "value": 200 }, {"value": 50} diff --git a/examples/compiled/selection_project_single.vg.json b/examples/compiled/selection_project_single.vg.json index 5cee51d6533..59c8b70347e 100644 --- a/examples/compiled/selection_project_single.vg.json +++ b/examples/compiled/selection_project_single.vg.json @@ -6,7 +6,10 @@ "height": 200, "style": "cell", "data": [ - {"name": "pts_store"}, + { + "name": "pts_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -37,13 +40,12 @@ "on": [ { "events": [{"source": "scope", "type": "click"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: pts_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - {"name": "pts_tuple_fields", "value": [{"type": "E", "field": "_vgsid_"}]}, { "name": "pts_modify", "on": [ @@ -67,7 +69,7 @@ "fill": {"value": "transparent"}, "stroke": [ { - "test": "!length(data(\"pts_store\")) || vlSelectionTest(\"pts_store\", datum)", + "test": "!length(data(\"pts_store\")) || vlSelectionIdTest(\"pts_store\", datum)", "scale": "color", "field": "Cylinders" }, @@ -81,7 +83,7 @@ "y": {"scale": "y", "field": "Miles_per_Gallon"}, "size": [ { - "test": "!length(data(\"pts_store\")) || vlSelectionTest(\"pts_store\", datum)", + "test": "!length(data(\"pts_store\")) || vlSelectionIdTest(\"pts_store\", datum)", "value": 200 }, {"value": 50} diff --git a/examples/compiled/selection_toggle_altKey.vg.json b/examples/compiled/selection_toggle_altKey.vg.json index 362b0a21d9c..07928502140 100644 --- a/examples/compiled/selection_toggle_altKey.vg.json +++ b/examples/compiled/selection_toggle_altKey.vg.json @@ -6,7 +6,10 @@ "height": 200, "style": "cell", "data": [ - {"name": "paintbrush_store"}, + { + "name": "paintbrush_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -37,16 +40,12 @@ "on": [ { "events": [{"source": "scope", "type": "click"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: paintbrush_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "paintbrush_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "paintbrush_toggle", "value": false, @@ -80,7 +79,7 @@ "opacity": {"value": 0.7}, "fill": [ { - "test": "!length(data(\"paintbrush_store\")) || vlSelectionTest(\"paintbrush_store\", datum)", + "test": "!length(data(\"paintbrush_store\")) || vlSelectionIdTest(\"paintbrush_store\", datum)", "scale": "color", "field": "Cylinders" }, diff --git a/examples/compiled/selection_toggle_altKey_shiftKey.vg.json b/examples/compiled/selection_toggle_altKey_shiftKey.vg.json index 580346ab5fd..e085b61dbe1 100644 --- a/examples/compiled/selection_toggle_altKey_shiftKey.vg.json +++ b/examples/compiled/selection_toggle_altKey_shiftKey.vg.json @@ -6,7 +6,10 @@ "height": 200, "style": "cell", "data": [ - {"name": "paintbrush_store"}, + { + "name": "paintbrush_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -37,16 +40,12 @@ "on": [ { "events": [{"source": "scope", "type": "click"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: paintbrush_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "paintbrush_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "paintbrush_toggle", "value": false, @@ -80,7 +79,7 @@ "opacity": {"value": 0.7}, "fill": [ { - "test": "!length(data(\"paintbrush_store\")) || vlSelectionTest(\"paintbrush_store\", datum)", + "test": "!length(data(\"paintbrush_store\")) || vlSelectionIdTest(\"paintbrush_store\", datum)", "scale": "color", "field": "Cylinders" }, diff --git a/examples/compiled/selection_toggle_shiftKey.vg.json b/examples/compiled/selection_toggle_shiftKey.vg.json index 74e6934c788..6cbdbd05e07 100644 --- a/examples/compiled/selection_toggle_shiftKey.vg.json +++ b/examples/compiled/selection_toggle_shiftKey.vg.json @@ -6,7 +6,10 @@ "height": 200, "style": "cell", "data": [ - {"name": "paintbrush_store"}, + { + "name": "paintbrush_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -37,16 +40,12 @@ "on": [ { "events": [{"source": "scope", "type": "click"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: paintbrush_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - { - "name": "paintbrush_tuple_fields", - "value": [{"type": "E", "field": "_vgsid_"}] - }, { "name": "paintbrush_toggle", "value": false, @@ -80,7 +79,7 @@ "opacity": {"value": 0.7}, "fill": [ { - "test": "!length(data(\"paintbrush_store\")) || vlSelectionTest(\"paintbrush_store\", datum)", + "test": "!length(data(\"paintbrush_store\")) || vlSelectionIdTest(\"paintbrush_store\", datum)", "scale": "color", "field": "Cylinders" }, diff --git a/examples/compiled/selection_type_point.vg.json b/examples/compiled/selection_type_point.vg.json index 46fc908ff60..74719ff3bd0 100644 --- a/examples/compiled/selection_type_point.vg.json +++ b/examples/compiled/selection_type_point.vg.json @@ -4,7 +4,10 @@ "padding": 5, "style": "cell", "data": [ - {"name": "pts_store"}, + { + "name": "pts_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -46,13 +49,12 @@ "on": [ { "events": [{"source": "scope", "type": "click"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: pts_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - {"name": "pts_tuple_fields", "value": [{"type": "E", "field": "_vgsid_"}]}, { "name": "pts_toggle", "value": false, @@ -85,7 +87,7 @@ "update": { "fill": [ { - "test": "!length(data(\"pts_store\")) || vlSelectionTest(\"pts_store\", datum)", + "test": "!length(data(\"pts_store\")) || vlSelectionIdTest(\"pts_store\", datum)", "scale": "color", "field": "__count" }, diff --git a/examples/compiled/selection_type_single_dblclick.vg.json b/examples/compiled/selection_type_single_dblclick.vg.json index c05f63449c7..781463377f7 100644 --- a/examples/compiled/selection_type_single_dblclick.vg.json +++ b/examples/compiled/selection_type_single_dblclick.vg.json @@ -4,7 +4,10 @@ "padding": 5, "style": "cell", "data": [ - {"name": "pts_store"}, + { + "name": "pts_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -46,13 +49,12 @@ "on": [ { "events": [{"source": "scope", "type": "dblclick"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: pts_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - {"name": "pts_tuple_fields", "value": [{"type": "E", "field": "_vgsid_"}]}, { "name": "pts_toggle", "value": false, @@ -85,7 +87,7 @@ "update": { "fill": [ { - "test": "!length(data(\"pts_store\")) || vlSelectionTest(\"pts_store\", datum)", + "test": "!length(data(\"pts_store\")) || vlSelectionIdTest(\"pts_store\", datum)", "scale": "color", "field": "__count" }, diff --git a/examples/compiled/selection_type_single_mouseover.vg.json b/examples/compiled/selection_type_single_mouseover.vg.json index c8c7b17ebef..e5e484e255a 100644 --- a/examples/compiled/selection_type_single_mouseover.vg.json +++ b/examples/compiled/selection_type_single_mouseover.vg.json @@ -4,7 +4,10 @@ "padding": 5, "style": "cell", "data": [ - {"name": "pts_store"}, + { + "name": "pts_store", + "transform": [{"type": "collect", "sort": {"field": "_vgsid_"}}] + }, { "name": "source_0", "url": "data/cars.json", @@ -46,13 +49,12 @@ "on": [ { "events": [{"source": "scope", "type": "mouseover"}], - "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", fields: pts_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "update": "datum && item().mark.marktype !== 'group' ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", "force": true }, {"events": [{"source": "view", "type": "dblclick"}], "update": "null"} ] }, - {"name": "pts_tuple_fields", "value": [{"type": "E", "field": "_vgsid_"}]}, { "name": "pts_toggle", "value": false, @@ -85,7 +87,7 @@ "update": { "fill": [ { - "test": "!length(data(\"pts_store\")) || vlSelectionTest(\"pts_store\", datum)", + "test": "!length(data(\"pts_store\")) || vlSelectionIdTest(\"pts_store\", datum)", "scale": "color", "field": "__count" }, diff --git a/package.json b/package.json index 3adba345856..0853e92cc23 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "watch": "tsc -p tsconfig.build.json -w", "watch:site": "yarn build:site -w", "watch:test": "yarn jest --watch test/", + "watch:test:runtime": "NODE_OPTIONS=--experimental-vm-modules TZ=America/Los_Angeles npx jest --watch test-runtime/ --config test-runtime/jest-config.json", "release": "yarn run prebuild && yarn build && auto shipit" }, "repository": { diff --git a/site/_includes/docs_toc.md b/site/_includes/docs_toc.md index 35cfa70926f..e658f7baf1b 100644 --- a/site/_includes/docs_toc.md +++ b/site/_includes/docs_toc.md @@ -287,7 +287,6 @@ - [Nominal]({{site.baseurl}}/docs/type.html#nominal) - [GeoJSON]({{site.baseurl}}/docs/type.html#geojson) - [Value]({{site.baseurl}}/docs/value.html) - - [Examples]({{site.baseurl}}/docs/value.html#examples) - [Projection]({{site.baseurl}}/docs/projection.html) - [Documentation Overview]({{site.baseurl}}/docs/projection.html#documentation-overview) - [Projection Properties]({{site.baseurl}}/docs/projection.html#projection-properties) @@ -330,7 +329,6 @@ - [Using Parameters]({{site.baseurl}}/docs/parameter.html#using-parameters) - [Selection Configuration]({{site.baseurl}}/docs/parameter.html#config) - [Value]({{site.baseurl}}/docs/value.html) - - [Examples]({{site.baseurl}}/docs/value.html#examples) - [Expr]({{site.baseurl}}/docs/parameter.html) - [Documentation Overview]({{site.baseurl}}/docs/parameter.html#documentation-overview) - [Defining a Parameter]({{site.baseurl}}/docs/parameter.html#defining-a-parameter) diff --git a/src/compile/selection/assemble.ts b/src/compile/selection/assemble.ts index 57d11710aee..e3069c47954 100644 --- a/src/compile/selection/assemble.ts +++ b/src/compile/selection/assemble.ts @@ -4,7 +4,7 @@ import {identity, isArray, stringValue} from 'vega-util'; import {MODIFY, STORE, unitName, VL_SELECTION_RESOLVE, TUPLE, selectionCompilers} from '.'; import {dateTimeToExpr, isDateTime, dateTimeToTimestamp} from '../../datetime'; import {hasContinuousDomain} from '../../scale'; -import {SelectionInit, SelectionInitInterval, ParameterExtent} from '../../selection'; +import {SelectionInit, SelectionInitInterval, ParameterExtent, SELECTION_ID} from '../../selection'; import {keys, stringify, vals} from '../../util'; import {VgData, VgDomain} from '../../vega.schema'; import {FacetModel} from '../facet'; @@ -114,23 +114,29 @@ export function assembleTopLevelSignals(model: UnitModel, signals: Signal[]) { export function assembleUnitSelectionData(model: UnitModel, data: readonly VgData[]): VgData[] { const dataCopy = [...data]; + const unit = unitName(model, {escape: false}); + for (const selCmpt of vals(model.component.selection ?? {})) { - const init: VgData = {name: selCmpt.name + STORE}; + const store: VgData = {name: selCmpt.name + STORE}; + + if (selCmpt.project.hasSelectionId) { + store.transform = [{type: 'collect', sort: {field: SELECTION_ID}}]; + } + if (selCmpt.init) { const fields = selCmpt.project.items.map(proj => { const {signals, ...rest} = proj; return rest; }); - init.values = selCmpt.init.map(i => ({ - unit: unitName(model, {escape: false}), - fields, - values: assembleInit(i, false) - })); + store.values = selCmpt.project.hasSelectionId + ? selCmpt.init.map(i => ({unit, [SELECTION_ID]: assembleInit(i, false)[0]})) + : selCmpt.init.map(i => ({unit, fields, values: assembleInit(i, false)})); } + const contains = dataCopy.filter(d => d.name === selCmpt.name + STORE); if (!contains.length) { - dataCopy.push(init); + dataCopy.push(store); } } diff --git a/src/compile/selection/index.ts b/src/compile/selection/index.ts index 2aca7e34157..b6221ed3fb0 100644 --- a/src/compile/selection/index.ts +++ b/src/compile/selection/index.ts @@ -7,8 +7,7 @@ import { SelectionInit, SelectionInitInterval, SelectionResolution, - SelectionType, - SELECTION_ID + SelectionType } from '../../selection'; import {Dict, vals} from '../../util'; import {OutputNode} from '../data/dataflow'; @@ -112,7 +111,7 @@ export function unitName(model: Model, {escape} = {escape: true}) { export function requiresSelectionId(model: Model) { return vals(model.component.selection ?? {}).reduce((identifier, selCmpt) => { - return identifier || selCmpt.project.items.some(proj => proj.field === SELECTION_ID); + return identifier || selCmpt.project.hasSelectionId; }, false); } diff --git a/src/compile/selection/parse.ts b/src/compile/selection/parse.ts index 32708996b24..554ef8fa365 100644 --- a/src/compile/selection/parse.ts +++ b/src/compile/selection/parse.ts @@ -90,9 +90,9 @@ export function parseSelectionPredicate( } } - const test = `vlSelectionTest(${store}, ${datum}${ - selCmpt.resolve === 'global' ? ')' : `, ${stringValue(selCmpt.resolve)})` - }`; + const fn = selCmpt.project.hasSelectionId ? 'vlSelectionIdTest(' : 'vlSelectionTest('; + const resolve = selCmpt.resolve === 'global' ? ')' : `, ${stringValue(selCmpt.resolve)})`; + const test = `${fn}${store}, ${datum}${resolve}`; const length = `length(data(${store}))`; return pred.empty === false ? `${length} && ${test}` : `!${length} || ${test}`; diff --git a/src/compile/selection/point.ts b/src/compile/selection/point.ts index 8d9755aee79..137386cfcc6 100644 --- a/src/compile/selection/point.ts +++ b/src/compile/selection/point.ts @@ -1,6 +1,7 @@ import {Stream} from 'vega'; import {stringValue} from 'vega-util'; import {SelectionCompiler, TUPLE, unitName} from '.'; +import {SELECTION_ID} from '../../selection'; import {vals} from '../../util'; import {BRUSH} from './interval'; import {TUPLE_FIELDS} from './project'; @@ -13,16 +14,6 @@ const point: SelectionCompiler<'point'> = { const fieldsSg = name + TUPLE_FIELDS; const project = selCmpt.project; const datum = '(item().isVoronoi ? datum.datum : datum)'; - const values = project.items - .map(p => { - const fieldDef = model.fieldDef(p.channel); - // Binned fields should capture extents, for a range test against the raw field. - return fieldDef?.bin - ? `[${datum}[${stringValue(model.vgField(p.channel, {}))}], ` + - `${datum}[${stringValue(model.vgField(p.channel, {binSuffix: 'end'}))}]]` - : `${datum}[${stringValue(p.field)}]`; - }) - .join(', '); // Only add a discrete selection to the store if a datum is present _and_ // the interaction isn't occurring on a group mark. This guards against @@ -31,10 +22,6 @@ const point: SelectionCompiler<'point'> = { // for constant null states but varying toggles (e.g., shift-click in // whitespace followed by a click in whitespace; the store should only // be cleared on the second click). - const update = `unit: ${unitName(model)}, fields: ${fieldsSg}, values`; - - const events: Stream[] = selCmpt.events; - const brushes = vals(model.component.selection ?? {}) .reduce((acc, cmpt) => { return cmpt.type === 'interval' ? acc.concat(cmpt.name + BRUSH) : acc; @@ -44,6 +31,26 @@ const point: SelectionCompiler<'point'> = { const test = `datum && item().mark.marktype !== 'group'${brushes ? ` && ${brushes}` : ''}`; + let update = `unit: ${unitName(model)}, `; + + if (selCmpt.project.hasSelectionId) { + update += `${SELECTION_ID}: ${datum}[${stringValue(SELECTION_ID)}]`; + } else { + const values = project.items + .map(p => { + const fieldDef = model.fieldDef(p.channel); + // Binned fields should capture extents, for a range test against the raw field. + return fieldDef?.bin + ? `[${datum}[${stringValue(model.vgField(p.channel, {}))}], ` + + `${datum}[${stringValue(model.vgField(p.channel, {binSuffix: 'end'}))}]]` + : `${datum}[${stringValue(p.field)}]`; + }) + .join(', '); + + update += `fields: ${fieldsSg}, values: [${values}]`; + } + + const events: Stream[] = selCmpt.events; return signals.concat([ { name: name + TUPLE, @@ -51,7 +58,7 @@ const point: SelectionCompiler<'point'> = { ? [ { events, - update: `${test} ? {${update}: [${values}]} : null`, + update: `${test} ? {${update}} : null`, force: true } ] diff --git a/src/compile/selection/project.ts b/src/compile/selection/project.ts index 6290d4162c2..706e75ea168 100644 --- a/src/compile/selection/project.ts +++ b/src/compile/selection/project.ts @@ -2,7 +2,7 @@ import {array, isObject} from 'vega-util'; import {isSingleDefUnitChannel, ScaleChannel, SingleDefUnitChannel} from '../../channel'; import * as log from '../../log'; import {hasContinuousDomain} from '../../scale'; -import {PointSelectionConfig, SelectionInitIntervalMapping, SelectionInitMapping} from '../../selection'; +import {PointSelectionConfig, SelectionInitIntervalMapping, SelectionInitMapping, SELECTION_ID} from '../../selection'; import {Dict, hash, keys, replacePathInField, varName, isEmpty} from '../../util'; import {TimeUnitComponent, TimeUnitNode} from '../data/timeunit'; import {SelectionCompiler} from '.'; @@ -30,6 +30,7 @@ export interface SelectionProjection { export class SelectionProjectionComponent { public hasChannel: Partial>; public hasField: Record; + public hasSelectionId: boolean; public timeUnit?: TimeUnitNode; public items: SelectionProjection[]; @@ -37,6 +38,7 @@ export class SelectionProjectionComponent { this.items = items; this.hasChannel = {}; this.hasField = {}; + this.hasSelectionId = false; } } @@ -152,6 +154,7 @@ const project: SelectionCompiler = { p.signals = {...signalName(p, 'data'), ...signalName(p, 'visual')}; proj.items.push((parsed[field] = p)); proj.hasField[field] = proj.hasChannel[channel] = parsed[field]; + proj.hasSelectionId = proj.hasSelectionId || field === SELECTION_ID; } } else { log.warn(log.message.cannotProjectOnChannelWithoutField(channel)); @@ -164,6 +167,7 @@ const project: SelectionCompiler = { p.signals = {...signalName(p, 'data')}; proj.items.push(p); proj.hasField[field] = p; + proj.hasSelectionId = proj.hasSelectionId || field === SELECTION_ID; } if (init) { @@ -182,7 +186,7 @@ const project: SelectionCompiler = { signals: (model, selCmpt, allSignals) => { const name = selCmpt.name + TUPLE_FIELDS; const hasSignal = allSignals.filter(s => s.name === name); - return hasSignal.length > 0 + return hasSignal.length > 0 || selCmpt.project.hasSelectionId ? allSignals : allSignals.concat({ name, diff --git a/test-runtime/discrete.test.ts b/test-runtime/discrete.test.ts index 811afebf0b7..2d0a4f3b0af 100644 --- a/test-runtime/discrete.test.ts +++ b/test-runtime/discrete.test.ts @@ -27,10 +27,7 @@ describe(`point selections at runtime in unit views`, () => { await embed(spec('unit', i, {type})); const store = await page.evaluate(pt('qq', i)); expect(store).toHaveLength(1); - expect(store[0].fields).toHaveLength(1); - expect(store[0].values).toHaveLength(1); - expect(store[0].fields[0].field).toEqual(SELECTION_ID); - expect(store[0].fields[0].type).toBe('E'); + expect(store[0]).toHaveProperty(SELECTION_ID); await testRender(`click_${i}`); } }); diff --git a/test/compile/selection/clear.test.ts b/test/compile/selection/clear.test.ts index 197a73125a9..48786566001 100644 --- a/test/compile/selection/clear.test.ts +++ b/test/compile/selection/clear.test.ts @@ -61,7 +61,7 @@ describe('Clear selection transform, point types', () => { { events: selCmpts['one'].events, update: - 'datum && item().mark.marktype !== \'group\' ? {unit: "", fields: one_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)["_vgsid_"]]} : null', + 'datum && item().mark.marktype !== \'group\' ? {unit: "", _vgsid_: (item().isVoronoi ? datum.datum : datum)["_vgsid_"]} : null', force: true }, {events: parseSelector('dblclick', 'view'), update: 'null'} @@ -78,7 +78,7 @@ describe('Clear selection transform, point types', () => { { events: selCmpts['three'].events, update: - 'datum && item().mark.marktype !== \'group\' ? {unit: "", fields: three_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)["_vgsid_"]]} : null', + 'datum && item().mark.marktype !== \'group\' ? {unit: "", _vgsid_: (item().isVoronoi ? datum.datum : datum)["_vgsid_"]} : null', force: true }, {events: parseSelector('mouseout', 'view'), update: 'null'} @@ -95,7 +95,7 @@ describe('Clear selection transform, point types', () => { { events: selCmpts['four'].events, update: - 'datum && item().mark.marktype !== \'group\' ? {unit: "", fields: four_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)["_vgsid_"]]} : null', + 'datum && item().mark.marktype !== \'group\' ? {unit: "", _vgsid_: (item().isVoronoi ? datum.datum : datum)["_vgsid_"]} : null', force: true }, {events: parseSelector('mouseout', 'view'), update: 'null'} diff --git a/test/compile/selection/point.test.ts b/test/compile/selection/point.test.ts index 90ee24a406a..dc281b29a76 100644 --- a/test/compile/selection/point.test.ts +++ b/test/compile/selection/point.test.ts @@ -90,6 +90,11 @@ describe('Multi Selection', () => { name: 'eight', value: 75, select: 'point' + }, + { + name: 'nine', + value: [{_vgsid_: 75}, {_vgsid_: 80}], + select: 'point' } ])); @@ -102,7 +107,7 @@ describe('Multi Selection', () => { { events: selCmpts['one'].events, update: - 'datum && item().mark.marktype !== \'group\' ? {unit: "", fields: one_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)["_vgsid_"]]} : null', + 'datum && item().mark.marktype !== \'group\' ? {unit: "", _vgsid_: (item().isVoronoi ? datum.datum : datum)["_vgsid_"]} : null', force: true } ] @@ -203,7 +208,7 @@ describe('Multi Selection', () => { { events: selCmpts['one'].events, update: - "datum && item().mark.marktype !== 'group' && indexof(item().mark.name, 'two_brush') < 0 && indexof(item().mark.name, 'three_brush') < 0 ? {unit: \"\", fields: one_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]]} : null", + "datum && item().mark.marktype !== 'group' && indexof(item().mark.name, 'two_brush') < 0 && indexof(item().mark.name, 'three_brush') < 0 ? {unit: \"\", _vgsid_: (item().isVoronoi ? datum.datum : datum)[\"_vgsid_\"]} : null", force: true } ] @@ -261,7 +266,10 @@ describe('Multi Selection', () => { it('builds unit datasets', () => { expect(assembleUnitSelectionData(model, [])).toEqual([ - {name: 'one_store'}, + { + name: 'one_store', + transform: [{type: 'collect', sort: {field: '_vgsid_'}}] + }, {name: 'two_store'}, { name: 'thr_ee_store', @@ -320,12 +328,15 @@ describe('Multi Selection', () => { }, { name: 'eight_store', + transform: [{type: 'collect', sort: {field: '_vgsid_'}}], + values: [{unit: '', _vgsid_: 75}] + }, + { + name: 'nine_store', + transform: [{type: 'collect', sort: {field: '_vgsid_'}}], values: [ - { - unit: '', - fields: [{type: 'E', field: '_vgsid_'}], - values: [75] - } + {unit: '', _vgsid_: 75}, + {unit: '', _vgsid_: 80} ] } ]); diff --git a/test/compile/selection/predicate.test.ts b/test/compile/selection/predicate.test.ts index edd36dd1b58..1bd8261276b 100644 --- a/test/compile/selection/predicate.test.ts +++ b/test/compile/selection/predicate.test.ts @@ -21,7 +21,7 @@ describe('Selection Predicate', () => { field: 'Acceleration', type: 'quantitative', condition: { - param: 'one', + param: 'two', empty: false, value: 0.5 } @@ -47,9 +47,11 @@ describe('Selection Predicate', () => { it('generates the predicate expression', () => { // Different resolutions - expect(predicate(model, {param: 'one'})).toBe('!length(data("one_store")) || vlSelectionTest("one_store", datum)'); + expect(predicate(model, {param: 'one'})).toBe( + '!length(data("one_store")) || vlSelectionIdTest("one_store", datum)' + ); expect(predicate(model, {param: 'two'})).toBe( - '!length(data("two_store")) || vlSelectionTest("two_store", datum, "union")' + '!length(data("two_store")) || vlSelectionIdTest("two_store", datum, "union")' ); expect(predicate(model, {param: 'thr-ee'})).toBe( '!length(data("thr_ee_store")) || vlSelectionTest("thr_ee_store", datum, "intersect")' @@ -57,10 +59,10 @@ describe('Selection Predicate', () => { // Different emptiness expect(predicate(model, {param: 'one', empty: true})).toBe( - '!length(data("one_store")) || vlSelectionTest("one_store", datum)' + '!length(data("one_store")) || vlSelectionIdTest("one_store", datum)' ); - expect(predicate(model, {param: 'one', empty: false})).toBe( - 'length(data("one_store")) && vlSelectionTest("one_store", datum)' + expect(predicate(model, {param: 'two', empty: false})).toBe( + 'length(data("two_store")) && vlSelectionIdTest("two_store", datum, "union")' ); // Variable parameters @@ -70,7 +72,7 @@ describe('Selection Predicate', () => { it('generates Vega production rules', () => { expect(nonPosition('color', model, {vgChannel: 'fill'})).toEqual({ fill: [ - {test: '!length(data("one_store")) || vlSelectionTest("one_store", datum)', value: 'grey'}, + {test: '!length(data("one_store")) || vlSelectionIdTest("one_store", datum)', value: 'grey'}, {scale: 'color', field: 'Cylinders'} ] }); @@ -78,7 +80,7 @@ describe('Selection Predicate', () => { expect(nonPosition('opacity', model)).toEqual({ opacity: [ { - test: 'length(data("one_store")) && vlSelectionTest("one_store", datum)', + test: 'length(data("two_store")) && vlSelectionIdTest("two_store", datum, "union")', value: 0.5 }, {scale: 'opacity', field: 'Acceleration'} @@ -98,10 +100,12 @@ describe('Selection Predicate', () => { }); it('generates a selection filter', () => { - expect(expression(model, {param: 'one'})).toBe('!length(data("one_store")) || vlSelectionTest("one_store", datum)'); + expect(expression(model, {param: 'one'})).toBe( + '!length(data("one_store")) || vlSelectionIdTest("one_store", datum)' + ); - expect(expression(model, {not: {param: 'one', empty: false}})).toBe( - '!(length(data("one_store")) && vlSelectionTest("one_store", datum))' + expect(expression(model, {not: {param: 'two', empty: false}})).toBe( + '!(length(data("two_store")) && vlSelectionIdTest("two_store", datum, "union"))' ); expect(expression(model, {not: {param: 'varHelloWorld'}})).toBe('!(!!varHelloWorld)');