diff --git a/draftlogs/7580_add.md b/draftlogs/7580_add.md new file mode 100644 index 00000000000..62f469782ad --- /dev/null +++ b/draftlogs/7580_add.md @@ -0,0 +1 @@ +- Add support for arrays for the pie properties `showlegend` and `legend`, so that these can be configured per slice. [[#7580](https://github.com/plotly/plotly.js/pull/7580)] diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index ec8e91bca69..9e9f2134fe4 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -45,8 +45,44 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) { var shapesWithLegend = (layoutOut.shapes || []).filter(function(d) { return d.showlegend; }); + function isPieWithLegendArray(trace) { + return Registry.traceIs(trace, 'pie-like') + && trace._length != null + && (Array.isArray(trace.legend) || Array.isArray(trace.showlegend)); + }; + fullData + .filter(isPieWithLegendArray) + .map(function (trace, idx) { + if (trace.visible) { + legendTraceCount++; + } + for(var index = 0; index < trace._length; index++) { + var legend = (Array.isArray(trace.legend) ? trace.legend[index] : trace.legend) || 'legend'; + if(legend === legendId) { + // showlegend can be undefined, boolean or a boolean array. + // will fall back to default if undefined or if array index is out-of-range + if( + !Array.isArray(trace.showlegend) + ? trace.showlegend || trace._dfltShowLegend + : trace.showlegend[index] == null + ? trace._dfltShowLegend + : trace.showlegend[index] + ) { + legendReallyHasATrace = true; + legendTraceCount++; + } + } + } + if(legendId === 'legend' && trace._length > trace.legend.length) { + for(var idx = trace.legend.length; idx < trace._length; idx++) { + legendReallyHasATrace = true; + legendTraceCount++; + } + } + }); + var allLegendItems = fullData.concat(shapesWithLegend).filter(function(d) { - return legendId === (d.legend || 'legend'); + return !isPieWithLegendArray(trace) && legendId === (d.legend || 'legend'); }); for(var i = 0; i < allLegendItems.length; i++) { @@ -82,7 +118,6 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) { Lib.coerceFont(traceCoerce, 'legendgrouptitle.font', grouptitlefont); } - if((!isShape && Registry.traceIs(trace, 'bar') && layoutOut.barmode === 'stack') || ['tonextx', 'tonexty'].indexOf(trace.fill) !== -1) { defaultOrder = helpers.isGrouped({ traceorder: defaultOrder }) ? @@ -95,9 +130,15 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) { } } - var showLegend = Lib.coerce(layoutIn, layoutOut, - basePlotLayoutAttributes, 'showlegend', - legendReallyHasATrace && (legendTraceCount > (legendId === 'legend' ? 1 : 0))); + var showLegend = Lib.coerce( + layoutIn, + layoutOut, + basePlotLayoutAttributes, + 'showlegend', + layoutOut.showlegend || + (legendReallyHasATrace && + legendTraceCount > (legendId === 'legend' ? 1 : 0)) + ); // delete legend if(showLegend === false) layoutOut[legendId] = undefined; @@ -230,7 +271,11 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { var legends = ['legend']; for(i = 0; i < allLegendsData.length; i++) { - Lib.pushUnique(legends, allLegendsData[i].legend); + if (Array.isArray(allLegendsData[i].legend)) { + Lib.extendFlat(legends, allLegendsData[i].legend); + } else { + Lib.pushUnique(legends, allLegendsData[i].legend); + } } layoutOut._legends = []; diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 8c7907358a1..8f8cbc9c01e 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -633,7 +633,11 @@ function textLayout(s, g, gd, legendObj, aTitle) { function computeTextDimensions(g, gd, legendObj, aTitle) { var legendItem = g.data()[0][0]; - if(!legendObj._inHover && legendItem && !legendItem.trace.showlegend) { + var showlegend = legendItem && legendItem.trace.showlegend; + if (Array.isArray(showlegend)) { + showlegend = showlegend[legendItem.i] !== false; + } + if(!legendObj._inHover && legendItem && !showlegend) { g.remove(); return; } diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index 4bb7ec60f27..c389ad4d726 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -47,9 +47,17 @@ module.exports = function getLegendData(calcdata, opts, hasMultipleLegends) { if(!inHover && (!trace.visible || !trace.showlegend)) continue; if(Registry.traceIs(trace, 'pie-like')) { + var legendPerSlice = Array.isArray(trace.legend); + var showlegendPerSlice = Array.isArray(trace.showlegend); if(!slicesShown[lgroup]) slicesShown[lgroup] = {}; for(j = 0; j < cd.length; j++) { + if (showlegendPerSlice && trace.showlegend[cd[j].i] === false) { + continue; + } + if (legendPerSlice) { + lid = trace.legend[cd[j].i] || 'legend'; + } var labelj = cd[j].label; if(!slicesShown[lgroup][labelj]) { diff --git a/src/lib/coerce.js b/src/lib/coerce.js index 9fa59255bc1..c44361dc110 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -86,7 +86,7 @@ exports.valObjectMeta = { boolean: { description: 'A boolean (true/false) value.', requiredOpts: [], - otherOpts: ['dflt'], + otherOpts: ['dflt', 'arrayOk'], coerceFunction: function(v, propOut, dflt) { if(v === true || v === false) propOut.set(v); else propOut.set(dflt); @@ -225,8 +225,12 @@ exports.valObjectMeta = { '\'geo\', \'geo2\', \'geo3\', ...' ].join(' '), requiredOpts: ['dflt'], - otherOpts: ['regex'], + otherOpts: ['regex', 'arrayOk'], coerceFunction: function(v, propOut, dflt, opts) { + if(opts.arrayOk && isArrayOrTypedArray(v)) { + propOut.set(v); + return; + } var regex = opts.regex || counterRegex(dflt); if(typeof v === 'string' && regex.test(v)) { propOut.set(v); diff --git a/src/plots/plots.js b/src/plots/plots.js index 6550eb353a8..e68454a5196 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1254,8 +1254,10 @@ plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, trac _module.attributes.showlegend ? _module.attributes : plots.attributes, 'showlegend' ); - - coerce('legend'); + Lib.coerce(traceIn, traceOut, + _module.attributes.legend ? _module.attributes : plots.attributes, + 'legend' + ); coerce('legendwidth'); coerce('legendgroup'); coerce('legendgrouptitle.text'); diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js index cd1c657346f..fccfdb4ac9f 100644 --- a/src/traces/pie/attributes.js +++ b/src/traces/pie/attributes.js @@ -196,7 +196,12 @@ module.exports = { 'Determines whether outside text labels can push the margins.' ].join(' ') }, - + showlegend: extendFlat({}, baseAttrs.showlegend, { + arrayOk: true + }), + legend: extendFlat({}, baseAttrs.legend, { + arrayOk: true + }), title: { text: { valType: 'string', diff --git a/test/image/baselines/zz-pie-slice-legend.png b/test/image/baselines/zz-pie-slice-legend.png new file mode 100644 index 00000000000..f128d1bb3a6 Binary files /dev/null and b/test/image/baselines/zz-pie-slice-legend.png differ diff --git a/test/image/baselines/zz-pie-slice-legend2.png b/test/image/baselines/zz-pie-slice-legend2.png new file mode 100644 index 00000000000..3a3d8bdacae Binary files /dev/null and b/test/image/baselines/zz-pie-slice-legend2.png differ diff --git a/test/image/mocks/zz-pie-slice-legend.json b/test/image/mocks/zz-pie-slice-legend.json new file mode 100644 index 00000000000..a52cdd8410f --- /dev/null +++ b/test/image/mocks/zz-pie-slice-legend.json @@ -0,0 +1,50 @@ +{ + "data": [ + { + "labels": [ + "with", + "pie", + "multiple", + "hidden", + "legends", + "A", + "hidden too" + ], + "values": [3, 4, 2, 7, 1, 5, 8], + "type": "pie", + "showlegend": [true, true, true, false, true, true, false], + "legend": ["legend2", "legend", "legend2", "legend4", "legend3", "legend", "legend4"] + } + ], + "layout": { + "title": { + "text": "Test array version of 'showlegend' and 'legend'" + }, + "width": 400, + "height": 400, + "legend": { + "title": { + "text": "L1" + }, + "y": 1 + }, + "legend2": { + "title": { + "text": "L2" + }, + "y": 0.7 + }, + "legend3": { + "title": { + "text": "L3" + }, + "y": 0.2 + }, + "legend4": { + "title": { + "text": "L4" + }, + "y": 0 + } + } +} diff --git a/test/image/mocks/zz-pie-slice-legend2.json b/test/image/mocks/zz-pie-slice-legend2.json new file mode 100644 index 00000000000..bd1fa3cc295 --- /dev/null +++ b/test/image/mocks/zz-pie-slice-legend2.json @@ -0,0 +1,50 @@ +{ + "data": [ + { + "labels": [ + "with", + "multiple", + "hidden", + "legends", + "A", + "hidden too", + "pie" + ], + "values": [3, 2, 7, 1, 5, 8, 4], + "type": "pie", + "showlegend": [true, true, false, true, true, false], + "legend": ["legend2", "legend2", "legend4", "legend3", "legend", "legend4"] + } + ], + "layout": { + "title": { + "text": "Test short array version of 'showlegend' and 'legend'" + }, + "width": 450, + "height": 400, + "legend": { + "title": { + "text": "L1" + }, + "y": 1 + }, + "legend2": { + "title": { + "text": "L2" + }, + "y": 0.7 + }, + "legend3": { + "title": { + "text": "L3" + }, + "y": 0.2 + }, + "legend4": { + "title": { + "text": "L4" + }, + "y": 0 + } + } +} diff --git a/test/plot-schema.json b/test/plot-schema.json index dbc320a4631..a9a9f22ed56 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -448,7 +448,8 @@ "boolean": { "description": "A boolean (true/false) value.", "otherOpts": [ - "dflt" + "dflt", + "arrayOk" ], "requiredOpts": [] }, @@ -549,7 +550,8 @@ "subplotid": { "description": "An id string of a subplot type (given by dflt), optionally followed by an integer >1. e.g. if dflt='geo', we can have 'geo', 'geo2', 'geo3', ...", "otherOpts": [ - "regex" + "regex", + "arrayOk" ], "requiredOpts": [ "dflt" @@ -56338,6 +56340,7 @@ "valType": "string" }, "legend": { + "arrayOk": true, "description": "Sets the reference to a legend to show this trace in. References to these legends are *legend*, *legend2*, *legend3*, etc. Settings for these legends are set in the layout, under `layout.legend`, `layout.legend2`, etc.", "dflt": "legend", "editType": "style", @@ -56454,6 +56457,11 @@ "editType": "style", "valType": "number" }, + "legendsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `legend`.", + "editType": "none", + "valType": "string" + }, "legendwidth": { "description": "Sets the width (in px or fraction) of the legend for this trace.", "editType": "style", @@ -56801,11 +56809,17 @@ "valType": "string" }, "showlegend": { + "arrayOk": true, "description": "Determines whether or not an item corresponding to this trace is shown in the legend.", "dflt": true, "editType": "style", "valType": "boolean" }, + "showlegendsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `showlegend`.", + "editType": "none", + "valType": "string" + }, "sort": { "description": "Determines whether or not the sectors are reordered from largest to smallest.", "dflt": true,