Skip to content

Commit 541460a

Browse files
committed
scatter marker color gradients
- radial, horizontal, and vertical - for all svg scatter types
1 parent 5e7eaaf commit 541460a

File tree

19 files changed

+184
-32
lines changed

19 files changed

+184
-32
lines changed

src/components/drawing/index.js

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
var d3 = require('d3');
1313
var isNumeric = require('fast-isnumeric');
14+
var tinycolor = require('tinycolor2');
1415

1516
var Registry = require('../../registry');
1617
var Color = require('../color');
@@ -202,7 +203,7 @@ drawing.symbolNumber = function(v) {
202203
return Math.floor(Math.max(v, 0));
203204
};
204205

205-
function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine) {
206+
function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine, gd) {
206207
// only scatter & box plots get marker path and opacity
207208
// bars, histograms don't
208209
if(Registry.traceIs(trace, 'symbols')) {
@@ -271,24 +272,83 @@ function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerL
271272
});
272273
}
273274
else {
274-
sel.style('stroke-width', lineWidth + 'px')
275-
.call(Color.fill, fillColor);
275+
sel.style('stroke-width', lineWidth + 'px');
276+
277+
var markerGradient = marker.gradient;
278+
var gradientColor = markerGradient && (d.mgc || markerGradient.color);
279+
var gradientType = d.mgt || markerGradient.type;
280+
if(gradientType && gradientType !== 'none') {
281+
sel.call(drawing.gradient, gd, gradientType, fillColor, gradientColor);
282+
}
283+
else {
284+
sel.call(Color.fill, fillColor);
285+
}
286+
276287
if(lineWidth) {
277288
sel.call(Color.stroke, lineColor);
278289
}
279290
}
280291
}
281292

282-
drawing.singlePointStyle = function(d, sel, trace) {
283-
var marker = trace.marker,
284-
markerLine = marker.line;
293+
var HORZGRADIENT = {x1: 1, x2: 0, y1: 0, y2: 0};
294+
var VERTGRADIENT = {x1: 0, x2: 0, y1: 1, y2: 0};
285295

286-
// allow array marker and marker line colors to be
287-
// scaled by given max and min to colorscales
288-
var markerScale = drawing.tryColorscale(marker, ''),
289-
lineScale = drawing.tryColorscale(marker, 'line');
296+
drawing.gradient = function(sel, gd, type, color1, color2) {
297+
var fullLayout = gd._fullLayout;
298+
var gradientID = (
299+
type + fullLayout._uid + '-' + color1 + '-' + color2
300+
).replace(/[^\w\-]+/g, '_');
301+
var gradient = fullLayout._defs.select('.gradients').selectAll('#' + gradientID).data([0]);
302+
303+
gradient.enter()
304+
.append(type === 'radial' ? 'radialGradient' : 'linearGradient')
305+
.each(function() {
306+
var el = d3.select(this);
307+
if(type === 'horizontal') el.attr(HORZGRADIENT);
308+
else if(type === 'vertical') el.attr(VERTGRADIENT);
309+
310+
el.attr('id', gradientID);
311+
312+
var tc1 = tinycolor(color1);
313+
var tc2 = tinycolor(color2);
314+
315+
el.append('stop').attr({
316+
offset: '0%',
317+
'stop-color': Color.tinyRGB(tc2),
318+
'stop-opacity': tc2.getAlpha()
319+
});
320+
321+
el.append('stop').attr({
322+
offset: '100%',
323+
'stop-color': Color.tinyRGB(tc1),
324+
'stop-opacity': tc1.getAlpha()
325+
});
326+
});
327+
328+
sel.style({
329+
fill: 'url(#' + gradientID + ')',
330+
'fill-opacity': null
331+
});
332+
};
333+
334+
/*
335+
* Make the gradients container and clear out any previous gradients.
336+
* We never collect all the gradients we need in one place,
337+
* so we can't ever remove gradients that have stopped being useful,
338+
* except all at once before a full redraw.
339+
* The upside of this is arbitrary points can share gradient defs
340+
*/
341+
drawing.initGradients = function(gd) {
342+
var gradientsGroup = gd._fullLayout._defs.selectAll('.gradients').data([0]);
343+
gradientsGroup.enter().append('g').classed('gradients', true);
344+
345+
gradientsGroup.selectAll('linearGradient,radialGradient').remove();
346+
};
347+
348+
drawing.singlePointStyle = function(d, sel, trace, markerScale, lineScale, gd) {
349+
var marker = trace.marker;
290350

291-
singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine);
351+
singlePointStyle(d, sel, trace, markerScale, lineScale, marker, marker.line, gd);
292352

293353
};
294354

@@ -298,11 +358,12 @@ drawing.pointStyle = function(s, trace) {
298358
// allow array marker and marker line colors to be
299359
// scaled by given max and min to colorscales
300360
var marker = trace.marker;
301-
var markerScale = drawing.tryColorscale(marker, ''),
302-
lineScale = drawing.tryColorscale(marker, 'line');
361+
var markerScale = drawing.tryColorscale(marker, '');
362+
var lineScale = drawing.tryColorscale(marker, 'line');
363+
var gd = Lib.getPlotDiv(s.node());
303364

304365
s.each(function(d) {
305-
drawing.singlePointStyle(d, d3.select(this), trace, markerScale, lineScale);
366+
drawing.singlePointStyle(d, d3.select(this), trace, markerScale, lineScale, gd);
306367
});
307368
};
308369

src/plot_api/plot_api.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ Plotly.plot = function(gd, data, layout, config) {
153153
makePlotFramework(gd);
154154
}
155155

156+
// clear gradient defs on each .plot call, because we know we'll loop through all traces
157+
Drawing.initGradients(gd);
158+
156159
// save initial show spikes once per graph
157160
if(graphWasEmpty) Plotly.Axes.saveShowSpikeInitial(gd);
158161

src/traces/scatter/arrays_to_calcdata.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,11 @@ module.exports = function arraysToCalcdata(cd, trace) {
3737
Lib.mergeArray(markerLine.color, cd, 'mlc');
3838
Lib.mergeArray(markerLine.width, cd, 'mlw');
3939
}
40+
41+
var markerGradient = marker.gradient;
42+
if(markerGradient && markerGradient.type !== 'none') {
43+
Lib.mergeArray(markerGradient.type, cd, 'mgt');
44+
Lib.mergeArray(markerGradient.color, cd, 'mgc');
45+
}
4046
}
4147
};

src/traces/scatter/attributes.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,29 @@ module.exports = {
311311
}
312312
},
313313
colorAttributes('marker.line')
314-
)
314+
),
315+
gradient: {
316+
type: {
317+
valType: 'enumerated',
318+
values: ['radial', 'horizontal', 'vertical', 'none'],
319+
arrayOk: true,
320+
dflt: 'none',
321+
role: 'style',
322+
description: [
323+
'Sets the type of gradient used to fill the markers'
324+
].join(' ')
325+
},
326+
color: {
327+
valType: 'color',
328+
arrayOk: true,
329+
role: 'style',
330+
description: [
331+
'Sets the final color of the gradient fill:',
332+
'the center color for radial, the right for horizontal,',
333+
'or the bottom for vertical.',
334+
].join(' ')
335+
}
336+
}
315337
},
316338
colorAttributes('marker')
317339
),

src/traces/scatter/defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
5050
}
5151

5252
if(subTypes.hasMarkers(traceOut)) {
53-
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce);
53+
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
5454
}
5555

5656
if(subTypes.hasText(traceOut)) {

src/traces/scatter/marker_defaults.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@ var colorscaleDefaults = require('../../components/colorscale/defaults');
1515

1616
var subTypes = require('./subtypes');
1717

18-
18+
/*
19+
* opts: object of flags to control features not all marker users support
20+
* noLine: caller does not support marker lines
21+
* gradient: caller supports gradients
22+
*/
1923
module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts) {
2024
var isBubble = subTypes.isBubble(traceIn),
2125
lineColor = (traceIn.line || {}).color,
2226
defaultMLC;
2327

28+
opts = opts || {};
29+
2430
// marker.color inherit from line.color (even if line.color is an array)
2531
if(lineColor) defaultColor = lineColor;
2632

@@ -33,7 +39,7 @@ module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout
3339
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'});
3440
}
3541

36-
if(!(opts || {}).noLine) {
42+
if(!opts.noLine) {
3743
// if there's a line with a different color than the marker, use
3844
// that line color as the default marker line color
3945
// (except when it's an array)
@@ -57,4 +63,11 @@ module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout
5763
coerce('marker.sizemin');
5864
coerce('marker.sizemode');
5965
}
66+
67+
if(opts.gradient) {
68+
var gradientType = coerce('marker.gradient.type');
69+
if(gradientType !== 'none') {
70+
coerce('marker.gradient.color');
71+
}
72+
}
6073
};

src/traces/scatter/plot.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,11 +427,14 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
427427
.style('opacity', 1);
428428
}
429429

430+
var markerScale = showMarkers && Drawing.tryColorscale(trace.marker, '');
431+
var lineScale = showMarkers && Drawing.tryColorscale(trace.marker, 'line');
432+
430433
join.each(function(d) {
431434
var el = d3.select(this);
432435
var sel = transition(el);
433436
Drawing.translatePoint(d, sel, xa, ya);
434-
Drawing.singlePointStyle(d, sel, trace);
437+
Drawing.singlePointStyle(d, sel, trace, markerScale, lineScale, gd);
435438

436439
if(trace.customdata) {
437440
el.classed('plotly-customdata', d.data !== null && d.data !== undefined);

src/traces/scattercarpet/attributes.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ module.exports = {
107107
line: extendFlat({},
108108
{width: scatterMarkerLineAttrs.width},
109109
colorAttributes('marker'.line)
110-
)
110+
),
111+
gradient: scatterMarkerAttrs.gradient
111112
}, colorAttributes('marker'), {
112113
showscale: scatterMarkerAttrs.showscale,
113114
colorbar: colorbarAttrs

src/traces/scattercarpet/defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
6262
}
6363

6464
if(subTypes.hasMarkers(traceOut)) {
65-
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce);
65+
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
6666
}
6767

6868
if(subTypes.hasText(traceOut)) {

src/traces/scattergeo/attributes.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ module.exports = {
9696
line: extendFlat({},
9797
{width: scatterMarkerLineAttrs.width},
9898
colorAttributes('marker.line')
99-
)
99+
),
100+
gradient: scatterMarkerAttrs.gradient
100101
},
101102
colorAttributes('marker')
102103
),

src/traces/scattergeo/defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
4141
}
4242

4343
if(subTypes.hasMarkers(traceOut)) {
44-
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce);
44+
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
4545
}
4646

4747
if(subTypes.hasText(traceOut)) {

src/traces/scatterternary/attributes.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ module.exports = {
121121
line: extendFlat({},
122122
{width: scatterMarkerLineAttrs.width},
123123
colorAttributes('marker'.line)
124-
)
124+
),
125+
gradient: scatterMarkerAttrs.gradient
125126
}, colorAttributes('marker'), {
126127
showscale: scatterMarkerAttrs.showscale,
127128
colorbar: colorbarAttrs

src/traces/scatterternary/defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
7575
}
7676

7777
if(subTypes.hasMarkers(traceOut)) {
78-
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce);
78+
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
7979
}
8080

8181
if(subTypes.hasText(traceOut)) {

test/image/baselines/bubblechart.png

3.49 KB
Loading

test/image/baselines/geo_bg-color.png

2.34 KB
Loading
3.04 KB
Loading

test/image/mocks/bubblechart.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@
3232
0.3,
3333
0.2,
3434
0.1
35-
]
35+
],
36+
"gradient":{
37+
"type": "radial",
38+
"color": "#fff"
39+
}
3640
},
3741
"type":"scatter"
3842
},
@@ -58,7 +62,11 @@
5862
"square",
5963
"diamond",
6064
"cross"
61-
]
65+
],
66+
"gradient": {
67+
"type": "horizontal",
68+
"color": ["rgba(31, 119, 180, 0)", "rgba(0, 0, 0, 0)", "red", "#000"]
69+
}
6270
},
6371
"type":"scatter"
6472
},
@@ -91,6 +99,10 @@
9199
6,
92100
2
93101
]
102+
},
103+
"gradient": {
104+
"type": ["vertical", "horizontal", "none", "radial"],
105+
"color": ["#ff0", "#0ff", "", "#f0f"]
94106
}
95107
},
96108
"type":"scatter"

test/image/mocks/geo_bg-color.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
-30
1616
],
1717
"marker": {
18-
"size": 20
18+
"size": 20,
19+
"gradient": {
20+
"type": "radial",
21+
"color": "rgba(31,119,180,0.5)"
22+
}
1923
},
2024
"uid": "7e4393"
2125
},
@@ -33,7 +37,11 @@
3337
],
3438
"geo": "geo2",
3539
"marker": {
36-
"size": 20
40+
"size": 20,
41+
"gradient": {
42+
"type": "vertical",
43+
"color": ["#000", "#fff", "#888"]
44+
}
3745
},
3846
"uid": "9d01ba"
3947
}

0 commit comments

Comments
 (0)