10000 Add spikelines by apalchys · Pull Request #2247 · plotly/plotly.js · GitHub
[go: up one dir, main page]

Skip to content

Add spikelines #2247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 17, 2018
Prev Previous commit
Next Next commit
re-work spikeline based on PR review
  • Loading branch information
apalchys committed Jan 11, 2018
commit 911ae0e51516a29fbde016715e4dd12c7f3c34cf
264 changes: 128 additions & 136 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ var Registry = require('../../registry');

var helpers = require('./helpers');
var constants = require('./constants');
var getTraceColor = require('../../traces/scatter/get_trace_color');

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

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

var hoverdistance = fullLayout.hoverdistance === 0 ? Infinity : fullLayout.hoverdistance;
var spikedistance = fullLayout.spikedistance === 0 ? Infinity : fullLayout.spikedistance;

// hoverData: the set of candidate points we've found to highlight
var hoverData = [],

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

var hoverdistance = fullLayout.hoverdistance === 0 ? Infinity : fullLayout.hoverdistance;
var spikedistance = fullLayout.spikedistance === 0 ? Infinity : fullLayout.spikedistance;

// container for new point, also used to pass info into module.hoverPoints
pointData = {
// trace properties
Expand Down Expand Up @@ -389,17 +389,6 @@ function _hover(gd, evt, subplot, noHoverEvent) {
yval = yvalArray[subploti];
}

// Find the points for the spikes first to avoid overwriting the hoverLabels data.
if(fullLayout._has('cartesian')) {
if(fullLayout.hovermode === 'closest') {
spikePoints.hLinePoint = setClosestPoint(pointData, xval, yval, 'closest', spikePoints.hLinePoint, spikedistance, 'h');
spikePoints.vLinePoint = setClosestPoint(pointData, xval, yval, 'closest', spikePoints.vLinePoint, spikedistance, 'v');
} else {
spikePoints.hLinePoint = setClosestPoint(pointData, xval, yval, 'y', spikePoints.hLinePoint, spikedistance, 'h');
spikePoints.vLinePoint = setClosestPoint(pointData, xval, yval, 'x', spikePoints.vLinePoint, spikedistance, 'v');
}
}

// Now find the points.
if(trace._module && trace._module.hoverPoints) {
var newPoints = trace._module.hoverPoints(pointData, xval, yval, mode, fullLayout._hoverlayer);
Expand All @@ -423,105 +412,96 @@ function _hover(gd, evt, subplot, noHoverEvent) {
hoverData.splice(0, closedataPreviousLength);
distance = hoverData[0].distance;
}
}

function findClosestPoints(pointData, xval, yval, hovermode) {
var cd = pointData.cd,
trace = cd[0].trace,
xa = pointData.xa,
ya = pointData.ya,
xpx = xa.c2p(xval),
ypx = ya.c2p(yval),
hoveron = trace.hoveron || '';

if(hoveron.indexOf('points') !== -1) {
var dx = function(di) {
// scatter points: d.mrc is the calculated marker radius
// adjust the distance so if you're inside the marker it
// always will show up regardless of point size, but
// prioritize smaller points
var rad = Math.max(3, di.mrc || 0);
return Math.max(Math.abs(xa.c2p(di.x) - xpx) - rad, 1 - 3 / rad);
},
dy = function(di) {
var rad = Math.max(3, di.mrc || 0);
return Math.max(Math.abs(ya.c2p(di.y) - ypx) - rad, 1 - 3 / rad);
},
dxy = function(di) {
var rad = Math.max(3, di.mrc || 0),
dx = xa.c2p(di.x) - xpx,
dy = ya.c2p(di.y) - ypx;
return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad);
},
distfn = helpers.getDistanceFunction(hovermode, dx, dy, dxy);

helpers.getClosest(cd, distfn, pointData);

// skip the rest (for this trace) if we didn't find a close point
if(pointData.index !== false) {

// the closest data point
var di = cd[pointData.index],
xc = xa.c2p(di.x, true),
yc = ya.c2p(di.y, true),
rad = di.mrc || 1;

Lib.extendFlat(pointData, {
color: getTraceColor(trace, di),
x0: xc - rad,
x1: xc + rad,
y0: yc - rad,
y1: yc + rad,
});
return [pointData];
// Now look for the points to draw the spikelines
// Do it only if there is no hoverData
if(fullLayout._has('cartesian')) {
if(hoverData.length === 0) {
pointData.distance = spikedistance;
pointData.index = false;
var closestPoints = trace._module.hoverPoints(pointData, xval, yval, 'closest');
if(closestPoints) {
var tmpPoint;
var closestVPoints = closestPoints.filter(function(point) {
return point.xa.showspikes;
});
if(closestVPoints.length) {
var closestVPt = closestVPoints[0];
if(isNumeric(closestVPt.x0) && isNumeric(closestVPt.y0)) {
tmpPoint = clearClosestPoint(closestVPt);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clearClosestPoint method is doing mostly initialisation as opposed to clearing something that exists already.

Would be mind rename it either initClosestPoint or fillClosestPoint? This would make it more aligned with the rest of our codebase.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I've renamed it to fillClosestPoint.

if(!spikePoints.vLinePoint || (spikePoints.vLinePoint.distance > tmpPoint.distance)) {
spikePoints.vLinePoint = tmpPoint;
}
}
}

var closestHPoints = closestPoints.filter(function(point) {
return point.ya.showspikes;
});
if(closestHPoints.length) {
var closestHPt = closestHPoints[0];
if(isNumeric(closestHPt.x0) && isNumeric(closestHPt.y0)) {
tmpPoint = clearClosestPoint(closestHPt);
if(!spikePoints.hLinePoint || (spikePoints.hLinePoint.distance > tmpPoint.distance)) {
spikePoints.hLinePoint = tmpPoint;
}
}
}
}
}
}
}

function setClosestPoint(pointData, xval, yval, mode, endPoint, spikedistance, type) {
var tmpDistance = pointData.distance;
var tmpIndex = pointData.index;
var resultPoint = endPoint;
pointData.distance = spikedistance;
pointData.index = false;
var closestPoints = findClosestPoints(pointData, xval, yval, mode);
if(closestPoints) {
closestPoints = closestPoints.filter(function(point) {
if(type === 'v') {
return point.xa.showspikes;
} else if(type === 'h') {
return point.ya.showspikes;
}
function selectClosestPoint(pointsData, spikedistance) {
if(!pointsData.length) return null;
var resultPoint;
var pointsDistances = pointsData.map(function(point, index) {
var xa = point.xa,
ya = point.ya,
xpx = xa.c2p(xval),
ypx = ya.c2p(yval),
dxy = function(point) {
var rad = Math.max(3, point.mrc || 0),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Math.max(3, point.mrc || 0) only applies to scatter traces and is a duplicate of this line in scatter/hover.js.

Perhaps we should make the relevant traces/*/hover.js methods (I can think of scatter at the moment, but there might be more) add a .kink field to their output point data to not have to duplicate the somewhat tricky logic here again?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved it to the point.kink property of the scatter/hover returned object.

dx = (point.x1 + point.x0) / 2 - xpx,
dy = (point.y1 + point.y0) / 2 - ypx;
return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad);
};
var distance = dxy(point);
return {distance: distance, index: index};
});
pointsDistances = pointsDistances
.filter(function(point) {
return point.distance <= spikedistance;
})
.sort(function(a, b) {
return a.distance - b.distance;
});
if(closestPoints.length) {
var closestPt = closestPoints[0];
if(isNumeric(closestPt.x0) && isNumeric(closestPt.y0)) {
var tmpPoint = {
xa: closestPt.xa,
ya: closestPt.ya,
x0: closestPt.x0,
x1: closestPt.x1,
y0: closestPt.y0,
y1: closestPt.y1,
distance: closestPt.distance,
curveNumber: closestPt.trace.index,
color: closestPt.color,
pointNumber: closestPt.index
};
if(!resultPoint || (resultPoint.distance > tmpPoint.distance)) {
resultPoint = tmpPoint;
}
}
}
if(pointsDistances.length) {
resultPoint = pointsData[pointsDistances[0].index];
} else {
resultPoint = null;
}
pointData.index = tmpIndex;
pointData.distance = tmpDistance;
return resultPoint;
}

function clearClosestPoint(point) {
if(!point) return null;
return {
xa: point.xa,
ya: point.ya,
x0: point.x0,
x1: point.x1,
y0: point.y0,
y1: point.y1,
distance: point.distance,
curveNumber: point.trace.index,
color: point.color,
pointNumber: point.index
};
}

// if hoverData is empty check for the spikes to draw and quit if there are none
var spikelineOpts = {
hovermode: hovermode,
fullLayout: fullLayout,
container: fullLayout._hoverlayer,
outerContainer: fullLayout._paperdiv,
10000 Expand All @@ -534,6 +514,22 @@ function _hover(gd, evt, subplot, noHoverEvent) {
};
gd._spikepoints = newspikepoints;

if(fullLayout._has('cartesian')) {
if(hoverData.length !== 0) {
var tmpHPointData = hoverData.filter(function(point) {
return point.ya.showspikes;
});
var tmpHPoint = selectClosestPoint(tmpHPointData, spikedistance);
spikePoints.hLinePoint = clearClosestPoint(tmpHPoint);

var tmpVPointData = hoverData.filter(function(point) {
return point.xa.showspikes;
});
var tmpVPoint = selectClosestPoint(tmpVPointData, spikedistance);
spikePoints.vLinePoint = clearClosestPoint(tmpVPoint);
}
}

if(hoverData.length === 0) {
var result = dragElement.unhoverRaw(gd, evt);
if(fullLayout._has('cartesian') && ((spikePoints.hLinePoint !== null) || (spikePoints.vLinePoint !== null))) {
Expand Down Expand Up @@ -580,7 +576,8 @@ function _hover(gd, evt, subplot, noHoverEvent) {
bgColor: bgColor,
container: fullLayout._hoverlayer,
outerContainer: fullLayout._paperdiv,
commonLabelOpts: fullLayout.hoverlabel
commonLabelOpts: fullLayout.hoverlabel,
hoverdistance: fullLayout.hoverdistance
};

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

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

function createSpikelines(closestPoints, opts) {
var hovermode = opts.hovermode;
var container = opts.container;
var fullLayout = opts.fullLayout;
var evt = opts.event;
Expand All @@ -1242,12 +1238,13 @@ function createSpikelines(closestPoints, opts) {

// Horizontal line (to y-axis)
if(showY) {
var hLinePoint = closestPoints.hLinePoint;
xa = hLinePoint && hLinePoint.xa;
ya = hLinePoint && hLinePoint.ya;
var ySnap = ya.spikesnap,
var hLinePoint = closestPoints.hLinePoint,
hLinePointX,
hLinePointY;
xa = hLinePoint && hLinePoint.xa;
ya = hLinePoint && hLinePoint.ya;
var ySnap = ya.spikesnap;

if(ySnap === 'cursor') {
hLinePointY = evt.offsetY;
} else {
Expand All @@ -1256,16 +1253,13 @@ function createSpikelines(closestPoints, opts) {
hLinePointX = xa._offset + (hLinePoint.x0 + hLinePoint.x1) / 2;
var dfltHLineColor = tinycolor.readability(hLinePoint.color, contrastColor) < 1.5 ?
Color.contrast(contrastColor) : hLinePoint.color;
var yMode = ya.spikemode;
if(hovermode !== 'closest' && yMode.indexOf('toaxis') !== -1) {
yMode = yMode.replace('toaxis', 'across');
}
var yThickness = ya.spikethickness;
var yColor = ya.spikecolor || dfltHLineColor;
var yBB = ya._boundingBox;
var xEdge = ((yBB.left + yBB.right) / 2) < hLinePointX ? yBB.right : yBB.left;
var xBase;
var xEndSpike;
var yMode = ya.spikemode,
yThickness = ya.spikethickness,
yColor = ya.spikecolor || dfltHLineColor,
yBB = ya._boundingBox,
xEdge = ((yBB.left + yBB.right) / 2) < hLinePointX ? yBB.r CFD6 ight : yBB.left,
xBase,
xEndSpike;

if(yMode.indexOf('toaxis') !== -1 || yMode.indexOf('across') !== -1) {
if(yMode.indexOf('toaxis') !== -1) {
Expand Down Expand Up @@ -1318,31 +1312,29 @@ function createSpikelines(closestPoints, opts) {
}

if(showX) {
var vLinePoint = closestPoints.vLinePoint;
xa = vLinePoint && vLinePoint.xa;
ya = vLinePoint && vLinePoint.ya;
var xSnap = xa.spikesnap,
var vLinePoint = closestPoints.vLinePoint,
vLinePointX,
vLinePointY;

xa = vLinePoint && vLinePoint.xa;
ya = vLinePoint && vLinePoint.ya;
var xSnap = xa.spikesnap;

if(xSnap === 'cursor') {
vLinePointX = evt.offsetX;
} else {
vLinePointX = xa._offset + (vLinePoint.x0 + vLinePoint.x1) / 2;
}
vLinePointY = ya._offset + (vLinePoint.y0 + vLinePoint.y1) / 2;
var dfltVLineColor = tinycolor.readability(vLinePoint.color, contrastColor) < 1.5 ?
Color.contrast(contrastColor) : vLinePoint.color;
var xMode = xa.spikemode;
if(hovermode !== 'closest' && xMode.indexOf('toaxis') !== -1) {
xMode = xMode.replace('toaxis', 'across');
}
var xThickness = xa.spikethickness;
var xColor = xa.spikecolor || dfltVLineColor;
var xBB = xa._boundingBox;
var yEdge = ((xBB.top + xBB.bottom) / 2) < vLinePointY ? xBB.bottom : xBB.top;

var yBase;
var yEndSpike;
Color.contrast(contrastColor) : vLinePoint.color;
var xMode = xa.spikemode,
xThickness = xa.spikethickness,
xColor = xa.spikecolor || dfltVLineColor,
xBB = xa._boundingBox,
yEdge = ((xBB.top + xBB.bottom) / 2) < vLinePointY ? xBB.bottom : xBB.top,
yBase,
yEndSpike;

if(xMode.indexOf('toaxis') !== -1 || xMode.indexOf('across') !== -1) {
if(xMode.indexOf('toaxis') !== -1) {
Expand Down
Loading
0