diff --git a/draftlogs/7960_fix.md b/draftlogs/7960_fix.md new file mode 100644 index 00000000000..69ad9ca9d90 --- /dev/null +++ b/draftlogs/7960_fix.md @@ -0,0 +1 @@ +- Ensure that hoverlabels on category axes with hovermode "x", "y", "x unified" and "y unified" never include data points from other categories than the hovered one [[#7960](https://github.com/plotly/plotly.js/pull/7960)] diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 543a4606f46..97600420bda 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -878,6 +878,21 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { for (k = hoverData.length - 1; k > initLen - 1; k--) { insert(hoverData[k]); } + + // on category axes only keep points that fall in the hovered category + var winAx = winningPoint[axLetter + 'a']; + if (winAx && (winAx.type === 'category' || winAx.type === 'multicategory')) { + var winCat = winningPoint[axLetter + 'LabelVal']; + finalPoints = finalPoints.filter(function (point) { + var pointAx = point[axLetter + 'a']; + if (!pointAx || (pointAx.type !== 'category' && pointAx.type !== 'multicategory')) { + // rare case of data point on overlaying axis + return true; + } + return point[axLetter + 'LabelVal'] === winCat; + }); + } + hoverData = finalPoints; sortHoverData(); } diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index 73c76275dc1..4117c95d237 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -137,7 +137,10 @@ module.exports = { 'This is only a real distance for hovering on point-like objects,', 'like scatter points. For area-like objects (bars, scatter fills, etc)', 'hovering is on inside the area and off outside, but these objects', - 'will not supersede hover on point-like objects in case of conflict.' + 'will not supersede hover on point-like objects in case of conflict.', + 'For hovermodes *x*, *y*, *x unified* and *y unified*', + 'on a category axis, only points that fall in the hovered category', + 'are shown in the hover label, regardless of this distance.' ].join(' ') }, spikedistance: { diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 2076c3fcf41..b530f08ec4d 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -7436,6 +7436,38 @@ describe('hovermode: (x|y)unified', function () { }) .then(done, done.fail); }); + + it('hover label should only show values of hovered category', function (done) { + Plotly.newPlot(gd, { + data: [ + { name: 'bar', + x: ['A', 'B'], + y: [1, 2], + type: 'bar' + }, + { + name: 'scatter', + x: ['A', 'B'], + y: [10, null], + type: 'scatter' + } + ], + layout: { + width: 400, + hovermode: 'x unified', + hoverdistance: 110 + } + }) + .then(function () { + _hover(gd, { xval: 1.1 }); + assertLabel({ title: 'B', items: ['bar : 2'] }); + }) + .then(function () { + _hover(gd, { xval: 0 }); + assertLabel({ title: 'A', items: ['bar : 1', 'scatter : 10'] }); + }) + .then(done, done.fail); + }); }); describe('hover on traces with (x|y)hoverformat', function () { diff --git a/test/plot-schema.json b/test/plot-schema.json index 78614b86693..a1ace0c0d3a 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -2825,7 +2825,7 @@ "valType": "boolean" }, "hoverdistance": { - "description": "Sets the default distance (in pixels) to look for data to add hover labels (-1 means no cutoff, 0 means no looking for data). This is only a real distance for hovering on point-like objects, like scatter points. For area-like objects (bars, scatter fills, etc) hovering is on inside the area and off outside, but these objects will not supersede hover on point-like objects in case of conflict.", + "description": "Sets the default distance (in pixels) to look for data to add hover labels (-1 means no cutoff, 0 means no looking for data). This is only a real distance for hovering on point-like objects, like scatter points. For area-like objects (bars, scatter fills, etc) hovering is on inside the area and off outside, but these objects will not supersede hover on point-like objects in case of conflict. For hovermodes *x*, *y*, *x unified* and *y unified* on a category axis, only points that fall in the hovered category are shown in the hover label, regardless of this distance.", "dflt": 20, "editType": "none", "min": -1,