Skip to content

Commit 425900e

Browse files
authored
Merge pull request #3862 from plotly/legend-trace-toggle
Add legend click interaction attrs
2 parents 08e2d7e + bde19ee commit 425900e

File tree

4 files changed

+119
-14
lines changed

4 files changed

+119
-14
lines changed

Diff for: src/components/legend/attributes.js

+27
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,33 @@ module.exports = {
9090
].join(' ')
9191
},
9292

93+
itemclick: {
94+
valType: 'enumerated',
95+
values: ['toggle', 'toggleothers', false],
96+
dflt: 'toggle',
97+
role: 'info',
98+
editType: 'legend',
99+
description: [
100+
'Determines the behavior on legend item click.',
101+
'*toggle* toggles the visibility of the item clicked on the graph.',
102+
'*toggleothers* makes the clicked item the sole visible item on the graph.',
103+
'*false* disable legend item click interactions.'
104+
].join(' ')
105+
},
106+
itemdoubleclick: {
107+
valType: 'enumerated',
108+
values: ['toggle', 'toggleothers', false],
109+
dflt: 'toggleothers',
110+
role: 'info',
111+
editType: 'legend',
112+
description: [
113+
'Determines the behavior on legend item double-click.',
114+
'*toggle* toggles the visibility of the item clicked on the graph.',
115+
'*toggleothers* makes the clicked item the sole visible item on the graph.',
116+
'*false* disable legend item double-click interactions.'
117+
].join(' ')
118+
},
119+
93120
x: {
94121
valType: 'number',
95122
min: -2,

Diff for: src/components/legend/defaults.js

+3
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) {
104104

105105
coerce('itemsizing');
106106

107+
coerce('itemclick');
108+
coerce('itemdoubleclick');
109+
107110
coerce('x', defaultX);
108111
coerce('xanchor', defaultXAnchor);
109112
coerce('y', defaultY);

Diff for: src/components/legend/handle_click.js

+25-13
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,29 @@ var Registry = require('../../registry');
1414
var SHOWISOLATETIP = true;
1515

1616
module.exports = function handleClick(g, gd, numClicks) {
17+
var fullLayout = gd._fullLayout;
18+
1719
if(gd._dragged || gd._editing) return;
1820

19-
var hiddenSlices = gd._fullLayout.hiddenlabels ?
20-
gd._fullLayout.hiddenlabels.slice() :
21+
var itemClick = fullLayout.legend.itemclick;
22+
var itemDoubleClick = fullLayout.legend.itemdoubleclick;
23+
24+
if(numClicks === 1 && itemClick === 'toggle' && itemDoubleClick === 'toggleothers' &&
25+
SHOWISOLATETIP && gd.data && gd._context.showTips
26+
) {
27+
Lib.notifier(Lib._(gd, 'Double-click on legend to isolate one trace'), 'long');
28+
SHOWISOLATETIP = false;
29+
} else {
30+
SHOWISOLATETIP = false;
31+
}
32+
33+
var mode;
34+
if(numClicks === 1) mode = itemClick;
35+
else if(numClicks === 2) mode = itemDoubleClick;
36+
if(!mode) return;
37+
38+
var hiddenSlices = fullLayout.hiddenlabels ?
39+
fullLayout.hiddenlabels.slice() :
2140
[];
2241

2342
var legendItem = g.data()[0][0];
@@ -85,21 +104,14 @@ module.exports = function handleClick(g, gd, numClicks) {
85104
}
86105
}
87106

88-
if(numClicks === 1 && SHOWISOLATETIP && gd.data && gd._context.showTips) {
89-
Lib.notifier(Lib._(gd, 'Double-click on legend to isolate one trace'), 'long');
90-
SHOWISOLATETIP = false;
91-
} else {
92-
SHOWISOLATETIP = false;
93-
}
94-
95107
if(Registry.traceIs(fullTrace, 'pie')) {
96108
var thisLabel = legendItem.label;
97109
var thisLabelIndex = hiddenSlices.indexOf(thisLabel);
98110

99-
if(numClicks === 1) {
111+
if(mode === 'toggle') {
100112
if(thisLabelIndex === -1) hiddenSlices.push(thisLabel);
101113
else hiddenSlices.splice(thisLabelIndex, 1);
102-
} else if(numClicks === 2) {
114+
} else if(mode === 'toggleothers') {
103115
hiddenSlices = [];
104116
gd.calcdata[0].forEach(function(d) {
105117
if(thisLabel !== d.label) {
@@ -126,7 +138,7 @@ module.exports = function handleClick(g, gd, numClicks) {
126138
}
127139
}
128140

129-
if(numClicks === 1) {
141+
if(mode === 'toggle') {
130142
var nextVisibility;
131143

132144
switch(fullTrace.visible) {
@@ -150,7 +162,7 @@ module.exports = function handleClick(g, gd, numClicks) {
150162
} else {
151163
setVisibility(fullTrace, nextVisibility);
152164
}
153-
} else if(numClicks === 2) {
165+
} else if(mode === 'toggleothers') {
154166
// Compute the clicked index. expandedIndex does what we want for expanded traces
155167
// but also culls hidden traces. That means we have some work to do.
156168
var isClicked, isInGroup, otherState;

Diff for: test/jasmine/tests/legend_test.js

+64-1
Original file line numberDiff line numberDiff line change
@@ -1067,7 +1067,6 @@ describe('legend interaction', function() {
10671067
});
10681068
});
10691069

1070-
10711070
describe('editable mode interactions', function() {
10721071
var gd;
10731072

@@ -1667,6 +1666,70 @@ describe('legend interaction', function() {
16671666
.then(done);
16681667
});
16691668
});
1669+
1670+
describe('should honor *itemclick* and *itemdoubleclick* settings', function() {
1671+
var _assert;
1672+
1673+
function run() {
1674+
return Promise.resolve()
1675+
.then(click(0, 1)).then(_assert(['legendonly', true, true]))
1676+
.then(click(0, 1)).then(_assert([true, true, true]))
1677+
.then(click(0, 2)).then(_assert([true, 'legendonly', 'legendonly']))
1678+
.then(click(0, 2)).then(_assert([true, true, true]))
1679+
.then(function() {
1680+
return Plotly.relayout(gd, {
1681+
'legend.itemclick': false,
1682+
'legend.itemdoubleclick': false
1683+
});
1684+
})
1685+
.then(click(0, 1)).then(_assert([true, true, true]))
1686+
.then(click(0, 2)).then(_assert([true, true, true]))
1687+
.then(function() {
1688+
return Plotly.relayout(gd, {
1689+
'legend.itemclick': 'toggleothers',
1690+
'legend.itemdoubleclick': 'toggle'
1691+
});
1692+
})
1693+
.then(click(0, 1)).then(_assert([true, 'legendonly', 'legendonly']))
1694+
.then(click(0, 1)).then(_assert([true, true, true]))
1695+
.then(click(0, 2)).then(_assert(['legendonly', true, true]))
1696+
.then(click(0, 2)).then(_assert([true, true, true]));
1697+
}
1698+
1699+
it('- regular trace case', function(done) {
1700+
_assert = assertVisible;
1701+
1702+
Plotly.plot(gd, [
1703+
{ y: [1, 2, 1] },
1704+
{ y: [2, 1, 2] },
1705+
{ y: [3, 5, 0] }
1706+
])
1707+
.then(run)
1708+
.catch(failTest)
1709+
.then(done);
1710+
});
1711+
1712+
it('- pie case', function(done) {
1713+
_assert = function(_exp) {
1714+
return function() {
1715+
var exp = [];
1716+
if(_exp[0] === 'legendonly') exp.push('C');
1717+
if(_exp[1] === 'legendonly') exp.push('B');
1718+
if(_exp[2] === 'legendonly') exp.push('A');
1719+
expect(gd._fullLayout.hiddenlabels || []).toEqual(exp);
1720+
};
1721+
};
1722+
1723+
Plotly.plot(gd, [{
1724+
type: 'pie',
1725+
labels: ['A', 'B', 'C'],
1726+
values: [1, 2, 3]
1727+
}])
1728+
.then(run)
1729+
.catch(failTest)
1730+
.then(done);
1731+
});
1732+
});
16701733
});
16711734
});
16721735

0 commit comments

Comments
 (0)