highcharts
Version:
JavaScript charting framework
202 lines (201 loc) • 6.7 kB
JavaScript
/* *
*
* (c) 2010-2026 Highsoft AS
* Author: Torstein Honsi
*
* A commercial license may be required depending on use.
* See www.highcharts.com/license
*
*
* */
;
import Color from './Color.js';
import H from '../Globals.js';
const { charts } = H;
import PaletteDefaults from './PaletteDefaults.js';
import { diffObjects, extend, isString, objectEach, merge } from '../../Shared/Utilities.js';
/* *
*
* Constants
*
* */
const defaultColors = [
'#2caffe',
'#544fc5',
'#00e272',
'#fe6a35',
'#6b8abc',
'#d568fb',
'#2ee0ca',
'#fa4b42',
'#feb56a',
'#91e8e1'
];
const defaultDarkOverrideColors = [
null, // Use the same color for the first item in dark mode
'#00e272',
'#efdf00'
];
/* *
* Build the text content for the style tag
*/
const getStyles = (specifier, cssVars) => {
const reduceToCSS = (
// For browsers that don't support color-scheme and light-dark, we need
// to first set the CSS variables as the light scheme.
supportsColorScheme) => {
let css = '';
objectEach(cssVars.light, (value, key) => {
css += supportsColorScheme && value !== cssVars.dark[key] ?
` ${key}: light-dark(${value}, ${cssVars.dark[key]});\n` :
` ${key}: ${value};\n`;
});
return css;
};
const legacy = reduceToCSS(), lightDark = reduceToCSS(true);
const css = `${specifier || ':root'} {
${legacy}
}
@supports (color: light-dark(#fff, #000)) {
${specifier || ':root'} {
${lightDark}
}
}
.highcharts-container {
color-scheme: light dark;
}
.highcharts-light .highcharts-container {
color-scheme: light;
}
.highcharts-dark .highcharts-container {
color-scheme: dark;
}`;
return css;
};
/* *
*
* Class
*
* */
/* eslint-disable valid-jsdoc */
/**
* A Palette class holding the palette colors and lifecycle methods for each
* chart.
*
* @class
* @name Highcharts.Palette
*
* @param {Highcharts.Chart} chart
* The chart instance
* @param {Highcharts.PaletteOptions} options
* Palette options
*/
export default class Palette {
/* *
*
* Constructor
*
* */
constructor(renderer, options) {
this.defaultOptions = PaletteDefaults;
this.options = merge(PaletteDefaults);
this.cssVars = { light: {}, dark: {} };
this.renderer = renderer;
this.update(options);
}
/**
* Inject the CSS for the palette into the document head or chart
* container.
*
* @internal
*
* @param {Highcharts.PaletteOptions} options
* Palette options
*/
injectCSS(options) {
const { cssVars, renderer } = this, hasSpecificPalette = Object.keys(diffObjects(options, this.defaultOptions)).length > 0, colorCount = Math.max(options.colors?.length || 0, defaultColors.length);
let colorScheme = 'light';
const addKebab = (color, key) => {
if (isString(color)) {
// Kebab-case the key. Sequences of numbers should be kept
// but with a preceding dash.
key = key
.replace(/([0-9]+)/g, '-$1')
.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
cssVars[colorScheme][`--highcharts-${key}`] = color;
}
};
for (const cScheme of ['light', 'dark']) {
const paletteColors = options[cScheme] || {}, interpolated = {}, neutralColor = new Color(paletteColors?.neutralColor || ''), backgroundColor = new Color(paletteColors?.backgroundColor || ''), highlightColor = new Color(paletteColors?.highlightColor || '');
colorScheme = cScheme;
// Interpolate keys
[3, 5, 10, 20, 40, 60, 80, 100].forEach((fraction) => {
interpolated[`neutralColor${fraction}`] = backgroundColor
.tweenTo(neutralColor, fraction / 100);
interpolated[`highlightColor${fraction}`] = backgroundColor
.tweenTo(highlightColor, fraction / 100);
});
// Extended data colors per scheme
for (let i = 0; i < colorCount; i++) {
const color =
// 1. priority to scheme-specific colors
paletteColors?.colors?.[i] ||
// 2. then fall back to palette-level colors
options.colors?.[i] ||
// 3. then fall back to dark default colors
(cScheme === 'dark' ? defaultDarkOverrideColors : [])?.[i] ||
// 4. finally, use default colors
defaultColors[i];
addKebab(color || '#888a', `color${i}`);
}
// The rest are stored as named properties
objectEach(paletteColors, addKebab);
objectEach(interpolated, addKebab);
// Default to the light scheme, the dark scheme doesn't define
// all colors.
if (cScheme === 'light') {
extend(cssVars.dark, cssVars.light);
}
}
// Add a style tag to the chart renderer box
const defs = renderer.defs.element, specifier = hasSpecificPalette ?
`*[data-highcharts-chart="${renderer.chartIndex}"]` :
'', style = defs
.querySelector('style.highcharts-palette') ||
renderer.box.ownerDocument.createElement('style');
if (!style.parentNode) {
style.nonce = 'highcharts';
style.className = 'highcharts-palette';
defs.appendChild(style);
}
style.textContent = getStyles(specifier, cssVars);
}
/**
* Update the palette with new options. May be called directly as
* `chart.palette.update()` or indirectly from `chart.update({ palette })`.
*
* @function Highcharts.Palette#update
*
* @param {Highcharts.PaletteOptions} options
* New palette options
*/
update(options) {
const renderTo = this.renderer.box.parentElement, chart = charts?.[this.renderer.chartIndex];
options = merge(true, this.options, options);
if (chart) {
chart.options.palette = options;
}
if (options.injectCSS !== false) {
this.injectCSS(options);
}
if (renderTo) {
if (isString(options.colorScheme) &&
['light', 'dark', 'inherit'].includes(options.colorScheme)) {
renderTo.style.colorScheme = options.colorScheme;
}
else {
renderTo.style.removeProperty('color-scheme');
}
}
}
}