diff --git a/draftlogs/7997_add.md b/draftlogs/7997_add.md new file mode 100644 index 00000000000..d124caa320d --- /dev/null +++ b/draftlogs/7997_add.md @@ -0,0 +1 @@ +- Add `legend.groupdoubleclick` to set the group behavior for a legend double-click [[#7997](https://github.com/plotly/plotly.js/pull/7997)] diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index 3fb8e5781e9..f8164a550a6 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -176,6 +176,17 @@ module.exports = { '*togglegroup* toggles the visibility of all items in the same legendgroup as the item clicked on the graph.' ].join(' ') }, + groupdoubleclick: { + valType: 'enumerated', + values: ['toggleitem', 'togglegroup'], + editType: 'legend', + description: [ + 'Determines the behavior on legend group item double-click.', + '*toggleitem* toggles the visibility of the individual item clicked on the graph.', + '*togglegroup* toggles the visibility of all items in the same legendgroup as the item clicked on the graph.', + 'Defaults to the value of `groupclick`.' + ].join(' ') + }, titleclick: { valType: 'enumerated', values: ['toggle', 'toggleothers', false], diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index eb8a6212a0f..2a65034b3bc 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -222,7 +222,8 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData, legendCount) { coerce('itemclick'); coerce('itemdoubleclick'); - coerce('groupclick'); + const groupClick = coerce('groupclick'); + coerce('groupdoubleclick', groupClick); coerce('xanchor', defaultXAnchor); coerce('yanchor', defaultYAnchor); diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 707f2a77364..7ed481e2393 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -544,7 +544,7 @@ function clickOrDoubleClick(gd, legend, legendItem, numClicks, evt) { if(clickVal === false) return; legend._clickTimeout = setTimeout(function() { if(!gd._fullLayout) return; - if(itemClick) handleItemClick(legendItem, gd, legend, itemClick); + if(itemClick) handleItemClick(legendItem, gd, legend, itemClick, numClicks); }, gd._context.doubleClickDelay); } else if(numClicks === 2) { if(legend._clickTimeout) clearTimeout(legend._clickTimeout); @@ -553,7 +553,7 @@ function clickOrDoubleClick(gd, legend, legendItem, numClicks, evt) { var dblClickVal = Events.triggerHandler(gd, 'plotly_legenddoubleclick', evtData); // Activate default double click behaviour only when both single click and double click values are not false if(dblClickVal !== false && clickVal !== false && itemDoubleClick) { - handleItemClick(legendItem, gd, legend, itemDoubleClick); + handleItemClick(legendItem, gd, legend, itemDoubleClick, numClicks); } } } diff --git a/src/components/legend/handle_click.js b/src/components/legend/handle_click.js index 6170f03bf8b..480e754a21a 100644 --- a/src/components/legend/handle_click.js +++ b/src/components/legend/handle_click.js @@ -14,10 +14,12 @@ var SHOWISOLATETIP = true; * @param {object} gd graph div * @param {object} legendObj the legend object from fullLayout * @param {string} mode toggle mode for the current action: 'toggle' | 'toggleothers' - * - 'toggle': Toggle visibility of this item (or group if groupclick is 'togglegroup') + * - 'toggle': Toggle visibility of this item (or group if the group behavior is 'togglegroup') * - 'toggleothers': Show only this item, hide all others (isolation mode) + * @param {number} numClicks 1 for a single click, 2 for a double click. Selects `groupclick` + * or `groupdoubleclick` as the group behavior. */ -exports.handleItemClick = function handleItemClick(g, gd, legendObj, mode) { +exports.handleItemClick = function handleItemClick(g, gd, legendObj, mode, numClicks) { var fullLayout = gd._fullLayout; if (gd._dragged || gd._editing) return; @@ -25,7 +27,7 @@ exports.handleItemClick = function handleItemClick(g, gd, legendObj, mode) { var legendItem = g.data()[0][0]; if (legendItem.groupTitle && legendItem.noClick) return; - var groupClick = legendObj.groupclick; + const groupClick = numClicks === 2 ? legendObj.groupdoubleclick : legendObj.groupclick; // Show isolate tip on first single click when default behavior is active if ( @@ -196,13 +198,15 @@ exports.handleItemClick = function handleItemClick(g, gd, legendObj, mode) { // but also culls hidden traces. That means we have some work to do. var isClicked, isInGroup, notInLegend, otherState, _item; var isIsolated = true; + // 'toggleitem' isolates the clicked trace alone, so its group peers hide with the rest. + const isolateGroup = hasLegendgroup && toggleGroup; for (i = 0; i < allLegendItems.length; i++) { _item = allLegendItems[i]; isClicked = _item === fullTrace; notInLegend = _item.showlegend !== true; if (isClicked || notInLegend) continue; - isInGroup = hasLegendgroup && _item.legendgroup === legendgroup; + isInGroup = isolateGroup && _item.legendgroup === legendgroup; if ( !isInGroup && @@ -234,7 +238,7 @@ exports.handleItemClick = function handleItemClick(g, gd, legendObj, mode) { isClicked = _item === fullTrace; // N.B. consider traces that have a set legendgroup as toggleable notInLegend = _item.showlegend !== true && !_item.legendgroup; - isInGroup = isClicked || (hasLegendgroup && _item.legendgroup === legendgroup); + isInGroup = isClicked || (isolateGroup && _item.legendgroup === legendgroup); setVisibility(_item, isInGroup || notInLegend ? true : otherState); break; } diff --git a/src/types/generated/schema.d.ts b/src/types/generated/schema.d.ts index 2c08fa0db33..2ac90f66e91 100644 --- a/src/types/generated/schema.d.ts +++ b/src/types/generated/schema.d.ts @@ -12727,6 +12727,8 @@ export interface Legend { * @default 'togglegroup' */ groupclick?: 'toggleitem' | 'togglegroup'; + /** Determines the behavior on legend group item double-click. *toggleitem* toggles the visibility of the individual item clicked on the graph. *togglegroup* toggles the visibility of all items in the same legendgroup as the item clicked on the graph. Defaults to the value of `groupclick`. */ + groupdoubleclick?: 'toggleitem' | 'togglegroup'; /** Sets the font for group titles in legend. Defaults to `legend.font` with its size increased about 10%. */ grouptitlefont?: Font; /** diff --git a/test/jasmine/tests/legend_test.js b/test/jasmine/tests/legend_test.js index 97cd23f145a..bb4d8b61ac0 100644 --- a/test/jasmine/tests/legend_test.js +++ b/test/jasmine/tests/legend_test.js @@ -286,6 +286,28 @@ describe('legend defaults', function () { expect(layoutOut.legend.title.side).toEqual('left'); }); + it('should default `groupdoubleclick` to the `groupclick` value', function () { + fullData = allShown([{ type: 'scatter' }, { type: 'scatter' }]); + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.legend.groupdoubleclick).toBe('togglegroup'); + + layoutIn.legend = { groupclick: 'toggleitem' }; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.legend.groupdoubleclick).toBe('toggleitem'); + }); + + it('should coerce `groupdoubleclick` independently of `groupclick`', function () { + fullData = allShown([{ type: 'scatter' }, { type: 'scatter' }]); + + layoutIn.legend = { groupdoubleclick: 'toggleitem' }; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.legend.groupclick).toBe('togglegroup'); + expect(layoutOut.legend.groupdoubleclick).toBe('toggleitem'); + }); + describe('for horizontal legends', function () { var layoutInForHorizontalLegends; @@ -2482,6 +2504,58 @@ describe('legend interaction', function () { }); }); + describe('legendgroup visibility case of groupdoubleclick: "toggleitem"', function () { + beforeEach(function (done) { + Plotly.newPlot( + gd, + [ + { + x: [1, 2], + y: [3, 4], + visible: false + }, + { + x: [1, 2, 3, 4], + y: [0, 1, 2, 3], + legendgroup: 'foo' + }, + { + x: [1, 2, 3, 4], + y: [1, 3, 2, 4] + }, + { + x: [1, 2, 3, 4], + y: [1, 3, 2, 4], + legendgroup: 'foo' + } + ], + { + legend: { + groupdoubleclick: 'toggleitem' + } + } + ).then(done); + }); + + it('isolates the clicked item instead of its legendgroup', function (done) { + Promise.resolve() + .then(click(1, 2)) + .then(assertVisible([false, 'legendonly', 'legendonly', true])) + .then(click(1, 2)) + .then(assertVisible([false, true, true, true])) + .then(done, done.fail); + }); + + it('leaves the single click behavior to groupclick', function (done) { + Promise.resolve() + .then(click(1)) + .then(assertVisible([false, 'legendonly', true, 'legendonly'])) + .then(click(1)) + .then(assertVisible([false, true, true, true])) + .then(done, done.fail); + }); + }); + describe('legend visibility with *showlegend:false* traces', function () { beforeEach(function (done) { Plotly.newPlot(gd, [ diff --git a/test/plot-schema.json b/test/plot-schema.json index 4598de7f54c..3c8278724fe 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -3312,6 +3312,15 @@ "togglegroup" ] }, + "groupdoubleclick": { + "description": "Determines the behavior on legend group item double-click. *toggleitem* toggles the visibility of the individual item clicked on the graph. *togglegroup* toggles the visibility of all items in the same legendgroup as the item clicked on the graph. Defaults to the value of `groupclick`.", + "editType": "legend", + "valType": "enumerated", + "values": [ + "toggleitem", + "togglegroup" + ] + }, "grouptitlefont": { "color": { "editType": "legend",