Skip to content

Commit 9de5b3d

Browse files
committed
Add attribute-based legends (colorlegend, sizelegend, symbollegend)
Implementation for Issue #5099 adds three new legend components that show unique values from marker attributes (color, size, symbol) rather than trace-based legends. New components: - colorlegend: displays unique color values as swatches - sizelegend: displays size ranges as graduated circles - symbollegend: displays unique marker symbols Features: - Click to toggle visibility of points with that attribute value - Support for multiple legends (colorlegend, colorlegend2, etc.) - Configurable positioning, styling, and orientation - Works with scatter and scatter-variant traces
1 parent f07f1c7 commit 9de5b3d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+4654
-12
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
'use strict';
2+
3+
var fontAttrs = require('../../plots/font_attributes');
4+
var colorAttrs = require('../color/attributes');
5+
6+
7+
module.exports = {
8+
// not really a 'subplot' attribute container,
9+
// but this is the flag we use to denote attributes that
10+
// support colorlegend, colorlegend2, colorlegend3, ... counters
11+
_isSubplotObj: true,
12+
13+
visible: {
14+
valType: 'boolean',
15+
dflt: true,
16+
editType: 'legend',
17+
description: 'Determines whether this color legend is visible.'
18+
},
19+
20+
title: {
21+
text: {
22+
valType: 'string',
23+
dflt: '',
24+
editType: 'legend',
25+
description: 'Sets the title of the color legend.'
26+
},
27+
font: fontAttrs({
28+
editType: 'legend',
29+
description: 'Sets the font for the color legend title.'
30+
}),
31+
side: {
32+
valType: 'enumerated',
33+
values: ['top', 'left', 'right'],
34+
dflt: 'top',
35+
editType: 'legend',
36+
description: 'Determines the location of the legend title.'
37+
},
38+
editType: 'legend'
39+
},
40+
41+
// Positioning (same pattern as legend)
42+
x: {
43+
valType: 'number',
44+
dflt: 1.02,
45+
editType: 'legend',
46+
description: 'Sets the x position with respect to `xref`.'
47+
},
48+
xref: {
49+
valType: 'enumerated',
50+
values: ['container', 'paper'],
51+
dflt: 'paper',
52+
editType: 'legend',
53+
description: 'Sets the container `x` refers to.'
54+
},
55+
xanchor: {
56+
valType: 'enumerated',
57+
values: ['auto', 'left', 'center', 'right'],
58+
dflt: 'left',
59+
editType: 'legend',
60+
description: 'Sets the horizontal anchor.'
61+
},
62+
y: {
63+
valType: 'number',
64+
dflt: 1,
65+
editType: 'legend',
66+
description: 'Sets the y position with respect to `yref`.'
67+
},
68+
yref: {
69+
valType: 'enumerated',
70+
values: ['container', 'paper'],
71+
dflt: 'paper',
72+
editType: 'legend',
73+
description: 'Sets the container `y` refers to.'
74+
},
75+
yanchor: {
76+
valType: 'enumerated',
77+
values: ['auto', 'top', 'middle', 'bottom'],
78+
dflt: 'auto',
79+
editType: 'legend',
80+
description: 'Sets the vertical anchor.'
81+
},
82+
83+
// Styling
84+
bgcolor: {
85+
valType: 'color',
86+
editType: 'legend',
87+
description: 'Sets the background color.'
88+
},
89+
bordercolor: {
90+
valType: 'color',
91+
dflt: colorAttrs.defaultLine,
92+
editType: 'legend',
93+
description: 'Sets the border color.'
94+
},
95+
borderwidth: {
96+
valType: 'number',
97+
min: 0,
98+
dflt: 0,
99+
editType: 'legend',
100+
description: 'Sets the border width.'
101+
},
102+
font: fontAttrs({
103+
editType: 'legend',
104+
description: 'Sets the font for legend item text.'
105+
}),
106+
107+
// Orientation
108+
orientation: {
109+
valType: 'enumerated',
110+
values: ['v', 'h'],
111+
dflt: 'v',
112+
editType: 'legend',
113+
description: 'Sets the orientation of the legend items.'
114+
},
115+
116+
// Item sizing
117+
itemsizing: {
118+
valType: 'enumerated',
119+
values: ['trace', 'constant'],
120+
dflt: 'constant',
121+
editType: 'legend',
122+
description: [
123+
'Determines if legend items symbols scale with their corresponding data values',
124+
'or remain constant.'
125+
].join(' ')
126+
},
127+
itemwidth: {
128+
valType: 'number',
129+
min: 30,
130+
dflt: 30,
131+
editType: 'legend',
132+
description: 'Sets the width of the legend item symbols.'
133+
},
134+
135+
// Behavior
136+
itemclick: {
137+
valType: 'enumerated',
138+
values: ['toggle', 'toggleothers', false],
139+
dflt: 'toggle',
140+
editType: 'legend',
141+
description: 'Determines the behavior on legend item click.'
142+
},
143+
itemdoubleclick: {
144+
valType: 'enumerated',
145+
values: ['toggle', 'toggleothers', false],
146+
dflt: 'toggleothers',
147+
editType: 'legend',
148+
description: 'Determines the behavior on legend item double-click.'
149+
},
150+
151+
// Continuous data binning
152+
binning: {
153+
valType: 'enumerated',
154+
values: ['auto', 'discrete'],
155+
dflt: 'auto',
156+
editType: 'calc',
157+
description: [
158+
'For numeric color data, *auto* creates bins while',
159+
'*discrete* treats each unique value as a category.'
160+
].join(' ')
161+
},
162+
nbins: {
163+
valType: 'integer',
164+
min: 1,
165+
dflt: 5,
166+
editType: 'calc',
167+
description: 'Sets the number of bins for continuous data when binning is *auto*.'
168+
},
169+
170+
editType: 'legend'
171+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
module.exports = {
4+
itemGap: 5,
5+
textGap: 8,
6+
padding: 10
7+
};
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use strict';
2+
3+
var Lib = require('../../lib');
4+
var Template = require('../../plot_api/plot_template');
5+
var attributes = require('./attributes');
6+
7+
module.exports = function colorlegendDefaults(layoutIn, layoutOut, fullData) {
8+
var colorlegendIds = findColorlegendIds(fullData);
9+
10+
layoutOut._colorlegends = [];
11+
12+
for(var i = 0; i < colorlegendIds.length; i++) {
13+
var id = colorlegendIds[i];
14+
var containerIn = layoutIn[id] || {};
15+
var containerOut = Template.newContainer(layoutOut, id);
16+
17+
handleColorlegendDefaults(containerIn, containerOut, layoutOut, id);
18+
19+
if(containerOut.visible) {
20+
containerOut._id = id;
21+
layoutOut._colorlegends.push(id);
22+
}
23+
}
24+
};
25+
26+
function findColorlegendIds(fullData) {
27+
var ids = [];
28+
29+
for(var i = 0; i < fullData.length; i++) {
30+
var trace = fullData[i];
31+
if(!trace.visible) continue;
32+
33+
var marker = trace.marker;
34+
if(marker && marker.colorlegend) {
35+
var id = marker.colorlegend;
36+
if(ids.indexOf(id) === -1) {
37+
ids.push(id);
38+
}
39+
}
40+
}
41+
42+
return ids;
43+
}
44+
45+
function handleColorlegendDefaults(containerIn, containerOut, layoutOut, id) {
46+
function coerce(attr, dflt) {
47+
return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
48+
}
49+
50+
var visible = coerce('visible');
51+
if(!visible) return;
52+
53+
// Title
54+
coerce('title.text');
55+
Lib.coerceFont(coerce, 'title.font', layoutOut.font);
56+
coerce('title.side');
57+
58+
// Orientation first - affects positioning defaults
59+
var orientation = coerce('orientation');
60+
61+
// Positioning - defaults depend on orientation
62+
var isHorizontal = orientation === 'h';
63+
if(isHorizontal) {
64+
// Horizontal: top-right, outside the chart
65+
coerce('x', 1.02);
66+
coerce('xanchor', 'left');
67+
coerce('y', 1);
68+
coerce('yanchor', 'top');
69+
} else {
70+
// Vertical: right side of chart (default from attributes)
71+
coerce('x');
72+
coerce('xanchor');
73+
coerce('y');
74+
coerce('yanchor');
75+
}
76+
coerce('xref');
77+
coerce('yref');
78+
79+
// Styling
80+
coerce('bgcolor', layoutOut.paper_bgcolor);
81+
coerce('bordercolor');
82+
coerce('borderwidth');
83+
Lib.coerceFont(coerce, 'font', layoutOut.font);
84+
coerce('itemsizing');
85+
coerce('itemwidth');
86+
87+
// Behavior
88+
coerce('itemclick');
89+
coerce('itemdoubleclick');
90+
91+
// Binning
92+
coerce('binning');
93+
coerce('nbins');
94+
}

0 commit comments

Comments
 (0)