Skip to content
Open
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
1 change: 1 addition & 0 deletions draftlogs/8052_fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fix issue causing `scattergl` plot to freeze up when hovering over points [[#8052](https://github.com/plotly/plotly.js/pull/8052)]
199 changes: 107 additions & 92 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,16 @@ exports.loneHover = function loneHover(hoverItems, opts) {
return multiHover ? hoverLabel : hoverLabel.node();
};

// Helper function for use with spikeline logic
// Returns true if either axis has spikeline settings which allow a spikeline to be drawn
// to a point that is not currently hovered over; returns false otherwise.
function canSpikeToNonHoveredPoint(xa, ya) {
return Boolean(
(xa && xa.showspikes && xa.spikesnap !== 'hovered data') ||
(ya && ya.showspikes && ya.spikesnap !== 'hovered data')
);
Comment on lines +269 to +272

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Or perhaps

Suggested change
return Boolean(
(xa && xa.showspikes && xa.spikesnap !== 'hovered data') ||
(ya && ya.showspikes && ya.spikesnap !== 'hovered data')
);
return (xa?.showspikes && xa.spikesnap !== 'hovered data') ||
(ya?.showspikes && ya.spikesnap !== 'hovered data');

}

// The actual implementation is here:
function _hover(gd, evt, subplot, noHoverEvent, eventTarget) {
if (!subplot) subplot = 'xy';
Expand Down Expand Up @@ -658,53 +668,58 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) {
distance = hoverData[0].distance;
}

// Now if there is range to look in, find the points to draw the spikelines
// Do it only if there is no hoverData
if (hasCartesian && spikedistance !== 0) {
if (hoverData.length === 0) {
pointData.distance = spikedistance;
pointData.index = false;
var closestPoints = trace._module.hoverPoints(pointData, xval, yval, 'closest', {
hoverLayer: fullLayout._hoverlayer
// If there is no hoverData, and if the axis spikeline settings permit us to draw
// a spikeline to the nearest non-hovered point, search for points to spike to.
// This search is _expensive_ (especially when spikedistance is -1 which requires
// a search over all points), so we should only do it when absolutely required
if (hasCartesian
&& hoverData.length === 0
&& spikedistance !== 0
&& canSpikeToNonHoveredPoint(pointData.xa, pointData.ya)
) {
// Find the points to draw the spikelines
pointData.distance = spikedistance;
pointData.index = false;
var closestPoints = trace._module.hoverPoints(pointData, xval, yval, 'closest', {
hoverLayer: fullLayout._hoverlayer
});
if (closestPoints) {
closestPoints = closestPoints.filter(function (point) {
// some hover points, like scatter fills, do not allow spikes,
// so will generate a hover point but without a valid spikeDistance
return point.spikeDistance <= spikedistance;
});
if (closestPoints) {
closestPoints = closestPoints.filter(function (point) {
// some hover points, like scatter fills, do not allow spikes,
// so will generate a hover point but without a valid spikeDistance
return point.spikeDistance <= spikedistance;
});
}
if (closestPoints && closestPoints.length) {
var tmpPoint;
var closestVPoints = closestPoints.filter(function (point) {
return point.xa.showspikes && point.xa.spikesnap !== 'hovered data';
});
if (closestVPoints.length) {
var closestVPt = closestVPoints[0];
if (isNumeric(closestVPt.x0) && isNumeric(closestVPt.y0)) {
tmpPoint = fillSpikePoint(closestVPt);
if (
!spikePoints.vLinePoint ||
spikePoints.vLinePoint.spikeDistance > tmpPoint.spikeDistance
) {
spikePoints.vLinePoint = tmpPoint;
}
}
if (closestPoints && closestPoints.length) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (closestPoints && closestPoints.length) {
if (closestPoints?.length) {

var tmpPoint;
var closestVPoints = closestPoints.filter(function (point) {
return point.xa.showspikes && point.xa.spikesnap !== 'hovered data';
});
if (closestVPoints.length) {
var closestVPt = closestVPoints[0];
if (isNumeric(closestVPt.x0) && isNumeric(closestVPt.y0)) {
tmpPoint = fillSpikePoint(closestVPt);
if (
!spikePoints.vLinePoint ||
spikePoints.vLinePoint.spikeDistance > tmpPoint.spikeDistance
) {
spikePoints.vLinePoint = tmpPoint;
}
}
}

var closestHPoints = closestPoints.filter(function (point) {
return point.ya.showspikes && point.ya.spikesnap !== 'hovered data';
});
if (closestHPoints.length) {
var closestHPt = closestHPoints[0];
if (isNumeric(closestHPt.x0) && isNumeric(closestHPt.y0)) {
tmpPoint = fillSpikePoint(closestHPt);
if (
!spikePoints.hLinePoint ||
spikePoints.hLinePoint.spikeDistance > tmpPoint.spikeDistance
) {
spikePoints.hLinePoint = tmpPoint;
}
var closestHPoints = closestPoints.filter(function (point) {
return point.ya.showspikes && point.ya.spikesnap !== 'hovered data';
});
if (closestHPoints.length) {
var closestHPt = closestHPoints[0];
if (isNumeric(closestHPt.x0) && isNumeric(closestHPt.y0)) {
tmpPoint = fillSpikePoint(closestHPt);
if (
!spikePoints.hLinePoint ||
spikePoints.hLinePoint.spikeDistance > tmpPoint.spikeDistance
) {
spikePoints.hLinePoint = tmpPoint;
}
}
}
Expand Down Expand Up @@ -1156,26 +1171,26 @@ function createHoverText(hoverData, opts) {
lpath.attr(
'd',
'M' +
(lx - tooltipMidX) +
',0' +
'L' +
(lx - tooltipMidX + HOVERARROWSIZE) +
',' +
topsign +
HOVERARROWSIZE +
'H' +
halfWidth +
'v' +
topsign +
(HOVERTEXTPAD * 2 + tbb.height) +
'H' +
-halfWidth +
'V' +
topsign +
HOVERARROWSIZE +
'H' +
(lx - tooltipMidX - HOVERARROWSIZE) +
'Z'
(lx - tooltipMidX) +
',0' +
'L' +
(lx - tooltipMidX + HOVERARROWSIZE) +
',' +
topsign +
HOVERARROWSIZE +
'H' +
halfWidth +
'v' +
topsign +
(HOVERTEXTPAD * 2 + tbb.height) +
'H' +
-halfWidth +
'V' +
topsign +
HOVERARROWSIZE +
'H' +
(lx - tooltipMidX - HOVERARROWSIZE) +
'Z'
);

lx = tooltipMidX;
Expand Down Expand Up @@ -1212,24 +1227,24 @@ function createHoverText(hoverData, opts) {
lpath.attr(
'd',
'M0,0' +
'L' +
leftsign +
HOVERARROWSIZE +
',' +
HOVERARROWSIZE +
'V' +
(HOVERTEXTPAD + tbb.height / 2) +
'h' +
leftsign +
(HOVERTEXTPAD * 2 + tbb.width) +
'V-' +
(HOVERTEXTPAD + tbb.height / 2) +
'H' +
leftsign +
HOVERARROWSIZE +
'V-' +
HOVERARROWSIZE +
'Z'
'L' +
leftsign +
HOVERARROWSIZE +
',' +
HOVERARROWSIZE +
'V' +
(HOVERTEXTPAD + tbb.height / 2) +
'h' +
leftsign +
(HOVERTEXTPAD * 2 + tbb.width) +
'V-' +
(HOVERTEXTPAD + tbb.height / 2) +
'H' +
leftsign +
HOVERARROWSIZE +
'V-' +
HOVERARROWSIZE +
'Z'
);

commonLabelRect.minY = ly - (HOVERTEXTPAD + tbb.height / 2);
Expand Down Expand Up @@ -1312,12 +1327,12 @@ function createHoverText(hoverData, opts) {
var mainText = !unifiedhovertitleText
? t0
: Lib.hovertemplateString({
data:
hovermode === 'x unified' ? [{ xa: item0.xa, x: item0.xVal }] : [{ ya: item0.ya, y: item0.yVal }],
fallback: item0.trace.hovertemplatefallback,
locale: fullLayout._d3locale,
template: unifiedhovertitleText
});
data:
hovermode === 'x unified' ? [{ xa: item0.xa, x: item0.xVal }] : [{ ya: item0.ya, y: item0.yVal }],
fallback: item0.trace.hovertemplatefallback,
locale: fullLayout._d3locale,
template: unifiedhovertitleText
});

var mockLayoutIn = {
showlegend: true,
Expand Down Expand Up @@ -2182,12 +2197,12 @@ function cleanPoint(d, hovermode) {

var getVal = Array.isArray(index)
? function (calcKey, traceKey) {
var v = Lib.castOption(cd0, index, calcKey);
return pass(v) ? v : Lib.extractOption({}, trace, '', traceKey);
}
var v = Lib.castOption(cd0, index, calcKey);
return pass(v) ? v : Lib.extractOption({}, trace, '', traceKey);
}
: function (calcKey, traceKey) {
return Lib.extractOption(cd, trace, calcKey, traceKey);
};
return Lib.extractOption(cd, trace, calcKey, traceKey);
};

function fill(key, calcKey, traceKey) {
var val = getVal(calcKey, traceKey);
Expand Down
Loading
Loading