Skip to content

Use new multistop gradients for continuous colorbars #2910

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

Merged
merged 3 commits into from
Aug 16, 2018
Merged
Changes from all 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
16 changes: 2 additions & 14 deletions src/components/colorbar/connect.js
Original file line number Diff line number Diff line change
@@ -9,7 +9,6 @@

'use strict';

var Colorscale = require('../colorscale');
var drawColorbar = require('./draw');

/**
@@ -48,20 +47,9 @@ module.exports = function connectColorbar(gd, cd, moduleOpts) {
gd._fullLayout._infolayer.selectAll('.' + cbId).remove();
if(!container || !container.showscale) return;

var zmin = container[moduleOpts.min];
var zmax = container[moduleOpts.max];

var cb = cd[0].t.cb = drawColorbar(gd, cbId);
var sclFunc = Colorscale.makeColorScaleFunc(
Colorscale.extractScale(
container.colorscale,
zmin,
zmax
),
{ noNumericCheck: true }
);

cb.fillcolor(sclFunc)
.filllevels({start: zmin, end: zmax, size: (zmax - zmin) / 254})
cb.fillgradient(container.colorscale)
.zrange([container[moduleOpts.min], container[moduleOpts.max]])
.options(container.colorbar)();
};
55 changes: 36 additions & 19 deletions src/components/colorbar/draw.js
Original file line number Diff line number Diff line change
@@ -39,9 +39,9 @@ module.exports = function draw(gd, id) {
// opts: options object, containing everything from attributes
// plus a few others that are the equivalent of the colorbar "data"
var opts = {};
Object.keys(attributes).forEach(function(k) {
for(var k in attributes) {
opts[k] = null;
});
}
// 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
@@ -57,17 +57,23 @@ module.exports = function draw(gd, id) {
// contour map) if this is omitted, fillcolors will be
// evaluated halfway between levels
opts.filllevels = null;
// for continuous colorscales: fill with a gradient instead of explicit levels
// value should be the colorscale [[0, c0], [v1, c1], ..., [1, cEnd]]
opts.fillgradient = null;
// when using a gradient, we need the data range specified separately
opts.zrange = null;

function component() {
var fullLayout = gd._fullLayout,
gs = fullLayout._size;
if((typeof opts.fillcolor !== 'function') &&
(typeof opts.line.color !== 'function')) {
(typeof opts.line.color !== 'function') &&
!opts.fillgradient) {
fullLayout._infolayer.selectAll('g.' + id).remove();
return;
}
var zrange = d3.extent(((typeof opts.fillcolor === 'function') ?
opts.fillcolor : opts.line.color).domain());
var zrange = opts.zrange || (d3.extent(((typeof opts.fillcolor === 'function') ?
opts.fillcolor : opts.line.color).domain()));
var linelevels = [];
var filllevels = [];
var linecolormap = typeof opts.line.color === 'function' ?
@@ -87,7 +93,10 @@ module.exports = function draw(gd, id) {
if(l > zr0 && l < zr1) linelevels.push(l);
}

if(typeof opts.fillcolor === 'function') {
if(opts.fillgradient) {
filllevels = [0];
}
else if(typeof opts.fillcolor === 'function') {
if(opts.filllevels) {
l0 = opts.filllevels.end + opts.filllevels.size / 100;
ls = opts.filllevels.size;
@@ -358,6 +367,12 @@ module.exports = function draw(gd, id) {
.classed(cn.cbfill, true)
.style('stroke', 'none');
fills.exit().remove();

var zBounds = zrange
.map(cbAxisOut.c2p)
.map(Math.round)
.sort(function(a, b) { return a - b; });

fills.each(function(d, i) {
var z = [
(i === 0) ? zrange[0] :
@@ -370,25 +385,27 @@ module.exports = function draw(gd, id) {

// offset the side adjoining the next rectangle so they
// overlap, to prevent antialiasing gaps
if(i !== filllevels.length - 1) {
z[1] += (z[1] > z[0]) ? 1 : -1;
}


// Tinycolor can't handle exponents and
// at this scale, removing it makes no difference.
var colorString = fillcolormap(d).replace('e-', ''),
opaqueColor = tinycolor(colorString).toHexString();
z[1] = Lib.constrain(z[1] + (z[1] > z[0]) ? 1 : -1, zBounds[0], zBounds[1]);

// Colorbar cannot currently support opacities so we
// use an opaque fill even when alpha channels present
d3.select(this).attr({
var fillEl = d3.select(this).attr({
x: xLeft,
width: Math.max(thickPx, 2),
y: d3.min(z),
height: Math.max(d3.max(z) - d3.min(z), 2),
fill: opaqueColor
});

if(opts.fillgradient) {
Drawing.gradient(fillEl, gd, id, 'vertical',
opts.fillgradient, 'fill');
}
else {
// Tinycolor can't handle exponents and
// at this scale, removing it makes no difference.
var colorString = fillcolormap(d).replace('e-', '');
fillEl.attr('fill', tinycolor(colorString).toHexString());
}
});

var lines = container.select('.cblines')
@@ -650,13 +667,13 @@ module.exports = function draw(gd, id) {

// or use .options to set multiple options at once via a dictionary
component.options = function(o) {
Object.keys(o).forEach(function(name) {
for(var name in o) {
// in case something random comes through
// that's not an option, ignore it
if(typeof component[name] === 'function') {
component[name](o[name]);
}
});
}
return component;
};

27 changes: 10 additions & 17 deletions src/traces/contour/colorbar.js
Original file line number Diff line number Diff line change
@@ -16,32 +16,25 @@ var endPlus = require('./end_plus');


module.exports = function colorbar(gd, cd) {
var trace = cd[0].trace,
cbId = 'cb' + trace.uid;
var trace = cd[0].trace;
var cbId = 'cb' + trace.uid;

gd._fullLayout._infolayer.selectAll('.' + cbId).remove();

if(!trace.showscale) return;

var cb = drawColorbar(gd, cbId);
cd[0].t.cb = cb;
var cb = cd[0].t.cb = drawColorbar(gd, cbId);

var contours = trace.contours,
line = trace.line,
cs = contours.size || 1,
coloring = contours.coloring;
var contours = trace.contours;
var line = trace.line;
var cs = contours.size || 1;
var coloring = contours.coloring;

var colorMap = makeColorMap(trace, {isColorbar: true});

if(coloring === 'heatmap') {
cb.filllevels({
start: trace.zmin,
end: trace.zmax,
size: (trace.zmax - trace.zmin) / 254
});
}

cb.fillcolor((coloring === 'fill' || coloring === 'heatmap') ? colorMap : '')
cb.fillgradient(coloring === 'heatmap' ? trace.colorscale : '')
.zrange(coloring === 'heatmap' ? [trace.zmin, trace.zmax] : '')
.fillcolor((coloring === 'fill') ? colorMap : '')
.line({
color: coloring === 'lines' ? colorMap : line.color,
width: contours.showlines !== false ? line.width : 0,
Binary file modified test/image/baselines/16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/colorbar_enumerated_ticks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/colorscale_constraint.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/geo_multiple-usa-choropleths.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/gl2d_parcoords_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/gl2d_parcoords_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/hist2d_summed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/zsmooth_methods.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
150 changes: 90 additions & 60 deletions test/jasmine/tests/colorbar_test.js
Original file line number Diff line number Diff line change
@@ -62,37 +62,47 @@ describe('Test colorbar:', function() {
.then(done);
});

// let heatmap stand in for all traces with trace.{showscale, colorbar}
// also test impliedEdits for colorbars at the trace root
it('can show and hide heatmap colorbars and sizes correctly with automargin', function(done) {
function assertCB(msg, present, expandedMarginR, expandedMarginT, cbTop, cbHeight) {
var colorbars = d3.select(gd).selectAll('.colorbar');
expect(colorbars.size()).toBe(present ? 1 : 0);

var cbbg = colorbars.selectAll('.colorbar .cbbg');

// check that the displayed object has the right size,
// not just that fullLayout._size changed
var plotSizeTest = {};
if(expandedMarginR) plotSizeTest.widthLessThan = 400;
else plotSizeTest.width = 400;

if(expandedMarginT) plotSizeTest.heightLessThan = 400;
else plotSizeTest.height = 400;

assertPlotSize(plotSizeTest);

if(present) {
if(!cbHeight) cbHeight = 400;
var bgHeight = +cbbg.attr('height');
if(expandedMarginT) expect(bgHeight).toBeLessThan(cbHeight - 2);
else expect(bgHeight).toBeWithin(cbHeight, 2);

function assertCB(msg, present, opts) {
var expandedMarginR = opts.expandedMarginR;
var expandedMarginT = opts.expandedMarginT;
var cbTop = opts.top;
var cbHeight = opts.height;
var multiFill = opts.multiFill;
var colorbars = d3.select(gd).selectAll('.colorbar');
expect(colorbars.size()).toBe(present ? 1 : 0);

// check that the displayed object has the right size,
// not just that fullLayout._size changed
var plotSizeTest = {};
if(expandedMarginR) plotSizeTest.widthLessThan = 400;
else plotSizeTest.width = 400;

if(expandedMarginT) plotSizeTest.heightLessThan = 400;
else plotSizeTest.height = 400;

assertPlotSize(plotSizeTest);

if(present) {
var cbbg = colorbars.selectAll('.cbbg');
var cbfills = colorbars.selectAll('.cbfill');

expect(cbfills.size()).negateIf(multiFill).toBe(1);

if(!cbHeight) cbHeight = 400;
var bgHeight = +cbbg.attr('height');
if(expandedMarginT) expect(bgHeight).toBeLessThan(cbHeight - 2);
else expect(bgHeight).toBeWithin(cbHeight, 2);

if(cbTop !== undefined) {
var topShift = cbbg.node().getBoundingClientRect().top - gd.getBoundingClientRect().top;
expect(topShift).toBeWithin(cbTop, 2);
}
}
}

// let heatmap stand in for all traces with trace.{showscale, colorbar}
// also test impliedEdits for colorbars at the trace root
it('can show and hide heatmap colorbars and sizes correctly with automargin', function(done) {
var thickPx, lenFrac;

Plotly.newPlot(gd, [{
@@ -104,22 +114,22 @@ describe('Test colorbar:', function() {
margin: {l: 50, r: 50, t: 50, b: 50}
})
.then(function() {
assertCB('initial', true, true, false, 50);
assertCB('initial', true, {expandedMarginR: true, expandedMarginT: false, top: 50});

return Plotly.restyle(gd, {showscale: false});
})
.then(function() {
assertCB('hidden', false, false, false);
assertCB('hidden', false, {expandedMarginR: false, expandedMarginT: false});

return Plotly.restyle(gd, {showscale: true, 'colorbar.y': 0.8});
})
.then(function() {
assertCB('up high', true, true, true, 12);
assertCB('up high', true, {expandedMarginR: true, expandedMarginT: true, top: 12});

return Plotly.restyle(gd, {'colorbar.y': 0.7});
})
.then(function() {
assertCB('a little lower', true, true, true, 12);
assertCB('a little lower', true, {expandedMarginR: true, expandedMarginT: true, top: 12});

return Plotly.restyle(gd, {
'colorbar.x': 0.7,
@@ -129,7 +139,7 @@ describe('Test colorbar:', function() {
});
})
.then(function() {
assertCB('mid-plot', true, false, false, 150, 200);
assertCB('mid-plot', true, {expandedMarginR: false, expandedMarginT: false, top: 150, height: 200});

thickPx = gd._fullData[0].colorbar.thickness;
lenFrac = gd._fullData[0].colorbar.len;
@@ -146,21 +156,14 @@ describe('Test colorbar:', function() {
expect(gd._fullData[0].colorbar.len)
.toBeCloseTo(lenFrac * 400, 3);

assertCB('changed size modes', true, true, false, 150, 200);
assertCB('changed size modes', true, {expandedMarginR: true, expandedMarginT: false, top: 150, height: 200});
})
.catch(failTest)
.then(done);
});

// scatter has trace.marker.{showscale, colorbar}
it('can show and hide scatter colorbars', function(done) {
function assertCB(present, expandedMargin) {
var colorbars = d3.select(gd).selectAll('.colorbar');
expect(colorbars.size()).toBe(present ? 1 : 0);

assertPlotSize(expandedMargin ? {widthLessThan: 400} : {width: 400});
}

Plotly.newPlot(gd, [{
y: [1, 2, 3],
marker: {color: [1, 2, 3], showscale: true}
@@ -170,36 +173,29 @@ describe('Test colorbar:', function() {
margin: {l: 50, r: 50, t: 50, b: 50}
})
.then(function() {
assertCB(true, true);
assertCB('initial', true, {expandedMarginR: true});

return Plotly.restyle(gd, {'marker.showscale': false});
})
.then(function() {
assertCB(false, false);
assertCB('hidden', false, {expandedMarginR: false});

return Plotly.restyle(gd, {'marker.showscale': true, 'marker.colorbar.x': 0.7});
})
.then(function() {
assertCB(true, false);
assertCB('mid-plot', true, {expandedMarginR: false});

return Plotly.restyle(gd, {'marker.colorbar.x': 1.1});
})
.then(function() {
assertCB(true, true);
assertCB('far right', true, {expandedMarginR: true});
})
.catch(failTest)
.then(done);
});

// histogram colorbars could not be edited before
it('can show and hide scatter colorbars', function(done) {
function assertCB(present, expandedMargin) {
var colorbars = d3.select(gd).selectAll('.colorbar');
expect(colorbars.size()).toBe(present ? 1 : 0);

assertPlotSize(expandedMargin ? {widthLessThan: 400} : {width: 400});
}

it('can show and hide histogram colorbars', function(done) {
Plotly.newPlot(gd, [{
type: 'histogram',
x: [0, 1, 1, 2, 2, 2, 3, 3, 4],
@@ -211,22 +207,53 @@ describe('Test colorbar:', function() {
margin: {l: 50, r: 50, t: 50, b: 50}
})
.then(function() {
assertCB(true, true);
assertCB('initial', true, {expandedMarginR: true});

return Plotly.restyle(gd, {'marker.showscale': false});
})
.then(function() {
assertCB(false, false);
assertCB('hidden', false, {expandedMarginR: false});

return Plotly.restyle(gd, {'marker.showscale': true, 'marker.colorbar.x': 0.7});
})
.then(function() {
assertCB(true, false);
assertCB('mid-plot', true, {expandedMarginR: false});

return Plotly.restyle(gd, {'marker.colorbar.x': 1.1});
})
.then(function() {
assertCB(true, true);
assertCB('far right', true, {expandedMarginR: true});
})
.catch(failTest)
.then(done);
});

it('creates multiple fills for contour colorbars', function(done) {
Plotly.newPlot(gd, [{
type: 'contour',
z: [[1, 10], [100, 1000]]
}], {
height: 500,
width: 500,
margin: {l: 50, r: 50, t: 50, b: 50}
})
.then(function() {
assertCB('initial', true, {expandedMarginR: true, expandedMarginT: false, top: 50, multiFill: true});

return Plotly.restyle(gd, {showscale: false});
})
.then(function() {
assertCB('hidden', false, {expandedMarginR: false, expandedMarginT: false});

return Plotly.restyle(gd, {showscale: true, 'colorbar.y': 0.8});
})
.then(function() {
assertCB('up high', true, {expandedMarginR: true, expandedMarginT: true, top: 12, multiFill: true});

return Plotly.restyle(gd, {'contours.coloring': 'heatmap'});
})
.then(function() {
assertCB('up high', true, {expandedMarginR: true, expandedMarginT: true, top: 12, multiFill: false});
})
.catch(failTest)
.then(done);
@@ -235,7 +262,7 @@ describe('Test colorbar:', function() {
// parcoords has trace.marker.{showscale, colorbar}
// also tests impliedEdits for colorbars in containers
it('can show and hide parcoords colorbars', function(done) {
function assertCB(present, expandedMargin) {
function assertParcoordsCB(present, expandedMargin) {
var colorbars = d3.select(gd).selectAll('.colorbar');
expect(colorbars.size()).toBe(present ? 1 : 0);

@@ -244,6 +271,9 @@ describe('Test colorbar:', function() {
var transform = yAxes[0][1].getAttribute('transform');
if(expandedMargin) expect(transform).not.toBe('translate(400, 0)');
else expect(transform).toBe('translate(400, 0)');

var cbfills = colorbars.selectAll('.cbfill');
expect(cbfills.size()).toBe(present ? 1 : 0);
}

var thickPx, lenFrac;
@@ -258,20 +288,20 @@ describe('Test colorbar:', function() {
margin: {l: 50, r: 50, t: 50, b: 50}
})
.then(function() {
assertCB(true, true);
assertParcoordsCB(true, true);

return Plotly.restyle(gd, {'line.showscale': false});
})
.then(function() {
assertCB(false, false);
assertParcoordsCB(false, false);

return Plotly.restyle(gd, {
'line.showscale': true,
'line.colorbar.x': 0.7
});
})
.then(function() {
assertCB(true, false);
assertParcoordsCB(true, false);

thickPx = gd._fullData[0].line.colorbar.thickness;
lenFrac = gd._fullData[0].line.colorbar.len;
@@ -288,7 +318,7 @@ describe('Test colorbar:', function() {
expect(gd._fullData[0].line.colorbar.len)
.toBeCloseTo(lenFrac * 400, 3);

assertCB(true, true);
assertParcoordsCB(true, true);
})
.catch(failTest)
.then(done);