Skip to content

Commit 911ae0e

Browse files
committed
re-work spikeline based on PR review
1 parent 37e419f commit 911ae0e

File tree

3 files changed

+462
-144
lines changed

3 files changed

+462
-144
lines changed

src/components/fx/hover.js

+128-136
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ var Registry = require('../../registry');
2424

2525
var helpers = require('./helpers');
2626
var constants = require('./constants');
27-
var getTraceColor = require('../../traces/scatter/get_trace_color');
2827

2928
// hover labels for multiple horizontal bars get tilted by some angle,
3029
// then need to be offset differently if they overlap
@@ -145,7 +144,8 @@ exports.loneHover = function loneHover(hoverItem, opts) {
145144
rotateLabels: false,
146145
bgColor: opts.bgColor || Color.background,
147146
container: container3,
148-
outerContainer: outerContainer3
147+
outerContainer: outerContainer3,
148+
hoverdistance: constants.MAXDIST
149149
};
150150

151151
var hoverLabel = createHoverText([pointData], fullOpts, opts.gd);
@@ -208,6 +208,9 @@ function _hover(gd, evt, subplot, noHoverEvent) {
208208
return dragElement.unhoverRaw(gd, evt);
209209
}
210210

211+
var hoverdistance = fullLayout.hoverdistance === 0 ? Infinity : fullLayout.hoverdistance;
212+
var spikedistance = fullLayout.spikedistance === 0 ? Infinity : fullLayout.spikedistance;
213+
211214
// hoverData: the set of candidate points we've found to highlight
212215
var hoverData = [],
213216

@@ -332,9 +335,6 @@ function _hover(gd, evt, subplot, noHoverEvent) {
332335
// within one trace mode can sometimes be overridden
333336
mode = hovermode;
334337

335-
var hoverdistance = fullLayout.hoverdistance === 0 ? Infinity : fullLayout.hoverdistance;
336-
var spikedistance = fullLayout.spikedistance === 0 ? Infinity : fullLayout.spikedistance;
337-
338338
// container for new point, also used to pass info into module.hoverPoints
339339
pointData = {
340340
// trace properties
@@ -389,17 +389,6 @@ function _hover(gd, evt, subplot, noHoverEvent) {
389389
yval = yvalArray[subploti];
390390
}
391391

392-
// Find the points for the spikes first to avoid overwriting the hoverLabels data.
393-
if(fullLayout._has('cartesian')) {
394-
if(fullLayout.hovermode === 'closest') {
395-
spikePoints.hLinePoint = setClosestPoint(pointData, xval, yval, 'closest', spikePoints.hLinePoint, spikedistance, 'h');
396-
spikePoints.vLinePoint = setClosestPoint(pointData, xval, yval, 'closest', spikePoints.vLinePoint, spikedistance, 'v');
397-
} else {
398-
spikePoints.hLinePoint = setClosestPoint(pointData, xval, yval, 'y', spikePoints.hLinePoint, spikedistance, 'h');
399-
spikePoints.vLinePoint = setClosestPoint(pointData, xval, yval, 'x', spikePoints.vLinePoint, spikedistance, 'v');
400-
}
401-
}
402-
403392
// Now find the points.
404393
if(trace._module && trace._module.hoverPoints) {
405394
var newPoints = trace._module.hoverPoints(pointData, xval, yval, mode, fullLayout._hoverlayer);
@@ -423,105 +412,96 @@ function _hover(gd, evt, subplot, noHoverEvent) {
423412
hoverData.splice(0, closedataPreviousLength);
424413
distance = hoverData[0].distance;
425414
}
426-
}
427415

428-
function findClosestPoints(pointData, xval, yval, hovermode) {
429-
var cd = pointData.cd,
430-
trace = cd[0].trace,
431-
xa = pointData.xa,
432-
ya = pointData.ya,
433-
xpx = xa.c2p(xval),
434-
ypx = ya.c2p(yval),
435-
hoveron = trace.hoveron || '';
436-
437-
if(hoveron.indexOf('points') !== -1) {
438-
var dx = function(di) {
439-
// scatter points: d.mrc is the calculated marker radius
440-
// adjust the distance so if you're inside the marker it
441-
// always will show up regardless of point size, but
442-
// prioritize smaller points
443-
var rad = Math.max(3, di.mrc || 0);
444-
return Math.max(Math.abs(xa.c2p(di.x) - xpx) - rad, 1 - 3 / rad);
445-
},
446-
dy = function(di) {
447-
var rad = Math.max(3, di.mrc || 0);
448-
return Math.max(Math.abs(ya.c2p(di.y) - ypx) - rad, 1 - 3 / rad);
449-
},
450-
dxy = function(di) {
451-
var rad = Math.max(3, di.mrc || 0),
452-
dx = xa.c2p(di.x) - xpx,
453-
dy = ya.c2p(di.y) - ypx;
454-
return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad);
455-
},
456-
distfn = helpers.getDistanceFunction(hovermode, dx, dy, dxy);
457-
458-
helpers.getClosest(cd, distfn, pointData);
459-
460-
// skip the rest (for this trace) if we didn't find a close point
461-
if(pointData.index !== false) {
462-
463-
// the closest data point
464-
var di = cd[pointData.index],
465-
xc = xa.c2p(di.x, true),
466-
yc = ya.c2p(di.y, true),
467-
rad = di.mrc || 1;
468-
469-
Lib.extendFlat(pointData, {
470-
color: getTraceColor(trace, di),
471-
x0: xc - rad,
472-
x1: xc + rad,
473-
y0: yc - rad,
474-
y1: yc + rad,
475-
});
476-
return [pointData];
416+
// Now look for the points to draw the spikelines
417+
// Do it only if there is no hoverData
418+
if(fullLayout._has('cartesian')) {
419+
if(hoverData.length === 0) {
420+
pointData.distance = spikedistance;
421+
pointData.index = false;
422+
var closestPoints = trace._module.hoverPoints(pointData, xval, yval, 'closest');
423+
if(closestPoints) {
424+
var tmpPoint;
425+
var closestVPoints = closestPoints.filter(function(point) {
426+
return point.xa.showspikes;
427+
});
428+
if(closestVPoints.length) {
429+
var closestVPt = closestVPoints[0];
430+
if(isNumeric(closestVPt.x0) && isNumeric(closestVPt.y0)) {
431+
tmpPoint = clearClosestPoint(closestVPt);
432+
if(!spikePoints.vLinePoint || (spikePoints.vLinePoint.distance > tmpPoint.distance)) {
433+
spikePoints.vLinePoint = tmpPoint;
434+
}
435+
}
436+
}
437+
438+
var closestHPoints = closestPoints.filter(function(point) {
439+
return point.ya.showspikes;
440+
});
441+
if(closestHPoints.length) {
442+
var closestHPt = closestHPoints[0];
443+
if(isNumeric(closestHPt.x0) && isNumeric(closestHPt.y0)) {
444+
tmpPoint = clearClosestPoint(closestHPt);
445+
if(!spikePoints.hLinePoint || (spikePoints.hLinePoint.distance > tmpPoint.distance)) {
446+
spikePoints.hLinePoint = tmpPoint;
447+
}
448+
}
449+
}
450+
}
477451
}
478452
}
479453
}
480454

481-
function setClosestPoint(pointData, xval, yval, mode, endPoint, spikedistance, type) {
482-
var tmpDistance = pointData.distance;
483-
var tmpIndex = pointData.index;
484-
var resultPoint = endPoint;
485-
pointData.distance = spikedistance;
486-
pointData.index = false;
487-
var closestPoints = findClosestPoints(pointData, xval, yval, mode);
488-
if(closestPoints) {
489-
closestPoints = closestPoints.filter(function(point) {
490-
if(type === 'v') {
491-
return point.xa.showspikes;
492-
} else if(type === 'h') {
493-
return point.ya.showspikes;
494-
}
455+
function selectClosestPoint(pointsData, spikedistance) {
456+
if(!pointsData.length) return null;
457+
var resultPoint;
458+
var pointsDistances = pointsData.map(function(point, index) {
459+
var xa = point.xa,
460+
ya = point.ya,
461+
xpx = xa.c2p(xval),
462+
ypx = ya.c2p(yval),
463+
dxy = function(point) {
464+
var rad = Math.max(3, point.mrc || 0),
465+
dx = (point.x1 + point.x0) / 2 - xpx,
466+
dy = (point.y1 + point.y0) / 2 - ypx;
467+
return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad);
468+
};
469+
var distance = dxy(point);
470+
return {distance: distance, index: index};
471+
});
472+
pointsDistances = pointsDistances
473+
.filter(function(point) {
474+
return point.distance <= spikedistance;
475+
})
476+
.sort(function(a, b) {
477+
return a.distance - b.distance;
495478
});
496-
if(closestPoints.length) {
497-
var closestPt = closestPoints[0];
498-
if(isNumeric(closestPt.x0) && isNumeric(closestPt.y0)) {
499-
var tmpPoint = {
500-
xa: closestPt.xa,
501-
ya: closestPt.ya,
502-
x0: closestPt.x0,
503-
x1: closestPt.x1,
504-
y0: closestPt.y0,
505-
y1: closestPt.y1,
506-
distance: closestPt.distance,
507-
curveNumber: closestPt.trace.index,
508-
color: closestPt.color,
509-
pointNumber: closestPt.index
510-
};
511-
if(!resultPoint || (resultPoint.distance > tmpPoint.distance)) {
512-
resultPoint = tmpPoint;
513-
}
514-
}
515-
}
479+
if(pointsDistances.length) {
480+
resultPoint = pointsData[pointsDistances[0].index];
481+
} else {
482+
resultPoint = null;
516483
}
517-
pointData.index = tmpIndex;
518-
pointData.distance = tmpDistance;
519484
return resultPoint;
520485
}
521486

487+
function clearClosestPoint(point) {
488+
if(!point) return null;
489+
return {
490+
xa: point.xa,
491+
ya: point.ya,
492+
x0: point.x0,
493+
x1: point.x1,
494+
y0: point.y0,
495+
y1: point.y1,
496+
distance: point.distance,
497+
curveNumber: point.trace.index,
498+
color: point.color,
499+
pointNumber: point.index
500+
};
501+
}
502+
522503
// if hoverData is empty check for the spikes to draw and quit if there are none
523504
var spikelineOpts = {
524-
hovermode: hovermode,
525505
fullLayout: fullLayout,
526506
container: fullLayout._hoverlayer,
527507
outerContainer: fullLayout._paperdiv,
@@ -534,6 +514,22 @@ function _hover(gd, evt, subplot, noHoverEvent) {
534514
};
535515
gd._spikepoints = newspikepoints;
536516

517+
if(fullLayout._has('cartesian')) {
518+
if(hoverData.length !== 0) {
519+
var tmpHPointData = hoverData.filter(function(point) {
520+
return point.ya.showspikes;
521+
});
522+
var tmpHPoint = selectClosestPoint(tmpHPointData, spikedistance);
523+
spikePoints.hLinePoint = clearClosestPoint(tmpHPoint);
524+
525+
var tmpVPointData = hoverData.filter(function(point) {
526+
return point.xa.showspikes;
527+
});
528+
var tmpVPoint = selectClosestPoint(tmpVPointData, spikedistance);
529+
spikePoints.vLinePoint = clearClosestPoint(tmpVPoint);
530+
}
531+
}
532+
537533
if(hoverData.length === 0) {
538534
var result = dragElement.unhoverRaw(gd, evt);
539535
if(fullLayout._has('cartesian') && ((spikePoints.hLinePoint !== null) || (spikePoints.vLinePoint !== null))) {
@@ -580,7 +576,8 @@ function _hover(gd, evt, subplot, noHoverEvent) {
580576
bgColor: bgColor,
581577
container: fullLayout._hoverlayer,
582578
outerContainer: fullLayout._paperdiv,
583-
commonLabelOpts: fullLayout.hoverlabel
579+
commonLabelOpts: fullLayout.hoverlabel,
580+
hoverdistance: fullLayout.hoverdistance
584581
};
585582

586583
var hoverLabels = createHoverText(hoverData, labelOpts, gd);
@@ -644,7 +641,7 @@ function createHoverText(hoverData, opts, gd) {
644641
// show the common label, if any, on the axis
645642
// never show a common label in array mode,
646643
// even if sometimes there could be one
647-
var showCommonLabel = c0.distance <= constants.MAXDIST &&
644+
var showCommonLabel = c0.distance <= opts.hoverdistance &&
648645
(hovermode === 'x' || hovermode === 'y');
649646

650647
// all hover traces hoverinfo must contain the hovermode
@@ -1223,7 +1220,6 @@ function cleanPoint(d, hovermode) {
12231220
}
12241221

12251222
function createSpikelines(closestPoints, opts) {
1226-
var hovermode = opts.hovermode;
12271223
var container = opts.container;
12281224
var fullLayout = opts.fullLayout;
12291225
var evt = opts.event;
@@ -1242,12 +1238,13 @@ function createSpikelines(closestPoints, opts) {
12421238

12431239
// Horizontal line (to y-axis)
12441240
if(showY) {
1245-
var hLinePoint = closestPoints.hLinePoint;
1246-
xa = hLinePoint && hLinePoint.xa;
1247-
ya = hLinePoint && hLinePoint.ya;
1248-
var ySnap = ya.spikesnap,
1241+
var hLinePoint = closestPoints.hLinePoint,
12491242
hLinePointX,
12501243
hLinePointY;
1244+
xa = hLinePoint && hLinePoint.xa;
1245+
ya = hLinePoint && hLinePoint.ya;
1246+
var ySnap = ya.spikesnap;
1247+
12511248
if(ySnap === 'cursor') {
12521249
hLinePointY = evt.offsetY;
12531250
} else {
@@ -1256,16 +1253,13 @@ function createSpikelines(closestPoints, opts) {
12561253
hLinePointX = xa._offset + (hLinePoint.x0 + hLinePoint.x1) / 2;
12571254
var dfltHLineColor = tinycolor.readability(hLinePoint.color, contrastColor) < 1.5 ?
12581255
Color.contrast(contrastColor) : hLinePoint.color;
1259-
var yMode = ya.spikemode;
1260-
if(hovermode !== 'closest' && yMode.indexOf('toaxis') !== -1) {
1261-
yMode = yMode.replace('toaxis', 'across');
1262-
}
1263-
var yThickness = ya.spikethickness;
1264-
var yColor = ya.spikecolor || dfltHLineColor;
1265-
var yBB = ya._boundingBox;
1266-
var xEdge = ((yBB.left + yBB.right) / 2) < hLinePointX ? yBB.right : yBB.left;
1267-
var xBase;
1268-
var xEndSpike;
1256+
var yMode = ya.spikemode,
1257+
yThickness = ya.spikethickness,
1258+
yColor = ya.spikecolor || dfltHLineColor,
1259+
yBB = ya._boundingBox,
1260+
xEdge = ((yBB.left + yBB.right) / 2) < hLinePointX ? yBB.right : yBB.left,
1261+
xBase,
1262+
xEndSpike;
12691263

12701264
if(yMode.indexOf('toaxis') !== -1 || yMode.indexOf('across') !== -1) {
12711265
if(yMode.indexOf('toaxis') !== -1) {
@@ -1318,31 +1312,29 @@ function createSpikelines(closestPoints, opts) {
13181312
}
13191313

13201314
if(showX) {
1321-
var vLinePoint = closestPoints.vLinePoint;
1322-
xa = vLinePoint && vLinePoint.xa;
1323-
ya = vLinePoint && vLinePoint.ya;
1324-
var xSnap = xa.spikesnap,
1315+
var vLinePoint = closestPoints.vLinePoint,
13251316
vLinePointX,
13261317
vLinePointY;
1318+
1319+
xa = vLinePoint && vLinePoint.xa;
1320+
ya = vLinePoint && vLinePoint.ya;
1321+
var xSnap = xa.spikesnap;
1322+
13271323
if(xSnap === 'cursor') {
13281324
vLinePointX = evt.offsetX;
13291325
} else {
13301326
vLinePointX = xa._offset + (vLinePoint.x0 + vLinePoint.x1) / 2;
13311327
}
13321328
vLinePointY = ya._offset + (vLinePoint.y0 + vLinePoint.y1) / 2;
13331329
var dfltVLineColor = tinycolor.readability(vLinePoint.color, contrastColor) < 1.5 ?
1334-
Color.contrast(contrastColor) : vLinePoint.color;
1335-
var xMode = xa.spikemode;
1336-
if(hovermode !== 'closest' && xMode.indexOf('toaxis') !== -1) {
1337-
xMode = xMode.replace('toaxis', 'across');
1338-
}
1339-
var xThickness = xa.spikethickness;
1340-
var xColor = xa.spikecolor || dfltVLineColor;
1341-
var xBB = xa._boundingBox;
1342-
var yEdge = ((xBB.top + xBB.bottom) / 2) < vLinePointY ? xBB.bottom : xBB.top;
1343-
1344-
var yBase;
1345-
var yEndSpike;
1330+
Color.contrast(contrastColor) : vLinePoint.color;
1331+
var xMode = xa.spikemode,
1332+
xThickness = xa.spikethickness,
1333+
xColor = xa.spikecolor || dfltVLineColor,
1334+
xBB = xa._boundingBox,
1335+
yEdge = ((xBB.top + xBB.bottom) / 2) < vLinePointY ? xBB.bottom : xBB.top,
1336+
yBase,
1337+
yEndSpike;
13461338

13471339
if(xMode.indexOf('toaxis') !== -1 || xMode.indexOf('across') !== -1) {
13481340
if(xMode.indexOf('toaxis') !== -1) {

0 commit comments

Comments
 (0)