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 28 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 shapesWithLegend = (layoutOut.shapes || []).filter(function(d) { return d.showlegend; });

var allLegendItems = fullData.concat(shapesWithLegend).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
50 changes: 45 additions & 5 deletions src/components/legend/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,44 @@ 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 = {
_isShape: true,
_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 Expand Up @@ -491,9 +527,9 @@ function drawTexts(g, gd, legendObj) {

if(Registry.hasTransform(fullInput, 'groupby')) {
var groupbyIndices = Registry.getTransformIndices(fullInput, 'groupby');
var index = groupbyIndices[groupbyIndices.length - 1];
var _index = groupbyIndices[groupbyIndices.length - 1];

var kcont = Lib.keyedContainer(fullInput, 'transforms[' + index + '].styles', 'target', 'value.name');
var kcont = Lib.keyedContainer(fullInput, 'transforms[' + _index + '].styles', 'target', 'value.name');

kcont.set(legendItem.trace._group, newName);

Expand All @@ -502,7 +538,11 @@ function drawTexts(g, gd, legendObj) {
update.name = newName;
}

return Registry.call('_guiRestyle', gd, update, trace.index);
if(fullInput._isShape) {
return Registry.call('_guiRelayout', gd, 'shapes[' + trace.index + '].name', update.name);
} else {
return Registry.call('_guiRestyle', gd, update, trace.index);
}
});
} else {
textLayout(textEl, g, gd, legendObj);
Expand Down
117 changes: 77 additions & 40 deletions src/components/legend/handle_click.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,43 +39,65 @@ module.exports = function handleClick(g, gd, numClicks) {
if(legendItem.groupTitle && legendItem.noClick) return;

var fullData = gd._fullData;
var shapesWithLegend = (fullLayout.shapes || []).filter(function(d) { return d.showlegend; });
var allLegendItems = fullData.concat(shapesWithLegend);

var fullTrace = legendItem.trace;
if(fullTrace._isShape) {
fullTrace = fullTrace._fullInput;
}

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 fullInput = fullTrace._fullInput || fullTrace;
var isShape = fullInput._isShape;
var index = fullInput.index;
if(index === undefined) 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 +115,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 @@ -149,8 +178,8 @@ module.exports = function handleClick(g, gd, numClicks) {
var traceIndicesInGroup = [];
var tracei;
if(hasLegendgroup) {
for(i = 0; i < fullData.length; i++) {
tracei = fullData[i];
for(i = 0; i < allLegendItems.length; i++) {
tracei = allLegendItems[i];
if(!tracei.visible) continue;
if(tracei.legendgroup === legendgroup) {
traceIndicesInGroup.push(i);
Expand All @@ -175,9 +204,10 @@ 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);
for(i = 0; i < allLegendItems.length; i++) {
var item = allLegendItems[i];
if(item.visible !== false && item.legendgroup === legendgroup) {
setVisibility(item, nextVisibility);
}
}
} else {
Expand All @@ -189,40 +219,43 @@ module.exports = function handleClick(g, gd, numClicks) {
} 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, notInLegend, otherState;
var isClicked, isInGroup, notInLegend, otherState, _item;
var isIsolated = true;
for(i = 0; i < fullData.length; i++) {
isClicked = fullData[i] === fullTrace;
notInLegend = fullData[i].showlegend !== true;
for(i = 0; i < allLegendItems.length; i++) {
_item = allLegendItems[i];
isClicked = _item === fullTrace;
notInLegend = _item.showlegend !== true;
if(isClicked || notInLegend) continue;

isInGroup = (hasLegendgroup && fullData[i].legendgroup === legendgroup);
isInGroup = (hasLegendgroup && _item.legendgroup === legendgroup);

if(!isInGroup && fullData[i].visible === true && !Registry.traceIs(fullData[i], 'notLegendIsolatable')) {
if(!isInGroup && _item.visible === true && !Registry.traceIs(_item, 'notLegendIsolatable')) {
isIsolated = false;
break;
}
}

for(i = 0; i < fullData.length; i++) {
for(i = 0; i < allLegendItems.length; i++) {
_item = allLegendItems[i];

// False is sticky; we don't change it. Also ensure we don't change states of itmes in other legend
if(fullData[i].visible === false || fullData[i].legend !== thisLegend) continue;
if(_item.visible === false || _item.legend !== thisLegend) continue;

if(Registry.traceIs(fullData[i], 'notLegendIsolatable')) {
if(Registry.traceIs(_item, 'notLegendIsolatable')) {
continue;
}

switch(fullTrace.visible) {
case 'legendonly':
setVisibility(fullData[i], true);
setVisibility(_item, true);
break;
case true:
otherState = isIsolated ? true : 'legendonly';
isClicked = fullData[i] === fullTrace;
isClicked = _item === fullTrace;
// N.B. consider traces that have a set legendgroup as toggleable
notInLegend = (fullData[i].showlegend !== true && !fullData[i].legendgroup);
isInGroup = isClicked || (hasLegendgroup && fullData[i].legendgroup === legendgroup);
setVisibility(fullData[i], (isInGroup || notInLegend) ? true : otherState);
notInLegend = (_item.showlegend !== true && !_item.legendgroup);
isInGroup = isClicked || (hasLegendgroup && _item.legendgroup === legendgroup);
setVisibility(_item, (isInGroup || notInLegend) ? true : otherState);
break;
}
}
Expand All @@ -236,7 +269,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 +278,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
}
};