Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

add options to include shapes and newshape in legends #6653

Merged
merged 30 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6545353
register legends after shapes
archmoj Jun 27, 2023
786d9e6
add options to include shapes in legends
archmoj Jun 27, 2023
2d7d3da
test clicking shape legends
archmoj Jun 28, 2023
332a0dd
draftlog
archmoj Jun 28, 2023
8a0f60a
improve descriptions
archmoj Jun 28, 2023
f0c6b72
add comments to core.js about order of registering components
archmoj Jun 29, 2023
d3004c1
use hexagon2 for path symbols
archmoj Jun 29, 2023
51a28f9
use overrideAll in newshape and fix bundling issue
archmoj Jun 29, 2023
0fa6914
Merge branch 'master' into shape-legends
archmoj Jun 30, 2023
4728e86
Merge remote-tracking branch 'origin/master' into shape-legends
archmoj Jul 4, 2023
2f1f812
Merge remote-tracking branch 'origin/master' into shape-legends
archmoj Jul 5, 2023
3511eb4
test showing shapes in groups
archmoj Jul 5, 2023
8e181cc
rename vars to point to data updates
archmoj Jul 6, 2023
6c0ab2e
handle shape legends group click
archmoj Jul 6, 2023
7870720
avoid unrecognized messages on click
archmoj Jul 7, 2023
ef0fe6f
add grouptitle and handle click
archmoj Jul 7, 2023
2e77fc7
test legendrank
archmoj Jul 7, 2023
58b1cb1
mention traces and shapes order in legendrank description
archmoj Jul 7, 2023
3168078
test shape legends and groups in multiple legends
archmoj Jul 7, 2023
6343338
add name to newshape and improve descriptions
archmoj Jul 17, 2023
7bf5029
coerce newshape.name
archmoj Jul 17, 2023
948fd02
add name to newshapes when creating a new one
archmoj Jul 18, 2023
e5a600d
Update src/components/shapes/draw_newshape/attributes.js
archmoj Jul 18, 2023
5b02b62
more name appears fixes and update the schema
archmoj Jul 18, 2023
b35add8
fix shape.name updates via editing legends
archmoj Jul 18, 2023
6111aba
fix double click shape legends
archmoj Jul 19, 2023
5c0168c
simplify handling shape legends in click
archmoj Jul 20, 2023
15986b8
jasmine tests for editable shape legends
archmoj Jul 21, 2023
759dbb4
use update instead of restyle & relayout when updating shape legends
archmoj Jul 24, 2023
9123425
Merge remote-tracking branch 'origin/master' into shape-legends
archmoj Jul 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions draftlogs/6653_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- add options to include shapes and `newshape` in legends [[#6653](https://github.com/plotly/plotly.js/pull/6653)]
1 change: 1 addition & 0 deletions src/components/colorbar/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ function makeColorBarData(gd) {
for(var i = 0; i < calcdata.length; i++) {
var cd = calcdata[i];
trace = cd[0].trace;
if(!trace._module) continue;
var moduleOpts = trace._module.colorbar;

if(trace.visible === true && moduleOpts) {
Expand Down
38 changes: 31 additions & 7 deletions src/components/legend/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) {
var legendReallyHasATrace = false;
var defaultOrder = 'normal';

var allLegendItems = fullData.filter(function(d) {
var shapes = (layoutOut.shapes || []).filter(function(d) { return d.showlegend; });

var allLegendItems = fullData.concat(shapes).filter(function(d) {
return legendId === (d.legend || 'legend');
});

Expand All @@ -50,6 +52,8 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) {

if(!trace.visible) continue;

var isShape = trace._isShape;

// Note that we explicitly count any trace that is either shown or
// *would* be shown by default, toward the two traces you need to
// ensure the legend is shown by default, because this can still help
Expand All @@ -67,7 +71,7 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) {
legendReallyHasATrace = true;
// Always show the legend by default if there's a pie,
// or if there's only one trace but it's explicitly shown
if(Registry.traceIs(trace, 'pie-like') ||
if(!isShape && Registry.traceIs(trace, 'pie-like') ||
trace._input.showlegend === true
) {
legendTraceCount++;
Expand All @@ -77,7 +81,7 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) {
Lib.coerceFont(traceCoerce, 'legendgrouptitle.font', grouptitlefont);
}

if((Registry.traceIs(trace, 'bar') && layoutOut.barmode === 'stack') ||
if((!isShape && Registry.traceIs(trace, 'bar') && layoutOut.barmode === 'stack') ||
['tonextx', 'tonexty'].indexOf(trace.fill) !== -1) {
defaultOrder = helpers.isGrouped({traceorder: defaultOrder}) ?
'grouped+reversed' : 'reversed';
Expand Down Expand Up @@ -199,17 +203,37 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) {

module.exports = function legendDefaults(layoutIn, layoutOut, fullData) {
var i;
var legends = ['legend'];

for(i = 0; i < fullData.length; i++) {
Lib.pushUnique(legends, fullData[i].legend);
var allLegendsData = fullData.slice();

// shapes could also show up in legends
var shapes = layoutOut.shapes;
if(shapes) {
for(i = 0; i < shapes.length; i++) {
var shape = shapes[i];
if(!shape.showlegend) continue;

var mockTrace = {
_input: shape._input,
visible: shape.visible,
showlegend: shape.showlegend,
legend: shape.legend
};

allLegendsData.push(mockTrace);
}
}

var legends = ['legend'];
for(i = 0; i < allLegendsData.length; i++) {
Lib.pushUnique(legends, allLegendsData[i].legend);
}

layoutOut._legends = [];
for(i = 0; i < legends.length; i++) {
var legendId = legends[i];

groupDefaults(legendId, layoutIn, layoutOut, fullData);
groupDefaults(legendId, layoutIn, layoutOut, allLegendsData);

if(
layoutOut[legendId] &&
Expand Down
39 changes: 37 additions & 2 deletions src/components/legend/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,43 @@ function drawOne(gd, opts) {

var legendData;
if(!inHover) {
if(!gd.calcdata) return;
legendData = fullLayout.showlegend && getLegendData(gd.calcdata, legendObj, fullLayout._legends.length > 1);
var calcdata = (gd.calcdata || []).slice();

var shapes = fullLayout.shapes;
for(var i = 0; i < shapes.length; i++) {
var shape = shapes[i];
if(!shape.showlegend) continue;

var shapeLegend = {
_fullInput: shape,
index: shape._index,
name: shape.name || shape.label.text || ('shape ' + shape._index),
LiamConnors marked this conversation as resolved.
Show resolved Hide resolved
legend: shape.legend,
legendgroup: shape.legendgroup,
legendgrouptitle: shape.legendgrouptitle,
legendrank: shape.legendrank,
legendwidth: shape.legendwidth,
showlegend: shape.showlegend,
visible: shape.visible,
opacity: shape.opacity,
mode: shape.type === 'line' ? 'lines' : 'markers',
line: shape.line,
marker: {
line: shape.line,
color: shape.fillcolor,
size: 12,
symbol:
shape.type === 'rect' ? 'square' :
shape.type === 'circle' ? 'circle' :
// case of path
'hexagon2'
},
};

calcdata.push([{ trace: shapeLegend }]);
}
if(!calcdata.length) return;
legendData = fullLayout.showlegend && getLegendData(calcdata, legendObj, fullLayout._legends.length > 1);
} else {
if(!legendObj.entries) return;
legendData = getLegendData(legendObj.entries, legendObj);
Expand Down
79 changes: 56 additions & 23 deletions src/components/legend/handle_click.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,39 +43,54 @@ module.exports = function handleClick(g, gd, numClicks) {
var legendgroup = fullTrace.legendgroup;

var i, j, kcont, key, keys, val;
var attrUpdate = {};
var attrIndices = [];
var dataUpdate = {};
var dataIndices = [];
var carrs = [];
var carrIdx = [];

function insertUpdate(traceIndex, key, value) {
var attrIndex = attrIndices.indexOf(traceIndex);
var valueArray = attrUpdate[key];
function insertDataUpdate(traceIndex, value) {
var attrIndex = dataIndices.indexOf(traceIndex);
var valueArray = dataUpdate.visible;
if(!valueArray) {
valueArray = attrUpdate[key] = [];
valueArray = dataUpdate.visible = [];
}

if(attrIndices.indexOf(traceIndex) === -1) {
attrIndices.push(traceIndex);
attrIndex = attrIndices.length - 1;
if(dataIndices.indexOf(traceIndex) === -1) {
dataIndices.push(traceIndex);
attrIndex = dataIndices.length - 1;
}

valueArray[attrIndex] = value;

return attrIndex;
}

var updatedShapes = (fullLayout.shapes || []).map(function(d) {
return d._input;
});

var shapesUpdated = false;

function insertShapesUpdate(shapeIndex, value) {
updatedShapes[shapeIndex].visible = value;
shapesUpdated = true;
}

function setVisibility(fullTrace, visibility) {
if(legendItem.groupTitle && !toggleGroup) return;

var fullInput = fullTrace._fullInput;
var isShape = fullInput._isShape;
if(isShape) fullInput = fullTrace;
var index = fullInput.index;

if(Registry.hasTransform(fullInput, 'groupby')) {
var kcont = carrs[fullInput.index];
var kcont = carrs[index];
if(!kcont) {
var groupbyIndices = Registry.getTransformIndices(fullInput, 'groupby');
var lastGroupbyIndex = groupbyIndices[groupbyIndices.length - 1];
kcont = Lib.keyedContainer(fullInput, 'transforms[' + lastGroupbyIndex + '].styles', 'target', 'value.visible');
carrs[fullInput.index] = kcont;
carrs[index] = kcont;
}

var curState = kcont.get(fullTrace._group);
Expand All @@ -93,20 +108,27 @@ module.exports = function handleClick(g, gd, numClicks) {
// true -> legendonly. All others toggle to true:
kcont.set(fullTrace._group, visibility);
}
carrIdx[fullInput.index] = insertUpdate(fullInput.index, 'visible', fullInput.visible === false ? false : true);
carrIdx[index] = insertDataUpdate(index, fullInput.visible === false ? false : true);
} else {
// false -> false (not possible since will not be visible in legend)
// true -> legendonly
// legendonly -> true
var nextVisibility = fullInput.visible === false ? false : visibility;

insertUpdate(fullInput.index, 'visible', nextVisibility);
if(isShape) {
insertShapesUpdate(index, nextVisibility);
} else {
insertDataUpdate(index, nextVisibility);
}
}
}

var thisLegend = fullTrace.legend;

if(Registry.traceIs(fullTrace, 'pie-like')) {
var fullInput = fullTrace._fullInput;
var isShape = fullInput && fullInput._isShape;

if(!isShape && Registry.traceIs(fullTrace, 'pie-like')) {
var thisLabel = legendItem.label;
var thisLabelIndex = hiddenSlices.indexOf(thisLabel);

Expand Down Expand Up @@ -175,9 +197,16 @@ module.exports = function handleClick(g, gd, numClicks) {

if(hasLegendgroup) {
if(toggleGroup) {
for(i = 0; i < fullData.length; i++) {
if(fullData[i].visible !== false && fullData[i].legendgroup === legendgroup) {
setVisibility(fullData[i], nextVisibility);
var allLegendItems = fullData.concat(fullLayout.shapes || []);
for(i = 0; i < allLegendItems.length; i++) {
var item = allLegendItems[i];
if(item.visible !== false && item.legendgroup === legendgroup) {
if(i > fullData.length) { // case of shapes
item.index = i - fullData.length;
item._isShape = true;
item._fullInput = item;
}
setVisibility(item, nextVisibility);
}
}
} else {
Expand Down Expand Up @@ -236,7 +265,7 @@ module.exports = function handleClick(g, gd, numClicks) {
var updateKeys = Object.keys(update);
for(j = 0; j < updateKeys.length; j++) {
key = updateKeys[j];
val = attrUpdate[key] = attrUpdate[key] || [];
val = dataUpdate[key] = dataUpdate[key] || [];
val[carrIdx[i]] = update[key];
}
}
Expand All @@ -245,17 +274,21 @@ module.exports = function handleClick(g, gd, numClicks) {
// values should be explicitly undefined for them to get properly culled
// as updates and not accidentally reset to the default value. This fills
// out sparse arrays with the required number of undefined values:
keys = Object.keys(attrUpdate);
keys = Object.keys(dataUpdate);
for(i = 0; i < keys.length; i++) {
key = keys[i];
for(j = 0; j < attrIndices.length; j++) {
for(j = 0; j < dataIndices.length; j++) {
// Use hasOwnProperty to protect against falsy values:
if(!attrUpdate[key].hasOwnProperty(j)) {
attrUpdate[key][j] = undefined;
if(!dataUpdate[key].hasOwnProperty(j)) {
dataUpdate[key][j] = undefined;
}
}
}

Registry.call('_guiRestyle', gd, attrUpdate, attrIndices);
Registry.call('_guiRestyle', gd, dataUpdate, dataIndices).then(function() {
if(shapesUpdated) {
Registry.call('_guiRelayout', gd, {shapes: updatedShapes});
}
});
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved
}
};
69 changes: 66 additions & 3 deletions src/components/shapes/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,82 @@ var dash = require('../drawing/attributes').dash;
var extendFlat = require('../../lib/extend').extendFlat;
var templatedArray = require('../../plot_api/plot_template').templatedArray;
var axisPlaceableObjs = require('../../constants/axis_placeable_objects');
var basePlotAttributes = require('../../plots/attributes');
var shapeTexttemplateAttrs = require('../../plots/template_attributes').shapeTexttemplateAttrs;
var shapeLabelTexttemplateVars = require('./label_texttemplate');

module.exports = templatedArray('shape', {
visible: {
visible: extendFlat({}, basePlotAttributes.visible, {
editType: 'calc+arraydraw',
description: [
'Determines whether or not this shape is visible.',
'If *legendonly*, the shape is not drawn,',
'but can appear as a legend item',
'(provided that the legend itself is visible).'
].join(' ')
}),

showlegend: {
valType: 'boolean',
dflt: true,
dflt: false,
editType: 'calc+arraydraw',
description: [
'Determines whether or not this shape is visible.'
'Determines whether or not this',
'shape is shown in the legend.'
].join(' ')
},

legend: extendFlat({}, basePlotAttributes.legend, {
editType: 'calc+arraydraw',
description: [
'Sets the reference to a legend to show this shape 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.'
].join(' ')
}),

legendgroup: extendFlat({}, basePlotAttributes.legendgroup, {
editType: 'calc+arraydraw',
description: [
'Sets the legend group for this shape.',
'Traces and shapes part of the same legend group hide/show at the same time',
'when toggling legend items.'
].join(' ')
}),

legendgrouptitle: {
text: extendFlat({}, basePlotAttributes.legendgrouptitle.text, {
editType: 'calc+arraydraw'
}),
font: fontAttrs({
editType: 'calc+arraydraw',
description: [
'Sets this legend group\'s title font.'
].join(' '),
}),
editType: 'calc+arraydraw',
},

legendrank: extendFlat({}, basePlotAttributes.legendrank, {
editType: 'calc+arraydraw',
description: [
'Sets the legend rank for this shape.',
'Items and groups with smaller ranks are presented on top/left side while',
'with *reversed* `legend.traceorder` they are on bottom/right side.',
'The default legendrank is 1000,',
'so that you can use ranks less than 1000 to place certain items before all unranked items,',
'and ranks greater than 1000 to go after all unranked items.',
'When having unranked or equal rank items shapes would be displayed after traces',
'i.e. according to their order in data and layout.'
].join(' ')
}),

legendwidth: extendFlat({}, basePlotAttributes.legendwidth, {
editType: 'calc+arraydraw',
description: 'Sets the width (in px or fraction) of the legend for this shape.',
}),

type: {
valType: 'enumerated',
values: ['circle', 'rect', 'path', 'line'],
Expand Down
12 changes: 12 additions & 0 deletions src/components/shapes/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,21 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
return Lib.coerce(shapeIn, shapeOut, attributes, attr, dflt);
}

shapeOut._isShape = true;

var visible = coerce('visible');
if(!visible) return;

var showlegend = coerce('showlegend');
if(showlegend) {
coerce('legend');
coerce('legendwidth');
coerce('legendgroup');
coerce('legendgrouptitle.text');
Lib.coerceFont(coerce, 'legendgrouptitle.font');
coerce('legendrank');
}

var path = coerce('path');
var dfltType = path ? 'path' : 'rect';
var shapeType = coerce('type', dfltType);
Expand Down