plotly.js
Version:
The open source javascript graphing library that powers plotly
239 lines (206 loc) • 6.21 kB
JavaScript
/**
* Copyright 2012-2020, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
var d3 = require('d3');
var tinycolor = require('tinycolor2');
var isNumeric = require('fast-isnumeric');
var Lib = require('../../lib');
var Color = require('../color');
var isValidScale = require('./scales').isValid;
function hasColorscale(trace, containerStr, colorKey) {
var container = containerStr ?
Lib.nestedProperty(trace, containerStr).get() || {} :
trace;
var color = container[colorKey || 'color'];
var isArrayWithOneNumber = false;
if(Lib.isArrayOrTypedArray(color)) {
for(var i = 0; i < color.length; i++) {
if(isNumeric(color[i])) {
isArrayWithOneNumber = true;
break;
}
}
}
return (
Lib.isPlainObject(container) && (
isArrayWithOneNumber ||
container.showscale === true ||
(isNumeric(container.cmin) && isNumeric(container.cmax)) ||
isValidScale(container.colorscale) ||
Lib.isPlainObject(container.colorbar)
)
);
}
var constantAttrs = ['showscale', 'autocolorscale', 'colorscale', 'reversescale', 'colorbar'];
var letterAttrs = ['min', 'max', 'mid', 'auto'];
/**
* Extract 'c' / 'z', trace / color axis colorscale options
*
* Note that it would be nice to replace all z* with c* equivalents in v2
*
* @param {object} cont : attribute container
* @return {object}:
* - min: cmin or zmin
* - max: cmax or zmax
* - mid: cmid or zmid
* - auto: cauto or zauto
* - *scale: *scale attrs
* - colorbar: colorbar
* - _sync: function syncing attr and underscore dual (useful when calc'ing min/max)
*/
function extractOpts(cont) {
var colorAx = cont._colorAx;
var cont2 = colorAx ? colorAx : cont;
var out = {};
var cLetter;
var i, k;
for(i = 0; i < constantAttrs.length; i++) {
k = constantAttrs[i];
out[k] = cont2[k];
}
if(colorAx) {
cLetter = 'c';
for(i = 0; i < letterAttrs.length; i++) {
k = letterAttrs[i];
out[k] = cont2['c' + k];
}
} else {
var k2;
for(i = 0; i < letterAttrs.length; i++) {
k = letterAttrs[i];
k2 = 'c' + k;
if(k2 in cont2) {
out[k] = cont2[k2];
continue;
}
k2 = 'z' + k;
if(k2 in cont2) {
out[k] = cont2[k2];
}
}
cLetter = k2.charAt(0);
}
out._sync = function(k, v) {
var k2 = letterAttrs.indexOf(k) !== -1 ? cLetter + k : k;
cont2[k2] = cont2['_' + k2] = v;
};
return out;
}
/**
* Extract colorscale into numeric domain and color range.
*
* @param {object} cont colorscale container (e.g. trace, marker)
* - colorscale {array of arrays}
* - cmin/zmin {number}
* - cmax/zmax {number}
* - reversescale {boolean}
*
* @return {object}
* - domain {array}
* - range {array}
*/
function extractScale(cont) {
var cOpts = extractOpts(cont);
var cmin = cOpts.min;
var cmax = cOpts.max;
var scl = cOpts.reversescale ?
flipScale(cOpts.colorscale) :
cOpts.colorscale;
var N = scl.length;
var domain = new Array(N);
var range = new Array(N);
for(var i = 0; i < N; i++) {
var si = scl[i];
domain[i] = cmin + si[0] * (cmax - cmin);
range[i] = si[1];
}
return {domain: domain, range: range};
}
function flipScale(scl) {
var N = scl.length;
var sclNew = new Array(N);
for(var i = N - 1, j = 0; i >= 0; i--, j++) {
var si = scl[i];
sclNew[j] = [1 - si[0], si[1]];
}
return sclNew;
}
/**
* General colorscale function generator.
*
* @param {object} specs output of Colorscale.extractScale or precomputed domain, range.
* - domain {array}
* - range {array}
*
* @param {object} opts
* - noNumericCheck {boolean} if true, scale func bypasses numeric checks
* - returnArray {boolean} if true, scale func return 4-item array instead of color strings
*
* @return {function}
*/
function makeColorScaleFunc(specs, opts) {
opts = opts || {};
var domain = specs.domain;
var range = specs.range;
var N = range.length;
var _range = new Array(N);
for(var i = 0; i < N; i++) {
var rgba = tinycolor(range[i]).toRgb();
_range[i] = [rgba.r, rgba.g, rgba.b, rgba.a];
}
var _sclFunc = d3.scale.linear()
.domain(domain)
.range(_range)
.clamp(true);
var noNumericCheck = opts.noNumericCheck;
var returnArray = opts.returnArray;
var sclFunc;
if(noNumericCheck && returnArray) {
sclFunc = _sclFunc;
} else if(noNumericCheck) {
sclFunc = function(v) {
return colorArray2rbga(_sclFunc(v));
};
} else if(returnArray) {
sclFunc = function(v) {
if(isNumeric(v)) return _sclFunc(v);
else if(tinycolor(v).isValid()) return v;
else return Color.defaultLine;
};
} else {
sclFunc = function(v) {
if(isNumeric(v)) return colorArray2rbga(_sclFunc(v));
else if(tinycolor(v).isValid()) return v;
else return Color.defaultLine;
};
}
// colorbar draw looks into the d3 scale closure for domain and range
sclFunc.domain = _sclFunc.domain;
sclFunc.range = function() { return range; };
return sclFunc;
}
function makeColorScaleFuncFromTrace(trace, opts) {
return makeColorScaleFunc(extractScale(trace), opts);
}
function colorArray2rbga(colorArray) {
var colorObj = {
r: colorArray[0],
g: colorArray[1],
b: colorArray[2],
a: colorArray[3]
};
return tinycolor(colorObj).toRgbString();
}
module.exports = {
hasColorscale: hasColorscale,
extractOpts: extractOpts,
extractScale: extractScale,
flipScale: flipScale,
makeColorScaleFunc: makeColorScaleFunc,
makeColorScaleFuncFromTrace: makeColorScaleFuncFromTrace
};