Skip to content

Commit 1063ecb

Browse files
authored
Merge pull request #5683 from plotly/fix5681-closest-points-in-compare-mode
Pick closest points to the winning point in compare mode and position unified hover box in front of winning point
2 parents 13831b2 + 282a227 commit 1063ecb

File tree

6 files changed

+302
-95
lines changed

6 files changed

+302
-95
lines changed

src/components/fx/hover.js

+47-20
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ var YSHIFTY = Math.sin(YA_RADIANS);
3939
var HOVERARROWSIZE = constants.HOVERARROWSIZE;
4040
var HOVERTEXTPAD = constants.HOVERTEXTPAD;
4141

42+
var multipleHoverPoints = {
43+
box: true,
44+
ohlc: true,
45+
violin: true,
46+
candlestick: true
47+
};
48+
4249
// fx.hover: highlight data on hover
4350
// evt can be a mousemove event, or an object with data about what points
4451
// to hover on
@@ -651,39 +658,58 @@ function _hover(gd, evt, subplot, noHoverEvent) {
651658
};
652659
sortHoverData();
653660

654-
// If in compare mode, select every point at position
655661
if(
656662
helpers.isXYhover(_mode) &&
657663
hoverData[0].length !== 0 &&
658664
hoverData[0].trace.type !== 'splom' // TODO: add support for splom
659665
) {
666+
// pick winning point
660667
var winningPoint = hoverData[0];
668+
// discard other points
669+
if(multipleHoverPoints[winningPoint.trace.type]) {
670+
hoverData = hoverData.filter(function(d) {
671+
return d.trace.index === winningPoint.trace.index;
672+
});
673+
} else {
674+
hoverData = [winningPoint];
675+
}
676+
var initLen = hoverData.length;
661677

662-
var customXVal = customVal('x', winningPoint, fullLayout);
663-
var customYVal = customVal('y', winningPoint, fullLayout);
678+
var winX = getCoord('x', winningPoint, fullLayout);
679+
var winY = getCoord('y', winningPoint, fullLayout);
664680

665-
findHoverPoints(customXVal, customYVal);
681+
// in compare mode, select every point at position
682+
findHoverPoints(winX, winY);
666683

667684
var finalPoints = [];
668685
var seen = {};
669-
var insert = function(hd) {
670-
var type = hd.trace.type;
671-
var key = (
672-
type === 'box' ||
673-
type === 'violin' ||
674-
type === 'ohlc' ||
675-
type === 'candlestick'
676-
) ? hoverDataKey(hd) : hd.trace.index;
686+
var id = 0;
687+
var insert = function(newHd) {
688+
var key = multipleHoverPoints[newHd.trace.type] ? hoverDataKey(newHd) : newHd.trace.index;
677689
if(!seen[key]) {
678-
seen[key] = true;
679-
finalPoints.push(hd);
690+
id++;
691+
seen[key] = id;
692+
finalPoints.push(newHd);
693+
} else {
694+
var oldId = seen[key] - 1;
695+
var oldHd = finalPoints[oldId];
696+
if(oldId > 0 &&
697+
Math.abs(newHd.distance) <
698+
Math.abs(oldHd.distance)
699+
) {
700+
// replace with closest
701+
finalPoints[oldId] = newHd;
702+
}
680703
}
681704
};
682705

683-
// insert the winnig point first
684-
insert(winningPoint);
706+
var k;
707+
// insert the winnig point(s) first
708+
for(k = 0; k < initLen; k++) {
709+
insert(hoverData[k]);
710+
}
685711
// override from the end
686-
for(var k = hoverData.length - 1; k > 0; k--) {
712+
for(k = hoverData.length - 1; k > initLen - 1; k--) {
687713
insert(hoverData[k]);
688714
}
689715
hoverData = finalPoints;
@@ -1045,8 +1071,9 @@ function createHoverText(hoverData, opts, gd) {
10451071
legendDraw(gd, mockLegend);
10461072

10471073
// Position the hover
1048-
var ly = Lib.mean(hoverData.map(function(c) {return (c.y0 + c.y1) / 2;}));
1049-
var lx = Lib.mean(hoverData.map(function(c) {return (c.x0 + c.x1) / 2;}));
1074+
var winningPoint = hoverData[0];
1075+
var ly = (winningPoint.y0 + winningPoint.y1) / 2;
1076+
var lx = (winningPoint.x0 + winningPoint.x1) / 2;
10501077
var legendContainer = container.select('g.legend');
10511078
var tbb = legendContainer.node().getBoundingClientRect();
10521079
lx += xa._offset;
@@ -1892,7 +1919,7 @@ function orderRangePoints(hoverData, hovermode) {
18921919
return first.concat(second).concat(last);
18931920
}
18941921

1895-
function customVal(axLetter, winningPoint, fullLayout) {
1922+
function getCoord(axLetter, winningPoint, fullLayout) {
18961923
var ax = winningPoint[axLetter + 'a'];
18971924
var val = winningPoint[axLetter + 'Val'];
18981925

src/traces/bar/hover.js

+24-19
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,35 @@ function hoverOnBars(pointData, xval, yval, hovermode, opts) {
3535

3636
var posVal, sizeVal, posLetter, sizeLetter, dx, dy, pRangeCalc;
3737

38+
if(trace.orientation === 'h') {
39+
posVal = yval;
40+
sizeVal = xval;
41+
posLetter = 'y';
42+
sizeLetter = 'x';
43+
dx = sizeFn;
44+
dy = positionFn;
45+
} else {
46+
posVal = xval;
47+
sizeVal = yval;
48+
posLetter = 'x';
49+
sizeLetter = 'y';
50+
dy = sizeFn;
51+
dx = positionFn;
52+
}
53+
54+
var period = trace[posLetter + 'period'];
55+
3856
function thisBarMinPos(di) { return thisBarExtPos(di, -1); }
3957
function thisBarMaxPos(di) { return thisBarExtPos(di, 1); }
4058

4159
function thisBarExtPos(di, sgn) {
42-
return di[posLetter] + 0.5 * sgn * di.w;
60+
if(period) {
61+
return di.p + sgn * Math.abs(di.p - di.orig_p);
62+
}
63+
return di[posLetter] + sgn * di.w / 2;
4364
}
4465

45-
var minPos = isClosest ?
66+
var minPos = isClosest || period ?
4667
thisBarMinPos :
4768
function(di) {
4869
/*
@@ -60,7 +81,7 @@ function hoverOnBars(pointData, xval, yval, hovermode, opts) {
6081
return Math.min(thisBarMinPos(di), di.p - t.bardelta / 2);
6182
};
6283

63-
var maxPos = isClosest ?
84+
var maxPos = isClosest || period ?
6485
thisBarMaxPos :
6586
function(di) {
6687
return Math.max(thisBarMaxPos(di), di.p + t.bardelta / 2);
@@ -118,22 +139,6 @@ function hoverOnBars(pointData, xval, yval, hovermode, opts) {
118139
return Fx.inbox(b - v, s - v, maxSpikeDistance + (s - v) / (s - b) - 1);
119140
}
120141

121-
if(trace.orientation === 'h') {
122-
posVal = yval;
123-
sizeVal = xval;
124-
posLetter = 'y';
125-
sizeLetter = 'x';
126-
dx = sizeFn;
127-
dy = positionFn;
128-
} else {
129-
posVal = xval;
130-
sizeVal = yval;
131-
posLetter = 'x';
132-
sizeLetter = 'y';
133-
dy = sizeFn;
134-
dx = positionFn;
135-
}
136-
137142
var pa = pointData[posLetter + 'a'];
138143
var sa = pointData[sizeLetter + 'a'];
139144

src/traces/scatter/hover.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
2828
var rad = Math.max(3, di.mrc || 0);
2929
var kink = 1 - 1 / rad;
3030
var dxRaw = Math.abs(xa.c2p(di.x) - xpx);
31-
var d = (dxRaw < rad) ? (kink * dxRaw / rad) : (dxRaw - rad + kink);
32-
return d;
31+
if(di.orig_x !== undefined) dxRaw += xa.c2p(di.orig_x) - xa.c2p(di.x);
32+
return (dxRaw < rad) ? (kink * dxRaw / rad) : (dxRaw - rad + kink);
3333
};
3434
var dy = function(di) {
3535
var rad = Math.max(3, di.mrc || 0);
3636
var kink = 1 - 1 / rad;
3737
var dyRaw = Math.abs(ya.c2p(di.y) - ypx);
38+
if(di.orig_y !== undefined) dyRaw += ya.c2p(di.orig_y) - ya.c2p(di.y);
3839
return (dyRaw < rad) ? (kink * dyRaw / rad) : (dyRaw - rad + kink);
3940
};
4041
var dxy = function(di) {

src/traces/scattergl/hover.js

+2
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ function hoverPoints(pointData, xval, yval, hovermode) {
4848
for(i = 0; i < ids.length; i++) {
4949
ptx = x[ids[i]];
5050
dx = Math.abs(xa.c2p(ptx) - xpx);
51+
if(trace._origX && trace._origX[i] !== undefined) dx += xa.c2p(trace._origX[i]) - xa.c2p(ptx);
5152
if(dx < minDist) {
5253
minDist = dx;
5354
dy = ya.c2p(y[ids[i]]) - ypx;
55+
if(trace._origY && trace._origY[i] !== undefined) dy += ya.c2p(trace._origY[i]) - ya.c2p(pty);
5456
dxy = Math.sqrt(dx * dx + dy * dy);
5557
id = ids[i];
5658
}

0 commit comments

Comments
 (0)