Skip to content

add options to include shapes and newshape in legends #6653

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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
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
28 changes: 24 additions & 4 deletions src/components/legend/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,17 +195,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
35 changes: 33 additions & 2 deletions src/components/legend/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,39 @@ 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),
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,
symbol: shape.type === 'rect' ? 'square' : 'circle',
size: 12
},
};

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
16 changes: 12 additions & 4 deletions src/components/legend/handle_click.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,17 @@ module.exports = function handleClick(g, gd, numClicks) {
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 @@ -92,14 +96,18 @@ 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] = insertUpdate(index, 'visible', 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) {
Registry.call('_guiRelayout', gd, 'shapes[' + index + '].visible', nextVisibility);
} else {
insertUpdate(index, 'visible', nextVisibility);
}
}
}

Expand Down
67 changes: 64 additions & 3 deletions src/components/shapes/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,80 @@ 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.'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we say something about unranked or equal rank items? I presume traces according to their order in data, then shapes according to their order in layout.shapes?

Also I don't see shapes[].legendrank used in a test image to intersperse shapes and traces, or shapes[].legend to put shapes on multiple legends, can we add those?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call.
Addressed in 2e77fc7, 58b1cb1 and 3168078.

].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
4 changes: 2 additions & 2 deletions src/components/shapes/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function draw(gd) {
}

for(var i = 0; i < fullLayout.shapes.length; i++) {
if(fullLayout.shapes[i].visible) {
if(fullLayout.shapes[i].visible === true) {
drawOne(gd, i);
}
}
Expand Down Expand Up @@ -89,7 +89,7 @@ function drawOne(gd, index) {

// this shape is gone - quit now after deleting it
// TODO: use d3 idioms instead of deleting and redrawing every time
if(!options._input || options.visible === false) return;
if(!options._input || options.visible !== true) return;

if(options.layer !== 'below') {
drawShape(gd._fullLayout._shapeUpperLayer);
Expand Down
70 changes: 70 additions & 0 deletions src/components/shapes/draw_newshape/attributes.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

var basePlotAttributes = require('../../../plots/attributes');
var fontAttrs = require('../../../plots/font_attributes');
var dash = require('../../drawing/attributes').dash;
var extendFlat = require('../../../lib/extend').extendFlat;
Expand All @@ -9,6 +10,75 @@ var shapeLabelTexttemplateVars = require('../label_texttemplate');

module.exports = {
newshape: {
visible: extendFlat({}, basePlotAttributes.visible, {
editType: 'none',
description: [
'Determines whether or not new 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: false,
editType: 'none',
description: [
'Determines whether or not new',
'shape is shown in the legend.'
].join(' ')
},

legend: extendFlat({}, basePlotAttributes.legend, {
editType: 'none',
description: [
'Sets the reference to a legend to show new 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: 'none',
description: [
'Sets the legend group for new 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: 'none'
}),
font: fontAttrs({
editType: 'none',
description: [
'Sets this legend group\'s title font.'
].join(' '),
}),
editType: 'none',
},

legendrank: extendFlat({}, basePlotAttributes.legendrank, {
editType: 'none',
description: [
'Sets the legend rank for new 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.'
].join(' ')
}),

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

line: {
color: {
valType: 'color',
Expand Down
10 changes: 10 additions & 0 deletions src/components/shapes/draw_newshape/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ function dfltLabelYanchor(isLine, labelTextPosition) {
}

module.exports = function supplyDrawNewShapeDefaults(layoutIn, layoutOut, coerce) {
coerce('newshape.visible');

coerce('newshape.showlegend');
coerce('newshape.legend');
coerce('newshape.legendwidth');
coerce('newshape.legendgroup');
coerce('newshape.legendgrouptitle.text');
Lib.coerceFont(coerce, 'newshape.legendgrouptitle.font');
coerce('newshape.legendrank');

coerce('newshape.drawdirection');
coerce('newshape.layer');
coerce('newshape.fillcolor');
Expand Down
12 changes: 12 additions & 0 deletions src/components/shapes/draw_newshape/newshapes.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@ function createShapeObj(outlines, dragOptions, dragmode) {
var newShape = {
editable: true,

visible: newStyle.visible,

showlegend: newStyle.showlegend,
legend: newStyle.legend,
legendwidth: newStyle.legendwidth,
legendgroup: newStyle.legendgroup,
legendgrouptitle: {
text: newStyle.legendgrouptitle.text,
font: newStyle.legendgrouptitle.font
},
legendrank: newStyle.legendrank,

label: newStyle.label,

xref: xPaper ? 'paper' : xaxis._id,
Expand Down
4 changes: 2 additions & 2 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ register(require('./traces/scatter'));

// register all registrable components modules
register([
require('./components/legend'),
require('./components/fx'), // fx needs to come after legend
require('./components/annotations'),
require('./components/annotations3d'),
require('./components/selections'),
Expand All @@ -46,6 +44,8 @@ register([
require('./components/errorbars'),
require('./components/colorscale'),
require('./components/colorbar'),
require('./components/legend'),
require('./components/fx'), // fx needs to come after legend
require('./components/modebar')
]);

Expand Down
Loading