Skip to content

Commit efb54b2

Browse files
authored
Merge pull request #4669 from plotly/set-unified-hovermode-via-template
Set unified hovermode via template
2 parents 6de4c75 + abf3a55 commit efb54b2

File tree

7 files changed

+133
-60
lines changed

7 files changed

+133
-60
lines changed

src/components/fx/helpers.js

+29-9
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ var Lib = require('../../lib');
1212

1313
// look for either subplot or xaxis and yaxis attributes
1414
// does not handle splom case
15-
exports.getSubplot = function getSubplot(trace) {
15+
exports.getSubplot = function(trace) {
1616
return trace.subplot || (trace.xaxis + trace.yaxis) || trace.geo;
1717
};
1818

1919
// is trace in given list of subplots?
2020
// does handle splom case
21-
exports.isTraceInSubplots = function isTraceInSubplots(trace, subplots) {
21+
exports.isTraceInSubplots = function(trace, subplots) {
2222
if(trace.type === 'splom') {
2323
var xaxes = trace.xaxes || [];
2424
var yaxes = trace.yaxes || [];
@@ -36,28 +36,28 @@ exports.isTraceInSubplots = function isTraceInSubplots(trace, subplots) {
3636
};
3737

3838
// convenience functions for mapping all relevant axes
39-
exports.flat = function flat(subplots, v) {
39+
exports.flat = function(subplots, v) {
4040
var out = new Array(subplots.length);
4141
for(var i = 0; i < subplots.length; i++) {
4242
out[i] = v;
4343
}
4444
return out;
4545
};
4646

47-
exports.p2c = function p2c(axArray, v) {
47+
exports.p2c = function(axArray, v) {
4848
var out = new Array(axArray.length);
4949
for(var i = 0; i < axArray.length; i++) {
5050
out[i] = axArray[i].p2c(v);
5151
}
5252
return out;
5353
};
5454

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

60-
exports.getClosest = function getClosest(cd, distfn, pointData) {
60+
exports.getClosest = function(cd, distfn, pointData) {
6161
// do we already have a point number? (array mode only)
6262
if(pointData.index !== false) {
6363
if(pointData.index >= 0 && pointData.index < cd.length) {
@@ -87,11 +87,11 @@ exports.getClosest = function getClosest(cd, distfn, pointData) {
8787
* @param {number} v1: signed difference between the current position and the right edge
8888
* @param {number} passVal: the value to return on success
8989
*/
90-
exports.inbox = function inbox(v0, v1, passVal) {
90+
exports.inbox = function(v0, v1, passVal) {
9191
return (v0 * v1 < 0 || v0 === 0) ? passVal : Infinity;
9292
};
9393

94-
exports.quadrature = function quadrature(dx, dy) {
94+
exports.quadrature = function(dx, dy) {
9595
return function(di) {
9696
var x = dx(di);
9797
var y = dy(di);
@@ -114,7 +114,7 @@ exports.quadrature = function quadrature(dx, dy) {
114114
* @param {object} cd
115115
* @return {object}
116116
*/
117-
exports.makeEventData = function makeEventData(pt, trace, cd) {
117+
exports.makeEventData = function(pt, trace, cd) {
118118
// hover uses 'index', select uses 'pointNumber'
119119
var pointNumber = 'index' in pt ? pt.index : pt.pointNumber;
120120

@@ -238,3 +238,23 @@ function getPointData(val, pointNumber) {
238238
return val[pointNumber];
239239
}
240240
}
241+
242+
var xyHoverMode = {
243+
x: true,
244+
y: true
245+
};
246+
247+
var unifiedHoverMode = {
248+
'x unified': true,
249+
'y unified': true
250+
};
251+
252+
exports.isUnifiedHover = function(hovermode) {
253+
if(typeof hovermode !== 'string') return false;
254+
return !!unifiedHoverMode[hovermode];
255+
};
256+
257+
exports.isXYhover = function(hovermode) {
258+
if(typeof hovermode !== 'string') return false;
259+
return !!xyHoverMode[hovermode];
260+
};

src/components/fx/hover.js

+7-8
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ var YSHIFTY = Math.sin(YA_RADIANS);
4545
var HOVERARROWSIZE = constants.HOVERARROWSIZE;
4646
var HOVERTEXTPAD = constants.HOVERTEXTPAD;
4747

48-
var XY = {x: 1, y: 1};
49-
5048
// fx.hover: highlight data on hover
5149
// evt can be a mousemove event, or an object with data about what points
5250
// to hover on
@@ -394,7 +392,7 @@ function _hover(gd, evt, subplot, noHoverEvent) {
394392

395393
// within one trace mode can sometimes be overridden
396394
mode = hovermode;
397-
if(['x unified', 'y unified'].indexOf(mode) !== -1) {
395+
if(helpers.isUnifiedHover(mode)) {
398396
mode = mode.charAt(0);
399397
}
400398

@@ -631,9 +629,10 @@ function _hover(gd, evt, subplot, noHoverEvent) {
631629
hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; });
632630

633631
// If in compare mode, select every point at position
634-
if(hoverData[0].length !== 0 &&
635-
XY[mode] &&
636-
hoverData[0].trace.type !== 'splom' // TODO: add support for splom
632+
if(
633+
helpers.isXYhover(mode) &&
634+
hoverData[0].length !== 0 &&
635+
hoverData[0].trace.type !== 'splom' // TODO: add support for splom
637636
) {
638637
var hd = hoverData[0];
639638
var cd0 = hd.cd[hd.index];
@@ -715,7 +714,7 @@ function _hover(gd, evt, subplot, noHoverEvent) {
715714

716715
var hoverLabels = createHoverText(hoverData, labelOpts, gd);
717716

718-
if(['x unified', 'y unified'].indexOf(hovermode) === -1) {
717+
if(!helpers.isUnifiedHover(hovermode)) {
719718
hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout);
720719
alignHoverText(hoverLabels, rotateLabels);
721720
}
@@ -976,7 +975,7 @@ function createHoverText(hoverData, opts, gd) {
976975
}
977976

978977
// Show a single hover label
979-
if(['x unified', 'y unified'].indexOf(hovermode) !== -1) {
978+
if(helpers.isUnifiedHover(hovermode)) {
980979
// Delete leftover hover labels from other hovermodes
981980
container.selectAll('g.hovertext').remove();
982981

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright 2012-2020, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Lib = require('../../lib');
12+
var layoutAttributes = require('./layout_attributes');
13+
14+
module.exports = function handleHoverModeDefaults(layoutIn, layoutOut, fullData) {
15+
function coerce(attr, dflt) {
16+
// don't coerce if it is already coerced in other place e.g. in cartesian defaults
17+
if(layoutOut[attr] !== undefined) return layoutOut[attr];
18+
19+
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
20+
}
21+
22+
var clickmode = coerce('clickmode');
23+
24+
var hovermodeDflt;
25+
if(layoutOut._has('cartesian')) {
26+
if(clickmode.indexOf('select') > -1) {
27+
hovermodeDflt = 'closest';
28+
} else {
29+
// flag for 'horizontal' plots:
30+
// determines the state of the mode bar 'compare' hovermode button
31+
layoutOut._isHoriz = isHoriz(fullData, layoutOut);
32+
hovermodeDflt = layoutOut._isHoriz ? 'y' : 'x';
33+
}
34+
} else hovermodeDflt = 'closest';
35+
36+
return coerce('hovermode', hovermodeDflt);
37+
};
38+
39+
function isHoriz(fullData, fullLayout) {
40+
var stackOpts = fullLayout._scatterStackOpts || {};
41+
42+
for(var i = 0; i < fullData.length; i++) {
43+
var trace = fullData[i];
44+
var subplot = trace.xaxis + trace.yaxis;
45+
var subplotStackOpts = stackOpts[subplot] || {};
46+
var groupOpts = subplotStackOpts[trace.stackgroup] || {};
47+
48+
if(trace.orientation !== 'h' && groupOpts.orientation !== 'h') {
49+
return false;
50+
}
51+
}
52+
53+
return true;
54+
}

src/components/fx/layout_defaults.js

+7-40
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,24 @@
99
'use strict';
1010

1111
var Lib = require('../../lib');
12+
var isUnifiedHover = require('./helpers').isUnifiedHover;
1213
var layoutAttributes = require('./layout_attributes');
14+
var handleHoverModeDefaults = require('./hovermode_defaults');
1315

1416
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
1517
function coerce(attr, dflt) {
1618
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
1719
}
1820

19-
var clickmode = coerce('clickmode');
20-
21-
var dragMode = coerce('dragmode');
22-
if(dragMode === 'select') coerce('selectdirection');
23-
24-
var hovermodeDflt;
25-
if(layoutOut._has('cartesian')) {
26-
if(clickmode.indexOf('select') > -1) {
27-
hovermodeDflt = 'closest';
28-
} else {
29-
// flag for 'horizontal' plots:
30-
// determines the state of the mode bar 'compare' hovermode button
31-
layoutOut._isHoriz = isHoriz(fullData, layoutOut);
32-
hovermodeDflt = layoutOut._isHoriz ? 'y' : 'x';
33-
}
34-
} else hovermodeDflt = 'closest';
35-
36-
var hoverMode = coerce('hovermode', hovermodeDflt);
21+
var hoverMode = handleHoverModeDefaults(layoutIn, layoutOut, fullData);
3722
if(hoverMode) {
38-
var dflt;
39-
if(['x unified', 'y unified'].indexOf(hoverMode) !== -1) {
40-
dflt = -1;
41-
}
4223
coerce('hoverdistance');
43-
coerce('spikedistance', dflt);
24+
coerce('spikedistance', isUnifiedHover(hoverMode) ? -1 : undefined);
4425
}
4526

27+
var dragMode = coerce('dragmode');
28+
if(dragMode === 'select') coerce('selectdirection');
29+
4630
// if only mapbox or geo subplots is present on graph,
4731
// reset 'zoom' dragmode to 'pan' until 'zoom' is implemented,
4832
// so that the correct modebar button is active
@@ -57,20 +41,3 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
5741
layoutOut.dragmode = 'pan';
5842
}
5943
};
60-
61-
function isHoriz(fullData, fullLayout) {
62-
var stackOpts = fullLayout._scatterStackOpts || {};
63-
64-
for(var i = 0; i < fullData.length; i++) {
65-
var trace = fullData[i];
66-
var subplot = trace.xaxis + trace.yaxis;
67-
var subplotStackOpts = stackOpts[subplot] || {};
68-
var groupOpts = subplotStackOpts[trace.stackgroup] || {};
69-
70-
if(trace.orientation !== 'h' && groupOpts.orientation !== 'h') {
71-
return false;
72-
}
73-
}
74-
75-
return true;
76-
}

src/components/modebar/manage.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
var axisIds = require('../../plots/cartesian/axis_ids');
1313
var scatterSubTypes = require('../../traces/scatter/subtypes');
1414
var Registry = require('../../registry');
15+
var isUnifiedHover = require('../fx/helpers').isUnifiedHover;
1516

1617
var createModeBar = require('./modebar');
1718
var modeBarButtons = require('./buttons');
@@ -85,7 +86,7 @@ function getButtonGroups(gd) {
8586
var hasPolar = fullLayout._has('polar');
8687
var hasSankey = fullLayout._has('sankey');
8788
var allAxesFixed = areAllAxesFixed(fullLayout);
88-
var hasUnifiedHoverLabel = ['x unified', 'y unified'].indexOf(fullLayout.hovermode) !== -1;
89+
var hasUnifiedHoverLabel = isUnifiedHover(fullLayout.hovermode);
8990

9091
var groups = [];
9192

src/plots/cartesian/layout_defaults.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
var Lib = require('../../lib');
1313
var Color = require('../../components/color');
14+
var isUnifiedHover = require('../../components/fx/helpers').isUnifiedHover;
15+
var handleHoverModeDefaults = require('../../components/fx/hovermode_defaults');
1416
var Template = require('../../plot_api/plot_template');
1517
var basePlotLayoutAttributes = require('../layout_attributes');
1618

@@ -205,6 +207,9 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
205207
}
206208
}
207209

210+
var hovermode = handleHoverModeDefaults(layoutIn, layoutOut, fullData);
211+
var unifiedHover = isUnifiedHover(hovermode);
212+
208213
// first pass creates the containers, determines types, and handles most of the settings
209214
for(i = 0; i < axNames.length; i++) {
210215
axName = axNames[i];
@@ -249,8 +254,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
249254
handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions);
250255
handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut);
251256

252-
var unifiedHover = layoutIn.hovermode && ['x unified', 'y unified'].indexOf(layoutIn.hovermode) !== -1;
253-
var unifiedSpike = unifiedHover && axLetter === layoutIn.hovermode.charAt(0);
257+
var unifiedSpike = unifiedHover && axLetter === hovermode.charAt(0);
254258
var spikecolor = coerce2('spikecolor', unifiedHover ? axLayoutOut.color : undefined);
255259
var spikethickness = coerce2('spikethickness', unifiedHover ? 1.5 : undefined);
256260
var spikedash = coerce2('spikedash', unifiedHover ? 'dot' : undefined);

test/jasmine/tests/hover_label_test.js

+28
Original file line numberDiff line numberDiff line change
@@ -4093,6 +4093,34 @@ describe('hovermode: (x|y)unified', function() {
40934093
.then(done);
40944094
});
40954095

4096+
it('unified hover modes should work for x/y cartesian traces via template', function(done) {
4097+
var mockCopy = Lib.extendDeep({}, mock);
4098+
delete mockCopy.layout.hovermode;
4099+
mockCopy.layout.template = {
4100+
layout: {
4101+
hovermode: 'y unified'
4102+
}
4103+
};
4104+
Plotly.newPlot(gd, mockCopy)
4105+
.then(function(gd) {
4106+
expect(gd._fullLayout.hovermode).toBe('y unified');
4107+
var ax = gd._fullLayout.yaxis;
4108+
expect(ax.showspike).toBeTrue;
4109+
expect(ax.spikemode).toBe('across');
4110+
expect(ax.spikethickness).toBe(1.5);
4111+
expect(ax.spikedash).toBe('dot');
4112+
expect(ax.spikecolor).toBe('#444');
4113+
expect(ax.spikesnap).toBe('hovered data');
4114+
expect(gd._fullLayout.xaxis.showspike).toBeFalse;
4115+
4116+
_hover(gd, { yval: 6 });
4117+
4118+
assertLabel({title: '6', items: ['trace 0 : 2', 'trace 1 : 5']});
4119+
})
4120+
.catch(failTest)
4121+
.then(done);
4122+
});
4123+
40964124
it('x unified should work for x/y cartesian traces with legendgroup', function(done) {
40974125
var mockLegendGroup = require('@mocks/legendgroup.json');
40984126
var mockCopy = Lib.extendDeep({}, mockLegendGroup);

0 commit comments

Comments
 (0)