Skip to content

Commit 4107ce3

Browse files
authored
Merge pull request #3803 from plotly/coloraxes-finalist
Shared color axes
2 parents 5c01f53 + 29a92a6 commit 4107ce3

Some content is hidden

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

70 files changed

+2584
-434
lines changed

src/components/colorbar/draw.js

+80-40
Original file line numberDiff line numberDiff line change
@@ -72,66 +72,102 @@ function draw(gd) {
7272
}
7373

7474
function makeColorBarData(gd) {
75+
var fullLayout = gd._fullLayout;
7576
var calcdata = gd.calcdata;
7677
var out = [];
7778

79+
// single out item
80+
var opts;
81+
// colorbar attr parent container
82+
var cont;
83+
// trace attr container
84+
var trace;
85+
// colorbar options
86+
var cbOpt;
87+
88+
function initOpts(opts) {
89+
return extendFlat(opts, {
90+
// fillcolor can be a d3 scale, domain is z values, range is colors
91+
// or leave it out for no fill,
92+
// or set to a string constant for single-color fill
93+
_fillcolor: null,
94+
// line.color has the same options as fillcolor
95+
_line: {color: null, width: null, dash: null},
96+
// levels of lines to draw.
97+
// note that this DOES NOT determine the extent of the bar
98+
// that's given by the domain of fillcolor
99+
// (or line.color if no fillcolor domain)
100+
_levels: {start: null, end: null, size: null},
101+
// separate fill levels (for example, heatmap coloring of a
102+
// contour map) if this is omitted, fillcolors will be
103+
// evaluated halfway between levels
104+
_filllevels: null,
105+
// for continuous colorscales: fill with a gradient instead of explicit levels
106+
// value should be the colorscale [[0, c0], [v1, c1], ..., [1, cEnd]]
107+
_fillgradient: null,
108+
// when using a gradient, we need the data range specified separately
109+
_zrange: null
110+
});
111+
}
112+
113+
function calcOpts() {
114+
if(typeof cbOpt.calc === 'function') {
115+
cbOpt.calc(gd, trace, opts);
116+
} else {
117+
opts._fillgradient = cont.reversescale ?
118+
flipScale(cont.colorscale) :
119+
cont.colorscale;
120+
opts._zrange = [cont[cbOpt.min], cont[cbOpt.max]];
121+
}
122+
}
123+
78124
for(var i = 0; i < calcdata.length; i++) {
79125
var cd = calcdata[i];
80-
var trace = cd[0].trace;
126+
trace = cd[0].trace;
81127
var moduleOpts = trace._module.colorbar;
82128

83129
if(trace.visible === true && moduleOpts) {
84-
var cbOpts = Array.isArray(moduleOpts) ? moduleOpts : [moduleOpts];
130+
var allowsMultiplotCbs = Array.isArray(moduleOpts);
131+
var cbOpts = allowsMultiplotCbs ? moduleOpts : [moduleOpts];
85132

86133
for(var j = 0; j < cbOpts.length; j++) {
87-
var cbOpt = cbOpts[j];
134+
cbOpt = cbOpts[j];
88135
var contName = cbOpt.container;
89-
var cont = contName ? trace[contName] : trace;
136+
cont = contName ? trace[contName] : trace;
90137

91138
if(cont && cont.showscale) {
92-
var opts = cont.colorbar;
93-
opts._id = 'cb' + trace.uid;
139+
opts = initOpts(cont.colorbar);
140+
opts._id = 'cb' + trace.uid + (allowsMultiplotCbs && contName ? '-' + contName : '');
94141
opts._traceIndex = trace.index;
95142
opts._propPrefix = (contName ? contName + '.' : '') + 'colorbar.';
96-
97-
extendFlat(opts, {
98-
// fillcolor can be a d3 scale, domain is z values, range is colors
99-
// or leave it out for no fill,
100-
// or set to a string constant for single-color fill
101-
_fillcolor: null,
102-
// line.color has the same options as fillcolor
103-
_line: {color: null, width: null, dash: null},
104-
// levels of lines to draw.
105-
// note that this DOES NOT determine the extent of the bar
106-
// that's given by the domain of fillcolor
107-
// (or line.color if no fillcolor domain)
108-
_levels: {start: null, end: null, size: null},
109-
// separate fill levels (for example, heatmap coloring of a
110-
// contour map) if this is omitted, fillcolors will be
111-
// evaluated halfway between levels
112-
_filllevels: null,
113-
// for continuous colorscales: fill with a gradient instead of explicit levels
114-
// value should be the colorscale [[0, c0], [v1, c1], ..., [1, cEnd]]
115-
_fillgradient: null,
116-
// when using a gradient, we need the data range specified separately
117-
_zrange: null
118-
});
119-
120-
if(typeof cbOpt.calc === 'function') {
121-
cbOpt.calc(gd, cd, opts);
122-
} else {
123-
opts._fillgradient = cont.reversescale ?
124-
flipScale(cont.colorscale) :
125-
cont.colorscale;
126-
opts._zrange = [cont[cbOpt.min], cont[cbOpt.max]];
127-
}
128-
143+
calcOpts();
129144
out.push(opts);
130145
}
131146
}
132147
}
133148
}
134149

150+
for(var k in fullLayout._colorAxes) {
151+
cont = fullLayout[k];
152+
153+
if(cont.showscale) {
154+
var colorAxOpts = fullLayout._colorAxes[k];
155+
156+
opts = initOpts(cont.colorbar);
157+
opts._id = 'cb' + k;
158+
opts._propPrefix = k + '.colorbar.';
159+
160+
cbOpt = {min: 'cmin', max: 'cmax'};
161+
if(colorAxOpts[0] !== 'heatmap') {
162+
trace = colorAxOpts[1];
163+
cbOpt.calc = trace._module.colorbar.calc;
164+
}
165+
166+
calcOpts();
167+
out.push(opts);
168+
}
169+
}
170+
135171
return out;
136172
}
137173

@@ -561,7 +597,11 @@ function makeEditable(g, opts, gd) {
561597
var update = {};
562598
update[opts._propPrefix + 'x'] = xf;
563599
update[opts._propPrefix + 'y'] = yf;
564-
Registry.call('_guiRestyle', gd, update, opts._traceIndex);
600+
if(opts._traceIndex !== undefined) {
601+
Registry.call('_guiRestyle', gd, update, opts._traceIndex);
602+
} else {
603+
Registry.call('_guiRelayout', gd, update);
604+
}
565605
}
566606
}
567607
});

src/components/colorscale/attributes.js

+22
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
'use strict';
1010

11+
var colorbarAttrs = require('../colorbar/attributes');
12+
var counterRegex = require('../../lib/regex').counter;
13+
1114
var palettes = require('./scales.js').scales;
1215
var paletteStr = Object.keys(palettes);
1316

@@ -240,6 +243,25 @@ module.exports = function colorScaleAttrs(context, opts) {
240243
effectDesc
241244
].join('')
242245
};
246+
247+
attrs.colorbar = colorbarAttrs;
248+
}
249+
250+
if(!opts.noColorAxis) {
251+
attrs.coloraxis = {
252+
valType: 'subplotid',
253+
role: 'info',
254+
regex: counterRegex('coloraxis'),
255+
dflt: null,
256+
editType: 'calc',
257+
description: [
258+
'Sets a reference to a shared color axis.',
259+
'References to these shared color axes are *coloraxis*, *coloraxis2*, *coloraxis3*, etc.',
260+
'Settings for these shared color axes are set in the layout, under',
261+
'`layout.coloraxis`, `layout.coloraxis2`, etc.',
262+
'Note that multiple color scales can be linked to the same color axis.'
263+
].join(' ')
264+
};
243265
}
244266

245267
return attrs;

src/components/colorscale/calc.js

+33-20
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,50 @@
88

99
'use strict';
1010

11+
var isNumeric = require('fast-isnumeric');
12+
1113
var Lib = require('../../lib');
14+
var extractOpts = require('./helpers').extractOpts;
1215

1316
module.exports = function calc(gd, trace, opts) {
1417
var fullLayout = gd._fullLayout;
1518
var vals = opts.vals;
1619
var containerStr = opts.containerStr;
17-
var cLetter = opts.cLetter;
1820

1921
var container = containerStr ?
2022
Lib.nestedProperty(trace, containerStr).get() :
2123
trace;
2224

23-
var autoAttr = cLetter + 'auto';
24-
var minAttr = cLetter + 'min';
25-
var maxAttr = cLetter + 'max';
26-
var midAttr = cLetter + 'mid';
27-
var auto = container[autoAttr];
28-
var min = container[minAttr];
29-
var max = container[maxAttr];
30-
var mid = container[midAttr];
31-
var scl = container.colorscale;
25+
var cOpts = extractOpts(container);
26+
var auto = cOpts.auto !== false;
27+
var min = cOpts.min;
28+
var max = cOpts.max;
29+
var mid = cOpts.mid;
30+
31+
var minVal = function() { return Lib.aggNums(Math.min, null, vals); };
32+
var maxVal = function() { return Lib.aggNums(Math.max, null, vals); };
3233

33-
if(auto !== false || min === undefined) {
34-
min = Lib.aggNums(Math.min, null, vals);
34+
if(min === undefined) {
35+
min = minVal();
36+
} else if(auto) {
37+
if(container._colorAx && isNumeric(min)) {
38+
min = Math.min(min, minVal());
39+
} else {
40+
min = minVal();
41+
}
3542
}
3643

37-
if(auto !== false || max === undefined) {
38-
max = Lib.aggNums(Math.max, null, vals);
44+
if(max === undefined) {
45+
max = maxVal();
46+
} else if(auto) {
47+
if(container._colorAx && isNumeric(max)) {
48+
max = Math.max(max, maxVal());
49+
} else {
50+
max = maxVal();
51+
}
3952
}
4053

41-
if(auto !== false && mid !== undefined) {
54+
if(auto && mid !== undefined) {
4255
if(max - mid > mid - min) {
4356
min = mid - (max - mid);
4457
} else if(max - mid < mid - min) {
@@ -51,14 +64,14 @@ module.exports = function calc(gd, trace, opts) {
5164
max += 0.5;
5265
}
5366

54-
container['_' + minAttr] = container[minAttr] = min;
55-
container['_' + maxAttr] = container[maxAttr] = max;
67+
cOpts._sync('min', min);
68+
cOpts._sync('max', max);
5669

57-
if(container.autocolorscale) {
70+
if(cOpts.autocolorscale) {
71+
var scl;
5872
if(min * max < 0) scl = fullLayout.colorscale.diverging;
5973
else if(min >= 0) scl = fullLayout.colorscale.sequential;
6074
else scl = fullLayout.colorscale.sequentialminus;
61-
62-
container._colorscale = container.colorscale = scl;
75+
cOpts._sync('colorscale', scl);
6376
}
6477
};

src/components/colorscale/cross_trace_defaults.js

+30-31
Original file line numberDiff line numberDiff line change
@@ -10,67 +10,66 @@
1010

1111
var Lib = require('../../lib');
1212
var hasColorscale = require('./helpers').hasColorscale;
13+
var extractOpts = require('./helpers').extractOpts;
1314

14-
module.exports = function crossTraceDefaults(fullData) {
15+
module.exports = function crossTraceDefaults(fullData, fullLayout) {
1516
function replace(cont, k) {
1617
var val = cont['_' + k];
1718
if(val !== undefined) {
1819
cont[k] = val;
1920
}
2021
}
2122

22-
function relinkColorAtts(trace, cAttrs) {
23-
var cont = cAttrs.container ?
24-
Lib.nestedProperty(trace, cAttrs.container).get() :
25-
trace;
23+
function relinkColorAtts(outerCont, cbOpt) {
24+
var cont = cbOpt.container ?
25+
Lib.nestedProperty(outerCont, cbOpt.container).get() :
26+
outerCont;
2627

2728
if(cont) {
28-
var isAuto = cont.zauto || cont.cauto;
29-
var minAttr = cAttrs.min;
30-
var maxAttr = cAttrs.max;
29+
if(cont.coloraxis) {
30+
// stash ref to color axis
31+
cont._colorAx = fullLayout[cont.coloraxis];
32+
} else {
33+
var cOpts = extractOpts(cont);
34+
var isAuto = cOpts.auto;
3135

32-
if(isAuto || cont[minAttr] === undefined) {
33-
replace(cont, minAttr);
34-
}
35-
if(isAuto || cont[maxAttr] === undefined) {
36-
replace(cont, maxAttr);
37-
}
38-
if(cont.autocolorscale) {
39-
replace(cont, 'colorscale');
36+
if(isAuto || cOpts.min === undefined) {
37+
replace(cont, cbOpt.min);
38+
}
39+
if(isAuto || cOpts.max === undefined) {
40+
replace(cont, cbOpt.max);
41+
}
42+
if(cOpts.autocolorscale) {
43+
replace(cont, 'colorscale');
44+
}
4045
}
4146
}
4247
}
4348

4449
for(var i = 0; i < fullData.length; i++) {
4550
var trace = fullData[i];
46-
var colorbar = trace._module.colorbar;
51+
var cbOpts = trace._module.colorbar;
4752

48-
if(colorbar) {
49-
if(Array.isArray(colorbar)) {
50-
for(var j = 0; j < colorbar.length; j++) {
51-
relinkColorAtts(trace, colorbar[j]);
53+
if(cbOpts) {
54+
if(Array.isArray(cbOpts)) {
55+
for(var j = 0; j < cbOpts.length; j++) {
56+
relinkColorAtts(trace, cbOpts[j]);
5257
}
5358
} else {
54-
relinkColorAtts(trace, colorbar);
59+
relinkColorAtts(trace, cbOpts);
5560
}
5661
}
5762

58-
// TODO could generalize _module.colorscale and use it here?
59-
6063
if(hasColorscale(trace, 'marker.line')) {
6164
relinkColorAtts(trace, {
6265
container: 'marker.line',
6366
min: 'cmin',
6467
max: 'cmax'
6568
});
6669
}
70+
}
6771

68-
if(hasColorscale(trace, 'line')) {
69-
relinkColorAtts(trace, {
70-
container: 'line',
71-
min: 'cmin',
72-
max: 'cmax'
73-
});
74-
}
72+
for(var k in fullLayout._colorAxes) {
73+
relinkColorAtts(fullLayout[k], {min: 'cmin', max: 'cmax'});
7574
}
7675
};

0 commit comments

Comments
 (0)