Skip to content

Commit

Permalink
Merge pull request #3803 from plotly/coloraxes-finalist
Browse files Browse the repository at this point in the history
Shared color axes
  • Loading branch information
etpinard authored Apr 29, 2019
2 parents 5c01f53 + 29a92a6 commit 4107ce3
Show file tree
Hide file tree
Showing 70 changed files with 2,584 additions and 434 deletions.
120 changes: 80 additions & 40 deletions src/components/colorbar/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,66 +72,102 @@ function draw(gd) {
}

function makeColorBarData(gd) {
var fullLayout = gd._fullLayout;
var calcdata = gd.calcdata;
var out = [];

// single out item
var opts;
// colorbar attr parent container
var cont;
// trace attr container
var trace;
// colorbar options
var cbOpt;

function initOpts(opts) {
return extendFlat(opts, {
// fillcolor can be a d3 scale, domain is z values, range is colors
// or leave it out for no fill,
// or set to a string constant for single-color fill
_fillcolor: null,
// line.color has the same options as fillcolor
_line: {color: null, width: null, dash: null},
// levels of lines to draw.
// note that this DOES NOT determine the extent of the bar
// that's given by the domain of fillcolor
// (or line.color if no fillcolor domain)
_levels: {start: null, end: null, size: null},
// separate fill levels (for example, heatmap coloring of a
// contour map) if this is omitted, fillcolors will be
// evaluated halfway between levels
_filllevels: null,
// for continuous colorscales: fill with a gradient instead of explicit levels
// value should be the colorscale [[0, c0], [v1, c1], ..., [1, cEnd]]
_fillgradient: null,
// when using a gradient, we need the data range specified separately
_zrange: null
});
}

function calcOpts() {
if(typeof cbOpt.calc === 'function') {
cbOpt.calc(gd, trace, opts);
} else {
opts._fillgradient = cont.reversescale ?
flipScale(cont.colorscale) :
cont.colorscale;
opts._zrange = [cont[cbOpt.min], cont[cbOpt.max]];
}
}

for(var i = 0; i < calcdata.length; i++) {
var cd = calcdata[i];
var trace = cd[0].trace;
trace = cd[0].trace;
var moduleOpts = trace._module.colorbar;

if(trace.visible === true && moduleOpts) {
var cbOpts = Array.isArray(moduleOpts) ? moduleOpts : [moduleOpts];
var allowsMultiplotCbs = Array.isArray(moduleOpts);
var cbOpts = allowsMultiplotCbs ? moduleOpts : [moduleOpts];

for(var j = 0; j < cbOpts.length; j++) {
var cbOpt = cbOpts[j];
cbOpt = cbOpts[j];
var contName = cbOpt.container;
var cont = contName ? trace[contName] : trace;
cont = contName ? trace[contName] : trace;

if(cont && cont.showscale) {
var opts = cont.colorbar;
opts._id = 'cb' + trace.uid;
opts = initOpts(cont.colorbar);
opts._id = 'cb' + trace.uid + (allowsMultiplotCbs && contName ? '-' + contName : '');
opts._traceIndex = trace.index;
opts._propPrefix = (contName ? contName + '.' : '') + 'colorbar.';

extendFlat(opts, {
// fillcolor can be a d3 scale, domain is z values, range is colors
// or leave it out for no fill,
// or set to a string constant for single-color fill
_fillcolor: null,
// line.color has the same options as fillcolor
_line: {color: null, width: null, dash: null},
// levels of lines to draw.
// note that this DOES NOT determine the extent of the bar
// that's given by the domain of fillcolor
// (or line.color if no fillcolor domain)
_levels: {start: null, end: null, size: null},
// separate fill levels (for example, heatmap coloring of a
// contour map) if this is omitted, fillcolors will be
// evaluated halfway between levels
_filllevels: null,
// for continuous colorscales: fill with a gradient instead of explicit levels
// value should be the colorscale [[0, c0], [v1, c1], ..., [1, cEnd]]
_fillgradient: null,
// when using a gradient, we need the data range specified separately
_zrange: null
});

if(typeof cbOpt.calc === 'function') {
cbOpt.calc(gd, cd, opts);
} else {
opts._fillgradient = cont.reversescale ?
flipScale(cont.colorscale) :
cont.colorscale;
opts._zrange = [cont[cbOpt.min], cont[cbOpt.max]];
}

calcOpts();
out.push(opts);
}
}
}
}

for(var k in fullLayout._colorAxes) {
cont = fullLayout[k];

if(cont.showscale) {
var colorAxOpts = fullLayout._colorAxes[k];

opts = initOpts(cont.colorbar);
opts._id = 'cb' + k;
opts._propPrefix = k + '.colorbar.';

cbOpt = {min: 'cmin', max: 'cmax'};
if(colorAxOpts[0] !== 'heatmap') {
trace = colorAxOpts[1];
cbOpt.calc = trace._module.colorbar.calc;
}

calcOpts();
out.push(opts);
}
}

return out;
}

Expand Down Expand Up @@ -561,7 +597,11 @@ function makeEditable(g, opts, gd) {
var update = {};
update[opts._propPrefix + 'x'] = xf;
update[opts._propPrefix + 'y'] = yf;
Registry.call('_guiRestyle', gd, update, opts._traceIndex);
if(opts._traceIndex !== undefined) {
Registry.call('_guiRestyle', gd, update, opts._traceIndex);
} else {
Registry.call('_guiRelayout', gd, update);
}
}
}
});
Expand Down
22 changes: 22 additions & 0 deletions src/components/colorscale/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

'use strict';

var colorbarAttrs = require('../colorbar/attributes');
var counterRegex = require('../../lib/regex').counter;

var palettes = require('./scales.js').scales;
var paletteStr = Object.keys(palettes);

Expand Down Expand Up @@ -240,6 +243,25 @@ module.exports = function colorScaleAttrs(context, opts) {
effectDesc
].join('')
};

attrs.colorbar = colorbarAttrs;
}

if(!opts.noColorAxis) {
attrs.coloraxis = {
valType: 'subplotid',
role: 'info',
regex: counterRegex('coloraxis'),
dflt: null,
editType: 'calc',
description: [
'Sets a reference to a shared color axis.',
'References to these shared color axes are *coloraxis*, *coloraxis2*, *coloraxis3*, etc.',
'Settings for these shared color axes are set in the layout, under',
'`layout.coloraxis`, `layout.coloraxis2`, etc.',
'Note that multiple color scales can be linked to the same color axis.'
].join(' ')
};
}

return attrs;
Expand Down
53 changes: 33 additions & 20 deletions src/components/colorscale/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,50 @@

'use strict';

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

var Lib = require('../../lib');
var extractOpts = require('./helpers').extractOpts;

module.exports = function calc(gd, trace, opts) {
var fullLayout = gd._fullLayout;
var vals = opts.vals;
var containerStr = opts.containerStr;
var cLetter = opts.cLetter;

var container = containerStr ?
Lib.nestedProperty(trace, containerStr).get() :
trace;

var autoAttr = cLetter + 'auto';
var minAttr = cLetter + 'min';
var maxAttr = cLetter + 'max';
var midAttr = cLetter + 'mid';
var auto = container[autoAttr];
var min = container[minAttr];
var max = container[maxAttr];
var mid = container[midAttr];
var scl = container.colorscale;
var cOpts = extractOpts(container);
var auto = cOpts.auto !== false;
var min = cOpts.min;
var max = cOpts.max;
var mid = cOpts.mid;

var minVal = function() { return Lib.aggNums(Math.min, null, vals); };
var maxVal = function() { return Lib.aggNums(Math.max, null, vals); };

if(auto !== false || min === undefined) {
min = Lib.aggNums(Math.min, null, vals);
if(min === undefined) {
min = minVal();
} else if(auto) {
if(container._colorAx && isNumeric(min)) {
min = Math.min(min, minVal());
} else {
min = minVal();
}
}

if(auto !== false || max === undefined) {
max = Lib.aggNums(Math.max, null, vals);
if(max === undefined) {
max = maxVal();
} else if(auto) {
if(container._colorAx && isNumeric(max)) {
max = Math.max(max, maxVal());
} else {
max = maxVal();
}
}

if(auto !== false && mid !== undefined) {
if(auto && mid !== undefined) {
if(max - mid > mid - min) {
min = mid - (max - mid);
} else if(max - mid < mid - min) {
Expand All @@ -51,14 +64,14 @@ module.exports = function calc(gd, trace, opts) {
max += 0.5;
}

container['_' + minAttr] = container[minAttr] = min;
container['_' + maxAttr] = container[maxAttr] = max;
cOpts._sync('min', min);
cOpts._sync('max', max);

if(container.autocolorscale) {
if(cOpts.autocolorscale) {
var scl;
if(min * max < 0) scl = fullLayout.colorscale.diverging;
else if(min >= 0) scl = fullLayout.colorscale.sequential;
else scl = fullLayout.colorscale.sequentialminus;

container._colorscale = container.colorscale = scl;
cOpts._sync('colorscale', scl);
}
};
61 changes: 30 additions & 31 deletions src/components/colorscale/cross_trace_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,67 +10,66 @@

var Lib = require('../../lib');
var hasColorscale = require('./helpers').hasColorscale;
var extractOpts = require('./helpers').extractOpts;

module.exports = function crossTraceDefaults(fullData) {
module.exports = function crossTraceDefaults(fullData, fullLayout) {
function replace(cont, k) {
var val = cont['_' + k];
if(val !== undefined) {
cont[k] = val;
}
}

function relinkColorAtts(trace, cAttrs) {
var cont = cAttrs.container ?
Lib.nestedProperty(trace, cAttrs.container).get() :
trace;
function relinkColorAtts(outerCont, cbOpt) {
var cont = cbOpt.container ?
Lib.nestedProperty(outerCont, cbOpt.container).get() :
outerCont;

if(cont) {
var isAuto = cont.zauto || cont.cauto;
var minAttr = cAttrs.min;
var maxAttr = cAttrs.max;
if(cont.coloraxis) {
// stash ref to color axis
cont._colorAx = fullLayout[cont.coloraxis];
} else {
var cOpts = extractOpts(cont);
var isAuto = cOpts.auto;

if(isAuto || cont[minAttr] === undefined) {
replace(cont, minAttr);
}
if(isAuto || cont[maxAttr] === undefined) {
replace(cont, maxAttr);
}
if(cont.autocolorscale) {
replace(cont, 'colorscale');
if(isAuto || cOpts.min === undefined) {
replace(cont, cbOpt.min);
}
if(isAuto || cOpts.max === undefined) {
replace(cont, cbOpt.max);
}
if(cOpts.autocolorscale) {
replace(cont, 'colorscale');
}
}
}
}

for(var i = 0; i < fullData.length; i++) {
var trace = fullData[i];
var colorbar = trace._module.colorbar;
var cbOpts = trace._module.colorbar;

if(colorbar) {
if(Array.isArray(colorbar)) {
for(var j = 0; j < colorbar.length; j++) {
relinkColorAtts(trace, colorbar[j]);
if(cbOpts) {
if(Array.isArray(cbOpts)) {
for(var j = 0; j < cbOpts.length; j++) {
relinkColorAtts(trace, cbOpts[j]);
}
} else {
relinkColorAtts(trace, colorbar);
relinkColorAtts(trace, cbOpts);
}
}

// TODO could generalize _module.colorscale and use it here?

if(hasColorscale(trace, 'marker.line')) {
relinkColorAtts(trace, {
container: 'marker.line',
min: 'cmin',
max: 'cmax'
});
}
}

if(hasColorscale(trace, 'line')) {
relinkColorAtts(trace, {
container: 'line',
min: 'cmin',
max: 'cmax'
});
}
for(var k in fullLayout._colorAxes) {
relinkColorAtts(fullLayout[k], {min: 'cmin', max: 'cmax'});
}
};
Loading

0 comments on commit 4107ce3

Please sign in to comment.