Skip to content

Add crossline functionality #2150

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

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 134 additions & 3 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,13 @@ function _hover(gd, evt, subplot, noHoverEvent) {
xval,
yval,
pointData,
closedataPreviousLength;
closedataPreviousLength,

// crosslinePoints: the set of candidate points we've found to draw crosslines to
crosslinePoints = {
hLinePoint: null,
vLinePoint: null
};

// Figure out what we're hovering on:
// mouse location or user-supplied data
Expand Down Expand Up @@ -379,6 +385,19 @@ function _hover(gd, evt, subplot, noHoverEvent) {
yval = yvalArray[subploti];
}

var showSpikes = fullLayout.xaxis && fullLayout.xaxis.showspikes && fullLayout.yaxis && fullLayout.yaxis.showspikes;
var showCrosslines = fullLayout.xaxis && fullLayout.xaxis.showcrossline || fullLayout.yaxis && fullLayout.yaxis.showcrossline;

// Find the points for the crosslines first to avoid overwriting the hoverLabels data.
if(fullLayout._has('cartesian') && showCrosslines && !(showSpikes && hovermode === 'closest')) {
if(fullLayout.yaxis.showcrossline) {
crosslinePoints.hLinePoint = findCrosslinePoint(pointData, xval, yval, 'y', crosslinePoints.hLinePoint);
}
if(fullLayout.xaxis.showcrossline) {
crosslinePoints.vLinePoint = findCrosslinePoint(pointData, xval, yval, 'x', crosslinePoints.vLinePoint);
}
}

// Now find the points.
if(trace._module && trace._module.hoverPoints) {
var newPoints = trace._module.hoverPoints(pointData, xval, yval, mode, fullLayout._hoverlayer);
Expand All @@ -404,8 +423,49 @@ function _hover(gd, evt, subplot, noHoverEvent) {
}
}

// nothing left: remove all labels and quit
if(hoverData.length === 0) return dragElement.unhoverRaw(gd, evt);
function findCrosslinePoint(pointData, xval, yval, mode, endPoint) {
var tmpDistance = pointData.distance;
var tmpIndex = pointData.index;
var resultPoint = endPoint;
pointData.distance = Infinity;
pointData.index = false;
var closestPoints = trace._module.hoverPoints(pointData, xval, yval, mode);
if(closestPoints) {
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,
pointNumber: closestPt.index
};
if(!resultPoint || (resultPoint.distance > tmpPoint.distance)) {
resultPoint = tmpPoint;
}
}
}
pointData.index = tmpIndex;
pointData.distance = tmpDistance;
return resultPoint;
}

// if hoverData is empty check for the crosslines to draw and quit if there are none
if(hoverData.length === 0) {
var result = dragElement.unhoverRaw(gd, evt);
if(fullLayout._has('cartesian') && ((crosslinePoints.hLinePoint !== null) || (crosslinePoints.vLinePoint !== null))) {
createCrosslines(crosslinePoints, fullLayout);
}
return result;
}

if(fullLayout._has('cartesian')) {
createCrosslines(crosslinePoints, fullLayout);
}

hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; });

Expand Down Expand Up @@ -1108,6 +1168,77 @@ function cleanPoint(d, hovermode) {
return d;
}

function createCrosslines(closestPoints, fullLayout) {
var showXSpikeline = fullLayout.xaxis && fullLayout.xaxis.showspikes;
var showYSpikeline = fullLayout.yaxis && fullLayout.yaxis.showspikes;
var showH = fullLayout.yaxis && fullLayout.yaxis.showcrossline;
var showV = fullLayout.xaxis && fullLayout.xaxis.showcrossline;
var container = fullLayout._hoverlayer;
var hovermode = fullLayout.hovermode;
if(!(showV || showH) || (showXSpikeline && showYSpikeline && hovermode === 'closest')) return;
var hLinePoint,
vLinePoint,
xa,
ya,
hLinePointY,
vLinePointX;

// Remove old crossline items
container.selectAll('.crossline').remove();

var contrastColor = Color.combine(fullLayout.plot_bgcolor, fullLayout.paper_bgcolor);
var dfltCrosslineColor = Color.contrast(contrastColor);

// do not draw a crossline if there is a spikeline
if(showV && !(showXSpikeline && hovermode === 'closest')) {
vLinePoint = closestPoints.vLinePoint;
xa = vLinePoint.xa;
vLinePointX = xa._offset + (vLinePoint.x0 + vLinePoint.x1) / 2;

var xThickness = xa.crosslinethickness;
var xDash = xa.crosslinedash;
var xColor = xa.crosslinecolor || dfltCrosslineColor;

// Foreground vertical line (to x-axis)
container.insert('line', ':first-child')
.attr({
'x1': vLinePointX,
'x2': vLinePointX,
'y1': xa._counterSpan[0],
'y2': xa._counterSpan[1],
'stroke-width': xThickness,
'stroke': xColor,
'stroke-dasharray': Drawing.dashStyle(xDash, xThickness)
})
.classed('crossline', true)
.classed('crisp', true);
}

if(showH && !(showYSpikeline && hovermode === 'closest')) {
hLinePoint = closestPoints.hLinePoint;
ya = hLinePoint.ya;
hLinePointY = ya._offset + (hLinePoint.y0 + hLinePoint.y1) / 2;

var yThickness = ya.crosslinethickness;
var yDash = ya.crosslinedash;
var yColor = ya.crosslinecolor || dfltCrosslineColor;

// Foreground horizontal line (to y-axis)
container.insert('line', ':first-child')
.attr({
'x1': ya._counterSpan[0],
'x2': ya._counterSpan[1],
'y1': hLinePointY,
'y2': hLinePointY,
'stroke-width': yThickness,
'stroke': yColor,
'stroke-dasharray': Drawing.dashStyle(yDash, yThickness)
})
.classed('crossline', true)
.classed('crisp', true);
}
}

function createSpikelines(hoverData, opts) {
var hovermode = opts.hovermode;
var container = opts.container;
Expand Down
1 change: 1 addition & 0 deletions src/components/fx/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ function loneUnhover(containerOrSelection) {

selection.selectAll('g.hovertext').remove();
selection.selectAll('.spikeline').remove();
selection.selectAll('.crossline').remove();
}

// helpers for traces that use Fx.loneHover
Expand Down
3 changes: 3 additions & 0 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ Plotly.plot = function(gd, data, layout, config) {
// save initial show spikes once per graph
if(graphWasEmpty) Plotly.Axes.saveShowSpikeInitial(gd);

// save initial show crosslines once per graph
if(graphWasEmpty) Plotly.Axes.saveShowCrosslineInitial(gd);

// prepare the data and find the autorange

// generate calcdata, if we need to
Expand Down
26 changes: 25 additions & 1 deletion src/plots/cartesian/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,30 @@ axes.saveShowSpikeInitial = function(gd, overwrite) {
return hasOneAxisChanged;
};

// save a copy of the initial crossline visibility
axes.saveShowCrosslineInitial = function(gd, overwrite) {
var axList = axes.list(gd, '', true),
hasOneAxisChanged = false;

for(var i = 0; i < axList.length; i++) {
var ax = axList[i];

var isNew = (ax._showCrosslineInitial === undefined);
var hasChanged = (
isNew || !(
ax.showcrossline === ax._showcrossline
)
);

if((isNew) || (overwrite && hasChanged)) {
ax._showCrosslineInitial = ax.showcrossline;
hasOneAxisChanged = true;
}

}
return hasOneAxisChanged;
};

// axes.expand: if autoranging, include new data in the outer limits
// for this axis
// data is an array of numbers (ie already run through ax.d2c)
Expand Down Expand Up @@ -2121,7 +2145,7 @@ axes.doTicks = function(gd, axid, skipTitle) {
top: pos,
bottom: pos,
left: ax._offset,
rigth: ax._offset + ax._length,
right: ax._offset + ax._length,
width: ax._length,
height: 0
};
Expand Down
22 changes: 22 additions & 0 deletions src/plots/cartesian/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,28 @@ module.exports = {
'plotted on'
].join(' ')
},
showcrossline: {
valType: 'boolean',
dflt: false,
role: 'style',
editType: 'none',
description: 'Determines whether or not crossline are drawn for this axis.'
},
crosslinecolor: {
valType: 'color',
dflt: null,
role: 'style',
editType: 'none',
description: 'Sets the crossline color. If undefined, will use the contrast to background color'
},
crosslinethickness: {
valType: 'number',
dflt: 2,
role: 'style',
editType: 'none',
description: 'Sets the width (in px) of the zero line.'
},
crosslinedash: extendFlat({}, dash, {dflt: 'solid', editType: 'none'}),
tickfont: fontAttrs({
editType: 'ticks',
description: 'Sets the tick font.'
Expand Down
7 changes: 7 additions & 0 deletions src/plots/cartesian/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {

handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut);

var showCrossline = coerce('showcrossline');
if(showCrossline) {
coerce('crosslinecolor');
coerce('crosslinethickness');
coerce('crosslinedash');
}

var showSpikes = coerce('showspikes');
if(showSpikes) {
coerce('spikecolor');
Expand Down
Loading