Skip to content

Commit 7ea8b88

Browse files
author
greg
committed
implement makeBubbleSizeFn to proportionally size bubble type scatter plots using the following inputs:
- sizemin - min size of marker in pixels - sizemax - max size of marker in pixels - sizedatamin - any values 'size' array equal to or less than this value will have a marker size of sizemin - sizedatamax - any values 'size' array equal to or greater than this value will have a marker size of sizemax - sizedataislog - if the data in size array is logarithmic - supports negative values in size array - sizeref is NOT used.
1 parent c842e99 commit 7ea8b88

File tree

14 files changed

+249
-50
lines changed

14 files changed

+249
-50
lines changed

dist/plot-schema.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,35 @@
891891
"editType": "calc",
892892
"description": "Has an effect only if `marker.size` is set to a numerical array. Sets the minimum size (in px) of the rendered marker points."
893893
},
894+
"sizemax": {
895+
"valType": "number",
896+
"min": 0,
897+
"dflt": 0,
898+
"role": "style",
899+
"editType": "calc",
900+
"description": "Has an effect only if `marker.size` is set to a numerical array. Sets the maximum size (in px) of the rendered marker points."
901+
},
902+
"sizedatamin": {
903+
"valType": "number",
904+
"dflt": 0,
905+
"role": "style",
906+
"editType": "calc",
907+
"description": "Has an effect only if `marker.size` is set to a numerical array. Data values less than or equal to this value will have the minimum allowed symbol size."
908+
},
909+
"sizedatamax": {
910+
"valType": "number",
911+
"dflt": 0,
912+
"role": "style",
913+
"editType": "calc",
914+
"description": "Has an effect only if `marker.size` is set to a numerical array. Data values equal to or greater than what is defined here will have the maximum allowed symbol size."
915+
},
916+
"sizedataislog": {
917+
"valType": "boolean",
918+
"dflt": false,
919+
"role": "style",
920+
"editType": "calc",
921+
"description": "Has an effect only if `marker.size` is set to a numerical array. Data values are log scale. They will be converted with Math.log10 before being used for sizing."
922+
},
894923
"sizemode": {
895924
"valType": "enumerated",
896925
"values": [

src/components/legend/style.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,14 @@ module.exports = function style(s, gd) {
122122
dEdit.mlc = boundVal('marker.line.color', pickFirst);
123123
dEdit.mlw = boundVal('marker.line.width', Lib.mean, [0, 5]);
124124
tEdit.marker = {
125+
sizemode: 'diameter',
125126
sizeref: 1,
126127
sizemin: 1,
127-
sizemode: 'diameter'
128+
sizemax: null,
129+
sizedatamin: null,
130+
sizedatamax: null,
131+
sizedataislog: false,
132+
sizeforlegend: true
128133
};
129134
}
130135

src/traces/scatter/attributes.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,14 +302,52 @@ module.exports = {
302302
sizemin: {
303303
valType: 'number',
304304
min: 0,
305-
dflt: 0,
305+
dflt: 8,
306306
role: 'style',
307307
editType: 'calc',
308308
description: [
309309
'Has an effect only if `marker.size` is set to a numerical array.',
310310
'Sets the minimum size (in px) of the rendered marker points.'
311311
].join(' ')
312312
},
313+
sizemax: {
314+
valType: 'number',
315+
dflt: 48,
316+
role: 'style',
317+
editType: 'calc',
318+
description: [
319+
'Has an effect only if `marker.size` is set to a numerical array.',
320+
'Sets the maximum size (in px) of the rendered marker points.'
321+
].join(' ')
322+
},
323+
sizedatamin: {
324+
valType: 'number',
325+
role: 'style',
326+
editType: 'calc',
327+
description: [
328+
'Has an effect only if `marker.size` is set to a numerical array.',
329+
'Data values less than or equal to this value will have the minimum allowed symbol size.'
330+
].join(' ')
331+
},
332+
sizedatamax: {
333+
valType: 'number',
334+
role: 'style',
335+
editType: 'calc',
336+
description: [
337+
'Has an effect only if `marker.size` is set to a numerical array.',
338+
'Data values equal to or greater than what is defined here will have the maximum allowed symbol size.'
339+
].join(' ')
340+
},
341+
sizedataislog: {
342+
valType: 'boolean',
343+
dflt: false,
344+
role: 'style',
345+
editType: 'calc',
346+
description: [
347+
'Has an effect only if `marker.size` is set to a numerical array.',
348+
'Data values are log scale. They will be converted with Math.log10 before being used for sizing.'
349+
].join(' ')
350+
},
313351
sizemode: {
314352
valType: 'enumerated',
315353
values: ['diameter', 'area'],

src/traces/scatter/calc.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ var subTypes = require('./subtypes');
1818
var calcColorscale = require('./colorscale_calc');
1919
var arraysToCalcdata = require('./arrays_to_calcdata');
2020
var calcSelection = require('./calc_selection');
21+
var makeBubbleSizeFn = require('./make_bubble_size_func');
2122

2223
function calc(gd, trace) {
2324
var xa = Axes.getFromId(gd, trace.xaxis || 'x');
@@ -101,31 +102,32 @@ function calcMarkerSize(trace, serieslen) {
101102
// Treat size like x or y arrays --- Run d2c
102103
// this needs to go before ppad computation
103104
var marker = trace.marker;
104-
var sizeref = 1.6 * (trace.marker.sizeref || 1);
105-
var markerTrans;
106-
107-
if(trace.marker.sizemode === 'area') {
108-
markerTrans = function(v) {
109-
return Math.max(Math.sqrt((v || 0) / sizeref), 3);
110-
};
111-
} else {
112-
markerTrans = function(v) {
113-
return Math.max((v || 0) / sizeref, 3);
114-
};
115-
}
105+
106+
// use a modified version of the bubble size function
107+
// with smaller sizeref (resulting in a pad factor)
108+
// and making non-numeric and size-0 points count as pad=3.
109+
var sizeFn = makeBubbleSizeFn({
110+
marker: Lib.extendFlat({}, marker, {
111+
sizemode: marker.sizemode || 'diameter',
112+
sizeref: 0.8 * (marker.sizeref || 1),
113+
})
114+
});
115+
var markerTrans = function(v) {
116+
return Math.max(sizeFn(v), 3);
117+
};
116118

117119
if(Array.isArray(marker.size)) {
118-
// I tried auto-type but category and dates dont make much sense.
120+
// no need to auto-type as category and dates do not make much sense.
119121
var ax = {type: 'linear'};
120122
Axes.setConvert(ax);
121123

122124
var s = ax.makeCalcdata(trace.marker, 'size');
123125
if(s.length > serieslen) s.splice(serieslen, s.length - serieslen);
124126

125127
return s.map(markerTrans);
126-
} else {
127-
return markerTrans(marker.size);
128128
}
129+
130+
return markerTrans(marker.size);
129131
}
130132

131133
module.exports = {

src/traces/scatter/make_bubble_size_func.js

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,71 @@
1111

1212
var isNumeric = require('fast-isnumeric');
1313

14-
15-
// used in the drawing step for 'scatter' and 'scattegeo' and
16-
// in the convert step for 'scatter3d'
1714
module.exports = function makeBubbleSizeFn(trace) {
18-
var marker = trace.marker,
19-
sizeRef = marker.sizeref || 1,
20-
sizeMin = marker.sizemin || 0;
21-
22-
// for bubble charts, allow scaling the provided value linearly
23-
// and by area or diameter.
24-
// Note this only applies to the array-value sizes
25-
26-
var baseFn = (marker.sizemode === 'area') ?
27-
function(v) { return Math.sqrt(v / sizeRef); } :
28-
function(v) { return v / sizeRef; };
29-
30-
// TODO add support for position/negative bubbles?
31-
// TODO add 'sizeoffset' attribute?
32-
return function(v) {
33-
var baseSize = baseFn(v / 2);
34-
35-
// don't show non-numeric and negative sizes
36-
return (isNumeric(baseSize) && (baseSize > 0)) ?
37-
Math.max(baseSize, sizeMin) :
38-
0;
39-
};
40-
};
15+
// if (!subTypes.hasMarkers(trace)) return;
16+
17+
// Treat size like x or y arrays --- Run d2c
18+
// this needs to go before ppad computation
19+
var marker = trace.marker;
20+
21+
// only size the marker if Not called from the legend sytle code or maker.size set
22+
// sizeforlegend is only ever set in src/components/legend/sytle.js, not part of json interface.
23+
var sizemarker = marker.sizeforlegend ? false : (Array.isArray(marker.size) && marker.size.filter(isNumeric).length > 0);
24+
// ideally the sizemin and sizemax should always be set, try to pick a reasonable value when not set.
25+
// I've set default values in attributes.js for sizemin of 8 and sizemax of 48
26+
var markersizemin = marker.sizemin || 8;
27+
var markersizemax = marker.sizemax || markersizemin + 50; //(sizemarker ? marker.size.length : 50);
28+
var validateCalcSize = function (markersize) {
29+
if (markersize < markersizemin) {
30+
markersize = markersizemin;
31+
}
32+
else if (markersize > markersizemax) {
33+
markersize = markersizemax;
34+
}
35+
return markersize;
36+
}
37+
38+
var sizeByFunc = function (v) {
39+
var markersize;
40+
if (sizemarker) {
41+
var plotrangemin = marker.sizedatamin || Math.min(...marker.size.filter(isNumeric));
42+
var plotrangemax = marker.sizedatamax || Math.max(...marker.size.filter(isNumeric));
43+
if (marker.sizedataislog) {
44+
// convert to log base 10 equivalents
45+
plotrangemin = Math.log10(plotrangemin);
46+
plotrangemax = Math.log10(plotrangemax);
47+
}
48+
var plotrange = plotrangemax - plotrangemin;
49+
plotrange = (plotrange === Number.POSITIVE_INFINITY || Number.isNaN(plotrange)) ? Number.MAX_VALUE : plotrange;
50+
var makersizerange = markersizemax - markersizemin;
51+
var s = marker.sizedataislog ? Math.log10(v) : v;
52+
markersize = Math.round((((s - plotrangemin) / plotrange) * makersizerange) + markersizemin);
53+
return markersize;
54+
}
55+
else {
56+
return v / 2; // no sizing array (old way)
57+
}
58+
}
59+
var markerTrans;
60+
61+
if (trace.marker.sizemode === 'area') {
62+
markerTrans = function (v) {
63+
// don't display marker for non numeric size values (allow override to min?)
64+
if (!isNumeric(v)) return 0;
65+
var s = sizeByFunc(v);
66+
var area = Math.sqrt(s);
67+
return validateCalcSize(area);
68+
};
69+
} else {
70+
markerTrans = function (v) {
71+
// don't display marker for non numeric size values (allow override to min?)
72+
if (!isNumeric(v)) return 0;
73+
var s = sizeByFunc(v);
74+
// don't let the symbol size go outside of the allowed range
75+
return validateCalcSize(s);
76+
};
77+
}
78+
79+
return markerTrans;
80+
}
81+

src/traces/scatter/marker_defaults.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout
7070
coerce('marker.sizeref');
7171
coerce('marker.sizemin');
7272
coerce('marker.sizemode');
73+
coerce('marker.sizemax');
74+
coerce('marker.sizedatamin');
75+
coerce('marker.sizedatamax');
76+
coerce('marker.sizedataislog');
7377
}
7478

7579
if(opts.gradient) {

src/traces/scatter3d/attributes.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,13 @@ var attrs = module.exports = overrideAll({
139139
description: 'Sets the marker symbol type.'
140140
},
141141
size: extendFlat({}, scatterMarkerAttrs.size, {dflt: 8}),
142+
sizemode: scatterMarkerAttrs.sizemode,
142143
sizeref: scatterMarkerAttrs.sizeref,
143144
sizemin: scatterMarkerAttrs.sizemin,
144-
sizemode: scatterMarkerAttrs.sizemode,
145+
sizemax: scatterMarkerAttrs.sizemax,
146+
sizedatamin: scatterMarkerAttrs.sizedatamin,
147+
sizedatamax: scatterMarkerAttrs.sizedatamax,
148+
sizedataislog: scatterMarkerAttrs.sizedataislog,
145149
opacity: extendFlat({}, scatterMarkerAttrs.opacity, {
146150
arrayOk: false,
147151
description: [

src/traces/scattercarpet/attributes.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,13 @@ module.exports = {
9292
opacity: scatterMarkerAttrs.opacity,
9393
maxdisplayed: scatterMarkerAttrs.maxdisplayed,
9494
size: scatterMarkerAttrs.size,
95+
sizemode: scatterMarkerAttrs.sizemode,
9596
sizeref: scatterMarkerAttrs.sizeref,
9697
sizemin: scatterMarkerAttrs.sizemin,
97-
sizemode: scatterMarkerAttrs.sizemode,
98+
sizemax: scatterMarkerAttrs.sizemax,
99+
sizedatamin: scatterMarkerAttrs.sizedatamin,
100+
sizedatamax: scatterMarkerAttrs.sizedatamax,
101+
sizedataislog: scatterMarkerAttrs.sizedataislog,
98102
line: extendFlat({
99103
width: scatterMarkerLineAttrs.width,
100104
editType: 'calc'

src/traces/scattergeo/attributes.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,13 @@ module.exports = overrideAll({
8989
symbol: scatterMarkerAttrs.symbol,
9090
opacity: scatterMarkerAttrs.opacity,
9191
size: scatterMarkerAttrs.size,
92+
sizemode: scatterMarkerAttrs.sizemode,
9293
sizeref: scatterMarkerAttrs.sizeref,
9394
sizemin: scatterMarkerAttrs.sizemin,
94-
sizemode: scatterMarkerAttrs.sizemode,
95+
sizemax: scatterMarkerAttrs.sizemax,
96+
sizedatamin: scatterMarkerAttrs.sizedatamin,
97+
sizedatamax: scatterMarkerAttrs.sizedatamax,
98+
sizedataislog: scatterMarkerAttrs.sizedataislog,
9599
showscale: scatterMarkerAttrs.showscale,
96100
colorbar: scatterMarkerAttrs.colorbar,
97101
line: extendFlat({

src/traces/scattergl/attributes.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,13 @@ var attrs = module.exports = overrideAll({
5959
marker: extendFlat({}, colorAttributes('marker'), {
6060
symbol: scatterMarkerAttrs.symbol,
6161
size: scatterMarkerAttrs.size,
62+
sizemode: scatterMarkerAttrs.sizemode,
6263
sizeref: scatterMarkerAttrs.sizeref,
6364
sizemin: scatterMarkerAttrs.sizemin,
64-
sizemode: scatterMarkerAttrs.sizemode,
65+
sizemax: scatterMarkerAttrs.sizemax,
66+
sizedatamin: scatterMarkerAttrs.sizedatamin,
67+
sizedatamax: scatterMarkerAttrs.sizedatamax,
68+
sizedataislog: scatterMarkerAttrs.sizedataislog,
6569
opacity: scatterMarkerAttrs.opacity,
6670
showscale: scatterMarkerAttrs.showscale,
6771
colorbar: scatterMarkerAttrs.colorbar,

src/traces/scattermapbox/attributes.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,13 @@ module.exports = overrideAll({
8585
},
8686
opacity: markerAttrs.opacity,
8787
size: markerAttrs.size,
88+
sizemode: markerAttrs.sizemode,
8889
sizeref: markerAttrs.sizeref,
8990
sizemin: markerAttrs.sizemin,
90-
sizemode: markerAttrs.sizemode,
91+
sizemax: markerAttrs.sizemax,
92+
sizedatamin: markerAttrs.sizedatamin,
93+
sizedatamax: markerAttrs.sizedatamax,
94+
sizedataislog: markerAttrs.sizedataislog,
9195
color: markerAttrs.color,
9296
colorscale: markerAttrs.colorscale,
9397
cauto: markerAttrs.cauto,

src/traces/scatterternary/attributes.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,13 @@ module.exports = {
121121
opacity: scatterMarkerAttrs.opacity,
122122
maxdisplayed: scatterMarkerAttrs.maxdisplayed,
123123
size: scatterMarkerAttrs.size,
124+
sizemode: scatterMarkerAttrs.sizemode,
124125
sizeref: scatterMarkerAttrs.sizeref,
125126
sizemin: scatterMarkerAttrs.sizemin,
126-
sizemode: scatterMarkerAttrs.sizemode,
127+
sizemax: scatterMarkerAttrs.sizemax,
128+
sizedatamin: scatterMarkerAttrs.sizedatamin,
129+
sizedatamax: scatterMarkerAttrs.sizedatamax,
130+
sizedataislog: scatterMarkerAttrs.sizedataislog,
127131
line: extendFlat({
128132
width: scatterMarkerLineAttrs.width,
129133
editType: 'calc'

test/image/mocks/bubble_nonnumeric-sizes.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@
131131
-759812.2658780153
132132
],
133133
"sizeref": 10000,
134-
"sizemin": 3
134+
"sizemin": 5,
135+
"sizemax": 60,
136+
"sizedatamax": 600000
135137
},
136138
"text": [
137139
"sup",

0 commit comments

Comments
 (0)