Skip to content

Commit 2105fd4

Browse files
authored
Merge pull request #1620 from plotly/gradient-fill
scatter marker color gradients
2 parents e54d009 + 21650c7 commit 2105fd4

File tree

98 files changed

+7834
-7796
lines changed

Some content is hidden

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

98 files changed

+7834
-7796
lines changed

Diff for: src/components/drawing/index.js

+92-15
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')) {
@@ -237,6 +238,8 @@ function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerL
237238
});
238239
}
239240

241+
var perPointGradient = false;
242+
240243
// 'so' is suspected outliers, for box plots
241244
var fillColor,
242245
lineColor,
@@ -256,8 +259,12 @@ function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerL
256259
else if(Array.isArray(markerLine.color)) lineColor = Color.defaultLine;
257260
else lineColor = markerLine.color;
258261

262+
if(Array.isArray(marker.color)) {
263+
fillColor = Color.defaultLine;
264+
perPointGradient = true;
265+
}
266+
259267
if('mc' in d) fillColor = d.mcc = markerScale(d.mc);
260-
else if(Array.isArray(marker.color)) fillColor = Color.defaultLine;
261268
else fillColor = marker.color || 'rgba(0,0,0,0)';
262269
}
263270

@@ -271,24 +278,93 @@ function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerL
271278
});
272279
}
273280
else {
274-
sel.style('stroke-width', lineWidth + 'px')
275-
.call(Color.fill, fillColor);
281+
sel.style('stroke-width', lineWidth + 'px');
282+
283+
var markerGradient = marker.gradient;
284+
285+
var gradientType = d.mgt;
286+
if(gradientType) perPointGradient = true;
287+
else gradientType = markerGradient && markerGradient.type;
288+
289+
if(gradientType && gradientType !== 'none') {
290+
var gradientColor = d.mgc;
291+
if(gradientColor) perPointGradient = true;
292+
else gradientColor = markerGradient.color;
293+
294+
var gradientID = 'g' + gd._fullLayout._uid + '-' + trace.uid;
295+
if(perPointGradient) gradientID += '-' + d.i;
296+
297+
sel.call(drawing.gradient, gd, gradientID, gradientType, fillColor, gradientColor);
298+
}
299+
else {
300+
sel.call(Color.fill, fillColor);
301+
}
302+
276303
if(lineWidth) {
277304
sel.call(Color.stroke, lineColor);
278305
}
279306
}
280307
}
281308

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

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');
312+
drawing.gradient = function(sel, gd, gradientID, type, color1, color2) {
313+
var gradient = gd._fullLayout._defs.select('.gradients')
314+
.selectAll('#' + gradientID)
315+
.data([type + color1 + color2], Lib.identity);
316+
317+
gradient.exit().remove();
318+
319+
gradient.enter()
320+
.append(type === 'radial' ? 'radialGradient' : 'linearGradient')
321+
.each(function() {
322+
var el = d3.select(this);
323+
if(type === 'horizontal') el.attr(HORZGRADIENT);
324+
else if(type === 'vertical') el.attr(VERTGRADIENT);
325+
326+
el.attr('id', gradientID);
327+
328+
var tc1 = tinycolor(color1);
329+
var tc2 = tinycolor(color2);
330+
331+
el.append('stop').attr({
332+
offset: '0%',
333+
'stop-color': Color.tinyRGB(tc2),
334+
'stop-opacity': tc2.getAlpha()
335+
});
336+
337+
el.append('stop').attr({
338+
offset: '100%',
339+
'stop-color': Color.tinyRGB(tc1),
340+
'stop-opacity': tc1.getAlpha()
341+
});
342+
});
343+
344+
sel.style({
345+
fill: 'url(#' + gradientID + ')',
346+
'fill-opacity': null
347+
});
348+
};
349+
350+
/*
351+
* Make the gradients container and clear out any previous gradients.
352+
* We never collect all the gradients we need in one place,
353+
* so we can't ever remove gradients that have stopped being useful,
354+
* except all at once before a full redraw.
355+
* The upside of this is arbitrary points can share gradient defs
356+
*/
357+
drawing.initGradients = function(gd) {
358+
var gradientsGroup = gd._fullLayout._defs.selectAll('.gradients').data([0]);
359+
gradientsGroup.enter().append('g').classed('gradients', true);
360+
361+
gradientsGroup.selectAll('linearGradient,radialGradient').remove();
362+
};
363+
364+
drawing.singlePointStyle = function(d, sel, trace, markerScale, lineScale, gd) {
365+
var marker = trace.marker;
290366

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

293369
};
294370

@@ -298,11 +374,12 @@ drawing.pointStyle = function(s, trace) {
298374
// allow array marker and marker line colors to be
299375
// scaled by given max and min to colorscales
300376
var marker = trace.marker;
301-
var markerScale = drawing.tryColorscale(marker, ''),
302-
lineScale = drawing.tryColorscale(marker, 'line');
377+
var markerScale = drawing.tryColorscale(marker, '');
378+
var lineScale = drawing.tryColorscale(marker, 'line');
379+
var gd = Lib.getPlotDiv(s.node());
303380

304381
s.each(function(d) {
305-
drawing.singlePointStyle(d, d3.select(this), trace, markerScale, lineScale);
382+
drawing.singlePointStyle(d, d3.select(this), trace, markerScale, lineScale, gd);
306383
});
307384
};
308385

Diff for: src/plot_api/plot_api.js

+3
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

Diff for: src/traces/scatter/arrays_to_calcdata.js

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ var Lib = require('../../lib');
1515
// arrayOk attributes, merge them into calcdata array
1616
module.exports = function arraysToCalcdata(cd, trace) {
1717

18+
// so each point knows which index it originally came from
19+
for(var i = 0; i < cd.length; i++) cd[i].i = i;
20+
1821
Lib.mergeArray(trace.text, cd, 'tx');
1922
Lib.mergeArray(trace.hovertext, cd, 'htx');
2023
Lib.mergeArray(trace.customdata, cd, 'data');
@@ -37,5 +40,11 @@ module.exports = function arraysToCalcdata(cd, trace) {
3740
Lib.mergeArray(markerLine.color, cd, 'mlc');
3841
Lib.mergeArray(markerLine.width, cd, 'mlw');
3942
}
43+
44+
var markerGradient = marker.gradient;
45+
if(markerGradient && markerGradient.type !== 'none') {
46+
Lib.mergeArray(markerGradient.type, cd, 'mgt');
47+
Lib.mergeArray(markerGradient.color, cd, 'mgc');
48+
}
4049
}
4150
};

Diff for: src/traces/scatter/attributes.js

+23-1
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
),

Diff for: src/traces/scatter/defaults.js

+1-1
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)) {

Diff for: src/traces/scatter/marker_defaults.js

+15-2
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
};

Diff for: src/traces/scatter/plot.js

+4-1
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);

Diff for: src/traces/scattercarpet/attributes.js

+2-1
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

Diff for: src/traces/scattercarpet/defaults.js

+1-1
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)) {

Diff for: src/traces/scattercarpet/plot.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ module.exports = function plot(gd, plotinfoproxy, data) {
2424
plot: plotinfoproxy.plot
2525
};
2626

27-
scatterPlot(plotinfo.graphDiv, plotinfo, data);
27+
scatterPlot(gd, plotinfo, data);
2828

2929
for(i = 0; i < data.length; i++) {
3030
trace = data[i][0].trace;

Diff for: src/traces/scattergeo/attributes.js

+2-1
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
),

Diff for: src/traces/scattergeo/defaults.js

+1-1
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)) {

Diff for: src/traces/scatterternary/attributes.js

+2-1
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

Diff for: src/traces/scatterternary/defaults.js

+1-1
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)) {

Diff for: test/image/baselines/bubblechart.png

3.49 KB
Loading

Diff for: test/image/baselines/geo_bg-color.png

2.34 KB
Loading

Diff for: test/image/baselines/scattercarpet.png

1.75 KB
Loading

Diff for: test/image/baselines/ternary_multiple.png

3.04 KB
Loading

Diff for: test/image/mocks/autorange-tozero-rangemode.json

+2-4
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@
3636
"name": "Revenue",
3737
"xaxis": "x",
3838
"yaxis": "y",
39-
"type": "bar",
40-
"uid": "33011d"
39+
"type": "bar"
4140
},
4241
{
4342
"x": [
@@ -76,8 +75,7 @@
7675
"name": "Take Rate",
7776
"xaxis": "x",
7877
"yaxis": "y2",
79-
"type": "scatter",
80-
"uid": "31ba98"
78+
"type": "scatter"
8179
}
8280
],
8381
"layout": {

0 commit comments

Comments
 (0)