plotly.js
Version:
The open source javascript graphing library that powers plotly
173 lines (136 loc) • 5.31 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.
*/
;
var tinycolor = require('tinycolor2');
var isNumeric = require('fast-isnumeric');
var color = module.exports = {};
var colorAttrs = require('./attributes');
color.defaults = colorAttrs.defaults;
var defaultLine = color.defaultLine = colorAttrs.defaultLine;
color.lightLine = colorAttrs.lightLine;
var background = color.background = colorAttrs.background;
/*
* tinyRGB: turn a tinycolor into an rgb string, but
* unlike the built-in tinycolor.toRgbString this never includes alpha
*/
color.tinyRGB = function(tc) {
var c = tc.toRgb();
return 'rgb(' + Math.round(c.r) + ', ' +
Math.round(c.g) + ', ' + Math.round(c.b) + ')';
};
color.rgb = function(cstr) { return color.tinyRGB(tinycolor(cstr)); };
color.opacity = function(cstr) { return cstr ? tinycolor(cstr).getAlpha() : 0; };
color.addOpacity = function(cstr, op) {
var c = tinycolor(cstr).toRgb();
return 'rgba(' + Math.round(c.r) + ', ' +
Math.round(c.g) + ', ' + Math.round(c.b) + ', ' + op + ')';
};
// combine two colors into one apparent color
// if back has transparency or is missing,
// color.background is assumed behind it
color.combine = function(front, back) {
var fc = tinycolor(front).toRgb();
if(fc.a === 1) return tinycolor(front).toRgbString();
var bc = tinycolor(back || background).toRgb();
var bcflat = bc.a === 1 ? bc : {
r: 255 * (1 - bc.a) + bc.r * bc.a,
g: 255 * (1 - bc.a) + bc.g * bc.a,
b: 255 * (1 - bc.a) + bc.b * bc.a
};
var fcflat = {
r: bcflat.r * (1 - fc.a) + fc.r * fc.a,
g: bcflat.g * (1 - fc.a) + fc.g * fc.a,
b: bcflat.b * (1 - fc.a) + fc.b * fc.a
};
return tinycolor(fcflat).toRgbString();
};
/*
* Create a color that contrasts with cstr.
*
* If cstr is a dark color, we lighten it; if it's light, we darken.
*
* If lightAmount / darkAmount are used, we adjust by these percentages,
* otherwise we go all the way to white or black.
*/
color.contrast = function(cstr, lightAmount, darkAmount) {
var tc = tinycolor(cstr);
if(tc.getAlpha() !== 1) tc = tinycolor(color.combine(cstr, background));
var newColor = tc.isDark() ?
(lightAmount ? tc.lighten(lightAmount) : background) :
(darkAmount ? tc.darken(darkAmount) : defaultLine);
return newColor.toString();
};
color.stroke = function(s, c) {
var tc = tinycolor(c);
s.style({'stroke': color.tinyRGB(tc), 'stroke-opacity': tc.getAlpha()});
};
color.fill = function(s, c) {
var tc = tinycolor(c);
s.style({
'fill': color.tinyRGB(tc),
'fill-opacity': tc.getAlpha()
});
};
// search container for colors with the deprecated rgb(fractions) format
// and convert them to rgb(0-255 values)
color.clean = function(container) {
if(!container || typeof container !== 'object') return;
var keys = Object.keys(container);
var i, j, key, val;
for(i = 0; i < keys.length; i++) {
key = keys[i];
val = container[key];
if(key.substr(key.length - 5) === 'color') {
// only sanitize keys that end in "color" or "colorscale"
if(Array.isArray(val)) {
for(j = 0; j < val.length; j++) val[j] = cleanOne(val[j]);
} else container[key] = cleanOne(val);
} else if(key.substr(key.length - 10) === 'colorscale' && Array.isArray(val)) {
// colorscales have the format [[0, color1], [frac, color2], ... [1, colorN]]
for(j = 0; j < val.length; j++) {
if(Array.isArray(val[j])) val[j][1] = cleanOne(val[j][1]);
}
} else if(Array.isArray(val)) {
// recurse into arrays of objects, and plain objects
var el0 = val[0];
if(!Array.isArray(el0) && el0 && typeof el0 === 'object') {
for(j = 0; j < val.length; j++) color.clean(val[j]);
}
} else if(val && typeof val === 'object') color.clean(val);
}
};
function cleanOne(val) {
if(isNumeric(val) || typeof val !== 'string') return val;
var valTrim = val.trim();
if(valTrim.substr(0, 3) !== 'rgb') return val;
var match = valTrim.match(/^rgba?\s*\(([^()]*)\)$/);
if(!match) return val;
var parts = match[1].trim().split(/\s*[\s,]\s*/);
var rgba = valTrim.charAt(3) === 'a' && parts.length === 4;
if(!rgba && parts.length !== 3) return val;
for(var i = 0; i < parts.length; i++) {
if(!parts[i].length) return val;
parts[i] = Number(parts[i]);
if(!(parts[i] >= 0)) {
// all parts must be non-negative numbers
return val;
}
if(i === 3) {
// alpha>1 gets clipped to 1
if(parts[i] > 1) parts[i] = 1;
} else if(parts[i] >= 1) {
// r, g, b must be < 1 (ie 1 itself is not allowed)
return val;
}
}
var rgbStr = Math.round(parts[0] * 255) + ', ' +
Math.round(parts[1] * 255) + ', ' +
Math.round(parts[2] * 255);
if(rgba) return 'rgba(' + rgbStr + ', ' + parts[3] + ')';
return 'rgb(' + rgbStr + ')';
}