import { LinearScale, LogarithmicScale, LogarithmicScaleOptions, LinearScaleOptions } from 'chart.js'; import { merge } from 'chart.js/helpers'; import { interpolateBlues, interpolateBrBG, interpolateBuGn, interpolateBuPu, interpolateCividis, interpolateCool, interpolateCubehelixDefault, interpolateGnBu, interpolateGreens, interpolateGreys, interpolateInferno, interpolateMagma, interpolateOrRd, interpolateOranges, interpolatePRGn, interpolatePiYG, interpolatePlasma, interpolatePuBu, interpolatePuBuGn, interpolatePuOr, interpolatePuRd, interpolatePurples, interpolateRainbow, interpolateRdBu, interpolateRdGy, interpolateRdPu, interpolateRdYlBu, interpolateRdYlGn, interpolateReds, interpolateSinebow, interpolateSpectral, interpolateTurbo, interpolateViridis, interpolateWarm, interpolateYlGn, interpolateYlGnBu, interpolateYlOrBr, interpolateYlOrRd, } from 'd3-scale-chromatic'; import { baseDefaults, LegendScale, LogarithmicLegendScale, ILegendScaleOptions } from './LegendScale'; const lookup: { [key: string]: (normalizedValue: number) => string } = { interpolateBlues, interpolateBrBG, interpolateBuGn, interpolateBuPu, interpolateCividis, interpolateCool, interpolateCubehelixDefault, interpolateGnBu, interpolateGreens, interpolateGreys, interpolateInferno, interpolateMagma, interpolateOrRd, interpolateOranges, interpolatePRGn, interpolatePiYG, interpolatePlasma, interpolatePuBu, interpolatePuBuGn, interpolatePuOr, interpolatePuRd, interpolatePurples, interpolateRainbow, interpolateRdBu, interpolateRdGy, interpolateRdPu, interpolateRdYlBu, interpolateRdYlGn, interpolateReds, interpolateSinebow, interpolateSpectral, interpolateTurbo, interpolateViridis, interpolateWarm, interpolateYlGn, interpolateYlGnBu, interpolateYlOrBr, interpolateYlOrRd, }; Object.keys(lookup).forEach((key) => { lookup[`${key.charAt(11).toLowerCase()}${key.slice(12)}`] = lookup[key]; lookup[key.slice(11)] = lookup[key]; }); function quantize(v: number, steps: number) { const perStep = 1 / steps; if (v <= perStep) { return 0; } if (v >= 1 - perStep) { return 1; } for (let acc = 0; acc < 1; acc += perStep) { if (v < acc) { return acc - perStep / 2; // center } } return v; } export interface IColorScaleOptions extends ILegendScaleOptions { // support all options from linear scale -> https://www.chartjs.org/docs/latest/axes/cartesian/linear.html#linear-cartesian-axis // e.g. for tick manipulation, ... /** * color interpolation method which is either a function * converting a normalized value to string or a * well defined string of all the interpolation scales * from https://github.com/d3/d3-scale-chromatic. * e.g. interpolateBlues -> blues * * @default blues */ interpolate: | ((normalizedValue: number) => string) | 'blues' | 'brBG' | 'buGn' | 'buPu' | 'cividis' | 'cool' | 'cubehelixDefault' | 'gnBu' | 'greens' | 'greys' | 'inferno' | 'magma' | 'orRd' | 'oranges' | 'pRGn' | 'piYG' | 'plasma' | 'puBu' | 'puBuGn' | 'puOr' | 'puRd' | 'purples' | 'rainbow' | 'rdBu' | 'rdGy' | 'rdPu' | 'rdYlBu' | 'rdYlGn' | 'reds' | 'sinebow' | 'spectral' | 'turbo' | 'viridis' | 'warm' | 'ylGn' | 'ylGnBu' | 'ylOrBr' | 'ylOrRd'; /** * color value to render for missing values * @default transparent */ missing: string; /** * allows to split the color scale in N quantized equal bins. * @default 0 */ quantize: number; } const colorScaleDefaults = { interpolate: 'blues', missing: 'transparent', quantize: 0, }; export class ColorScale extends LegendScale { /** * @hidden */ get interpolate(): (v: number) => string { const o = this.options as IColorScaleOptions & LinearScaleOptions; if (!o) { return (v: number) => `rgb(${v},${v},${v})`; } if (typeof o.interpolate === 'function') { return o.interpolate; } return lookup[o.interpolate] || lookup.blues; } /** * @hidden */ getColorForValue(value: number): string { const v = this._getNormalizedValue(value); if (v == null || Number.isNaN(v)) { return this.options.missing; } return this.getColor(v); } /** * @hidden */ getColor(normalized: number): string { let v = normalized; if (this.options.quantize > 0) { v = quantize(v, this.options.quantize); } return this.interpolate(v); } /** * @hidden */ _drawIndicator(): void { const { indicatorWidth: indicatorSize } = this.options.legend; const reverse = (this as any)._reversePixels; if (this.isHorizontal()) { const w = this.width; if (this.options.quantize > 0) { const stepWidth = w / this.options.quantize; const offset = !reverse ? (i: number) => i : (i: number) => w - stepWidth - i; for (let i = 0; i < w; i += stepWidth) { const v = (i + stepWidth / 2) / w; this.ctx.fillStyle = this.getColor(v); this.ctx.fillRect(offset(i), 0, stepWidth, indicatorSize); } } else { const offset = !reverse ? (i: number) => i : (i: number) => w - 1 - i; for (let i = 0; i < w; i += 1) { this.ctx.fillStyle = this.getColor((i + 0.5) / w); this.ctx.fillRect(offset(i), 0, 1, indicatorSize); } } } else { const h = this.height; if (this.options.quantize > 0) { const stepWidth = h / this.options.quantize; const offset = !reverse ? (i: number) => i : (i: number) => h - stepWidth - i; for (let i = 0; i < h; i += stepWidth) { const v = (i + stepWidth / 2) / h; this.ctx.fillStyle = this.getColor(v); this.ctx.fillRect(0, offset(i), indicatorSize, stepWidth); } } else { const offset = !reverse ? (i: number) => i : (i: number) => h - 1 - i; for (let i = 0; i < h; i += 1) { this.ctx.fillStyle = this.getColor((i + 0.5) / h); this.ctx.fillRect(0, offset(i), indicatorSize, 1); } } } } static readonly id = 'color'; /** * @hidden */ static readonly defaults: any = /* #__PURE__ */ merge({}, [LinearScale.defaults, baseDefaults, colorScaleDefaults]); /** * @hidden */ static readonly descriptors = /* #__PURE__ */ { _scriptable: (name: string): boolean => name !== 'interpolate', _indexable: false, }; } export class ColorLogarithmicScale extends LogarithmicLegendScale { private interpolate = (v: number) => `rgb(${v},${v},${v})`; /** * @hidden */ init(options: IColorScaleOptions & LinearScaleOptions): void { super.init(options); if (typeof options.interpolate === 'function') { this.interpolate = options.interpolate; } else { this.interpolate = lookup[options.interpolate] || lookup.blues; } } /** * @hidden */ getColorForValue(value: number): string { return ColorScale.prototype.getColorForValue.call(this, value); } /** * @hidden */ getColor(normalized: number): string { let v = normalized; if (this.options.quantize > 0) { v = quantize(v, this.options.quantize); } return this.interpolate(v); } protected _drawIndicator(): void { return ColorScale.prototype._drawIndicator.call(this); } static readonly id = 'colorLogarithmic'; /** * @hidden */ static readonly defaults: any = /* #__PURE__ */ merge({}, [ LogarithmicScale.defaults, baseDefaults, colorScaleDefaults, ]); /** * @hidden */ static readonly descriptors = /* #__PURE__ */ { _scriptable: (name: string): boolean => name !== 'interpolate', _indexable: false, }; } declare module 'chart.js' { export interface ColorScaleTypeRegistry { color: { options: IColorScaleOptions & LinearScaleOptions; }; colorLogarithmic: { options: IColorScaleOptions & LogarithmicScaleOptions; }; } // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface ScaleTypeRegistry extends ColorScaleTypeRegistry {} }