From e2976c89e0d38824331c9d4f00a8f9635a04950f Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Wed, 3 Apr 2024 14:37:30 -0400 Subject: [PATCH 01/17] hover on subplots when they anchored to same x or y --- src/components/fx/hover.js | 31 ++++++++++++++++++++++--- src/components/fx/hovermode_defaults.js | 1 + src/components/fx/layout_attributes.js | 9 +++++++ test/plot-schema.json | 6 +++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index c04b0a7f65b..b2c3d305c25 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -5,6 +5,7 @@ var isNumeric = require('fast-isnumeric'); var tinycolor = require('tinycolor2'); var Lib = require('../../lib'); +var pushUnique = Lib.pushUnique; var strTranslate = Lib.strTranslate; var strRotate = Lib.strRotate; var Events = require('../../lib/events'); @@ -257,11 +258,37 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { // use those instead of finding overlayed plots var subplots = Array.isArray(subplot) ? subplot : [subplot]; + var spId; + var fullLayout = gd._fullLayout; + var hoversameaxis = fullLayout.hoversameaxis; var plots = fullLayout._plots || []; var plotinfo = plots[subplot]; var hasCartesian = fullLayout._has('cartesian'); + var hovermode = evt.hovermode || fullLayout.hovermode; + var hovermodeHasX = (hovermode || '').charAt(0) === 'x'; + var hovermodeHasY = (hovermode || '').charAt(0) === 'y'; + + if(hoversameaxis && hasCartesian && (hovermodeHasX || hovermodeHasY)) { + for(var p = 0; p < subplots.length; p++) { + spId = subplots[p]; + if(plots[spId]) { + // 'cartesian' case + + var subplotsWith = ( + Axes.getFromId(gd, spId, hovermodeHasX ? 'x' : 'y') + )._subplotsWith; + + if(subplotsWith && subplotsWith.length) { + for(var q = 0; q < subplotsWith.length; q++) { + pushUnique(subplots, subplotsWith[q]); + } + } + } + } + } + // list of all overlaid subplots to look at if(plotinfo) { var overlayedSubplots = plotinfo.overlays.map(function(pi) { @@ -277,7 +304,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var supportsCompare = false; for(var i = 0; i < len; i++) { - var spId = subplots[i]; + spId = subplots[i]; if(plots[spId]) { // 'cartesian' case @@ -295,8 +322,6 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { } } - var hovermode = evt.hovermode || fullLayout.hovermode; - if(hovermode && !supportsCompare) hovermode = 'closest'; if(['x', 'y', 'closest', 'x unified', 'y unified'].indexOf(hovermode) === -1 || !gd.calcdata || diff --git a/src/components/fx/hovermode_defaults.js b/src/components/fx/hovermode_defaults.js index e9a038bc19e..790da520eab 100644 --- a/src/components/fx/hovermode_defaults.js +++ b/src/components/fx/hovermode_defaults.js @@ -12,5 +12,6 @@ module.exports = function handleHoverModeDefaults(layoutIn, layoutOut) { } coerce('clickmode'); + coerce('hoversameaxis'); return coerce('hovermode'); }; diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index af5ce341e21..089c97c48f8 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -78,6 +78,15 @@ module.exports = { 'If false, hover interactions are disabled.' ].join(' ') }, + hoversameaxis: { + valType: 'boolean', + dflt: false, + editType: 'none', + description: [ + 'Determines expansion of hover effects to other subplots in case of sharing an axis.', + 'Has an effect only when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.', + ].join(' ') + }, hoverdistance: { valType: 'integer', min: -1, diff --git a/test/plot-schema.json b/test/plot-schema.json index 334c9a918ed..f5ed29ba72b 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -2634,6 +2634,12 @@ "y unified" ] }, + "hoversameaxis": { + "description": "Determines expansion of hover effects to other subplots in case of sharing an axis. Has an effect only when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.", + "dflt": false, + "editType": "none", + "valType": "boolean" + }, "images": { "items": { "image": { From 4f136206900ae53cce654a564bb11dabf039a902 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Wed, 3 Apr 2024 19:30:21 -0400 Subject: [PATCH 02/17] add jasmine tests --- test/jasmine/tests/hover_label_test.js | 162 +++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 165b90912f5..89a1eb1fbfe 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -2378,6 +2378,168 @@ describe('hover info on stacked subplots', function() { }); }); +describe('hover on subplots when hoversameaxis is set to true and x hovermode', function() { + 'use strict'; + + var mock = { + layout: { + hoversameaxis: true, + hovermode: 'x', + grid: { + rows: 3, + columns: 2, + pattern: 'coupled' + } + }, + + data: [ + { + y: [1, 2, 3] + }, + { + y: [10, 20, 30], + yaxis: 'y2' + }, + { + y: [100, 200, 300], + yaxis: 'y3' + }, + { + y: [10, 20, 30], + xaxis: 'x2', + yaxis: 'y2' + } + ], + }; + + var gd; + + beforeEach(function(done) { + gd = createGraphDiv(); + Plotly.newPlot(gd, mock).then(done); + }); + + afterEach(destroyGraphDiv); + + it('hovermode: x with hoversameaxis: true', function() { + var pos = 0; + var subplot = 'xy'; + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {xval: pos}, subplot); + expect(gd._hoverdata.length).toBe(3); + assertHoverLabelContent({ + nums: ['1', '10', '100'], + name: ['trace 0', 'trace 1', 'trace 2'], + axis: String([pos]) + }); + + pos = 1; + subplot = 'xy2'; + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {xval: pos}, subplot); + + expect(gd._hoverdata.length).toBe(3); + assertHoverLabelContent({ + nums: ['2', '20', '200'], + name: ['trace 0', 'trace 1', 'trace 2'], + axis: String(pos) + }); + + pos = 2; + subplot = 'xy3'; + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {xval: pos}, subplot); + + expect(gd._hoverdata.length).toBe(3); + assertHoverLabelContent({ + nums: ['3', '30', '300'], + name: ['trace 0', 'trace 1', 'trace 2'], + axis: String(pos) + }); + }); +}); + +describe('hover on subplots when hoversameaxis is set to true and y hovermode', function() { + 'use strict'; + + var mock = { + layout: { + hoversameaxis: true, + hovermode: 'y', + grid: { + rows: 2, + columns: 3, + pattern: 'coupled' + } + }, + + data: [ + { + x: [1, 2, 3] + }, + { + x: [10, 20, 30], + xaxis: 'x2' + }, + { + x: [100, 200, 300], + xaxis: 'x3' + }, + { + x: [10, 20, 30], + xaxis: 'x2', + yaxis: 'y2' + } + ], + }; + + var gd; + + beforeEach(function(done) { + gd = createGraphDiv(); + Plotly.newPlot(gd, mock).then(done); + }); + + afterEach(destroyGraphDiv); + + it('hovermode: y with hoversameaxis: true', function() { + var pos = 0; + var subplot = 'xy'; + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {yval: pos}, subplot); + expect(gd._hoverdata.length).toBe(3); + assertHoverLabelContent({ + nums: ['1', '10', '100'], + name: ['trace 0', 'trace 1', 'trace 2'], + axis: String([pos]) + }); + + pos = 1; + subplot = 'x2y'; + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {yval: pos}, subplot); + + expect(gd._hoverdata.length).toBe(3); + assertHoverLabelContent({ + nums: ['2', '20', '200'], + name: ['trace 0', 'trace 1', 'trace 2'], + axis: String(pos) + }); + + pos = 2; + subplot = 'x3y'; + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {yval: pos}, subplot); + + expect(gd._hoverdata.length).toBe(3); + assertHoverLabelContent({ + nums: ['3', '30', '300'], + name: ['trace 0', 'trace 1', 'trace 2'], + axis: String(pos) + }); + }); +}); + describe('hover on many lines+bars', function() { 'use strict'; From 39c90137a10cd2d608f2353015dd90e23764c3fd Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Thu, 4 Apr 2024 09:03:57 -0400 Subject: [PATCH 03/17] test unified hover including points in other plots --- test/jasmine/tests/hover_label_test.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 89a1eb1fbfe..fcb0854a98c 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -2378,7 +2378,7 @@ describe('hover info on stacked subplots', function() { }); }); -describe('hover on subplots when hoversameaxis is set to true and x hovermode', function() { +describe('hover on subplots when hoversameaxis is set to true and x hovermodes', function() { 'use strict'; var mock = { @@ -2421,7 +2421,7 @@ describe('hover on subplots when hoversameaxis is set to true and x hovermode', afterEach(destroyGraphDiv); - it('hovermode: x with hoversameaxis: true', function() { + it('hovermode: *x* | *x unified* with hoversameaxis: true', function() { var pos = 0; var subplot = 'xy'; Lib.clearThrottle(); @@ -2456,10 +2456,17 @@ describe('hover on subplots when hoversameaxis is set to true and x hovermode', name: ['trace 0', 'trace 1', 'trace 2'], axis: String(pos) }); + + Plotly.relayout(gd, 'hovermode', 'x unified'); + pos = 0; + subplot = 'xy'; + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {xval: pos}, subplot); + expect(gd._hoverdata.length).toBe(3); }); }); -describe('hover on subplots when hoversameaxis is set to true and y hovermode', function() { +describe('hover on subplots when hoversameaxis is set to true and y hovermodes', function() { 'use strict'; var mock = { @@ -2502,7 +2509,7 @@ describe('hover on subplots when hoversameaxis is set to true and y hovermode', afterEach(destroyGraphDiv); - it('hovermode: y with hoversameaxis: true', function() { + it('hovermode: *y* | *y unified* with hoversameaxis: true', function() { var pos = 0; var subplot = 'xy'; Lib.clearThrottle(); @@ -2537,6 +2544,13 @@ describe('hover on subplots when hoversameaxis is set to true and y hovermode', name: ['trace 0', 'trace 1', 'trace 2'], axis: String(pos) }); + + Plotly.relayout(gd, 'hovermode', 'y unified'); + pos = 0; + subplot = 'xy'; + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {yval: pos}, subplot); + expect(gd._hoverdata.length).toBe(3); }); }); From f29c7fcb734e6ed38ac8540145f06c35b663c65b Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Thu, 4 Apr 2024 09:10:27 -0400 Subject: [PATCH 04/17] draft log --- draftlogs/6947_add.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 draftlogs/6947_add.md diff --git a/draftlogs/6947_add.md b/draftlogs/6947_add.md new file mode 100644 index 00000000000..f447296f549 --- /dev/null +++ b/draftlogs/6947_add.md @@ -0,0 +1 @@ + - Add `layout.hoversameaxis` to enable hover effects across multiple cartesian suplots sharing one axis [[#6947](https://github.com/plotly/plotly.js/pull/6947)] From 935b8f142ec5c7561dadcc86963076d7616fd09b Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Thu, 4 Apr 2024 09:29:09 -0400 Subject: [PATCH 05/17] refactor --- src/components/fx/hover.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index b2c3d305c25..9f170a6489e 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -466,6 +466,12 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { // the rest of this function from running and failing if(['carpet', 'contourcarpet'].indexOf(trace._module.name) !== -1) continue; + // within one trace mode can sometimes be overridden + _mode = hovermode; + if(helpers.isUnifiedHover(_mode)) { + _mode = _mode.charAt(0); + } + if(trace.type === 'splom') { // splom traces do not generate overlay subplots, // it is safe to assume here splom traces correspond to the 0th subplot @@ -476,12 +482,6 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { subploti = subplots.indexOf(subplotId); } - // within one trace mode can sometimes be overridden - _mode = hovermode; - if(helpers.isUnifiedHover(_mode)) { - _mode = _mode.charAt(0); - } - // container for new point, also used to pass info into module.hoverPoints pointData = { // trace properties From 6497363fe63857b9031ac53f0eab9a4ebef8d132 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Thu, 4 Apr 2024 09:50:58 -0400 Subject: [PATCH 06/17] refactor --- src/components/fx/hover.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 9f170a6489e..515e91974c9 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -533,8 +533,6 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { pointData.scene = fullLayout._splomScenes[trace.uid]; } - closedataPreviousLength = hoverData.length; - // for a highlighting array, figure out what // we're searching for with this element if(_mode === 'array') { @@ -561,6 +559,8 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { yval = yvalArray[subploti]; } + closedataPreviousLength = hoverData.length; + // Now if there is range to look in, find the points to hover. if(hoverdistance !== 0) { if(trace._module && trace._module.hoverPoints) { From 680d8bdc6729fe8f0b2aa36a70165a079caa63e8 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Thu, 4 Apr 2024 10:59:53 -0400 Subject: [PATCH 07/17] handle splom --- src/components/fx/hover.js | 6 ++- src/traces/splom/hover.js | 84 ++++++++++++++++++++++++++++++++++---- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 515e91974c9..7f9d5123d98 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -566,7 +566,11 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { if(trace._module && trace._module.hoverPoints) { var newPoints = trace._module.hoverPoints(pointData, xval, yval, _mode, { finiteRange: true, - hoverLayer: fullLayout._hoverlayer + hoverLayer: fullLayout._hoverlayer, + + // options for splom when hovering on same axis + hoversameaxis: hoversameaxis, + gd: gd }); if(newPoints) { diff --git a/src/traces/splom/hover.js b/src/traces/splom/hover.js index 1ae60244525..a8837027815 100644 --- a/src/traces/splom/hover.js +++ b/src/traces/splom/hover.js @@ -2,16 +2,64 @@ var helpers = require('./helpers'); var calcHover = require('../scattergl/hover').calcHover; +var getFromId = require('../../plots/cartesian/axes').getFromId; +var extendFlat = require('../../lib/extend').extendFlat; -function hoverPoints(pointData, xval, yval) { +function hoverPoints(pointData, xval, yval, hovermode, opts) { + if(!opts) opts = {}; + + var hovermodeHasX = (hovermode || '').charAt(0) === 'x'; + var hovermodeHasY = (hovermode || '').charAt(0) === 'y'; + + var xpx = pointData.xa.c2p(xval); + var ypx = pointData.ya.c2p(yval); + + var points = _hoverPoints(pointData, xpx, ypx); + + if(opts.hoversameaxis && (hovermodeHasX || hovermodeHasY)) { + var _xpx = points[0]._xpx; + var _ypx = points[0]._ypx; + + if( + (hovermodeHasX && _xpx !== undefined) || + (hovermodeHasY && _ypx !== undefined) + ) { + var subplotsWith = ( + hovermodeHasX ? + pointData.xa : + pointData.ya + )._subplotsWith; + + var gd = opts.gd; + + var _pointData = extendFlat({}, pointData); + + for(var i = 0; i < subplotsWith.length; i++) { + var spId = subplotsWith[i]; + + if(hovermodeHasY) { + _pointData.xa = getFromId(gd, spId, 'x'); + } else { // hovermodeHasX + _pointData.ya = getFromId(gd, spId, 'y'); + } + + var newPoints = _hoverPoints(_pointData, _xpx, _ypx, hovermodeHasX, hovermodeHasY); + + points = points.concat(newPoints); + } + } + } + + return points; +} + +function _hoverPoints(pointData, xpx, ypx, hoversameaxisX, hoversameaxisY) { var cd = pointData.cd; var trace = cd[0].trace; var scene = pointData.scene; var cdata = scene.matrixOptions.cdata; var xa = pointData.xa; var ya = pointData.ya; - var xpx = xa.c2p(xval); - var ypx = ya.c2p(yval); var maxDistance = pointData.distance; var xi = helpers.getDimIndex(trace, xa); @@ -21,19 +69,34 @@ function hoverPoints(pointData, xval, yval) { var x = cdata[xi]; var y = cdata[yi]; - var id, dxy; + var id, dxy, _xpx, _ypx; var minDist = maxDistance; for(var i = 0; i < x.length; i++) { var ptx = x[i]; var pty = y[i]; - var dx = xa.c2p(ptx) - xpx; - var dy = ya.c2p(pty) - ypx; - var dist = Math.sqrt(dx * dx + dy * dy); + var thisXpx = xa.c2p(ptx); + var thisYpx = ya.c2p(pty); + + var dx = thisXpx - xpx; + var dy = thisYpx - ypx; + var dist = 0; + + var pick = false; + if(hoversameaxisX) { + if(dx === 0) pick = true; + } else if(hoversameaxisY) { + if(dy === 0) pick = true; + } else { + dist = Math.sqrt(dx * dx + dy * dy); + if(dist < minDist) pick = true; + } - if(dist < minDist) { + if(pick) { minDist = dxy = dist; id = i; + _xpx = thisXpx; + _ypx = thisYpx; } } @@ -43,7 +106,10 @@ function hoverPoints(pointData, xval, yval) { if(id === undefined) return [pointData]; - return [calcHover(pointData, x, y, trace)]; + var out = calcHover(pointData, x, y, trace); + out._xpx = _xpx; + out._ypx = _ypx; + return [out]; } module.exports = { From 08917b5f4a7456ce15f0ad6d9a37d93d66df9efc Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 5 Apr 2024 11:50:59 -0400 Subject: [PATCH 08/17] splom test --- test/jasmine/tests/hover_label_test.js | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index fcb0854a98c..894b187957d 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -28,6 +28,8 @@ var assertElemInside = customAssertions.assertElemInside; var groupTitlesMock = require('../../image/mocks/legendgroup-titles'); +var splomLogMock = require('../../image/mocks/splom_log'); + function touch(path, options) { var len = path.length; Lib.clearThrottle(); @@ -2554,6 +2556,45 @@ describe('hover on subplots when hoversameaxis is set to true and y hovermodes', }); }); +describe('splom hover on subplots when hoversameaxis is set to true and (x|y) hovermodes', function() { + 'use strict'; + + var mock = Lib.extendDeep({}, splomLogMock); + mock.layout.hovermode = 'x'; + mock.layout.hoversameaxis = true; + + var gd; + + beforeEach(function(done) { + gd = createGraphDiv(); + Plotly.newPlot(gd, mock).then(done); + }); + + afterEach(destroyGraphDiv); + + it('splom hoversameaxis: true', function() { + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {x: 200, y: 200}, 'xy'); + expect(gd._hoverdata.length).toBe(3); + assertHoverLabelContent({ + nums: ['100', '100k'], + name: ['', ''], + axis: '100' + }); + + Plotly.relayout(gd, 'hovermode', 'x unified'); + + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {x: 200, y: 200}, 'xy'); + expect(gd._hoverdata.length).toBe(3); + + Plotly.relayout(gd, 'hovermode', 'y unified'); + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {x: 200, y: 200}, 'xy'); + expect(gd._hoverdata.length).toBe(3); + }); +}); + describe('hover on many lines+bars', function() { 'use strict'; From 808079e3930456cd2c0f6a8c05fe8043ab8df7f2 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 5 Apr 2024 12:17:04 -0400 Subject: [PATCH 09/17] do not loop in newly added items --- src/components/fx/hover.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 7f9d5123d98..50cfe0e28cb 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -271,7 +271,8 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var hovermodeHasY = (hovermode || '').charAt(0) === 'y'; if(hoversameaxis && hasCartesian && (hovermodeHasX || hovermodeHasY)) { - for(var p = 0; p < subplots.length; p++) { + var subplotsLength = subplots.length; + for(var p = 0; p < subplotsLength; p++) { spId = subplots[p]; if(plots[spId]) { // 'cartesian' case From ed065172a0537ef6ce8bcde1cc6dca0bd3b28ca7 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 5 Apr 2024 12:25:08 -0400 Subject: [PATCH 10/17] rename attribute --- draftlogs/6947_add.md | 2 +- src/components/fx/hover.js | 6 +++--- src/components/fx/hovermode_defaults.js | 2 +- src/components/fx/layout_attributes.js | 2 +- src/traces/splom/hover.js | 8 ++++---- test/jasmine/tests/hover_label_test.js | 18 +++++++++--------- test/plot-schema.json | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/draftlogs/6947_add.md b/draftlogs/6947_add.md index f447296f549..4d11461b193 100644 --- a/draftlogs/6947_add.md +++ b/draftlogs/6947_add.md @@ -1 +1 @@ - - Add `layout.hoversameaxis` to enable hover effects across multiple cartesian suplots sharing one axis [[#6947](https://github.com/plotly/plotly.js/pull/6947)] + - Add `layout.hoverthrough` to enable hover effects across multiple cartesian suplots sharing one axis [[#6947](https://github.com/plotly/plotly.js/pull/6947)] diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 50cfe0e28cb..41d6df64ce3 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -261,7 +261,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var spId; var fullLayout = gd._fullLayout; - var hoversameaxis = fullLayout.hoversameaxis; + var hoverthrough = fullLayout.hoverthrough; var plots = fullLayout._plots || []; var plotinfo = plots[subplot]; var hasCartesian = fullLayout._has('cartesian'); @@ -270,7 +270,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var hovermodeHasX = (hovermode || '').charAt(0) === 'x'; var hovermodeHasY = (hovermode || '').charAt(0) === 'y'; - if(hoversameaxis && hasCartesian && (hovermodeHasX || hovermodeHasY)) { + if(hoverthrough && hasCartesian && (hovermodeHasX || hovermodeHasY)) { var subplotsLength = subplots.length; for(var p = 0; p < subplotsLength; p++) { spId = subplots[p]; @@ -570,7 +570,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { hoverLayer: fullLayout._hoverlayer, // options for splom when hovering on same axis - hoversameaxis: hoversameaxis, + hoverthrough: hoverthrough, gd: gd }); diff --git a/src/components/fx/hovermode_defaults.js b/src/components/fx/hovermode_defaults.js index 790da520eab..4f356ee1de9 100644 --- a/src/components/fx/hovermode_defaults.js +++ b/src/components/fx/hovermode_defaults.js @@ -12,6 +12,6 @@ module.exports = function handleHoverModeDefaults(layoutIn, layoutOut) { } coerce('clickmode'); - coerce('hoversameaxis'); + coerce('hoverthrough'); return coerce('hovermode'); }; diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index 089c97c48f8..c62c4d7aff4 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -78,7 +78,7 @@ module.exports = { 'If false, hover interactions are disabled.' ].join(' ') }, - hoversameaxis: { + hoverthrough: { valType: 'boolean', dflt: false, editType: 'none', diff --git a/src/traces/splom/hover.js b/src/traces/splom/hover.js index a8837027815..458cd9b5a70 100644 --- a/src/traces/splom/hover.js +++ b/src/traces/splom/hover.js @@ -16,7 +16,7 @@ function hoverPoints(pointData, xval, yval, hovermode, opts) { var points = _hoverPoints(pointData, xpx, ypx); - if(opts.hoversameaxis && (hovermodeHasX || hovermodeHasY)) { + if(opts.hoverthrough && (hovermodeHasX || hovermodeHasY)) { var _xpx = points[0]._xpx; var _ypx = points[0]._ypx; @@ -53,7 +53,7 @@ function hoverPoints(pointData, xval, yval, hovermode, opts) { return points; } -function _hoverPoints(pointData, xpx, ypx, hoversameaxisX, hoversameaxisY) { +function _hoverPoints(pointData, xpx, ypx, hoverthroughX, hoverthroughY) { var cd = pointData.cd; var trace = cd[0].trace; var scene = pointData.scene; @@ -83,9 +83,9 @@ function _hoverPoints(pointData, xpx, ypx, hoversameaxisX, hoversameaxisY) { var dist = 0; var pick = false; - if(hoversameaxisX) { + if(hoverthroughX) { if(dx === 0) pick = true; - } else if(hoversameaxisY) { + } else if(hoverthroughY) { if(dy === 0) pick = true; } else { dist = Math.sqrt(dx * dx + dy * dy); diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 894b187957d..9af067736b7 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -2380,12 +2380,12 @@ describe('hover info on stacked subplots', function() { }); }); -describe('hover on subplots when hoversameaxis is set to true and x hovermodes', function() { +describe('hover on subplots when hoverthrough is set to true and x hovermodes', function() { 'use strict'; var mock = { layout: { - hoversameaxis: true, + hoverthrough: true, hovermode: 'x', grid: { rows: 3, @@ -2423,7 +2423,7 @@ describe('hover on subplots when hoversameaxis is set to true and x hovermodes', afterEach(destroyGraphDiv); - it('hovermode: *x* | *x unified* with hoversameaxis: true', function() { + it('hovermode: *x* | *x unified* with hoverthrough: true', function() { var pos = 0; var subplot = 'xy'; Lib.clearThrottle(); @@ -2468,12 +2468,12 @@ describe('hover on subplots when hoversameaxis is set to true and x hovermodes', }); }); -describe('hover on subplots when hoversameaxis is set to true and y hovermodes', function() { +describe('hover on subplots when hoverthrough is set to true and y hovermodes', function() { 'use strict'; var mock = { layout: { - hoversameaxis: true, + hoverthrough: true, hovermode: 'y', grid: { rows: 2, @@ -2511,7 +2511,7 @@ describe('hover on subplots when hoversameaxis is set to true and y hovermodes', afterEach(destroyGraphDiv); - it('hovermode: *y* | *y unified* with hoversameaxis: true', function() { + it('hovermode: *y* | *y unified* with hoverthrough: true', function() { var pos = 0; var subplot = 'xy'; Lib.clearThrottle(); @@ -2556,12 +2556,12 @@ describe('hover on subplots when hoversameaxis is set to true and y hovermodes', }); }); -describe('splom hover on subplots when hoversameaxis is set to true and (x|y) hovermodes', function() { +describe('splom hover on subplots when hoverthrough is set to true and (x|y) hovermodes', function() { 'use strict'; var mock = Lib.extendDeep({}, splomLogMock); mock.layout.hovermode = 'x'; - mock.layout.hoversameaxis = true; + mock.layout.hoverthrough = true; var gd; @@ -2572,7 +2572,7 @@ describe('splom hover on subplots when hoversameaxis is set to true and (x|y) ho afterEach(destroyGraphDiv); - it('splom hoversameaxis: true', function() { + it('splom hoverthrough: true', function() { Lib.clearThrottle(); Plotly.Fx.hover(gd, {x: 200, y: 200}, 'xy'); expect(gd._hoverdata.length).toBe(3); diff --git a/test/plot-schema.json b/test/plot-schema.json index f5ed29ba72b..c5ac6adc53c 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -2634,7 +2634,7 @@ "y unified" ] }, - "hoversameaxis": { + "hoverthrough": { "description": "Determines expansion of hover effects to other subplots in case of sharing an axis. Has an effect only when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.", "dflt": false, "editType": "none", From fd867c33b2c3f08606aa26df197fe06c098fc877 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 5 Apr 2024 12:37:05 -0400 Subject: [PATCH 11/17] revise attribute def --- src/components/fx/hover.js | 2 +- src/components/fx/layout_attributes.js | 6 ++++-- src/traces/splom/hover.js | 2 +- test/jasmine/tests/hover_label_test.js | 18 +++++++++--------- test/plot-schema.json | 8 ++++++-- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 41d6df64ce3..edf91b9ba1d 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -270,7 +270,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var hovermodeHasX = (hovermode || '').charAt(0) === 'x'; var hovermodeHasY = (hovermode || '').charAt(0) === 'y'; - if(hoverthrough && hasCartesian && (hovermodeHasX || hovermodeHasY)) { + if(hasCartesian && (hovermodeHasX || hovermodeHasY) && hoverthrough === 'axis') { var subplotsLength = subplots.length; for(var p = 0; p < subplotsLength; p++) { spId = subplots[p]; diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index c62c4d7aff4..6f66602ebda 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -79,11 +79,13 @@ module.exports = { ].join(' ') }, hoverthrough: { - valType: 'boolean', + valType: 'enumerated', + values: ['axis', false], dflt: false, editType: 'none', description: [ - 'Determines expansion of hover effects to other subplots in case of sharing an axis.', + 'Determines expansion of hover effects to other subplots', + 'If *axis*, points from subplots linked to the axis of hovered subplot are included.', 'Has an effect only when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.', ].join(' ') }, diff --git a/src/traces/splom/hover.js b/src/traces/splom/hover.js index 458cd9b5a70..266a4d16c54 100644 --- a/src/traces/splom/hover.js +++ b/src/traces/splom/hover.js @@ -16,7 +16,7 @@ function hoverPoints(pointData, xval, yval, hovermode, opts) { var points = _hoverPoints(pointData, xpx, ypx); - if(opts.hoverthrough && (hovermodeHasX || hovermodeHasY)) { + if((hovermodeHasX || hovermodeHasY) && opts.hoverthrough === 'axis') { var _xpx = points[0]._xpx; var _ypx = points[0]._ypx; diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 9af067736b7..cfb72f8a7dd 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -2380,12 +2380,12 @@ describe('hover info on stacked subplots', function() { }); }); -describe('hover on subplots when hoverthrough is set to true and x hovermodes', function() { +describe('hover on subplots when hoverthrough is set to *axis* and x hovermodes', function() { 'use strict'; var mock = { layout: { - hoverthrough: true, + hoverthrough: 'axis', hovermode: 'x', grid: { rows: 3, @@ -2423,7 +2423,7 @@ describe('hover on subplots when hoverthrough is set to true and x hovermodes', afterEach(destroyGraphDiv); - it('hovermode: *x* | *x unified* with hoverthrough: true', function() { + it('hovermode: *x* | *x unified* with hoverthrough: *axis*', function() { var pos = 0; var subplot = 'xy'; Lib.clearThrottle(); @@ -2468,12 +2468,12 @@ describe('hover on subplots when hoverthrough is set to true and x hovermodes', }); }); -describe('hover on subplots when hoverthrough is set to true and y hovermodes', function() { +describe('hover on subplots when hoverthrough is set to *axis* and y hovermodes', function() { 'use strict'; var mock = { layout: { - hoverthrough: true, + hoverthrough: 'axis', hovermode: 'y', grid: { rows: 2, @@ -2511,7 +2511,7 @@ describe('hover on subplots when hoverthrough is set to true and y hovermodes', afterEach(destroyGraphDiv); - it('hovermode: *y* | *y unified* with hoverthrough: true', function() { + it('hovermode: *y* | *y unified* with hoverthrough: *axis*', function() { var pos = 0; var subplot = 'xy'; Lib.clearThrottle(); @@ -2556,12 +2556,12 @@ describe('hover on subplots when hoverthrough is set to true and y hovermodes', }); }); -describe('splom hover on subplots when hoverthrough is set to true and (x|y) hovermodes', function() { +describe('splom hover on subplots when hoverthrough is set to *axis* and (x|y) hovermodes', function() { 'use strict'; var mock = Lib.extendDeep({}, splomLogMock); mock.layout.hovermode = 'x'; - mock.layout.hoverthrough = true; + mock.layout.hoverthrough = 'axis'; var gd; @@ -2572,7 +2572,7 @@ describe('splom hover on subplots when hoverthrough is set to true and (x|y) hov afterEach(destroyGraphDiv); - it('splom hoverthrough: true', function() { + it('splom hoverthrough: *axis*', function() { Lib.clearThrottle(); Plotly.Fx.hover(gd, {x: 200, y: 200}, 'xy'); expect(gd._hoverdata.length).toBe(3); diff --git a/test/plot-schema.json b/test/plot-schema.json index c5ac6adc53c..ca5394c6191 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -2635,10 +2635,14 @@ ] }, "hoverthrough": { - "description": "Determines expansion of hover effects to other subplots in case of sharing an axis. Has an effect only when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.", + "description": "Determines expansion of hover effects to other subplots If *axis*, points from subplots linked to the axis of hovered subplot are included. Has an effect only when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.", "dflt": false, "editType": "none", - "valType": "boolean" + "valType": "enumerated", + "values": [ + "axis", + false + ] }, "images": { "items": { From 8dff1a36c70b07467f88d9efd8f5b4a420eae5b7 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 5 Apr 2024 15:42:49 -0400 Subject: [PATCH 12/17] do not sort items when hovering over multiple subplots --- src/components/fx/hover.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index edf91b9ba1d..0b996dfd50d 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -692,7 +692,9 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { gd._spikepoints = newspikepoints; var sortHoverData = function() { - hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; }); + if(!hoverthrough) { + hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; }); + } // move period positioned points and box/bar-like traces to the end of the list hoverData = orderRangePoints(hoverData, hovermode); From b33e4dd79171755510a27910efe17dc58ebbdd18 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Mon, 8 Apr 2024 08:55:46 -0400 Subject: [PATCH 13/17] rename to hoversubplots --- draftlogs/6947_add.md | 2 +- src/components/fx/hover.js | 8 ++++---- src/components/fx/hovermode_defaults.js | 2 +- src/components/fx/layout_attributes.js | 2 +- src/traces/splom/hover.js | 8 ++++---- test/jasmine/tests/hover_label_test.js | 18 +++++++++--------- test/plot-schema.json | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/draftlogs/6947_add.md b/draftlogs/6947_add.md index 4d11461b193..5a6a20c4bbe 100644 --- a/draftlogs/6947_add.md +++ b/draftlogs/6947_add.md @@ -1 +1 @@ - - Add `layout.hoverthrough` to enable hover effects across multiple cartesian suplots sharing one axis [[#6947](https://github.com/plotly/plotly.js/pull/6947)] + - Add `layout.hoversubplots` to enable hover effects across multiple cartesian suplots sharing one axis [[#6947](https://github.com/plotly/plotly.js/pull/6947)] diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 0b996dfd50d..2b95d074a4e 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -261,7 +261,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var spId; var fullLayout = gd._fullLayout; - var hoverthrough = fullLayout.hoverthrough; + var hoversubplots = fullLayout.hoversubplots; var plots = fullLayout._plots || []; var plotinfo = plots[subplot]; var hasCartesian = fullLayout._has('cartesian'); @@ -270,7 +270,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var hovermodeHasX = (hovermode || '').charAt(0) === 'x'; var hovermodeHasY = (hovermode || '').charAt(0) === 'y'; - if(hasCartesian && (hovermodeHasX || hovermodeHasY) && hoverthrough === 'axis') { + if(hasCartesian && (hovermodeHasX || hovermodeHasY) && hoversubplots === 'axis') { var subplotsLength = subplots.length; for(var p = 0; p < subplotsLength; p++) { spId = subplots[p]; @@ -570,7 +570,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { hoverLayer: fullLayout._hoverlayer, // options for splom when hovering on same axis - hoverthrough: hoverthrough, + hoversubplots: hoversubplots, gd: gd }); @@ -692,7 +692,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { gd._spikepoints = newspikepoints; var sortHoverData = function() { - if(!hoverthrough) { + if(!hoversubplots) { hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; }); } diff --git a/src/components/fx/hovermode_defaults.js b/src/components/fx/hovermode_defaults.js index 4f356ee1de9..edb705e02cc 100644 --- a/src/components/fx/hovermode_defaults.js +++ b/src/components/fx/hovermode_defaults.js @@ -12,6 +12,6 @@ module.exports = function handleHoverModeDefaults(layoutIn, layoutOut) { } coerce('clickmode'); - coerce('hoverthrough'); + coerce('hoversubplots'); return coerce('hovermode'); }; diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index 6f66602ebda..c543af0b5fc 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -78,7 +78,7 @@ module.exports = { 'If false, hover interactions are disabled.' ].join(' ') }, - hoverthrough: { + hoversubplots: { valType: 'enumerated', values: ['axis', false], dflt: false, diff --git a/src/traces/splom/hover.js b/src/traces/splom/hover.js index 266a4d16c54..07f22d04c7f 100644 --- a/src/traces/splom/hover.js +++ b/src/traces/splom/hover.js @@ -16,7 +16,7 @@ function hoverPoints(pointData, xval, yval, hovermode, opts) { var points = _hoverPoints(pointData, xpx, ypx); - if((hovermodeHasX || hovermodeHasY) && opts.hoverthrough === 'axis') { + if((hovermodeHasX || hovermodeHasY) && opts.hoversubplots === 'axis') { var _xpx = points[0]._xpx; var _ypx = points[0]._ypx; @@ -53,7 +53,7 @@ function hoverPoints(pointData, xval, yval, hovermode, opts) { return points; } -function _hoverPoints(pointData, xpx, ypx, hoverthroughX, hoverthroughY) { +function _hoverPoints(pointData, xpx, ypx, hoversubplotsX, hoversubplotsY) { var cd = pointData.cd; var trace = cd[0].trace; var scene = pointData.scene; @@ -83,9 +83,9 @@ function _hoverPoints(pointData, xpx, ypx, hoverthroughX, hoverthroughY) { var dist = 0; var pick = false; - if(hoverthroughX) { + if(hoversubplotsX) { if(dx === 0) pick = true; - } else if(hoverthroughY) { + } else if(hoversubplotsY) { if(dy === 0) pick = true; } else { dist = Math.sqrt(dx * dx + dy * dy); diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index cfb72f8a7dd..4e4ac2336b9 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -2380,12 +2380,12 @@ describe('hover info on stacked subplots', function() { }); }); -describe('hover on subplots when hoverthrough is set to *axis* and x hovermodes', function() { +describe('hover on subplots when hoversubplots is set to *axis* and x hovermodes', function() { 'use strict'; var mock = { layout: { - hoverthrough: 'axis', + hoversubplots: 'axis', hovermode: 'x', grid: { rows: 3, @@ -2423,7 +2423,7 @@ describe('hover on subplots when hoverthrough is set to *axis* and x hovermodes' afterEach(destroyGraphDiv); - it('hovermode: *x* | *x unified* with hoverthrough: *axis*', function() { + it('hovermode: *x* | *x unified* with hoversubplots: *axis*', function() { var pos = 0; var subplot = 'xy'; Lib.clearThrottle(); @@ -2468,12 +2468,12 @@ describe('hover on subplots when hoverthrough is set to *axis* and x hovermodes' }); }); -describe('hover on subplots when hoverthrough is set to *axis* and y hovermodes', function() { +describe('hover on subplots when hoversubplots is set to *axis* and y hovermodes', function() { 'use strict'; var mock = { layout: { - hoverthrough: 'axis', + hoversubplots: 'axis', hovermode: 'y', grid: { rows: 2, @@ -2511,7 +2511,7 @@ describe('hover on subplots when hoverthrough is set to *axis* and y hovermodes' afterEach(destroyGraphDiv); - it('hovermode: *y* | *y unified* with hoverthrough: *axis*', function() { + it('hovermode: *y* | *y unified* with hoversubplots: *axis*', function() { var pos = 0; var subplot = 'xy'; Lib.clearThrottle(); @@ -2556,12 +2556,12 @@ describe('hover on subplots when hoverthrough is set to *axis* and y hovermodes' }); }); -describe('splom hover on subplots when hoverthrough is set to *axis* and (x|y) hovermodes', function() { +describe('splom hover on subplots when hoversubplots is set to *axis* and (x|y) hovermodes', function() { 'use strict'; var mock = Lib.extendDeep({}, splomLogMock); mock.layout.hovermode = 'x'; - mock.layout.hoverthrough = 'axis'; + mock.layout.hoversubplots = 'axis'; var gd; @@ -2572,7 +2572,7 @@ describe('splom hover on subplots when hoverthrough is set to *axis* and (x|y) h afterEach(destroyGraphDiv); - it('splom hoverthrough: *axis*', function() { + it('splom hoversubplots: *axis*', function() { Lib.clearThrottle(); Plotly.Fx.hover(gd, {x: 200, y: 200}, 'xy'); expect(gd._hoverdata.length).toBe(3); diff --git a/test/plot-schema.json b/test/plot-schema.json index ca5394c6191..f196a920d9f 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -2634,7 +2634,7 @@ "y unified" ] }, - "hoverthrough": { + "hoversubplots": { "description": "Determines expansion of hover effects to other subplots If *axis*, points from subplots linked to the axis of hovered subplot are included. Has an effect only when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.", "dflt": false, "editType": "none", From b6b5ccb5cefeea737e6b54cee268251e00263de9 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Mon, 8 Apr 2024 09:03:45 -0400 Subject: [PATCH 14/17] revise attributes --- src/components/fx/hover.js | 2 +- src/components/fx/layout_attributes.js | 9 +++++---- test/plot-schema.json | 8 ++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 2b95d074a4e..76e21a00266 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -692,7 +692,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { gd._spikepoints = newspikepoints; var sortHoverData = function() { - if(!hoversubplots) { + if(hoversubplots !== 'axis') { hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; }); } diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index c543af0b5fc..54e08f0d11c 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -80,13 +80,14 @@ module.exports = { }, hoversubplots: { valType: 'enumerated', - values: ['axis', false], - dflt: false, + values: ['overlaying', 'axis'], + dflt: 'overlaying', editType: 'none', description: [ 'Determines expansion of hover effects to other subplots', - 'If *axis*, points from subplots linked to the axis of hovered subplot are included.', - 'Has an effect only when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.', + 'If *overlaying* all subplots using the main axis and occupying the same space are included.', + 'If *axis*, also include stacked subplots using the same axis', + 'when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.', ].join(' ') }, hoverdistance: { diff --git a/test/plot-schema.json b/test/plot-schema.json index f196a920d9f..f3be05ed5df 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -2635,13 +2635,13 @@ ] }, "hoversubplots": { - "description": "Determines expansion of hover effects to other subplots If *axis*, points from subplots linked to the axis of hovered subplot are included. Has an effect only when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.", - "dflt": false, + "description": "Determines expansion of hover effects to other subplots If *overlaying* all subplots using the main axis and occupying the same space are included. If *axis*, also include stacked subplots using the same axis when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.", + "dflt": "overlaying", "editType": "none", "valType": "enumerated", "values": [ - "axis", - false + "overlaying", + "axis" ] }, "images": { From 1f5c427b2862470329cbb2c94218f7299627aa8c Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Mon, 8 Apr 2024 09:19:55 -0400 Subject: [PATCH 15/17] add single option --- src/components/fx/hover.js | 2 +- src/components/fx/layout_attributes.js | 3 ++- test/plot-schema.json | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 76e21a00266..4165bbaeab3 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -291,7 +291,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { } // list of all overlaid subplots to look at - if(plotinfo) { + if(plotinfo && hoversubplots !== 'single') { var overlayedSubplots = plotinfo.overlays.map(function(pi) { return pi.id; }); diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index 54e08f0d11c..0cb8750605b 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -80,11 +80,12 @@ module.exports = { }, hoversubplots: { valType: 'enumerated', - values: ['overlaying', 'axis'], + values: ['single', 'overlaying', 'axis'], dflt: 'overlaying', editType: 'none', description: [ 'Determines expansion of hover effects to other subplots', + 'If *single* just the axis pair of the primary point is included without overlaying subplots.', 'If *overlaying* all subplots using the main axis and occupying the same space are included.', 'If *axis*, also include stacked subplots using the same axis', 'when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.', diff --git a/test/plot-schema.json b/test/plot-schema.json index f3be05ed5df..727cfea0c85 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -2635,11 +2635,12 @@ ] }, "hoversubplots": { - "description": "Determines expansion of hover effects to other subplots If *overlaying* all subplots using the main axis and occupying the same space are included. If *axis*, also include stacked subplots using the same axis when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.", + "description": "Determines expansion of hover effects to other subplots If *single* just the axis pair of the primary point is included without overlaying subplots. If *overlaying* all subplots using the main axis and occupying the same space are included. If *axis*, also include stacked subplots using the same axis when `hovermode` is set to *x*, *x unified*, *y* or *y unified*.", "dflt": "overlaying", "editType": "none", "valType": "enumerated", "values": [ + "single", "overlaying", "axis" ] From 45f7febe1dd6731e920830371a92c9e998082f08 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Mon, 8 Apr 2024 09:31:50 -0400 Subject: [PATCH 16/17] add test --- test/jasmine/tests/hover_label_test.js | 65 ++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 4e4ac2336b9..0c6fb1fb989 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -2380,6 +2380,71 @@ describe('hover info on stacked subplots', function() { }); }); +describe('hover on subplots when hoversubplots is set to *single* and x hovermodes', function() { + 'use strict'; + + var mock = { + layout: { + hoversubplots: 'single', + hovermode: 'x', + yaxis2: { + anchor: 'x', + overlaying: 'y' + } + }, + + data: [ + { + y: [1, 2, 3] + }, + { + y: [1, 3, 2], + yaxis: 'y2' + } + ], + }; + + var gd; + + beforeEach(function(done) { + gd = createGraphDiv(); + Plotly.newPlot(gd, mock).then(done); + }); + + afterEach(destroyGraphDiv); + + it('hovermode: *x* | *x unified* with hoversubplots: *axis*', function() { + var pos = 0; + var subplot = 'xy'; + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {xval: pos}, subplot); + expect(gd._hoverdata.length).toBe(1); + assertHoverLabelContent({ + nums: '1', + name: 'trace 0', + axis: String(pos) + }); + + pos = 0; + subplot = 'xy2'; + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {xval: pos}, subplot); + expect(gd._hoverdata.length).toBe(1); + assertHoverLabelContent({ + nums: '1', + name: 'trace 1', + axis: String(pos) + }); + + Plotly.relayout(gd, 'hovermode', 'x unified'); + pos = 0; + subplot = 'xy'; + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {xval: pos}, subplot); + expect(gd._hoverdata.length).toBe(1); + }); +}); + describe('hover on subplots when hoversubplots is set to *axis* and x hovermodes', function() { 'use strict'; From d9aea4b63f2c6d60be2f2f84702359186125d135 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Tue, 9 Apr 2024 12:28:12 -0400 Subject: [PATCH 17/17] fix case of splom with multiple points at same (x|y) position --- src/traces/splom/hover.js | 2 + test/jasmine/tests/hover_label_test.js | 100 +++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/src/traces/splom/hover.js b/src/traces/splom/hover.js index 07f22d04c7f..eef4b23f40d 100644 --- a/src/traces/splom/hover.js +++ b/src/traces/splom/hover.js @@ -73,6 +73,8 @@ function _hoverPoints(pointData, xpx, ypx, hoversubplotsX, hoversubplotsY) { var minDist = maxDistance; for(var i = 0; i < x.length; i++) { + if((hoversubplotsX || hoversubplotsY) && i !== pointData.index) continue; + var ptx = x[i]; var pty = y[i]; var thisXpx = xa.c2p(ptx); diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 0c6fb1fb989..1fd2b8b461f 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -2660,6 +2660,106 @@ describe('splom hover on subplots when hoversubplots is set to *axis* and (x|y) }); }); +describe('splom hover *axis* hoversubplots splom points on same position should pick points with same index', function() { + 'use strict'; + + var mock = { + data: [{ + type: 'splom', + dimensions: [{ + values: [1, 1, 1, 1] + }, { + values: [1, 2, 3, 4] + }, { + values: [1, 2, 3, 4] + }, { + values: [1, null, 3, 4] + } + ]}], + layout: { + hoversubplots: 'axis', + hovermode: 'x', + width: 600, + height: 600 + } + }; + + var gd; + + beforeEach(function(done) { + gd = createGraphDiv(); + Plotly.newPlot(gd, mock).then(done); + }); + + afterEach(destroyGraphDiv); + + it('splom *axis* hoversubplots', function() { + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {}, 'xy'); + expect(gd._hoverdata.length).toBe(5); + assertHoverLabelContent({ + nums: ['1', '1', '1', '1'], + name: ['', '', '', ''], + axis: '1' + }); + + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {}, 'xy2'); + expect(gd._hoverdata.length).toBe(4); + assertHoverLabelContent({ + nums: ['1', '2', '2'], + name: ['', '', ''], + axis: '1' + }); + + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {}, 'xy3'); + expect(gd._hoverdata.length).toBe(4); + assertHoverLabelContent({ + nums: ['1', '2', '2'], + name: ['', '', ''], + axis: '1' + }); + + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {}, 'xy4'); + expect(gd._hoverdata.length).toBe(5); + assertHoverLabelContent({ + nums: ['1', '3', '3', '3'], + name: ['', '', '', ''], + axis: '1' + }); + + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {}, 'x2y'); + expect(gd._hoverdata.length).toBe(5); + assertHoverLabelContent({ + nums: ['1', '3', '3', '3'], + name: ['', '', '', ''], + axis: '3' + }); + + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {}, 'x3y'); + expect(gd._hoverdata.length).toBe(5); + assertHoverLabelContent({ + nums: ['1', '3', '3', '3'], + name: ['', '', '', ''], + axis: '3' + }); + + Lib.clearThrottle(); + Plotly.Fx.hover(gd, {}, 'x4y'); + expect(gd._hoverdata.length).toBe(5); + assertHoverLabelContent({ + nums: ['1', '3', '3', '3'], + name: ['', '', '', ''], + axis: '3' + }); + }); +}); + + describe('hover on many lines+bars', function() { 'use strict';