From 015d3f0659c1563c181e0e920575c431c5c2388b Mon Sep 17 00:00:00 2001 From: My-Tien Nguyen Date: Thu, 13 Aug 2026 15:06:03 +0200 Subject: [PATCH 1/5] Fix #7152 (Hover label on category sometimes shows label from neighboring category) Now, data points within hoverdistance but outside the hovered category are omitted from the hover label. Within the category, the hoverdistance still filters away too distant data points. --- src/components/fx/hover.js | 15 +++++++++++++++ src/components/fx/layout_attributes.js | 5 ++++- 2 files changed, 19 insertions(+), 1 deletion(-) 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: { From 25f83cd97273358c5cc41bc3f4c2dfc584c2782b Mon Sep 17 00:00:00 2001 From: My-Tien Nguyen Date: Thu, 13 Aug 2026 15:15:11 +0200 Subject: [PATCH 2/5] Update plot-schema.json with changes to hoverdistance --- test/plot-schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From 2f8ffa2defb87c8c644fde64629045857c0e8631 Mon Sep 17 00:00:00 2001 From: My-Tien Nguyen Date: Thu, 13 Aug 2026 15:20:59 +0200 Subject: [PATCH 3/5] Add draftlog for fix PR 7960. --- draftlogs/7960_fix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 draftlogs/7960_fix.md 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)] From fcebdfc24e221dc2ff3476aa52677d12b290f94f Mon Sep 17 00:00:00 2001 From: My-Tien Nguyen Date: Thu, 13 Aug 2026 16:28:06 +0200 Subject: [PATCH 4/5] Add jasmine test for fix that hover label should only include data from hovered category --- test/jasmine/tests/hover_label_test.js | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 2076c3fcf41..13c8c2a3b5b 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); }); + + fit('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 () { From 4da35d883630b813153aa63ef154642e56ecf69b Mon Sep 17 00:00:00 2001 From: My-Tien Nguyen Date: Thu, 13 Aug 2026 16:36:42 +0200 Subject: [PATCH 5/5] Remove accidental commit of `fit` call in jasmine test --- test/jasmine/tests/hover_label_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 13c8c2a3b5b..b530f08ec4d 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -7437,7 +7437,7 @@ describe('hovermode: (x|y)unified', function () { .then(done, done.fail); }); - fit('hover label should only show values of hovered category', function (done) { + it('hover label should only show values of hovered category', function (done) { Plotly.newPlot(gd, { data: [ { name: 'bar',