diff --git a/draftlogs/6963_fix.md b/draftlogs/6963_fix.md new file mode 100644 index 00000000000..0637298dc65 --- /dev/null +++ b/draftlogs/6963_fix.md @@ -0,0 +1 @@ + - Ensure winning points of hover are listed first when hoversubplots is set to "axis" and sorting by distance [[#6963](https://github.com/plotly/plotly.js/pull/6963)] diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 883bf8c9194..9c256b6650b 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -53,6 +53,10 @@ var cartesianScatterPoints = { splom: true }; +function distanceSort(a, b) { + return a.distance - b.distance; +} + // fx.hover: highlight data on hover // evt can be a mousemove event, or an object with data about what points // to hover on @@ -270,6 +274,9 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var hovermodeHasX = (hovermode || '').charAt(0) === 'x'; var hovermodeHasY = (hovermode || '').charAt(0) === 'y'; + var firstXaxis; + var firstYaxis; + if(hasCartesian && (hovermodeHasX || hovermodeHasY) && hoversubplots === 'axis') { var subplotsLength = subplots.length; for(var p = 0; p < subplotsLength; p++) { @@ -277,8 +284,11 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { if(plots[spId]) { // 'cartesian' case + firstXaxis = Axes.getFromId(gd, spId, 'x'); + firstYaxis = Axes.getFromId(gd, spId, 'y'); + var subplotsWith = ( - Axes.getFromId(gd, spId, hovermodeHasX ? 'x' : 'y') + hovermodeHasX ? firstXaxis : firstYaxis )._subplotsWith; if(subplotsWith && subplotsWith.length) { @@ -661,6 +671,9 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var thisSpikeDistance; for(var i = 0; i < pointsData.length; i++) { + if(firstXaxis && firstXaxis._id !== pointsData[i].xa._id) continue; + if(firstYaxis && firstYaxis._id !== pointsData[i].ya._id) continue; + thisSpikeDistance = pointsData[i].spikeDistance; if(spikeOnWinning && i === 0) thisSpikeDistance = -Infinity; @@ -700,9 +713,26 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { gd._spikepoints = newspikepoints; var sortHoverData = function() { - if(hoversubplots !== 'axis') { - hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; }); - } + // When sorting keep the points in the main subplot at the top + // then add points in other subplots + + var hoverDataInSubplot = hoverData.filter(function(a) { + return ( + (firstXaxis && firstXaxis._id === a.xa._id) && + (firstYaxis && firstYaxis._id === a.ya._id) + ); + }); + + var hoverDataOutSubplot = hoverData.filter(function(a) { + return !( + (firstXaxis && firstXaxis._id === a.xa._id) && + (firstYaxis && firstYaxis._id === a.ya._id) + ); + }); + + hoverDataInSubplot.sort(distanceSort); + hoverDataOutSubplot.sort(distanceSort); + hoverData = hoverDataInSubplot.concat(hoverDataOutSubplot); // move period positioned points and box/bar-like traces to the end of the list hoverData = orderRangePoints(hoverData, hovermode); diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index a45b1571fe2..417127fcb63 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -2445,6 +2445,12 @@ describe('hover on subplots when hoversubplots is set to *single* and x hovermod }); }); +function assertFirstPointOn(gd, xaxisId, yaxisId) { + // first point should be on the main subplot + expect(gd._hoverdata[0].xaxis._id).toBe(xaxisId); + expect(gd._hoverdata[0].yaxis._id).toBe(yaxisId); +} + describe('hover on subplots when hoversubplots is set to *axis* and x hovermodes', function() { 'use strict'; @@ -2494,6 +2500,9 @@ describe('hover on subplots when hoversubplots is set to *axis* and x hovermodes Lib.clearThrottle(); Plotly.Fx.hover(gd, {xval: pos}, subplot); expect(gd._hoverdata.length).toBe(3); + + assertFirstPointOn(gd, 'x', 'y'); + assertHoverLabelContent({ nums: ['1', '10', '100'], name: ['trace 0', 'trace 1', 'trace 2'], @@ -2505,6 +2514,8 @@ describe('hover on subplots when hoversubplots is set to *axis* and x hovermodes Lib.clearThrottle(); Plotly.Fx.hover(gd, {xval: pos}, subplot); + assertFirstPointOn(gd, 'x', 'y2'); + expect(gd._hoverdata.length).toBe(3); assertHoverLabelContent({ nums: ['2', '20', '200'], @@ -2517,6 +2528,8 @@ describe('hover on subplots when hoversubplots is set to *axis* and x hovermodes Lib.clearThrottle(); Plotly.Fx.hover(gd, {xval: pos}, subplot); + assertFirstPointOn(gd, 'x', 'y3'); + expect(gd._hoverdata.length).toBe(3); assertHoverLabelContent({ nums: ['3', '30', '300'], @@ -2530,6 +2543,8 @@ describe('hover on subplots when hoversubplots is set to *axis* and x hovermodes Lib.clearThrottle(); Plotly.Fx.hover(gd, {xval: pos}, subplot); expect(gd._hoverdata.length).toBe(3); + + assertFirstPointOn(gd, 'x', 'y'); }); }); @@ -2582,6 +2597,9 @@ describe('hover on subplots when hoversubplots is set to *axis* and y hovermodes Lib.clearThrottle(); Plotly.Fx.hover(gd, {yval: pos}, subplot); expect(gd._hoverdata.length).toBe(3); + + assertFirstPointOn(gd, 'x', 'y'); + assertHoverLabelContent({ nums: ['1', '10', '100'], name: ['trace 0', 'trace 1', 'trace 2'], @@ -2594,6 +2612,9 @@ describe('hover on subplots when hoversubplots is set to *axis* and y hovermodes Plotly.Fx.hover(gd, {yval: pos}, subplot); expect(gd._hoverdata.length).toBe(3); + + assertFirstPointOn(gd, 'x2', 'y'); + assertHoverLabelContent({ nums: ['2', '20', '200'], name: ['trace 0', 'trace 1', 'trace 2'], @@ -2606,6 +2627,9 @@ describe('hover on subplots when hoversubplots is set to *axis* and y hovermodes Plotly.Fx.hover(gd, {yval: pos}, subplot); expect(gd._hoverdata.length).toBe(3); + + assertFirstPointOn(gd, 'x3', 'y'); + assertHoverLabelContent({ nums: ['3', '30', '300'], name: ['trace 0', 'trace 1', 'trace 2'], @@ -2618,6 +2642,8 @@ describe('hover on subplots when hoversubplots is set to *axis* and y hovermodes Lib.clearThrottle(); Plotly.Fx.hover(gd, {yval: pos}, subplot); expect(gd._hoverdata.length).toBe(3); + + assertFirstPointOn(gd, 'x', 'y'); }); }); @@ -2640,6 +2666,7 @@ describe('splom hover on subplots when hoversubplots is set to *axis* and (x|y) it('splom hoversubplots: *axis*', function() { Lib.clearThrottle(); Plotly.Fx.hover(gd, {x: 200, y: 200}, 'xy'); + assertFirstPointOn(gd, 'x', 'y'); expect(gd._hoverdata.length).toBe(2); assertHoverLabelContent({ nums: ['100', '100k'], @@ -2648,14 +2675,15 @@ describe('splom hover on subplots when hoversubplots is set to *axis* and (x|y) }); Plotly.relayout(gd, 'hovermode', 'x unified'); - Lib.clearThrottle(); Plotly.Fx.hover(gd, {x: 200, y: 200}, 'xy'); + assertFirstPointOn(gd, 'x', 'y'); expect(gd._hoverdata.length).toBe(2); Plotly.relayout(gd, 'hovermode', 'y unified'); Lib.clearThrottle(); Plotly.Fx.hover(gd, {x: 200, y: 200}, 'xy'); + assertFirstPointOn(gd, 'x', 'y'); expect(gd._hoverdata.length).toBe(2); }); }); @@ -2696,6 +2724,7 @@ describe('splom hover *axis* hoversubplots splom points on same position should it('splom *axis* hoversubplots', function() { Lib.clearThrottle(); Plotly.Fx.hover(gd, {}, 'xy'); + assertFirstPointOn(gd, 'x', 'y'); expect(gd._hoverdata.length).toBe(4); assertHoverLabelContent({ nums: ['1', '1', '1', '1'], @@ -2705,6 +2734,7 @@ describe('splom hover *axis* hoversubplots splom points on same position should Lib.clearThrottle(); Plotly.Fx.hover(gd, {}, 'xy2'); + assertFirstPointOn(gd, 'x', 'y2'); expect(gd._hoverdata.length).toBe(3); assertHoverLabelContent({ nums: ['1', '2', '2'], @@ -2714,6 +2744,7 @@ describe('splom hover *axis* hoversubplots splom points on same position should Lib.clearThrottle(); Plotly.Fx.hover(gd, {}, 'xy3'); + assertFirstPointOn(gd, 'x', 'y3'); expect(gd._hoverdata.length).toBe(3); assertHoverLabelContent({ nums: ['1', '2', '2'], @@ -2723,6 +2754,7 @@ describe('splom hover *axis* hoversubplots splom points on same position should Lib.clearThrottle(); Plotly.Fx.hover(gd, {}, 'xy4'); + assertFirstPointOn(gd, 'x', 'y4'); expect(gd._hoverdata.length).toBe(4); assertHoverLabelContent({ nums: ['1', '3', '3', '3'], @@ -2732,6 +2764,7 @@ describe('splom hover *axis* hoversubplots splom points on same position should Lib.clearThrottle(); Plotly.Fx.hover(gd, {}, 'x2y'); + assertFirstPointOn(gd, 'x2', 'y'); expect(gd._hoverdata.length).toBe(4); assertHoverLabelContent({ nums: ['1', '3', '3', '3'], @@ -2741,6 +2774,7 @@ describe('splom hover *axis* hoversubplots splom points on same position should Lib.clearThrottle(); Plotly.Fx.hover(gd, {}, 'x3y'); + assertFirstPointOn(gd, 'x3', 'y'); expect(gd._hoverdata.length).toBe(4); assertHoverLabelContent({ nums: ['1', '3', '3', '3'], @@ -2750,6 +2784,7 @@ describe('splom hover *axis* hoversubplots splom points on same position should Lib.clearThrottle(); Plotly.Fx.hover(gd, {}, 'x4y'); + assertFirstPointOn(gd, 'x4', 'y'); expect(gd._hoverdata.length).toBe(4); assertHoverLabelContent({ nums: ['1', '3', '3', '3'],