UNPKG

chartjs-chart-funnel

Version:
1,664 lines (1,465 loc) 124 kB
/** * chartjs-chart-funnel * https://github.com/sgratzl/chartjs-chart-funnel * * Copyright (c) 2021 Samuel Gratzl <samu@sgratzl.com> */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('chart.js'), require('chart.js/helpers')) : typeof define === 'function' && define.amd ? define(['exports', 'chart.js', 'chart.js/helpers'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ChartFunnel = {}, global.Chart, global.Chart.helpers)); })(this, (function (exports, chart_js, helpers) { 'use strict'; const { min: min$4, max: max$4 } = Math; var limit = (x, low = 0, high = 1) => { return min$4(max$4(low, x), high); }; var clip_rgb = (rgb) => { rgb._clipped = false; rgb._unclipped = rgb.slice(0); for (let i = 0; i <= 3; i++) { if (i < 3) { if (rgb[i] < 0 || rgb[i] > 255) rgb._clipped = true; rgb[i] = limit(rgb[i], 0, 255); } else if (i === 3) { rgb[i] = limit(rgb[i], 0, 1); } } return rgb; }; // ported from jQuery's $.type const classToType = {}; for (let name of [ 'Boolean', 'Number', 'String', 'Function', 'Array', 'Date', 'RegExp', 'Undefined', 'Null' ]) { classToType[`[object ${name}]`] = name.toLowerCase(); } function type (obj) { return classToType[Object.prototype.toString.call(obj)] || 'object'; } var unpack = (args, keyOrder = null) => { // if called with more than 3 arguments, we return the arguments if (args.length >= 3) return Array.prototype.slice.call(args); // with less than 3 args we check if first arg is object // and use the keyOrder string to extract and sort properties if (type(args[0]) == 'object' && keyOrder) { return keyOrder .split('') .filter((k) => args[0][k] !== undefined) .map((k) => args[0][k]); } // otherwise we just return the first argument // (which we suppose is an array of args) return args[0].slice(0); }; var last = (args) => { if (args.length < 2) return null; const l = args.length - 1; if (type(args[l]) == 'string') return args[l].toLowerCase(); return null; }; const { PI: PI$2, min: min$3, max: max$3 } = Math; const rnd2 = (a) => Math.round(a * 100) / 100; const rnd3 = (a) => Math.round(a * 100) / 100; const TWOPI = PI$2 * 2; const PITHIRD = PI$2 / 3; const DEG2RAD = PI$2 / 180; const RAD2DEG = 180 / PI$2; /** * Reverse the first three elements of an array * * @param {any[]} arr * @returns {any[]} */ function reverse3(arr) { return [...arr.slice(0, 3).reverse(), ...arr.slice(3)]; } var input = { format: {}, autodetect: [] }; class Color { constructor(...args) { const me = this; if ( type(args[0]) === 'object' && args[0].constructor && args[0].constructor === this.constructor ) { // the argument is already a Color instance return args[0]; } // last argument could be the mode let mode = last(args); let autodetect = false; if (!mode) { autodetect = true; if (!input.sorted) { input.autodetect = input.autodetect.sort((a, b) => b.p - a.p); input.sorted = true; } // auto-detect format for (let chk of input.autodetect) { mode = chk.test(...args); if (mode) break; } } if (input.format[mode]) { const rgb = input.format[mode].apply( null, autodetect ? args : args.slice(0, -1) ); me._rgb = clip_rgb(rgb); } else { throw new Error('unknown format: ' + args); } // add alpha channel if (me._rgb.length === 3) me._rgb.push(1); } toString() { if (type(this.hex) == 'function') return this.hex(); return `[${this._rgb.join(',')}]`; } } // this gets updated automatically const version = '3.1.2'; const chroma = (...args) => { return new Color(...args); }; chroma.version = version; /** X11 color names http://www.w3.org/TR/css3-color/#svg-color */ const w3cx11 = { aliceblue: '#f0f8ff', antiquewhite: '#faebd7', aqua: '#00ffff', aquamarine: '#7fffd4', azure: '#f0ffff', beige: '#f5f5dc', bisque: '#ffe4c4', black: '#000000', blanchedalmond: '#ffebcd', blue: '#0000ff', blueviolet: '#8a2be2', brown: '#a52a2a', burlywood: '#deb887', cadetblue: '#5f9ea0', chartreuse: '#7fff00', chocolate: '#d2691e', coral: '#ff7f50', cornflowerblue: '#6495ed', cornsilk: '#fff8dc', crimson: '#dc143c', cyan: '#00ffff', darkblue: '#00008b', darkcyan: '#008b8b', darkgoldenrod: '#b8860b', darkgray: '#a9a9a9', darkgreen: '#006400', darkgrey: '#a9a9a9', darkkhaki: '#bdb76b', darkmagenta: '#8b008b', darkolivegreen: '#556b2f', darkorange: '#ff8c00', darkorchid: '#9932cc', darkred: '#8b0000', darksalmon: '#e9967a', darkseagreen: '#8fbc8f', darkslateblue: '#483d8b', darkslategray: '#2f4f4f', darkslategrey: '#2f4f4f', darkturquoise: '#00ced1', darkviolet: '#9400d3', deeppink: '#ff1493', deepskyblue: '#00bfff', dimgray: '#696969', dimgrey: '#696969', dodgerblue: '#1e90ff', firebrick: '#b22222', floralwhite: '#fffaf0', forestgreen: '#228b22', fuchsia: '#ff00ff', gainsboro: '#dcdcdc', ghostwhite: '#f8f8ff', gold: '#ffd700', goldenrod: '#daa520', gray: '#808080', green: '#008000', greenyellow: '#adff2f', grey: '#808080', honeydew: '#f0fff0', hotpink: '#ff69b4', indianred: '#cd5c5c', indigo: '#4b0082', ivory: '#fffff0', khaki: '#f0e68c', laserlemon: '#ffff54', lavender: '#e6e6fa', lavenderblush: '#fff0f5', lawngreen: '#7cfc00', lemonchiffon: '#fffacd', lightblue: '#add8e6', lightcoral: '#f08080', lightcyan: '#e0ffff', lightgoldenrod: '#fafad2', lightgoldenrodyellow: '#fafad2', lightgray: '#d3d3d3', lightgreen: '#90ee90', lightgrey: '#d3d3d3', lightpink: '#ffb6c1', lightsalmon: '#ffa07a', lightseagreen: '#20b2aa', lightskyblue: '#87cefa', lightslategray: '#778899', lightslategrey: '#778899', lightsteelblue: '#b0c4de', lightyellow: '#ffffe0', lime: '#00ff00', limegreen: '#32cd32', linen: '#faf0e6', magenta: '#ff00ff', maroon: '#800000', maroon2: '#7f0000', maroon3: '#b03060', mediumaquamarine: '#66cdaa', mediumblue: '#0000cd', mediumorchid: '#ba55d3', mediumpurple: '#9370db', mediumseagreen: '#3cb371', mediumslateblue: '#7b68ee', mediumspringgreen: '#00fa9a', mediumturquoise: '#48d1cc', mediumvioletred: '#c71585', midnightblue: '#191970', mintcream: '#f5fffa', mistyrose: '#ffe4e1', moccasin: '#ffe4b5', navajowhite: '#ffdead', navy: '#000080', oldlace: '#fdf5e6', olive: '#808000', olivedrab: '#6b8e23', orange: '#ffa500', orangered: '#ff4500', orchid: '#da70d6', palegoldenrod: '#eee8aa', palegreen: '#98fb98', paleturquoise: '#afeeee', palevioletred: '#db7093', papayawhip: '#ffefd5', peachpuff: '#ffdab9', peru: '#cd853f', pink: '#ffc0cb', plum: '#dda0dd', powderblue: '#b0e0e6', purple: '#800080', purple2: '#7f007f', purple3: '#a020f0', rebeccapurple: '#663399', red: '#ff0000', rosybrown: '#bc8f8f', royalblue: '#4169e1', saddlebrown: '#8b4513', salmon: '#fa8072', sandybrown: '#f4a460', seagreen: '#2e8b57', seashell: '#fff5ee', sienna: '#a0522d', silver: '#c0c0c0', skyblue: '#87ceeb', slateblue: '#6a5acd', slategray: '#708090', slategrey: '#708090', snow: '#fffafa', springgreen: '#00ff7f', steelblue: '#4682b4', tan: '#d2b48c', teal: '#008080', thistle: '#d8bfd8', tomato: '#ff6347', turquoise: '#40e0d0', violet: '#ee82ee', wheat: '#f5deb3', white: '#ffffff', whitesmoke: '#f5f5f5', yellow: '#ffff00', yellowgreen: '#9acd32' }; const RE_HEX = /^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; const RE_HEXA = /^#?([A-Fa-f0-9]{8}|[A-Fa-f0-9]{4})$/; const hex2rgb = (hex) => { if (hex.match(RE_HEX)) { // remove optional leading # if (hex.length === 4 || hex.length === 7) { hex = hex.substr(1); } // expand short-notation to full six-digit if (hex.length === 3) { hex = hex.split(''); hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; } const u = parseInt(hex, 16); const r = u >> 16; const g = (u >> 8) & 0xff; const b = u & 0xff; return [r, g, b, 1]; } // match rgba hex format, eg #FF000077 if (hex.match(RE_HEXA)) { if (hex.length === 5 || hex.length === 9) { // remove optional leading # hex = hex.substr(1); } // expand short-notation to full eight-digit if (hex.length === 4) { hex = hex.split(''); hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3]; } const u = parseInt(hex, 16); const r = (u >> 24) & 0xff; const g = (u >> 16) & 0xff; const b = (u >> 8) & 0xff; const a = Math.round(((u & 0xff) / 0xff) * 100) / 100; return [r, g, b, a]; } // we used to check for css colors here // if _input.css? and rgb = _input.css hex // return rgb throw new Error(`unknown hex color: ${hex}`); }; const { round: round$5 } = Math; const rgb2hex = (...args) => { let [r, g, b, a] = unpack(args, 'rgba'); let mode = last(args) || 'auto'; if (a === undefined) a = 1; if (mode === 'auto') { mode = a < 1 ? 'rgba' : 'rgb'; } r = round$5(r); g = round$5(g); b = round$5(b); const u = (r << 16) | (g << 8) | b; let str = '000000' + u.toString(16); //#.toUpperCase(); str = str.substr(str.length - 6); let hxa = '0' + round$5(a * 255).toString(16); hxa = hxa.substr(hxa.length - 2); switch (mode.toLowerCase()) { case 'rgba': return `#${str}${hxa}`; case 'argb': return `#${hxa}${str}`; default: return `#${str}`; } }; Color.prototype.name = function () { const hex = rgb2hex(this._rgb, 'rgb'); for (let n of Object.keys(w3cx11)) { if (w3cx11[n] === hex) return n.toLowerCase(); } return hex; }; input.format.named = (name) => { name = name.toLowerCase(); if (w3cx11[name]) return hex2rgb(w3cx11[name]); throw new Error('unknown color name: ' + name); }; input.autodetect.push({ p: 5, test: (h, ...rest) => { if (!rest.length && type(h) === 'string' && w3cx11[h.toLowerCase()]) { return 'named'; } } }); Color.prototype.alpha = function (a, mutate = false) { if (a !== undefined && type(a) === 'number') { if (mutate) { this._rgb[3] = a; return this; } return new Color([this._rgb[0], this._rgb[1], this._rgb[2], a], 'rgb'); } return this._rgb[3]; }; Color.prototype.clipped = function () { return this._rgb._clipped || false; }; const labConstants = { // Corresponds roughly to RGB brighter/darker Kn: 18, // D65 standard referent labWhitePoint: 'd65', Xn: 0.95047, Yn: 1, Zn: 1.08883, t0: 0.137931034, // 4 / 29 t1: 0.206896552, // 6 / 29 t2: 0.12841855, // 3 * t1 * t1 t3: 0.008856452, // t1 * t1 * t1, kE: 216.0 / 24389.0, kKE: 8.0, kK: 24389.0 / 27.0, RefWhiteRGB: { // sRGB X: 0.95047, Y: 1, Z: 1.08883 }, MtxRGB2XYZ: { m00: 0.4124564390896922, m01: 0.21267285140562253, m02: 0.0193338955823293, m10: 0.357576077643909, m11: 0.715152155287818, m12: 0.11919202588130297, m20: 0.18043748326639894, m21: 0.07217499330655958, m22: 0.9503040785363679 }, MtxXYZ2RGB: { m00: 3.2404541621141045, m01: -0.9692660305051868, m02: 0.055643430959114726, m10: -1.5371385127977166, m11: 1.8760108454466942, m12: -0.2040259135167538, m20: -0.498531409556016, m21: 0.041556017530349834, m22: 1.0572251882231791 }, // used in rgb2xyz As: 0.9414285350000001, Bs: 1.040417467, Cs: 1.089532651, MtxAdaptMa: { m00: 0.8951, m01: -0.7502, m02: 0.0389, m10: 0.2664, m11: 1.7135, m12: -0.0685, m20: -0.1614, m21: 0.0367, m22: 1.0296 }, MtxAdaptMaI: { m00: 0.9869929054667123, m01: 0.43230526972339456, m02: -0.008528664575177328, m10: -0.14705425642099013, m11: 0.5183602715367776, m12: 0.04004282165408487, m20: 0.15996265166373125, m21: 0.0492912282128556, m22: 0.9684866957875502 } }; // taken from https://de.mathworks.com/help/images/ref/whitepoint.html const ILLUMINANTS = new Map([ // ASTM E308-01 ['a', [1.0985, 0.35585]], // Wyszecki & Stiles, p. 769 ['b', [1.0985, 0.35585]], // C ASTM E308-01 ['c', [0.98074, 1.18232]], // D50 (ASTM E308-01) ['d50', [0.96422, 0.82521]], // D55 (ASTM E308-01) ['d55', [0.95682, 0.92149]], // D65 (ASTM E308-01) ['d65', [0.95047, 1.08883]], // E (ASTM E308-01) ['e', [1, 1, 1]], // F2 (ASTM E308-01) ['f2', [0.99186, 0.67393]], // F7 (ASTM E308-01) ['f7', [0.95041, 1.08747]], // F11 (ASTM E308-01) ['f11', [1.00962, 0.6435]], ['icc', [0.96422, 0.82521]] ]); function setLabWhitePoint(name) { const ill = ILLUMINANTS.get(String(name).toLowerCase()); if (!ill) { throw new Error('unknown Lab illuminant ' + name); } labConstants.labWhitePoint = name; labConstants.Xn = ill[0]; labConstants.Zn = ill[1]; } function getLabWhitePoint() { return labConstants.labWhitePoint; } /* * L* [0..100] * a [-100..100] * b [-100..100] */ const lab2rgb = (...args) => { args = unpack(args, 'lab'); const [L, a, b] = args; const [x, y, z] = lab2xyz(L, a, b); const [r, g, b_] = xyz2rgb(x, y, z); return [r, g, b_, args.length > 3 ? args[3] : 1]; }; const lab2xyz = (L, a, b) => { const { kE, kK, kKE, Xn, Yn, Zn } = labConstants; const fy = (L + 16.0) / 116.0; const fx = 0.002 * a + fy; const fz = fy - 0.005 * b; const fx3 = fx * fx * fx; const fz3 = fz * fz * fz; const xr = fx3 > kE ? fx3 : (116.0 * fx - 16.0) / kK; const yr = L > kKE ? Math.pow((L + 16.0) / 116.0, 3.0) : L / kK; const zr = fz3 > kE ? fz3 : (116.0 * fz - 16.0) / kK; const x = xr * Xn; const y = yr * Yn; const z = zr * Zn; return [x, y, z]; }; const compand = (linear) => { /* sRGB */ const sign = Math.sign(linear); linear = Math.abs(linear); return ( (linear <= 0.0031308 ? linear * 12.92 : 1.055 * Math.pow(linear, 1.0 / 2.4) - 0.055) * sign ); }; const xyz2rgb = (x, y, z) => { const { MtxAdaptMa, MtxAdaptMaI, MtxXYZ2RGB, RefWhiteRGB, Xn, Yn, Zn } = labConstants; const As = Xn * MtxAdaptMa.m00 + Yn * MtxAdaptMa.m10 + Zn * MtxAdaptMa.m20; const Bs = Xn * MtxAdaptMa.m01 + Yn * MtxAdaptMa.m11 + Zn * MtxAdaptMa.m21; const Cs = Xn * MtxAdaptMa.m02 + Yn * MtxAdaptMa.m12 + Zn * MtxAdaptMa.m22; const Ad = RefWhiteRGB.X * MtxAdaptMa.m00 + RefWhiteRGB.Y * MtxAdaptMa.m10 + RefWhiteRGB.Z * MtxAdaptMa.m20; const Bd = RefWhiteRGB.X * MtxAdaptMa.m01 + RefWhiteRGB.Y * MtxAdaptMa.m11 + RefWhiteRGB.Z * MtxAdaptMa.m21; const Cd = RefWhiteRGB.X * MtxAdaptMa.m02 + RefWhiteRGB.Y * MtxAdaptMa.m12 + RefWhiteRGB.Z * MtxAdaptMa.m22; const X1 = (x * MtxAdaptMa.m00 + y * MtxAdaptMa.m10 + z * MtxAdaptMa.m20) * (Ad / As); const Y1 = (x * MtxAdaptMa.m01 + y * MtxAdaptMa.m11 + z * MtxAdaptMa.m21) * (Bd / Bs); const Z1 = (x * MtxAdaptMa.m02 + y * MtxAdaptMa.m12 + z * MtxAdaptMa.m22) * (Cd / Cs); const X2 = X1 * MtxAdaptMaI.m00 + Y1 * MtxAdaptMaI.m10 + Z1 * MtxAdaptMaI.m20; const Y2 = X1 * MtxAdaptMaI.m01 + Y1 * MtxAdaptMaI.m11 + Z1 * MtxAdaptMaI.m21; const Z2 = X1 * MtxAdaptMaI.m02 + Y1 * MtxAdaptMaI.m12 + Z1 * MtxAdaptMaI.m22; const r = compand( X2 * MtxXYZ2RGB.m00 + Y2 * MtxXYZ2RGB.m10 + Z2 * MtxXYZ2RGB.m20 ); const g = compand( X2 * MtxXYZ2RGB.m01 + Y2 * MtxXYZ2RGB.m11 + Z2 * MtxXYZ2RGB.m21 ); const b = compand( X2 * MtxXYZ2RGB.m02 + Y2 * MtxXYZ2RGB.m12 + Z2 * MtxXYZ2RGB.m22 ); return [r * 255, g * 255, b * 255]; }; const rgb2lab = (...args) => { const [r, g, b, ...rest] = unpack(args, 'rgb'); const [x, y, z] = rgb2xyz(r, g, b); const [L, a, b_] = xyz2lab(x, y, z); return [L, a, b_, ...(rest.length > 0 && rest[0] < 1 ? [rest[0]] : [])]; }; function xyz2lab(x, y, z) { const { Xn, Yn, Zn, kE, kK } = labConstants; const xr = x / Xn; const yr = y / Yn; const zr = z / Zn; const fx = xr > kE ? Math.pow(xr, 1.0 / 3.0) : (kK * xr + 16.0) / 116.0; const fy = yr > kE ? Math.pow(yr, 1.0 / 3.0) : (kK * yr + 16.0) / 116.0; const fz = zr > kE ? Math.pow(zr, 1.0 / 3.0) : (kK * zr + 16.0) / 116.0; return [116.0 * fy - 16.0, 500.0 * (fx - fy), 200.0 * (fy - fz)]; } function gammaAdjustSRGB(companded) { const sign = Math.sign(companded); companded = Math.abs(companded); const linear = companded <= 0.04045 ? companded / 12.92 : Math.pow((companded + 0.055) / 1.055, 2.4); return linear * sign; } const rgb2xyz = (r, g, b) => { // normalize and gamma adjust r = gammaAdjustSRGB(r / 255); g = gammaAdjustSRGB(g / 255); b = gammaAdjustSRGB(b / 255); const { MtxRGB2XYZ, MtxAdaptMa, MtxAdaptMaI, Xn, Yn, Zn, As, Bs, Cs } = labConstants; let x = r * MtxRGB2XYZ.m00 + g * MtxRGB2XYZ.m10 + b * MtxRGB2XYZ.m20; let y = r * MtxRGB2XYZ.m01 + g * MtxRGB2XYZ.m11 + b * MtxRGB2XYZ.m21; let z = r * MtxRGB2XYZ.m02 + g * MtxRGB2XYZ.m12 + b * MtxRGB2XYZ.m22; const Ad = Xn * MtxAdaptMa.m00 + Yn * MtxAdaptMa.m10 + Zn * MtxAdaptMa.m20; const Bd = Xn * MtxAdaptMa.m01 + Yn * MtxAdaptMa.m11 + Zn * MtxAdaptMa.m21; const Cd = Xn * MtxAdaptMa.m02 + Yn * MtxAdaptMa.m12 + Zn * MtxAdaptMa.m22; let X = x * MtxAdaptMa.m00 + y * MtxAdaptMa.m10 + z * MtxAdaptMa.m20; let Y = x * MtxAdaptMa.m01 + y * MtxAdaptMa.m11 + z * MtxAdaptMa.m21; let Z = x * MtxAdaptMa.m02 + y * MtxAdaptMa.m12 + z * MtxAdaptMa.m22; X *= Ad / As; Y *= Bd / Bs; Z *= Cd / Cs; x = X * MtxAdaptMaI.m00 + Y * MtxAdaptMaI.m10 + Z * MtxAdaptMaI.m20; y = X * MtxAdaptMaI.m01 + Y * MtxAdaptMaI.m11 + Z * MtxAdaptMaI.m21; z = X * MtxAdaptMaI.m02 + Y * MtxAdaptMaI.m12 + Z * MtxAdaptMaI.m22; return [x, y, z]; }; Color.prototype.lab = function () { return rgb2lab(this._rgb); }; const lab$1 = (...args) => new Color(...args, 'lab'); Object.assign(chroma, { lab: lab$1, getLabWhitePoint, setLabWhitePoint }); input.format.lab = lab2rgb; input.autodetect.push({ p: 2, test: (...args) => { args = unpack(args, 'lab'); if (type(args) === 'array' && args.length === 3) { return 'lab'; } } }); Color.prototype.darken = function (amount = 1) { const me = this; const lab = me.lab(); lab[0] -= labConstants.Kn * amount; return new Color(lab, 'lab').alpha(me.alpha(), true); }; Color.prototype.brighten = function (amount = 1) { return this.darken(-amount); }; Color.prototype.darker = Color.prototype.darken; Color.prototype.brighter = Color.prototype.brighten; Color.prototype.get = function (mc) { const [mode, channel] = mc.split('.'); const src = this[mode](); if (channel) { const i = mode.indexOf(channel) - (mode.substr(0, 2) === 'ok' ? 2 : 0); if (i > -1) return src[i]; throw new Error(`unknown channel ${channel} in mode ${mode}`); } else { return src; } }; const { pow: pow$6 } = Math; const EPS = 1e-7; const MAX_ITER = 20; Color.prototype.luminance = function (lum, mode = 'rgb') { if (lum !== undefined && type(lum) === 'number') { if (lum === 0) { // return pure black return new Color([0, 0, 0, this._rgb[3]], 'rgb'); } if (lum === 1) { // return pure white return new Color([255, 255, 255, this._rgb[3]], 'rgb'); } // compute new color using... let cur_lum = this.luminance(); let max_iter = MAX_ITER; const test = (low, high) => { const mid = low.interpolate(high, 0.5, mode); const lm = mid.luminance(); if (Math.abs(lum - lm) < EPS || !max_iter--) { // close enough return mid; } return lm > lum ? test(low, mid) : test(mid, high); }; const rgb = ( cur_lum > lum ? test(new Color([0, 0, 0]), this) : test(this, new Color([255, 255, 255])) ).rgb(); return new Color([...rgb, this._rgb[3]]); } return rgb2luminance(...this._rgb.slice(0, 3)); }; const rgb2luminance = (r, g, b) => { // relative luminance // see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef r = luminance_x(r); g = luminance_x(g); b = luminance_x(b); return 0.2126 * r + 0.7152 * g + 0.0722 * b; }; const luminance_x = (x) => { x /= 255; return x <= 0.03928 ? x / 12.92 : pow$6((x + 0.055) / 1.055, 2.4); }; var index = {}; var mix = (col1, col2, f = 0.5, ...rest) => { let mode = rest[0] || 'lrgb'; if (!index[mode] && !rest.length) { // fall back to the first supported mode mode = Object.keys(index)[0]; } if (!index[mode]) { throw new Error(`interpolation mode ${mode} is not defined`); } if (type(col1) !== 'object') col1 = new Color(col1); if (type(col2) !== 'object') col2 = new Color(col2); return index[mode](col1, col2, f).alpha( col1.alpha() + f * (col2.alpha() - col1.alpha()) ); }; Color.prototype.mix = Color.prototype.interpolate = function ( col2, f = 0.5, ...rest ) { return mix(this, col2, f, ...rest); }; Color.prototype.premultiply = function (mutate = false) { const rgb = this._rgb; const a = rgb[3]; if (mutate) { this._rgb = [rgb[0] * a, rgb[1] * a, rgb[2] * a, a]; return this; } else { return new Color([rgb[0] * a, rgb[1] * a, rgb[2] * a, a], 'rgb'); } }; const { sin: sin$3, cos: cos$4 } = Math; const lch2lab = (...args) => { /* Convert from a qualitative parameter h and a quantitative parameter l to a 24-bit pixel. These formulas were invented by David Dalrymple to obtain maximum contrast without going out of gamut if the parameters are in the range 0-1. A saturation multiplier was added by Gregor Aisch */ let [l, c, h] = unpack(args, 'lch'); if (isNaN(h)) h = 0; h = h * DEG2RAD; return [l, cos$4(h) * c, sin$3(h) * c]; }; const lch2rgb = (...args) => { args = unpack(args, 'lch'); const [l, c, h] = args; const [L, a, b_] = lch2lab(l, c, h); const [r, g, b] = lab2rgb(L, a, b_); return [r, g, b, args.length > 3 ? args[3] : 1]; }; const hcl2rgb = (...args) => { const hcl = reverse3(unpack(args, 'hcl')); return lch2rgb(...hcl); }; const { sqrt: sqrt$4, atan2: atan2$2, round: round$4 } = Math; const lab2lch = (...args) => { const [l, a, b] = unpack(args, 'lab'); const c = sqrt$4(a * a + b * b); let h = (atan2$2(b, a) * RAD2DEG + 360) % 360; if (round$4(c * 10000) === 0) h = Number.NaN; return [l, c, h]; }; const rgb2lch = (...args) => { const [r, g, b, ...rest] = unpack(args, 'rgb'); const [l, a, b_] = rgb2lab(r, g, b); const [L, c, h] = lab2lch(l, a, b_); return [L, c, h, ...(rest.length > 0 && rest[0] < 1 ? [rest[0]] : [])]; }; Color.prototype.lch = function () { return rgb2lch(this._rgb); }; Color.prototype.hcl = function () { return reverse3(rgb2lch(this._rgb)); }; const lch$1 = (...args) => new Color(...args, 'lch'); const hcl = (...args) => new Color(...args, 'hcl'); Object.assign(chroma, { lch: lch$1, hcl }); input.format.lch = lch2rgb; input.format.hcl = hcl2rgb; ['lch', 'hcl'].forEach((m) => input.autodetect.push({ p: 2, test: (...args) => { args = unpack(args, m); if (type(args) === 'array' && args.length === 3) { return m; } } }) ); Color.prototype.saturate = function (amount = 1) { const me = this; const lch = me.lch(); lch[1] += labConstants.Kn * amount; if (lch[1] < 0) lch[1] = 0; return new Color(lch, 'lch').alpha(me.alpha(), true); }; Color.prototype.desaturate = function (amount = 1) { return this.saturate(-amount); }; Color.prototype.set = function (mc, value, mutate = false) { const [mode, channel] = mc.split('.'); const src = this[mode](); if (channel) { const i = mode.indexOf(channel) - (mode.substr(0, 2) === 'ok' ? 2 : 0); if (i > -1) { if (type(value) == 'string') { switch (value.charAt(0)) { case '+': src[i] += +value; break; case '-': src[i] += +value; break; case '*': src[i] *= +value.substr(1); break; case '/': src[i] /= +value.substr(1); break; default: src[i] = +value; } } else if (type(value) === 'number') { src[i] = value; } else { throw new Error(`unsupported value for Color.set`); } const out = new Color(src, mode); if (mutate) { this._rgb = out._rgb; return this; } return out; } throw new Error(`unknown channel ${channel} in mode ${mode}`); } else { return src; } }; Color.prototype.tint = function (f = 0.5, ...rest) { return mix(this, 'white', f, ...rest); }; Color.prototype.shade = function (f = 0.5, ...rest) { return mix(this, 'black', f, ...rest); }; const rgb$1 = (col1, col2, f) => { const xyz0 = col1._rgb; const xyz1 = col2._rgb; return new Color( xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), 'rgb' ); }; // register interpolator index.rgb = rgb$1; const { sqrt: sqrt$3, pow: pow$5 } = Math; const lrgb = (col1, col2, f) => { const [x1, y1, z1] = col1._rgb; const [x2, y2, z2] = col2._rgb; return new Color( sqrt$3(pow$5(x1, 2) * (1 - f) + pow$5(x2, 2) * f), sqrt$3(pow$5(y1, 2) * (1 - f) + pow$5(y2, 2) * f), sqrt$3(pow$5(z1, 2) * (1 - f) + pow$5(z2, 2) * f), 'rgb' ); }; // register interpolator index.lrgb = lrgb; const lab = (col1, col2, f) => { const xyz0 = col1.lab(); const xyz1 = col2.lab(); return new Color( xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), 'lab' ); }; // register interpolator index.lab = lab; var interpolate_hsx = (col1, col2, f, m) => { let xyz0, xyz1; if (m === 'hsl') { xyz0 = col1.hsl(); xyz1 = col2.hsl(); } else if (m === 'hsv') { xyz0 = col1.hsv(); xyz1 = col2.hsv(); } else if (m === 'hcg') { xyz0 = col1.hcg(); xyz1 = col2.hcg(); } else if (m === 'hsi') { xyz0 = col1.hsi(); xyz1 = col2.hsi(); } else if (m === 'lch' || m === 'hcl') { m = 'hcl'; xyz0 = col1.hcl(); xyz1 = col2.hcl(); } else if (m === 'oklch') { xyz0 = col1.oklch().reverse(); xyz1 = col2.oklch().reverse(); } let hue0, hue1, sat0, sat1, lbv0, lbv1; if (m.substr(0, 1) === 'h' || m === 'oklch') { [hue0, sat0, lbv0] = xyz0; [hue1, sat1, lbv1] = xyz1; } let sat, hue, lbv, dh; if (!isNaN(hue0) && !isNaN(hue1)) { // both colors have hue if (hue1 > hue0 && hue1 - hue0 > 180) { dh = hue1 - (hue0 + 360); } else if (hue1 < hue0 && hue0 - hue1 > 180) { dh = hue1 + 360 - hue0; } else { dh = hue1 - hue0; } hue = hue0 + f * dh; } else if (!isNaN(hue0)) { hue = hue0; if ((lbv1 == 1 || lbv1 == 0) && m != 'hsv') sat = sat0; } else if (!isNaN(hue1)) { hue = hue1; if ((lbv0 == 1 || lbv0 == 0) && m != 'hsv') sat = sat1; } else { hue = Number.NaN; } if (sat === undefined) sat = sat0 + f * (sat1 - sat0); lbv = lbv0 + f * (lbv1 - lbv0); return m === 'oklch' ? new Color([lbv, sat, hue], m) : new Color([hue, sat, lbv], m); }; const lch = (col1, col2, f) => { return interpolate_hsx(col1, col2, f, 'lch'); }; // register interpolator index.lch = lch; index.hcl = lch; const num2rgb = (num) => { if (type(num) == 'number' && num >= 0 && num <= 0xffffff) { const r = num >> 16; const g = (num >> 8) & 0xff; const b = num & 0xff; return [r, g, b, 1]; } throw new Error('unknown num color: ' + num); }; const rgb2num = (...args) => { const [r, g, b] = unpack(args, 'rgb'); return (r << 16) + (g << 8) + b; }; Color.prototype.num = function () { return rgb2num(this._rgb); }; const num$1 = (...args) => new Color(...args, 'num'); Object.assign(chroma, { num: num$1 }); input.format.num = num2rgb; input.autodetect.push({ p: 5, test: (...args) => { if ( args.length === 1 && type(args[0]) === 'number' && args[0] >= 0 && args[0] <= 0xffffff ) { return 'num'; } } }); const num = (col1, col2, f) => { const c1 = col1.num(); const c2 = col2.num(); return new Color(c1 + f * (c2 - c1), 'num'); }; // register interpolator index.num = num; const { floor: floor$3 } = Math; /* * this is basically just HSV with some minor tweaks * * hue.. [0..360] * chroma .. [0..1] * grayness .. [0..1] */ const hcg2rgb = (...args) => { args = unpack(args, 'hcg'); let [h, c, _g] = args; let r, g, b; _g = _g * 255; const _c = c * 255; if (c === 0) { r = g = b = _g; } else { if (h === 360) h = 0; if (h > 360) h -= 360; if (h < 0) h += 360; h /= 60; const i = floor$3(h); const f = h - i; const p = _g * (1 - c); const q = p + _c * (1 - f); const t = p + _c * f; const v = p + _c; switch (i) { case 0: [r, g, b] = [v, t, p]; break; case 1: [r, g, b] = [q, v, p]; break; case 2: [r, g, b] = [p, v, t]; break; case 3: [r, g, b] = [p, q, v]; break; case 4: [r, g, b] = [t, p, v]; break; case 5: [r, g, b] = [v, p, q]; break; } } return [r, g, b, args.length > 3 ? args[3] : 1]; }; const rgb2hcg = (...args) => { const [r, g, b] = unpack(args, 'rgb'); const minRgb = min$3(r, g, b); const maxRgb = max$3(r, g, b); const delta = maxRgb - minRgb; const c = (delta * 100) / 255; const _g = (minRgb / (255 - delta)) * 100; let h; if (delta === 0) { h = Number.NaN; } else { if (r === maxRgb) h = (g - b) / delta; if (g === maxRgb) h = 2 + (b - r) / delta; if (b === maxRgb) h = 4 + (r - g) / delta; h *= 60; if (h < 0) h += 360; } return [h, c, _g]; }; Color.prototype.hcg = function () { return rgb2hcg(this._rgb); }; const hcg$1 = (...args) => new Color(...args, 'hcg'); chroma.hcg = hcg$1; input.format.hcg = hcg2rgb; input.autodetect.push({ p: 1, test: (...args) => { args = unpack(args, 'hcg'); if (type(args) === 'array' && args.length === 3) { return 'hcg'; } } }); const hcg = (col1, col2, f) => { return interpolate_hsx(col1, col2, f, 'hcg'); }; // register interpolator index.hcg = hcg; const { cos: cos$3 } = Math; /* * hue [0..360] * saturation [0..1] * intensity [0..1] */ const hsi2rgb = (...args) => { /* borrowed from here: http://hummer.stanford.edu/museinfo/doc/examples/humdrum/keyscape2/hsi2rgb.cpp */ args = unpack(args, 'hsi'); let [h, s, i] = args; let r, g, b; if (isNaN(h)) h = 0; if (isNaN(s)) s = 0; // normalize hue if (h > 360) h -= 360; if (h < 0) h += 360; h /= 360; if (h < 1 / 3) { b = (1 - s) / 3; r = (1 + (s * cos$3(TWOPI * h)) / cos$3(PITHIRD - TWOPI * h)) / 3; g = 1 - (b + r); } else if (h < 2 / 3) { h -= 1 / 3; r = (1 - s) / 3; g = (1 + (s * cos$3(TWOPI * h)) / cos$3(PITHIRD - TWOPI * h)) / 3; b = 1 - (r + g); } else { h -= 2 / 3; g = (1 - s) / 3; b = (1 + (s * cos$3(TWOPI * h)) / cos$3(PITHIRD - TWOPI * h)) / 3; r = 1 - (g + b); } r = limit(i * r * 3); g = limit(i * g * 3); b = limit(i * b * 3); return [r * 255, g * 255, b * 255, args.length > 3 ? args[3] : 1]; }; const { min: min$2, sqrt: sqrt$2, acos } = Math; const rgb2hsi = (...args) => { /* borrowed from here: http://hummer.stanford.edu/museinfo/doc/examples/humdrum/keyscape2/rgb2hsi.cpp */ let [r, g, b] = unpack(args, 'rgb'); r /= 255; g /= 255; b /= 255; let h; const min_ = min$2(r, g, b); const i = (r + g + b) / 3; const s = i > 0 ? 1 - min_ / i : 0; if (s === 0) { h = NaN; } else { h = (r - g + (r - b)) / 2; h /= sqrt$2((r - g) * (r - g) + (r - b) * (g - b)); h = acos(h); if (b > g) { h = TWOPI - h; } h /= TWOPI; } return [h * 360, s, i]; }; Color.prototype.hsi = function () { return rgb2hsi(this._rgb); }; const hsi$1 = (...args) => new Color(...args, 'hsi'); chroma.hsi = hsi$1; input.format.hsi = hsi2rgb; input.autodetect.push({ p: 2, test: (...args) => { args = unpack(args, 'hsi'); if (type(args) === 'array' && args.length === 3) { return 'hsi'; } } }); const hsi = (col1, col2, f) => { return interpolate_hsx(col1, col2, f, 'hsi'); }; // register interpolator index.hsi = hsi; const hsl2rgb = (...args) => { args = unpack(args, 'hsl'); const [h, s, l] = args; let r, g, b; if (s === 0) { r = g = b = l * 255; } else { const t3 = [0, 0, 0]; const c = [0, 0, 0]; const t2 = l < 0.5 ? l * (1 + s) : l + s - l * s; const t1 = 2 * l - t2; const h_ = h / 360; t3[0] = h_ + 1 / 3; t3[1] = h_; t3[2] = h_ - 1 / 3; for (let i = 0; i < 3; i++) { if (t3[i] < 0) t3[i] += 1; if (t3[i] > 1) t3[i] -= 1; if (6 * t3[i] < 1) c[i] = t1 + (t2 - t1) * 6 * t3[i]; else if (2 * t3[i] < 1) c[i] = t2; else if (3 * t3[i] < 2) c[i] = t1 + (t2 - t1) * (2 / 3 - t3[i]) * 6; else c[i] = t1; } [r, g, b] = [c[0] * 255, c[1] * 255, c[2] * 255]; } if (args.length > 3) { // keep alpha channel return [r, g, b, args[3]]; } return [r, g, b, 1]; }; /* * supported arguments: * - rgb2hsl(r,g,b) * - rgb2hsl(r,g,b,a) * - rgb2hsl([r,g,b]) * - rgb2hsl([r,g,b,a]) * - rgb2hsl({r,g,b,a}) */ const rgb2hsl$1 = (...args) => { args = unpack(args, 'rgba'); let [r, g, b] = args; r /= 255; g /= 255; b /= 255; const minRgb = min$3(r, g, b); const maxRgb = max$3(r, g, b); const l = (maxRgb + minRgb) / 2; let s, h; if (maxRgb === minRgb) { s = 0; h = Number.NaN; } else { s = l < 0.5 ? (maxRgb - minRgb) / (maxRgb + minRgb) : (maxRgb - minRgb) / (2 - maxRgb - minRgb); } if (r == maxRgb) h = (g - b) / (maxRgb - minRgb); else if (g == maxRgb) h = 2 + (b - r) / (maxRgb - minRgb); else if (b == maxRgb) h = 4 + (r - g) / (maxRgb - minRgb); h *= 60; if (h < 0) h += 360; if (args.length > 3 && args[3] !== undefined) return [h, s, l, args[3]]; return [h, s, l]; }; Color.prototype.hsl = function () { return rgb2hsl$1(this._rgb); }; const hsl$1 = (...args) => new Color(...args, 'hsl'); chroma.hsl = hsl$1; input.format.hsl = hsl2rgb; input.autodetect.push({ p: 2, test: (...args) => { args = unpack(args, 'hsl'); if (type(args) === 'array' && args.length === 3) { return 'hsl'; } } }); const hsl = (col1, col2, f) => { return interpolate_hsx(col1, col2, f, 'hsl'); }; // register interpolator index.hsl = hsl; const { floor: floor$2 } = Math; const hsv2rgb = (...args) => { args = unpack(args, 'hsv'); let [h, s, v] = args; let r, g, b; v *= 255; if (s === 0) { r = g = b = v; } else { if (h === 360) h = 0; if (h > 360) h -= 360; if (h < 0) h += 360; h /= 60; const i = floor$2(h); const f = h - i; const p = v * (1 - s); const q = v * (1 - s * f); const t = v * (1 - s * (1 - f)); switch (i) { case 0: [r, g, b] = [v, t, p]; break; case 1: [r, g, b] = [q, v, p]; break; case 2: [r, g, b] = [p, v, t]; break; case 3: [r, g, b] = [p, q, v]; break; case 4: [r, g, b] = [t, p, v]; break; case 5: [r, g, b] = [v, p, q]; break; } } return [r, g, b, args.length > 3 ? args[3] : 1]; }; const { min: min$1, max: max$2 } = Math; /* * supported arguments: * - rgb2hsv(r,g,b) * - rgb2hsv([r,g,b]) * - rgb2hsv({r,g,b}) */ const rgb2hsl = (...args) => { args = unpack(args, 'rgb'); let [r, g, b] = args; const min_ = min$1(r, g, b); const max_ = max$2(r, g, b); const delta = max_ - min_; let h, s, v; v = max_ / 255.0; if (max_ === 0) { h = Number.NaN; s = 0; } else { s = delta / max_; if (r === max_) h = (g - b) / delta; if (g === max_) h = 2 + (b - r) / delta; if (b === max_) h = 4 + (r - g) / delta; h *= 60; if (h < 0) h += 360; } return [h, s, v]; }; Color.prototype.hsv = function () { return rgb2hsl(this._rgb); }; const hsv$1 = (...args) => new Color(...args, 'hsv'); chroma.hsv = hsv$1; input.format.hsv = hsv2rgb; input.autodetect.push({ p: 2, test: (...args) => { args = unpack(args, 'hsv'); if (type(args) === 'array' && args.length === 3) { return 'hsv'; } } }); const hsv = (col1, col2, f) => { return interpolate_hsx(col1, col2, f, 'hsv'); }; // register interpolator index.hsv = hsv; // from https://www.w3.org/TR/css-color-4/multiply-matrices.js function multiplyMatrices(A, B) { let m = A.length; if (!Array.isArray(A[0])) { // A is vector, convert to [[a, b, c, ...]] A = [A]; } if (!Array.isArray(B[0])) { // B is vector, convert to [[a], [b], [c], ...]] B = B.map((x) => [x]); } let p = B[0].length; let B_cols = B[0].map((_, i) => B.map((x) => x[i])); // transpose B let product = A.map((row) => B_cols.map((col) => { if (!Array.isArray(row)) { return col.reduce((a, c) => a + c * row, 0); } return row.reduce((a, c, i) => a + c * (col[i] || 0), 0); }) ); if (m === 1) { product = product[0]; // Avoid [[a, b, c, ...]] } if (p === 1) { return product.map((x) => x[0]); // Avoid [[a], [b], [c], ...]] } return product; } const oklab2rgb = (...args) => { args = unpack(args, 'lab'); const [L, a, b, ...rest] = args; const [X, Y, Z] = OKLab_to_XYZ([L, a, b]); const [r, g, b_] = xyz2rgb(X, Y, Z); return [r, g, b_, ...(rest.length > 0 && rest[0] < 1 ? [rest[0]] : [])]; }; // from https://www.w3.org/TR/css-color-4/#color-conversion-code function OKLab_to_XYZ(OKLab) { // Given OKLab, convert to XYZ relative to D65 var LMStoXYZ = [ [1.2268798758459243, -0.5578149944602171, 0.2813910456659647], [-0.0405757452148008, 1.112286803280317, -0.0717110580655164], [-0.0763729366746601, -0.4214933324022432, 1.5869240198367816] ]; var OKLabtoLMS = [ [1.0, 0.3963377773761749, 0.2158037573099136], [1.0, -0.1055613458156586, -0.0638541728258133], [1.0, -0.0894841775298119, -1.2914855480194092] ]; var LMSnl = multiplyMatrices(OKLabtoLMS, OKLab); return multiplyMatrices( LMStoXYZ, LMSnl.map((c) => c ** 3) ); } const rgb2oklab = (...args) => { const [r, g, b, ...rest] = unpack(args, 'rgb'); const xyz = rgb2xyz(r, g, b); const oklab = XYZ_to_OKLab(xyz); return [...oklab, ...(rest.length > 0 && rest[0] < 1 ? [rest[0]] : [])]; }; // from https://www.w3.org/TR/css-color-4/#color-conversion-code function XYZ_to_OKLab(XYZ) { // Given XYZ relative to D65, convert to OKLab const XYZtoLMS = [ [0.819022437996703, 0.3619062600528904, -0.1288737815209879], [0.0329836539323885, 0.9292868615863434, 0.0361446663506424], [0.0481771893596242, 0.2642395317527308, 0.6335478284694309] ]; const LMStoOKLab = [ [0.210454268309314, 0.7936177747023054, -0.0040720430116193], [1.9779985324311684, -2.4285922420485799, 0.450593709617411], [0.0259040424655478, 0.7827717124575296, -0.8086757549230774] ]; const LMS = multiplyMatrices(XYZtoLMS, XYZ); // JavaScript Math.cbrt returns a sign-matched cube root // beware if porting to other languages // especially if tempted to use a general power function return multiplyMatrices( LMStoOKLab, LMS.map((c) => Math.cbrt(c)) ); // L in range [0,1]. For use in CSS, multiply by 100 and add a percent } Color.prototype.oklab = function () { return rgb2oklab(this._rgb); }; const oklab$1 = (...args) => new Color(...args, 'oklab'); Object.assign(chroma, { oklab: oklab$1 }); input.format.oklab = oklab2rgb; input.autodetect.push({ p: 2, test: (...args) => { args = unpack(args, 'oklab'); if (type(args) === 'array' && args.length === 3) { return 'oklab'; } } }); const oklab = (col1, col2, f) => { const xyz0 = col1.oklab(); const xyz1 = col2.oklab(); return new Color( xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), 'ok