Skip to content

Commit 1c92511

Browse files
committed
introduce hovermode 'x unified' and 'y unified'
1 parent 18ddd5d commit 1c92511

File tree

11 files changed

+364
-54
lines changed

11 files changed

+364
-54
lines changed

src/components/fx/helpers.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ exports.p2c = function p2c(axArray, v) {
5454

5555
exports.getDistanceFunction = function getDistanceFunction(mode, dx, dy, dxy) {
5656
if(mode === 'closest') return dxy || exports.quadrature(dx, dy);
57-
return mode === 'x' ? dx : dy;
57+
return mode.charAt(0) === 'x' ? dx : dy;
5858
};
5959

6060
exports.getClosest = function getClosest(cd, distfn, pointData) {

src/components/fx/hover.js

+90-8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ var Registry = require('../../registry');
2525
var helpers = require('./helpers');
2626
var constants = require('./constants');
2727

28+
var legend = require('../legend');
29+
2830
// hover labels for multiple horizontal bars get tilted by some angle,
2931
// then need to be offset differently if they overlap
3032
var YANGLE = constants.YANGLE;
@@ -244,7 +246,7 @@ function _hover(gd, evt, subplot, noHoverEvent) {
244246

245247
if(hovermode && !supportsCompare) hovermode = 'closest';
246248

247-
if(['x', 'y', 'closest'].indexOf(hovermode) === -1 || !gd.calcdata ||
249+
if(['x', 'y', 'closest', 'x unified', 'y unified'].indexOf(hovermode) === -1 || !gd.calcdata ||
248250
gd.querySelector('.zoombox') || gd._dragging) {
249251
return dragElement.unhoverRaw(gd, evt);
250252
}
@@ -388,6 +390,9 @@ function _hover(gd, evt, subplot, noHoverEvent) {
388390

389391
// within one trace mode can sometimes be overridden
390392
mode = hovermode;
393+
if(['x unified', 'y unified'].indexOf(mode) !== -1) {
394+
mode = mode.charAt(0);
395+
}
391396

392397
// container for new point, also used to pass info into module.hoverPoints
393398
pointData = {
@@ -543,7 +548,7 @@ function _hover(gd, evt, subplot, noHoverEvent) {
543548
var thisSpikeDistance;
544549
for(var i = 0; i < pointsData.length; i++) {
545550
thisSpikeDistance = pointsData[i].spikeDistance;
546-
if(thisSpikeDistance < minDistance && thisSpikeDistance <= spikedistance) {
551+
if(thisSpikeDistance <= minDistance && thisSpikeDistance <= spikedistance) {
547552
resultPoint = pointsData[i];
548553
minDistance = thisSpikeDistance;
549554
}
@@ -661,9 +666,10 @@ function _hover(gd, evt, subplot, noHoverEvent) {
661666

662667
var hoverLabels = createHoverText(hoverData, labelOpts, gd);
663668

664-
hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout);
665-
666-
alignHoverText(hoverLabels, rotateLabels);
669+
if(['x unified', 'y unified'].indexOf(hovermode) === -1) {
670+
hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout);
671+
alignHoverText(hoverLabels, rotateLabels);
672+
}
667673

668674
// TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true
669675
// we should improve the "fx" API so other plots can use it without these hack.
@@ -712,7 +718,7 @@ function createHoverText(hoverData, opts, gd) {
712718
var c0 = hoverData[0];
713719
var xa = c0.xa;
714720
var ya = c0.ya;
715-
var commonAttr = hovermode === 'y' ? 'yLabel' : 'xLabel';
721+
var commonAttr = hovermode.charAt(0) === 'y' ? 'yLabel' : 'xLabel';
716722
var t0 = c0[commonAttr];
717723
var t00 = (String(t0) || '').split(' ')[0];
718724
var outerContainerBB = outerContainer.node().getBoundingClientRect();
@@ -906,11 +912,87 @@ function createHoverText(hoverData, opts, gd) {
906912

907913
// remove the "close but not quite" points
908914
// because of error bars, only take up to a space
909-
hoverData = hoverData.filter(function(d) {
915+
hoverData = filterClosePoints(hoverData);
916+
});
917+
918+
function filterClosePoints(hoverData) {
919+
return hoverData.filter(function(d) {
910920
return (d.zLabelVal !== undefined) ||
911921
(d[commonAttr] || '').split(' ')[0] === t00;
912922
});
913-
});
923+
}
924+
925+
// Show a single hover label
926+
if(['x unified', 'y unified'].indexOf(hovermode) !== -1) {
927+
// Delete leftover hover labels from other hovermodes
928+
container.selectAll('g.hovertext').remove();
929+
930+
// similarly to compare mode, we remove the "close but not quite together" points
931+
hoverData = filterClosePoints(hoverData);
932+
933+
// mock legend
934+
var mockLayoutIn = {
935+
showlegend: true,
936+
legend: {
937+
title: {text: t0},
938+
bgcolor: fullLayout.paper_bgcolor,
939+
borderwidth: 1,
940+
tracegroupgap: 7
941+
}
942+
};
943+
var mockLayoutOut = {};
944+
legend.supplyLayoutDefaults(mockLayoutIn, mockLayoutOut, gd._fullData, fullLayout);
945+
var legendOpts = mockLayoutOut.legend;
946+
947+
// prepare items for the legend
948+
legendOpts.entries = [];
949+
for(var j = 0; j < hoverData.length; j++) {
950+
var texts = getHoverLabelText(hoverData[j], true, hovermode, fullLayout, t0);
951+
var text = texts[0];
952+
var name = texts[1];
953+
hoverData[j].text = name + ' : ' + text;
954+
hoverData[j].name = name;
955+
legendOpts.entries.push([hoverData[j]]);
956+
}
957+
legendOpts.layer = container;
958+
959+
// Draw unified hover label
960+
legend.draw(gd, legendOpts);
961+
962+
// Position the hover
963+
var ly = Lib.mean(hoverData.map(function(c) {return (c.y0 + c.y1) / 2;}));
964+
var lx = Lib.mean(hoverData.map(function(c) {return (c.x0 + c.x1) / 2;}));
965+
var legendContainer = container.select('g.legend');
966+
var tbb = legendContainer.node().getBoundingClientRect();
967+
lx += xa._offset;
968+
ly += ya._offset - tbb.height / 2;
969+
970+
// Change horizontal alignment to end up on screen
971+
var txWidth = tbb.width + 2 * HOVERTEXTPAD;
972+
var anchorStartOK = lx + txWidth <= outerWidth;
973+
var anchorEndOK = lx - txWidth >= 0;
974+
if(!anchorStartOK && anchorEndOK) {
975+
lx -= txWidth;
976+
} else {
977+
lx += 2 * HOVERTEXTPAD;
978+
}
979+
980+
// Change vertical alignement to end up on screen
981+
var txHeight = tbb.height + 2 * HOVERTEXTPAD;
982+
var overflowTop = ly <= outerTop;
983+
var overflowBottom = ly + txHeight >= outerHeight;
984+
var canFit = txHeight <= outerHeight;
985+
if(canFit) {
986+
if(overflowTop) {
987+
ly = tbb.top - outerTop + 2 * HOVERTEXTPAD;
988+
} else if(overflowBottom) {
989+
ly = outerHeight - txHeight;
990+
}
991+
}
992+
legendContainer.attr('transform', 'translate(' + lx + ',' + ly + ')');
993+
994+
return legendContainer;
995+
}
914996

915997
// show all the individual labels
916998

src/components/fx/layout_attributes.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ module.exports = {
5757
hovermode: {
5858
valType: 'enumerated',
5959
role: 'info',
60-
values: ['x', 'y', 'closest', false],
60+
values: ['x', 'y', 'closest', false, 'x unified', 'y unified'],
6161
editType: 'modebar',
6262
description: [
6363
'Determines the mode of hover interactions.',

src/components/legend/defaults.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ var basePlotLayoutAttributes = require('../../plots/layout_attributes');
1717
var helpers = require('./helpers');
1818

1919

20-
module.exports = function legendDefaults(layoutIn, layoutOut, fullData) {
20+
module.exports = function legendDefaults(layoutIn, layoutOut, fullData, fullLayout) {
2121
var containerIn = layoutIn.legend || {};
2222

2323
var legendTraceCount = 0;
@@ -82,10 +82,10 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) {
8282

8383
if(showLegend === false) return;
8484

85-
coerce('bgcolor', layoutOut.paper_bgcolor);
85+
coerce('bgcolor', (fullLayout || layoutOut).paper_bgcolor);
8686
coerce('bordercolor');
8787
coerce('borderwidth');
88-
Lib.coerceFont(coerce, 'font', layoutOut.font);
88+
Lib.coerceFont(coerce, 'font', (fullLayout || layoutOut).font);
8989

9090
var orientation = coerce('orientation');
9191
var defaultX, defaultY, defaultYAnchor;
@@ -127,6 +127,6 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) {
127127
var titleText = coerce('title.text');
128128
if(titleText) {
129129
coerce('title.side', orientation === 'h' ? 'left' : 'top');
130-
Lib.coerceFont(coerce, 'title.font', layoutOut.font);
130+
Lib.coerceFont(coerce, 'title.font', (fullLayout || layoutOut).font);
131131
}
132132
};

0 commit comments

Comments
 (0)