Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shared color axes #3803

Merged
merged 14 commits into from
Apr 29, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 69 additions & 39 deletions src/components/colorbar/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,66 +72,96 @@ 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 : '');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commits essentially resolves #3555

I didn't add support for marker.line. colorbars (yet) - let me know you think that's a good idea.

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;

cbOpt = {min: 'cmin', max: 'cmax'};
calcOpts();
out.push(opts);
}
}

return out;
}

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)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how we get all traces sharing the same coloraxis to contribute to the auto cmin / cmax computations.

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