diff --git a/examples/compiled/concat_bar_layer_circle.vg.json b/examples/compiled/concat_bar_layer_circle.vg.json index 396de77abf..29724d6079 100644 --- a/examples/compiled/concat_bar_layer_circle.vg.json +++ b/examples/compiled/concat_bar_layer_circle.vg.json @@ -303,6 +303,7 @@ "from": {"data": "data_3"}, "encode": { "update": { + "cursor": {"value": "pointer"}, "fill": [ { "test": "!length(data(\"pts_store\")) || vlSelectionTest(\"pts_store\", datum)", diff --git a/examples/compiled/dynamic_color_legend.vg.json b/examples/compiled/dynamic_color_legend.vg.json index 8fe244ec85..9b1a78e095 100644 --- a/examples/compiled/dynamic_color_legend.vg.json +++ b/examples/compiled/dynamic_color_legend.vg.json @@ -497,6 +497,7 @@ "from": {"data": "data_1"}, "encode": { "update": { + "cursor": {"value": "pointer"}, "fill": [ { "test": "!length(data(\"click_store\")) || vlSelectionTest(\"click_store\", datum)", diff --git a/examples/compiled/interactive_concat_layer.vg.json b/examples/compiled/interactive_concat_layer.vg.json index c3a2ba3a16..5954b3af6e 100644 --- a/examples/compiled/interactive_concat_layer.vg.json +++ b/examples/compiled/interactive_concat_layer.vg.json @@ -313,6 +313,7 @@ "from": {"data": "data_3"}, "encode": { "update": { + "cursor": {"value": "pointer"}, "fill": [ { "test": "!length(data(\"pts_store\")) || vlSelectionTest(\"pts_store\", datum)", diff --git a/examples/compiled/interactive_global_development.vg.json b/examples/compiled/interactive_global_development.vg.json index abfdd0571a..2042fa30ba 100644 --- a/examples/compiled/interactive_global_development.vg.json +++ b/examples/compiled/interactive_global_development.vg.json @@ -295,6 +295,7 @@ "update": { "opacity": {"value": 0.9}, "size": {"value": 100}, + "cursor": {"value": "pointer"}, "fill": {"scale": "color", "field": "name"}, "ariaRoleDescription": {"value": "circle"}, "description": { diff --git a/examples/compiled/interactive_layered_crossfilter_discrete.vg.json b/examples/compiled/interactive_layered_crossfilter_discrete.vg.json index 3a58160721..4e3311aff2 100644 --- a/examples/compiled/interactive_layered_crossfilter_discrete.vg.json +++ b/examples/compiled/interactive_layered_crossfilter_discrete.vg.json @@ -259,6 +259,7 @@ "from": {"data": "data_6"}, "encode": { "update": { + "cursor": {"value": "pointer"}, "fill": {"value": "#ddd"}, "ariaRoleDescription": {"value": "bar"}, "description": { @@ -414,6 +415,7 @@ "from": {"data": "data_7"}, "encode": { "update": { + "cursor": {"value": "pointer"}, "fill": {"value": "#ddd"}, "ariaRoleDescription": {"value": "bar"}, "description": { @@ -569,6 +571,7 @@ "from": {"data": "data_2"}, "encode": { "update": { + "cursor": {"value": "pointer"}, "fill": {"value": "#ddd"}, "ariaRoleDescription": {"value": "bar"}, "description": { diff --git a/examples/compiled/interactive_point_init.vg.json b/examples/compiled/interactive_point_init.vg.json index 7ea5d46c2c..cca3980465 100644 --- a/examples/compiled/interactive_point_init.vg.json +++ b/examples/compiled/interactive_point_init.vg.json @@ -101,6 +101,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": [ { "test": "!length(data(\"CylYr_store\")) || vlSelectionTest(\"CylYr_store\", datum)", diff --git a/examples/compiled/interactive_seattle_weather.vg.json b/examples/compiled/interactive_seattle_weather.vg.json index 020b67c7dc..f906ff6935 100644 --- a/examples/compiled/interactive_seattle_weather.vg.json +++ b/examples/compiled/interactive_seattle_weather.vg.json @@ -492,6 +492,7 @@ "from": {"data": "data_1"}, "encode": { "update": { + "cursor": {"value": "pointer"}, "fill": [ { "test": "!length(data(\"click_store\")) || vlSelectionTest(\"click_store\", datum)", diff --git a/examples/compiled/interactive_stocks_nearest_index.vg.json b/examples/compiled/interactive_stocks_nearest_index.vg.json index b29d81cd45..0e40a52940 100644 --- a/examples/compiled/interactive_stocks_nearest_index.vg.json +++ b/examples/compiled/interactive_stocks_nearest_index.vg.json @@ -147,6 +147,7 @@ "encode": { "update": { "opacity": {"value": 0}, + "cursor": {"value": "pointer"}, "fill": {"value": "transparent"}, "stroke": {"value": "#4c78a8"}, "ariaRoleDescription": {"value": "point"}, diff --git a/examples/compiled/param_expr.vg.json b/examples/compiled/param_expr.vg.json index 8a5e816f5e..8e6aa64273 100644 --- a/examples/compiled/param_expr.vg.json +++ b/examples/compiled/param_expr.vg.json @@ -72,6 +72,7 @@ "update": { "opacity": {"signal": "opacityVar/100"}, "size": {"signal": "sel.Miles_per_Gallon * 10 || 75"}, + "cursor": {"value": "pointer"}, "fill": {"value": "transparent"}, "stroke": {"value": "#4c78a8"}, "ariaRoleDescription": {"value": "point"}, diff --git a/examples/compiled/selection_heatmap.vg.json b/examples/compiled/selection_heatmap.vg.json index c3d9ddae10..27100b840f 100644 --- a/examples/compiled/selection_heatmap.vg.json +++ b/examples/compiled/selection_heatmap.vg.json @@ -95,6 +95,7 @@ "encode": { "update": { "strokeWidth": {"value": 2}, + "cursor": {"value": "pointer"}, "fill": {"scale": "fill", "field": "count"}, "stroke": [ { diff --git a/examples/compiled/selection_insert.vg.json b/examples/compiled/selection_insert.vg.json index 4fe9089c4e..954e821522 100644 --- a/examples/compiled/selection_insert.vg.json +++ b/examples/compiled/selection_insert.vg.json @@ -66,6 +66,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": [ { "test": "!length(data(\"paintbrush_store\")) || vlSelectionIdTest(\"paintbrush_store\", datum)", diff --git a/examples/compiled/selection_project_multi.vg.json b/examples/compiled/selection_project_multi.vg.json index 88afcd2dae..5cc8334928 100644 --- a/examples/compiled/selection_project_multi.vg.json +++ b/examples/compiled/selection_project_multi.vg.json @@ -77,6 +77,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": {"value": "transparent"}, "stroke": [ { diff --git a/examples/compiled/selection_project_multi_cylinders.vg.json b/examples/compiled/selection_project_multi_cylinders.vg.json index 65e4a56ba1..4799c27788 100644 --- a/examples/compiled/selection_project_multi_cylinders.vg.json +++ b/examples/compiled/selection_project_multi_cylinders.vg.json @@ -77,6 +77,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": {"value": "transparent"}, "stroke": [ { diff --git a/examples/compiled/selection_project_multi_cylinders_origin.vg.json b/examples/compiled/selection_project_multi_cylinders_origin.vg.json index c2915a3315..f622afedae 100644 --- a/examples/compiled/selection_project_multi_cylinders_origin.vg.json +++ b/examples/compiled/selection_project_multi_cylinders_origin.vg.json @@ -80,6 +80,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": {"value": "transparent"}, "stroke": [ { diff --git a/examples/compiled/selection_project_multi_origin.vg.json b/examples/compiled/selection_project_multi_origin.vg.json index fe2706224b..b1e54542b8 100644 --- a/examples/compiled/selection_project_multi_origin.vg.json +++ b/examples/compiled/selection_project_multi_origin.vg.json @@ -74,6 +74,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": {"value": "transparent"}, "stroke": [ { diff --git a/examples/compiled/selection_project_single.vg.json b/examples/compiled/selection_project_single.vg.json index e65908bfec..7f1c46c87e 100644 --- a/examples/compiled/selection_project_single.vg.json +++ b/examples/compiled/selection_project_single.vg.json @@ -66,6 +66,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": {"value": "transparent"}, "stroke": [ { diff --git a/examples/compiled/selection_project_single_cylinders.vg.json b/examples/compiled/selection_project_single_cylinders.vg.json index 45bc7d1c36..ec70e81d10 100644 --- a/examples/compiled/selection_project_single_cylinders.vg.json +++ b/examples/compiled/selection_project_single_cylinders.vg.json @@ -66,6 +66,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": {"value": "transparent"}, "stroke": [ { diff --git a/examples/compiled/selection_project_single_cylinders_origin.vg.json b/examples/compiled/selection_project_single_cylinders_origin.vg.json index e057d5b625..2532c574c3 100644 --- a/examples/compiled/selection_project_single_cylinders_origin.vg.json +++ b/examples/compiled/selection_project_single_cylinders_origin.vg.json @@ -69,6 +69,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": {"value": "transparent"}, "stroke": [ { diff --git a/examples/compiled/selection_project_single_origin.vg.json b/examples/compiled/selection_project_single_origin.vg.json index 9f6e5e4d74..156ca50fad 100644 --- a/examples/compiled/selection_project_single_origin.vg.json +++ b/examples/compiled/selection_project_single_origin.vg.json @@ -63,6 +63,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": {"value": "transparent"}, "stroke": [ { diff --git a/examples/compiled/selection_toggle_altKey.vg.json b/examples/compiled/selection_toggle_altKey.vg.json index 02f223abcc..d8e7b32a00 100644 --- a/examples/compiled/selection_toggle_altKey.vg.json +++ b/examples/compiled/selection_toggle_altKey.vg.json @@ -77,6 +77,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": [ { "test": "!length(data(\"paintbrush_store\")) || vlSelectionIdTest(\"paintbrush_store\", datum)", diff --git a/examples/compiled/selection_toggle_altKey_shiftKey.vg.json b/examples/compiled/selection_toggle_altKey_shiftKey.vg.json index 7875b981b1..fe009373d7 100644 --- a/examples/compiled/selection_toggle_altKey_shiftKey.vg.json +++ b/examples/compiled/selection_toggle_altKey_shiftKey.vg.json @@ -77,6 +77,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": [ { "test": "!length(data(\"paintbrush_store\")) || vlSelectionIdTest(\"paintbrush_store\", datum)", diff --git a/examples/compiled/selection_toggle_shiftKey.vg.json b/examples/compiled/selection_toggle_shiftKey.vg.json index d135317d00..ea8c6bd8a9 100644 --- a/examples/compiled/selection_toggle_shiftKey.vg.json +++ b/examples/compiled/selection_toggle_shiftKey.vg.json @@ -77,6 +77,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": [ { "test": "!length(data(\"paintbrush_store\")) || vlSelectionIdTest(\"paintbrush_store\", datum)", diff --git a/examples/compiled/selection_type_point.vg.json b/examples/compiled/selection_type_point.vg.json index 77df606b03..1365255368 100644 --- a/examples/compiled/selection_type_point.vg.json +++ b/examples/compiled/selection_type_point.vg.json @@ -85,6 +85,7 @@ "from": {"data": "source_0"}, "encode": { "update": { + "cursor": {"value": "pointer"}, "fill": [ { "test": "!length(data(\"pts_store\")) || vlSelectionIdTest(\"pts_store\", datum)", diff --git a/examples/compiled/selection_type_single_dblclick.vg.json b/examples/compiled/selection_type_single_dblclick.vg.json index 29abd06d18..0c50668d86 100644 --- a/examples/compiled/selection_type_single_dblclick.vg.json +++ b/examples/compiled/selection_type_single_dblclick.vg.json @@ -85,6 +85,7 @@ "from": {"data": "source_0"}, "encode": { "update": { + "cursor": {"value": "pointer"}, "fill": [ { "test": "!length(data(\"pts_store\")) || vlSelectionIdTest(\"pts_store\", datum)", diff --git a/examples/compiled/vconcat_flatten.vg.json b/examples/compiled/vconcat_flatten.vg.json index bb1d36a123..f1932397e8 100644 --- a/examples/compiled/vconcat_flatten.vg.json +++ b/examples/compiled/vconcat_flatten.vg.json @@ -151,6 +151,7 @@ "encode": { "update": { "opacity": {"value": 0.7}, + "cursor": {"value": "pointer"}, "fill": [ { "test": "!length(data(\"pts_store\")) || vlSelectionTest(\"pts_store\", datum)", diff --git a/examples/specs/interactive_bar_select_highlight.vl.json b/examples/specs/interactive_bar_select_highlight.vl.json index 81ad0a99c4..3030f94ed0 100644 --- a/examples/specs/interactive_bar_select_highlight.vl.json +++ b/examples/specs/interactive_bar_select_highlight.vl.json @@ -18,8 +18,7 @@ "mark": { "type": "bar", "fill": "#4C78A8", - "stroke": "black", - "cursor": "pointer" + "stroke": "black" }, "encoding": { "x": {"field": "a", "type": "ordinal"}, diff --git a/src/compile/mark/init.ts b/src/compile/mark/init.ts index 3c730e5d39..85fdb08dee 100644 --- a/src/compile/mark/init.ts +++ b/src/compile/mark/init.ts @@ -61,7 +61,8 @@ export function initMarkdef(originalMarkDef: MarkDef, encoding: Encoding markDef.opacity = opacity(markDef.type, encoding); } - // set cursor, which should be pointer if href channel is present unless otherwise specified + // Set cursor, which should be pointer if href channel is present unless otherwise specified. + // We will also set the cursor in parse via getMarkGroup since we need access to the selections. const specifiedCursor = getMarkPropOrConfig('cursor', markDef, config); if (specifiedCursor === undefined) { markDef.cursor = cursor(markDef, encoding, config); diff --git a/src/compile/mark/mark.ts b/src/compile/mark/mark.ts index 6c3da0409b..d3f2f4f599 100644 --- a/src/compile/mark/mark.ts +++ b/src/compile/mark/mark.ts @@ -277,6 +277,21 @@ function getMarkGroup(model: UnitModel, opt: {fromPrefix: string} = {fromPrefix: const key = encoding.key; const sort = getSort(model); const interactive = interactiveFlag(model); + + // set pointer cursor for point selections that are not bound + if ( + interactive && + Object.values(model.component.selection).some( + s => + s.type === 'point' && + !s.bind && + // if on is a pointerover (hover) the pointer makes less sense since the mark is not clickable. + (s as any).on !== 'pointerover' + ) + ) { + model.markDef.cursor ??= 'pointer'; + } + const aria = getMarkPropOrConfig('aria', markDef, config); const postEncodingTransform = markCompiler[mark].postEncodingTransform diff --git a/test/compile/compile.test.ts b/test/compile/compile.test.ts index e4667bdc07..e589bd0ebd 100644 --- a/test/compile/compile.test.ts +++ b/test/compile/compile.test.ts @@ -573,3 +573,55 @@ describe('compile/compile', () => { expect(spec.autosize['resize']).toBeTruthy(); }); }); + +it('should generate right cursor for point selection', () => { + const {spec} = compile({ + data: {url: 'data/population.json'}, + params: [{name: 'select', select: 'point'}], + mark: 'bar', + encoding: { + x: {field: 'a', type: 'ordinal'}, + y: {field: 'b', type: 'quantitative'} + } + }); + + expect(spec.marks[0].encode.update.cursor).toEqual({value: 'pointer'}); +}); + +it('should not generate cursor for point selection with binding', () => { + const {spec} = compile({ + data: {url: 'data/population.json'}, + params: [ + { + name: 'highlight', + value: [{Cylinders: 4, Year: 1977}], + select: {type: 'point', fields: ['Cylinders', 'Year']}, + bind: { + Cylinders: {input: 'range', min: 3, max: 8, step: 1}, + Year: {input: 'range', min: 1969, max: 1981, step: 1} + } + } + ], + mark: 'bar', + encoding: { + x: {field: 'a', type: 'ordinal'}, + y: {field: 'b', type: 'quantitative'} + } + }); + + expect(spec.marks[0].encode.update.cursor).toBeUndefined(); +}); + +it('should not generate cursor for interval selection', () => { + const {spec} = compile({ + data: {url: 'data/population.json'}, + params: [{name: 'brush', select: {type: 'interval', encodings: ['x']}}], + mark: 'bar', + encoding: { + x: {field: 'a', type: 'ordinal'}, + y: {field: 'b', type: 'quantitative'} + } + }); + + expect(spec.marks[0].encode.update.cursor).toBeUndefined(); +});