Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a2f6c5e

Browse filesBrowse files
committed
implements hovermode (x|y)unified with a legend as a hover label
1 parent f5aad90 commit a2f6c5e
Copy full SHA for a2f6c5e

File tree

Expand file treeCollapse file tree

8 files changed

+133
-41
lines changed
Filter options
Expand file treeCollapse file tree

8 files changed

+133
-41
lines changed

‎src/components/fx/helpers.js

Copy file name to clipboardExpand all lines: src/components/fx/helpers.js
+1-1Lines changed: 1 addition & 1 deletion
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

Copy file name to clipboardExpand all lines: src/components/fx/hover.js
+36-7Lines changed: 36 additions & 7 deletions
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', 'xunified', 'yunified'].indexOf(hovermode) === -1 || !gd.calcdata ||
248250
gd.querySelector('.zoombox') || gd._dragging) {
249251
return dragElement.unhoverRaw(gd, evt);
250252
}
@@ -661,9 +663,10 @@ function _hover(gd, evt, subplot, noHoverEvent) {
661663

662664
var hoverLabels = createHoverText(hoverData, labelOpts, gd);
663665

664-
hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout);
665-
666-
alignHoverText(hoverLabels, rotateLabels);
666+
if(['xunified', 'yunified'].indexOf(hovermode) === -1) {
667+
hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout);
668+
alignHoverText(hoverLabels, rotateLabels);
669+
}
667670

668671
// TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true
669672
// we should improve the "fx" API so other plots can use it without these hack.
@@ -696,7 +699,7 @@ var EXTRA_STRING_REGEX = /<extra>([\s\S]*)<\/extra>/;
696699

697700
function createHoverText(hoverData, opts, gd) {
698701
var fullLayout = gd._fullLayout;
699-
var hovermode = opts.hovermode;
702+
var hovermode = fullLayout.hovermode;
700703
var rotateLabels = opts.rotateLabels;
701704
var bgColor = opts.bgColor;
702705
var container = opts.container;
@@ -712,7 +715,7 @@ function createHoverText(hoverData, opts, gd) {
712715
var c0 = hoverData[0];
713716
var xa = c0.xa;
714717
var ya = c0.ya;
715-
var commonAttr = hovermode === 'y' ? 'yLabel' : 'xLabel';
718+
var commonAttr = hovermode.charAt(0) === 'y' ? 'yLabel' : 'xLabel';
716719
var t0 = c0[commonAttr];
717720
var t00 = (String(t0) || '').split(' ')[0];
718721
var outerContainerBB = outerContainer.node().getBoundingClientRect();
@@ -793,7 +796,7 @@ function createHoverText(hoverData, opts, gd) {
793796
var tbb = ltext.node().getBoundingClientRect();
794797
var lx, ly;
795798

796-
if(hovermode === 'x') {
799+
if(hovermode.charAt(0) === 'x') {
797800
var topsign = xa.side === 'top' ? '-' : '';
798801

799802
ltext.attr('text-anchor', 'middle')
@@ -912,6 +915,32 @@ function createHoverText(hoverData, opts, gd) {
912915
});
913916
});
914917

918+
// Show a single hover label
919+
if(hovermode === 'xunified') {
920+
var mockLayoutIn = {
921+
showlegend: true,
922+
legend: {
923+
title: {text: t0},
924+
bgcolor: '#fff',
925+
borderwidth: 1,
926+
bordercolor: '#aaa'
927+
}
928+
};
929+
var mockLayoutOut = {};
930+
legend.supplyLayoutDefaults(mockLayoutIn, mockLayoutOut, gd._fullData, fullLayout);
931+
var legendOpts = mockLayoutOut.legend;
932+
legend.draw(gd, container, legendOpts);
933+
var ly = Lib.mean(hoverData.map(function(c) {return (c.y0 + c.y1) / 2;}));
934+
var lx = Lib.mean(hoverData.map(function(c) {return (c.x0 + c.x1) / 2;}));
935+
var legendContainer = container.select('g.legend');
936+
var tbb = legendContainer.node().getBoundingClientRect();
937+
lx += tbb.width;
938+
ly += tbb.height;
939+
legendContainer.attr('transform', 'translate(' + lx + ',' + ly + ')');
940+
941+
return legendContainer;
942+
}
943+
915944
// show all the individual labels
916945

917946
// first create the objects

‎src/components/fx/layout_attributes.js

Copy file name to clipboardExpand all lines: src/components/fx/layout_attributes.js
+1-1Lines changed: 1 addition & 1 deletion
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, 'xunified', 'yunified'],
6161
editType: 'modebar',
6262
description: [
6363
'Determines the mode of hover interactions.',

‎src/components/legend/defaults.js

Copy file name to clipboardExpand all lines: src/components/legend/defaults.js
+4-4Lines changed: 4 additions & 4 deletions
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
};

‎src/components/legend/draw.js

Copy file name to clipboardExpand all lines: src/components/legend/draw.js
+31-26Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,31 @@ var getLegendData = require('./get_legend_data');
3030
var style = require('./style');
3131
var helpers = require('./helpers');
3232

33-
module.exports = function draw(gd) {
33+
module.exports = function draw(gd, layer, opts) {
34+
var main;
3435
var fullLayout = gd._fullLayout;
3536
var clipId = 'legend' + fullLayout._uid;
3637

37-
if(!fullLayout._infolayer || !gd.calcdata) return;
38+
if(!layer) layer = fullLayout._infolayer;
39+
if(!layer || !gd.calcdata) return;
3840

3941
if(!gd._legendMouseDownTime) gd._legendMouseDownTime = 0;
4042

41-
var opts = fullLayout.legend;
42-
var legendData = fullLayout.showlegend && getLegendData(gd.calcdata, opts);
43+
if(!opts) {
44+
main = true;
45+
opts = fullLayout.legend;
46+
}
47+
var legendData = (!main || fullLayout.showlegend) && getLegendData(gd.calcdata, opts);
4348
var hiddenSlices = fullLayout.hiddenlabels || [];
4449

45-
if(!fullLayout.showlegend || !legendData.length) {
46-
fullLayout._infolayer.selectAll('.legend').remove();
50+
if(main && (!fullLayout.showlegend || !legendData.length)) {
51+
layer.selectAll('.legend').remove();
4752
fullLayout._topdefs.select('#' + clipId).remove();
4853
return Plots.autoMargin(gd, 'legend');
4954
}
5055

51-
var legend = Lib.ensureSingle(fullLayout._infolayer, 'g', 'legend', function(s) {
52-
s.attr('pointer-events', 'all');
56+
var legend = Lib.ensureSingle(layer, 'g', 'legend', function(s) {
57+
if(main) s.attr('pointer-events', 'all');
5358
});
5459

5560
var clipPath = Lib.ensureSingleById(fullLayout._topdefs, 'clipPath', clipId, function(s) {
@@ -75,7 +80,7 @@ module.exports = function draw(gd) {
7580
.call(Drawing.font, title.font)
7681
.text(title.text);
7782

78-
textLayout(titleEl, scrollBox, gd); // handle mathjax or multi-line text and compute title height
83+
textLayout(titleEl, scrollBox, gd, opts); // handle mathjax or multi-line text and compute title height
7984
}
8085

8186
var scrollBar = Lib.ensureSingle(legend, 'rect', 'scrollbar', function(s) {
@@ -99,26 +104,26 @@ module.exports = function draw(gd) {
99104
return trace.visible === 'legendonly' ? 0.5 : 1;
100105
}
101106
})
102-
.each(function() { d3.select(this).call(drawTexts, gd); })
103-
.call(style, gd)
104-
.each(function() { d3.select(this).call(setupTraceToggle, gd); });
107+
.each(function() { d3.select(this).call(drawTexts, gd, opts); })
108+
.call(style, gd, opts)
109+
.each(function() { if(main) d3.select(this).call(setupTraceToggle, gd); });
105110

106111
Lib.syncOrAsync([
107112
Plots.previousPromises,
108-
function() { return computeLegendDimensions(gd, groups, traces); },
113+
function() { return computeLegendDimensions(gd, groups, traces, opts); },
109114
function() {
110115
// IF expandMargin return a Promise (which is truthy),
111116
// we're under a doAutoMargin redraw, so we don't have to
112117
// draw the remaining pieces below
113-
if(expandMargin(gd)) return;
118+
if(main && expandMargin(gd)) return;
114119

115120
var gs = fullLayout._size;
116121
var bw = opts.borderwidth;
117122

118123
var lx = gs.l + gs.w * opts.x - FROM_TL[getXanchor(opts)] * opts._width;
119124
var ly = gs.t + gs.h * (1 - opts.y) - FROM_TL[getYanchor(opts)] * opts._effHeight;
120125

121-
if(fullLayout.margin.autoexpand) {
126+
if(main && fullLayout.margin.autoexpand) {
122127
var lx0 = lx;
123128
var ly0 = ly;
124129

@@ -310,7 +315,7 @@ module.exports = function draw(gd) {
310315
}
311316
},
312317
clickFn: function(numClicks, e) {
313-
var clickedTrace = fullLayout._infolayer.selectAll('g.traces').filter(function() {
318+
var clickedTrace = layer.selectAll('g.traces').filter(function() {
314319
var bbox = this.getBoundingClientRect();
315320
return (
316321
e.clientX >= bbox.left && e.clientX <= bbox.right &&
@@ -364,10 +369,10 @@ function clickOrDoubleClick(gd, legend, legendItem, numClicks, evt) {
364369
}
365370
}
366371

367-
function drawTexts(g, gd) {
372+
function drawTexts(g, gd, opts) {
368373
var legendItem = g.data()[0][0];
369374
var fullLayout = gd._fullLayout;
370-
var opts = fullLayout.legend;
375+
if(!opts) opts = fullLayout.legend;
371376
var trace = legendItem.trace;
372377
var isPieLike = Registry.traceIs(trace, 'pie-like');
373378
var traceIndex = trace.index;
@@ -414,7 +419,7 @@ function drawTexts(g, gd) {
414419
return Registry.call('_guiRestyle', gd, update, traceIndex);
415420
});
416421
} else {
417-
textLayout(textEl, g, gd);
422+
textLayout(textEl, g, gd, opts);
418423
}
419424
}
420425

@@ -467,13 +472,13 @@ function setupTraceToggle(g, gd) {
467472
});
468473
}
469474

470-
function textLayout(s, g, gd) {
475+
function textLayout(s, g, gd, opts) {
471476
svgTextUtils.convertToTspans(s, gd, function() {
472-
computeTextDimensions(g, gd);
477+
computeTextDimensions(g, gd, opts);
473478
});
474479
}
475480

476-
function computeTextDimensions(g, gd) {
481+
function computeTextDimensions(g, gd, opts) {
477482
var legendItem = g.data()[0][0];
478483
if(legendItem && !legendItem.trace.showlegend) {
479484
g.remove();
@@ -482,8 +487,8 @@ function computeTextDimensions(g, gd) {
482487

483488
var mathjaxGroup = g.select('g[class*=math-group]');
484489
var mathjaxNode = mathjaxGroup.node();
485-
var bw = gd._fullLayout.legend.borderwidth;
486-
var opts = gd._fullLayout.legend;
490+
if(!opts) opts = gd._fullLayout.legend;
491+
var bw = opts.borderwidth;
487492
var lineHeight = (legendItem ? opts : opts.title).font.size * LINE_SPACING;
488493
var height, width;
489494

@@ -555,9 +560,9 @@ function getTitleSize(opts) {
555560
* - _width: legend width
556561
* - _maxWidth (for orientation:h only): maximum width before starting new row
557562
*/
558-
function computeLegendDimensions(gd, groups, traces) {
563+
function computeLegendDimensions(gd, groups, traces, opts) {
559564
var fullLayout = gd._fullLayout;
560-
var opts = fullLayout.legend;
565+
if(!opts) opts = fullLayout.legend;
561566
var gs = fullLayout._size;
562567

563568
var isVertical = helpers.isVertical(opts);

‎src/components/legend/style.js

Copy file name to clipboardExpand all lines: src/components/legend/style.js
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ var CST_MARKER_LINE_WIDTH = 2;
2626
var MAX_LINE_WIDTH = 10;
2727
var MAX_MARKER_LINE_WIDTH = 5;
2828

29-
module.exports = function style(s, gd) {
29+
module.exports = function style(s, gd, legend) {
3030
var fullLayout = gd._fullLayout;
31-
var legend = fullLayout.legend;
31+
if(!legend) legend = fullLayout.legend;
3232
var constantItemSizing = legend.itemsizing === 'constant';
3333

3434
var boundLineWidth = function(mlw, cont, max, cst) {
29.3 KB
Loading
+58Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"data": [
3+
{
4+
"x": [
5+
0,
6+
1,
7+
2,
8+
3,
9+
4,
10+
5,
11+
6,
12+
7,
13+
8
14+
],
15+
"y": [
16+
0,
17+
3,
18+
6,
19+
4,
20+
5,
21+
2,
22+
3,
23+
5,
24+
4
25+
],
26+
"type": "scatter"
27+
},
28+
{
29+
"x": [
30+
0,
31+
1,
32+
2,
33+
3,
34+
4,
35+
5,
36+
6,
37+
7,
38+
8
39+
],
40+
"y": [
41+
0,
42+
4,
43+
7,
44+
8,
45+
3,
46+
6,
47+
3,
48+
3,
49+
4
50+
],
51+
"type": "scatter"
52+
}
53+
],
54+
"layout": {
55+
"showlegend": false,
56+
"hovermode": "xunified"
57+
}
58+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.