diff --git a/src/components/colorscale/attributes.js b/src/components/colorscale/attributes.js
index f1dad9b9573..014377c1c91 100644
--- a/src/components/colorscale/attributes.js
+++ b/src/components/colorscale/attributes.js
@@ -87,6 +87,8 @@ module.exports = function colorScaleAttrs(context, opts) {
var auto = cLetter + 'auto';
var min = cLetter + 'min';
var max = cLetter + 'max';
+ var mid = cLetter + 'mid';
+ var autoFull = code(contextHead + auto);
var minFull = code(contextHead + min);
var maxFull = code(contextHead + max);
var minmaxFull = minFull + ' and ' + maxFull;
@@ -160,6 +162,21 @@ module.exports = function colorScaleAttrs(context, opts) {
].join('')
};
+ attrs[mid] = {
+ valType: 'number',
+ role: 'info',
+ dflt: null,
+ editType: 'calc',
+ impliedEdits: autoImpliedEdits,
+ description: [
+ 'Sets the mid-point of the color domain by scaling ', minFull,
+ ' and/or ', maxFull, ' to be equidistant to this point.',
+ effectDesc,
+ ' Value should have the same units as ', colorAttrFull, '. ',
+ 'Has no effect when ', autoFull, ' is `false`.'
+ ].join('')
+ };
+
attrs.colorscale = {
valType: 'colorscale',
role: 'style',
diff --git a/src/components/colorscale/calc.js b/src/components/colorscale/calc.js
index 46690b37ca0..6fb87c06b0c 100644
--- a/src/components/colorscale/calc.js
+++ b/src/components/colorscale/calc.js
@@ -23,9 +23,11 @@ module.exports = function calc(gd, trace, opts) {
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;
if(auto !== false || min === undefined) {
@@ -36,6 +38,15 @@ module.exports = function calc(gd, trace, opts) {
max = Lib.aggNums(Math.max, null, vals);
}
+ if(auto !== false && mid !== undefined) {
+ if(max - mid > mid - min) {
+ min = mid - (max - mid);
+ }
+ else if(max - mid < mid - min) {
+ max = mid + (mid - min);
+ }
+ }
+
if(min === max) {
min -= 0.5;
max += 0.5;
diff --git a/src/components/colorscale/defaults.js b/src/components/colorscale/defaults.js
index 387ecc787b9..75cd284b2be 100644
--- a/src/components/colorscale/defaults.js
+++ b/src/components/colorscale/defaults.js
@@ -33,9 +33,14 @@ module.exports = function colorScaleDefaults(traceIn, traceOut, layout, coerce,
var minIn = containerIn[cLetter + 'min'];
var maxIn = containerIn[cLetter + 'max'];
var validMinMax = isNumeric(minIn) && isNumeric(maxIn) && (minIn < maxIn);
- coerce(prefix + cLetter + 'auto', !validMinMax);
- coerce(prefix + cLetter + 'min');
- coerce(prefix + cLetter + 'max');
+ var auto = coerce(prefix + cLetter + 'auto', !validMinMax);
+
+ if(auto) {
+ coerce(prefix + cLetter + 'mid');
+ } else {
+ coerce(prefix + cLetter + 'min');
+ coerce(prefix + cLetter + 'max');
+ }
// handles both the trace case (autocolorscale is false by default) and
// the marker and marker.line case (autocolorscale is true by default)
diff --git a/src/traces/scattermapbox/attributes.js b/src/traces/scattermapbox/attributes.js
index 55bb3b7d2e2..8609904e1a2 100644
--- a/src/traces/scattermapbox/attributes.js
+++ b/src/traces/scattermapbox/attributes.js
@@ -93,6 +93,7 @@ module.exports = overrideAll({
cauto: markerAttrs.cauto,
cmax: markerAttrs.cmax,
cmin: markerAttrs.cmin,
+ cmid: markerAttrs.cmid,
autocolorscale: markerAttrs.autocolorscale,
reversescale: markerAttrs.reversescale,
showscale: markerAttrs.showscale,
diff --git a/test/image/baselines/cmid-zmid.png b/test/image/baselines/cmid-zmid.png
new file mode 100644
index 00000000000..cb929f0f1e7
Binary files /dev/null and b/test/image/baselines/cmid-zmid.png differ
diff --git a/test/image/mocks/cmid-zmid.json b/test/image/mocks/cmid-zmid.json
new file mode 100644
index 00000000000..68b47fc7dbc
--- /dev/null
+++ b/test/image/mocks/cmid-zmid.json
@@ -0,0 +1,43 @@
+{
+ "data": [{
+ "y": [1, 2, 1],
+ "mode": "markers",
+ "marker": {
+ "size": 20,
+ "color": [-1, 1, 10],
+ "cmid": "0",
+ "colorbar": {
+ "len": 0.3,
+ "y": "1",
+ "yanchor": "top",
+ "title": {"text": "marker.cmid=0", "side": "right"}
+ }
+ }
+ }, {
+ "type": "heatmap",
+ "z": [[1,2], [2, 3]],
+ "zmid": -1,
+ "colorbar": {
+ "len": 0.3,
+ "y": "0.5",
+ "yanchor": "middle",
+ "title": {"text": "zmid=-1", "side": "right"}
+ }
+ }, {
+ "type": "bar",
+ "y": [0.5, 1, 0.5],
+ "name": "marker.line.cmid=2",
+ "hoverlabel": {"namelength": -1},
+ "marker": {
+ "line": {
+ "width": 2,
+ "color": [-1, -1, -10],
+ "cmid": 2
+ }
+ }
+ }],
+ "layout": {
+ "title": {"text": "Traces with set cmid / zmid"},
+ "showlegend": false
+ }
+}
diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js
index 837f1569f2d..c0cf010eab7 100644
--- a/test/jasmine/tests/plot_api_test.js
+++ b/test/jasmine/tests/plot_api_test.js
@@ -1195,6 +1195,77 @@ describe('Test plot api', function() {
.then(done);
});
+ it('turns on cauto when cmid is edited', function(done) {
+ function _assert(msg, exp) {
+ return function() {
+ var mk = gd._fullData[0].marker;
+ for(var k in exp) {
+ expect(mk[k]).toBe(exp[k], [msg, k].join(' - '));
+ }
+ };
+ }
+
+ function _restyle(arg) {
+ return function() { return Plotly.restyle(gd, arg); };
+ }
+
+ Plotly.plot(gd, [{
+ mode: 'markers',
+ y: [1, 2, 1],
+ marker: { color: [1, -1, 4] }
+ }])
+ .then(_assert('base', {
+ cauto: true,
+ cmid: undefined,
+ cmin: -1,
+ cmax: 4
+ }))
+ .then(_restyle({'marker.cmid': 0}))
+ .then(_assert('set cmid=0', {
+ cauto: true,
+ cmid: 0,
+ cmin: -4,
+ cmax: 4
+ }))
+ .then(_restyle({'marker.cmid': -2}))
+ .then(_assert('set cmid=-2', {
+ cauto: true,
+ cmid: -2,
+ cmin: -8,
+ cmax: 4
+ }))
+ .then(_restyle({'marker.cmid': 2}))
+ .then(_assert('set cmid=2', {
+ cauto: true,
+ cmid: 2,
+ cmin: -1,
+ cmax: 5
+ }))
+ .then(_restyle({'marker.cmin': 0}))
+ .then(_assert('set cmin=0', {
+ cauto: false,
+ cmid: undefined,
+ cmin: 0,
+ cmax: 5
+ }))
+ .then(_restyle({'marker.cmax': 10}))
+ .then(_assert('set cmin=0 + cmax=10', {
+ cauto: false,
+ cmid: undefined,
+ cmin: 0,
+ cmax: 10
+ }))
+ .then(_restyle({'marker.cauto': true, 'marker.cmid': null}))
+ .then(_assert('back to cauto=true', {
+ cauto: true,
+ cmid: undefined,
+ cmin: -1,
+ cmax: 4
+ }))
+ .catch(failTest)
+ .then(done);
+ });
+
it('turns off autobin when you edit bin specs', function(done) {
// test retained (modified) for backward compat with new autobin logic
var start0 = 0.2;
diff --git a/test/jasmine/tests/scattergeo_test.js b/test/jasmine/tests/scattergeo_test.js
index f84846d2714..fd87caddeac 100644
--- a/test/jasmine/tests/scattergeo_test.js
+++ b/test/jasmine/tests/scattergeo_test.js
@@ -90,7 +90,7 @@ describe('Test scattergeo defaults', function() {
describe('Test scattergeo calc', function() {
function _calc(opts) {
- var base = { type: 'scattermapbox' };
+ var base = { type: 'scattergeo' };
var trace = Lib.extendFlat({}, base, opts);
var gd = { data: [trace] };
diff --git a/test/jasmine/tests/surface_test.js b/test/jasmine/tests/surface_test.js
index 943189c325b..2d77fed1928 100644
--- a/test/jasmine/tests/surface_test.js
+++ b/test/jasmine/tests/surface_test.js
@@ -127,16 +127,14 @@ describe('Test surface', function() {
it('should coerce \'c\' attributes with \'c\' values regardless of `\'z\' if \'c\' is present', function() {
traceIn = {
z: [[1, 2, 3], [2, 1, 2]],
- zauto: false,
zmin: 0,
zmax: 10,
- cauto: true,
cmin: -10,
cmax: 20
};
supplyDefaults(traceIn, traceOut, defaultColor, layout);
- expect(traceOut.cauto).toEqual(true);
+ expect(traceOut.cauto).toEqual(false);
expect(traceOut.cmin).toEqual(-10);
expect(traceOut.cmax).toEqual(20);
});