Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d489d1c

Browse files
committed
Merge pull request #301 from john-soklaski/multi-subplot-hover
Add support for multiple subplots to fx.hover()
2 parents 15de896 + a2164e4 commit d489d1c

File tree

4 files changed

+176
-17
lines changed

4 files changed

+176
-17
lines changed

src/plots/cartesian/graph_interact.js

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,14 @@ function hover(gd, evt, subplot) {
313313

314314
var fullLayout = gd._fullLayout,
315315
plotinfo = fullLayout._plots[subplot],
316-
// list of all overlaid subplots to look at
317-
subplots = [subplot].concat(plotinfo.overlays
318-
.map(function(pi) { return pi.id; })),
316+
317+
//If the user passed in an array of subplots, use those instead of finding overlayed plots
318+
subplots = Array.isArray(subplot) ?
319+
subplot :
320+
// list of all overlaid subplots to look at
321+
[subplot].concat(plotinfo.overlays
322+
.map(function(pi) { return pi.id; })),
323+
319324
xaArray = subplots.map(function(spId) {
320325
return Plotly.Axes.getFromId(gd, spId, 'x');
321326
}),
@@ -533,7 +538,7 @@ function hover(gd, evt, subplot) {
533538
};
534539
var hoverLabels = createHoverText(hoverData, labelOpts);
535540

536-
hoverAvoidOverlaps(hoverData, rotateLabels ? xaArray[0] : yaArray[0]);
541+
hoverAvoidOverlaps(hoverData, rotateLabels ? 'xa' : 'ya');
537542

538543
alignHoverText(hoverLabels, rotateLabels);
539544

@@ -872,7 +877,7 @@ function createHoverText(hoverData, opts) {
872877
// first create the objects
873878
var hoverLabels = container.selectAll('g.hovertext')
874879
.data(hoverData,function(d) {
875-
return [d.trace.index,d.index,d.x0,d.y0,d.name,d.attr||''].join(',');
880+
return [d.trace.index,d.index,d.x0,d.y0,d.name,d.attr,d.xa,d.ya ||''].join(',');
876881
});
877882
hoverLabels.enter().append('g')
878883
.classed('hovertext',true)
@@ -978,8 +983,8 @@ function createHoverText(hoverData, opts) {
978983
stroke: contrastColor
979984
});
980985
var tbb = tx.node().getBoundingClientRect(),
981-
htx = xa._offset+(d.x0+d.x1)/2,
982-
hty = ya._offset+(d.y0+d.y1)/2,
986+
htx = d.xa._offset+(d.x0+d.x1)/2,
987+
hty = d.ya._offset+(d.y0+d.y1)/2,
983988
dx = Math.abs(d.x1-d.x0),
984989
dy = Math.abs(d.y1-d.y0),
985990
txTotalWidth = tbb.width+HOVERARROWSIZE+HOVERTEXTPAD+tx2width,
@@ -1042,18 +1047,19 @@ function createHoverText(hoverData, opts) {
10421047
// information then.
10431048
function hoverAvoidOverlaps(hoverData, ax) {
10441049
var nummoves = 0,
1045-
pmin = ax._offset,
1046-
pmax = ax._offset+ax._length,
10471050

10481051
// make groups of touching points
10491052
pointgroups = hoverData
10501053
.map(function(d,i) {
1054+
var axis = d[ax];
10511055
return [{
10521056
i: i,
10531057
dp: 0,
10541058
pos: d.pos,
10551059
posref: d.posref,
1056-
size: d.by*(ax._id.charAt(0)==='x' ? YFACTOR : 1)/2
1060+
size: d.by*(axis._id.charAt(0)==='x' ? YFACTOR : 1)/2,
1061+
pmin: axis._offset,
1062+
pmax: axis._offset+axis._length
10571063
}];
10581064
})
10591065
.sort(function(a,b) { return a[0].posref-b[0].posref; }),
@@ -1069,10 +1075,10 @@ function hoverAvoidOverlaps(hoverData, ax) {
10691075
maxPt = grp[grp.length-1];
10701076

10711077
// overlap with the top - positive vals are overlaps
1072-
topOverlap = pmin-minPt.pos-minPt.dp+minPt.size;
1078+
topOverlap = minPt.pmin-minPt.pos-minPt.dp+minPt.size;
10731079

10741080
// overlap with the bottom - positive vals are overlaps
1075-
bottomOverlap = maxPt.pos+maxPt.dp+maxPt.size-pmax;
1081+
bottomOverlap = maxPt.pos+maxPt.dp+maxPt.size-minPt.pmax;
10761082

10771083
// check for min overlap first, so that we always
10781084
// see the largest labels
@@ -1096,7 +1102,7 @@ function hoverAvoidOverlaps(hoverData, ax) {
10961102
var deleteCount = 0;
10971103
for(i=0; i<grp.length; i++) {
10981104
pti = grp[i];
1099-
if(pti.pos+pti.dp+pti.size>pmax) deleteCount++;
1105+
if(pti.pos+pti.dp+pti.size>minPt.pmax) deleteCount++;
11001106
}
11011107

11021108
// start by deleting points whose data is off screen
@@ -1106,7 +1112,7 @@ function hoverAvoidOverlaps(hoverData, ax) {
11061112

11071113
// pos has already been constrained to [pmin,pmax]
11081114
// so look for points close to that to delete
1109-
if(pti.pos>pmax-1) {
1115+
if(pti.pos>minPt.pmax-1) {
11101116
pti.del = true;
11111117
deleteCount--;
11121118
}
@@ -1117,7 +1123,7 @@ function hoverAvoidOverlaps(hoverData, ax) {
11171123

11181124
// pos has already been constrained to [pmin,pmax]
11191125
// so look for points close to that to delete
1120-
if(pti.pos<pmin+1) {
1126+
if(pti.pos<minPt.pmin+1) {
11211127
pti.del = true;
11221128
deleteCount--;
11231129

@@ -1130,7 +1136,7 @@ function hoverAvoidOverlaps(hoverData, ax) {
11301136
for(i=grp.length-1; i>=0; i--) {
11311137
if(deleteCount<=0) break;
11321138
pti = grp[i];
1133-
if(pti.pos+pti.dp+pti.size>pmax) {
1139+
if(pti.pos+pti.dp+pti.size>minPt.pmax) {
11341140
pti.del = true;
11351141
deleteCount--;
11361142
}
@@ -1158,7 +1164,9 @@ function hoverAvoidOverlaps(hoverData, ax) {
11581164
p0 = g0[g0.length-1],
11591165
p1 = g1[0];
11601166
topOverlap = p0.pos+p0.dp+p0.size-p1.pos-p1.dp+p1.size;
1161-
if(topOverlap>0.01) {
1167+
1168+
//Only group points that lie on the same axes
1169+
if(topOverlap>0.01 && (p0.pmin === p1.pmin) && (p0.pmax === p1.pmax)) {
11621170
// push the new point(s) added to this group out of the way
11631171
for(j=g1.length-1; j>=0; j--) g1[j].dp += topOverlap;
11641172

Loading
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"data": [
3+
{
4+
"x": [
5+
1,
6+
2,
7+
3
8+
]
9+
},
10+
{
11+
"x": [
12+
2.1,
13+
2
14+
],
15+
"xaxis": "x2"
16+
},
17+
{
18+
"x": [
19+
3,
20+
2,
21+
1
22+
],
23+
"xaxis": "x3"
24+
}
25+
],
26+
"layout": {
27+
"xaxis": {
28+
"domain": [
29+
0,
30+
0.3
31+
]
32+
},
33+
"xaxis2": {
34+
"domain": [
35+
0.35,
36+
0.65
37+
]
38+
},
39+
"xaxis3": {
40+
"domain": [
41+
0.7,
42+
1
43+
]
44+
},
45+
"hovermode": "y"
46+
}
47+
}

test/jasmine/tests/hover_label_test.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,107 @@ describe('hover info', function() {
327327
});
328328
});
329329
});
330+
331+
describe('hover info on stacked subplots', function() {
332+
'use strict';
333+
334+
afterEach(destroyGraphDiv);
335+
336+
describe('hover info on stacked subplots with shared x-axis', function() {
337+
var mock = require('@mocks/stacked_coupled_subplots.json');
338+
339+
beforeEach(function(done) {
340+
Plotly.plot(createGraphDiv(), mock.data, mock.layout).then(done);
341+
});
342+
343+
it('responds to hover', function() {
344+
var gd = document.getElementById('graph');
345+
Plotly.Fx.hover(gd, {xval: 3}, ['xy','xy2','xy3']);
346+
347+
expect(gd._hoverdata.length).toEqual(2);
348+
349+
expect(gd._hoverdata[0]).toEqual(jasmine.objectContaining(
350+
{
351+
curveNumber: 1,
352+
pointNumber: 1,
353+
x: 3,
354+
y: 110
355+
}));
356+
357+
expect(gd._hoverdata[1]).toEqual(jasmine.objectContaining(
358+
{
359+
curveNumber: 2,
360+
pointNumber: 0,
361+
x: 3,
362+
y: 1000
363+
}));
364+
365+
//There should be a single label on the x-axis with the shared x value, 3.
366+
expect(d3.selectAll('g.axistext').size()).toEqual(1);
367+
expect(d3.selectAll('g.axistext').select('text').html()).toEqual('3');
368+
369+
//There should be two points being hovered over, in two different traces, one in each plot.
370+
expect(d3.selectAll('g.hovertext').size()).toEqual(2);
371+
var textNodes = d3.selectAll('g.hovertext').selectAll('text');
372+
373+
expect(textNodes[0][0].innerHTML).toEqual('trace 1');
374+
expect(textNodes[0][1].innerHTML).toEqual('110');
375+
expect(textNodes[1][0].innerHTML).toEqual('trace 2');
376+
expect(textNodes[1][1].innerHTML).toEqual('1000');
377+
});
378+
});
379+
380+
describe('hover info on stacked subplots with shared y-axis', function() {
381+
var mock = require('@mocks/stacked_subplots_shared_yaxis.json');
382+
383+
beforeEach(function(done) {
384+
Plotly.plot(createGraphDiv(), mock.data, mock.layout).then(done);
385+
});
386+
387+
it('responds to hover', function() {
388+
var gd = document.getElementById('graph');
389+
Plotly.Fx.hover(gd, {yval: 0}, ['xy', 'x2y', 'x3y']);
390+
391+
expect(gd._hoverdata.length).toEqual(3);
392+
393+
expect(gd._hoverdata[0]).toEqual(jasmine.objectContaining(
394+
{
395+
curveNumber: 0,
396+
pointNumber: 0,
397+
x: 1,
398+
y: 0
399+
}));
400+
401+
expect(gd._hoverdata[1]).toEqual(jasmine.objectContaining(
402+
{
403+
curveNumber: 1,
404+
pointNumber: 0,
405+
x: 2.1,
406+
y: 0
407+
}));
408+
409+
expect(gd._hoverdata[2]).toEqual(jasmine.objectContaining(
410+
{
411+
curveNumber: 2,
412+
pointNumber: 0,
413+
x: 3,
414+
y: 0
415+
}));
416+
417+
//There should be a single label on the y-axis with the shared y value, 0.
418+
expect(d3.selectAll('g.axistext').size()).toEqual(1);
419+
expect(d3.selectAll('g.axistext').select('text').html()).toEqual('0');
420+
421+
//There should be three points being hovered over, in three different traces, one in each plot.
422+
expect(d3.selectAll('g.hovertext').size()).toEqual(3);
423+
var textNodes = d3.selectAll('g.hovertext').selectAll('text');
424+
425+
expect(textNodes[0][0].innerHTML).toEqual('trace 0');
426+
expect(textNodes[0][1].innerHTML).toEqual('1');
427+
expect(textNodes[1][0].innerHTML).toEqual('trace 1');
428+
expect(textNodes[1][1].innerHTML).toEqual('2.1');
429+
expect(textNodes[2][0].innerHTML).toEqual('trace 2');
430+
expect(textNodes[2][1].innerHTML).toEqual('3');
431+
});
432+
});
433+
});

0 commit comments

Comments
 (0)