diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index 1a11eda29c9..2eee7aa05cb 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -90,6 +90,33 @@ module.exports = { ].join(' ') }, + itemclick: { + valType: 'enumerated', + values: ['toggle', 'toggleothers', false], + dflt: 'toggle', + role: 'info', + editType: 'legend', + description: [ + 'Determines the behavior on legend item click.', + '*toggle* toggles the visibility of the item clicked on the graph.', + '*toggleothers* makes the clicked item the sole visible item on the graph.', + '*false* disable legend item click interactions.' + ].join(' ') + }, + itemdoubleclick: { + valType: 'enumerated', + values: ['toggle', 'toggleothers', false], + dflt: 'toggleothers', + role: 'info', + editType: 'legend', + description: [ + 'Determines the behavior on legend item double-click.', + '*toggle* toggles the visibility of the item clicked on the graph.', + '*toggleothers* makes the clicked item the sole visible item on the graph.', + '*false* disable legend item double-click interactions.' + ].join(' ') + }, + x: { valType: 'number', min: -2, diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index c56da87077a..c08fdb2ec8d 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -104,6 +104,9 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { coerce('itemsizing'); + coerce('itemclick'); + coerce('itemdoubleclick'); + coerce('x', defaultX); coerce('xanchor', defaultXAnchor); coerce('y', defaultY); diff --git a/src/components/legend/handle_click.js b/src/components/legend/handle_click.js index 45fefc951d7..6bc98641108 100644 --- a/src/components/legend/handle_click.js +++ b/src/components/legend/handle_click.js @@ -14,10 +14,29 @@ var Registry = require('../../registry'); var SHOWISOLATETIP = true; module.exports = function handleClick(g, gd, numClicks) { + var fullLayout = gd._fullLayout; + if(gd._dragged || gd._editing) return; - var hiddenSlices = gd._fullLayout.hiddenlabels ? - gd._fullLayout.hiddenlabels.slice() : + var itemClick = fullLayout.legend.itemclick; + var itemDoubleClick = fullLayout.legend.itemdoubleclick; + + if(numClicks === 1 && itemClick === 'toggle' && itemDoubleClick === 'toggleothers' && + SHOWISOLATETIP && gd.data && gd._context.showTips + ) { + Lib.notifier(Lib._(gd, 'Double-click on legend to isolate one trace'), 'long'); + SHOWISOLATETIP = false; + } else { + SHOWISOLATETIP = false; + } + + var mode; + if(numClicks === 1) mode = itemClick; + else if(numClicks === 2) mode = itemDoubleClick; + if(!mode) return; + + var hiddenSlices = fullLayout.hiddenlabels ? + fullLayout.hiddenlabels.slice() : []; var legendItem = g.data()[0][0]; @@ -85,21 +104,14 @@ module.exports = function handleClick(g, gd, numClicks) { } } - if(numClicks === 1 && SHOWISOLATETIP && gd.data && gd._context.showTips) { - Lib.notifier(Lib._(gd, 'Double-click on legend to isolate one trace'), 'long'); - SHOWISOLATETIP = false; - } else { - SHOWISOLATETIP = false; - } - if(Registry.traceIs(fullTrace, 'pie')) { var thisLabel = legendItem.label; var thisLabelIndex = hiddenSlices.indexOf(thisLabel); - if(numClicks === 1) { + if(mode === 'toggle') { if(thisLabelIndex === -1) hiddenSlices.push(thisLabel); else hiddenSlices.splice(thisLabelIndex, 1); - } else if(numClicks === 2) { + } else if(mode === 'toggleothers') { hiddenSlices = []; gd.calcdata[0].forEach(function(d) { if(thisLabel !== d.label) { @@ -126,7 +138,7 @@ module.exports = function handleClick(g, gd, numClicks) { } } - if(numClicks === 1) { + if(mode === 'toggle') { var nextVisibility; switch(fullTrace.visible) { @@ -150,7 +162,7 @@ module.exports = function handleClick(g, gd, numClicks) { } else { setVisibility(fullTrace, nextVisibility); } - } else if(numClicks === 2) { + } else if(mode === 'toggleothers') { // Compute the clicked index. expandedIndex does what we want for expanded traces // but also culls hidden traces. That means we have some work to do. var isClicked, isInGroup, otherState; diff --git a/test/jasmine/tests/legend_test.js b/test/jasmine/tests/legend_test.js index bb1af39da6c..f4b5cf5d7b7 100644 --- a/test/jasmine/tests/legend_test.js +++ b/test/jasmine/tests/legend_test.js @@ -1067,7 +1067,6 @@ describe('legend interaction', function() { }); }); - describe('editable mode interactions', function() { var gd; @@ -1667,6 +1666,70 @@ describe('legend interaction', function() { .then(done); }); }); + + describe('should honor *itemclick* and *itemdoubleclick* settings', function() { + var _assert; + + function run() { + return Promise.resolve() + .then(click(0, 1)).then(_assert(['legendonly', true, true])) + .then(click(0, 1)).then(_assert([true, true, true])) + .then(click(0, 2)).then(_assert([true, 'legendonly', 'legendonly'])) + .then(click(0, 2)).then(_assert([true, true, true])) + .then(function() { + return Plotly.relayout(gd, { + 'legend.itemclick': false, + 'legend.itemdoubleclick': false + }); + }) + .then(click(0, 1)).then(_assert([true, true, true])) + .then(click(0, 2)).then(_assert([true, true, true])) + .then(function() { + return Plotly.relayout(gd, { + 'legend.itemclick': 'toggleothers', + 'legend.itemdoubleclick': 'toggle' + }); + }) + .then(click(0, 1)).then(_assert([true, 'legendonly', 'legendonly'])) + .then(click(0, 1)).then(_assert([true, true, true])) + .then(click(0, 2)).then(_assert(['legendonly', true, true])) + .then(click(0, 2)).then(_assert([true, true, true])); + } + + it('- regular trace case', function(done) { + _assert = assertVisible; + + Plotly.plot(gd, [ + { y: [1, 2, 1] }, + { y: [2, 1, 2] }, + { y: [3, 5, 0] } + ]) + .then(run) + .catch(failTest) + .then(done); + }); + + it('- pie case', function(done) { + _assert = function(_exp) { + return function() { + var exp = []; + if(_exp[0] === 'legendonly') exp.push('C'); + if(_exp[1] === 'legendonly') exp.push('B'); + if(_exp[2] === 'legendonly') exp.push('A'); + expect(gd._fullLayout.hiddenlabels || []).toEqual(exp); + }; + }; + + Plotly.plot(gd, [{ + type: 'pie', + labels: ['A', 'B', 'C'], + values: [1, 2, 3] + }]) + .then(run) + .catch(failTest) + .then(done); + }); + }); }); });