UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

472 lines (458 loc) • 17.1 kB
/** * DevExtreme (esm/viz/palette.js) * Version: 24.2.6 * Build date: Mon Mar 17 2025 * * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import { normalizeEnum } from "./core/utils"; import { extend } from "../core/utils/extend"; const _floor = Math.floor; const _ceil = Math.ceil; import _Color from "../color"; const _isArray = Array.isArray; import { isString as _isString } from "../core/utils/type"; const HIGHLIGHTING_STEP = 50; const DEFAULT_PALETTE = "material"; const officePalette = { simpleSet: ["#5f8b95", "#ba4d51", "#af8a53", "#955f71", "#859666", "#7e688c"], indicatingSet: ["#a3b97c", "#e1b676", "#ec7f83"], gradientSet: ["#5f8b95", "#ba4d51"], accentColor: "#ba4d51" }; const palettes = { [DEFAULT_PALETTE]: { simpleSet: ["#1db2f5", "#f5564a", "#97c95c", "#ffc720", "#eb3573", "#a63db8"], indicatingSet: ["#97c95c", "#ffc720", "#f5564a"], gradientSet: ["#1db2f5", "#97c95c"], accentColor: "#1db2f5" }, office: officePalette, "harmony light": { simpleSet: ["#fcb65e", "#679ec5", "#ad79ce", "#7abd5c", "#e18e92", "#b6d623", "#b7abea", "#85dbd5"], indicatingSet: ["#b6d623", "#fcb65e", "#e18e92"], gradientSet: ["#7abd5c", "#fcb65e"], accentColor: "#679ec5" }, "soft pastel": { simpleSet: ["#60a69f", "#78b6d9", "#6682bb", "#a37182", "#eeba69", "#90ba58", "#456c68", "#7565a4"], indicatingSet: ["#90ba58", "#eeba69", "#a37182"], gradientSet: ["#78b6d9", "#eeba69"], accentColor: "#60a69f" }, pastel: { simpleSet: ["#bb7862", "#70b3a1", "#bb626a", "#057d85", "#ab394b", "#dac599", "#153459", "#b1d2c6"], indicatingSet: ["#70b3a1", "#dac599", "#bb626a"], gradientSet: ["#bb7862", "#70b3a1"], accentColor: "#bb7862" }, bright: { simpleSet: ["#70c92f", "#f8ca00", "#bd1550", "#e97f02", "#9d419c", "#7e4452", "#9ab57e", "#36a3a6"], indicatingSet: ["#70c92f", "#f8ca00", "#bd1550"], gradientSet: ["#e97f02", "#f8ca00"], accentColor: "#e97f02" }, soft: { simpleSet: ["#cbc87b", "#9ab57e", "#e55253", "#7e4452", "#e8c267", "#565077", "#6babac", "#ad6082"], indicatingSet: ["#9ab57e", "#e8c267", "#e55253"], gradientSet: ["#9ab57e", "#e8c267"], accentColor: "#565077" }, ocean: { simpleSet: ["#75c099", "#acc371", "#378a8a", "#5fa26a", "#064970", "#38c5d2", "#00a7c6", "#6f84bb"], indicatingSet: ["#c8e394", "#7bc59d", "#397c8b"], gradientSet: ["#acc371", "#38c5d2"], accentColor: "#378a8a" }, vintage: { simpleSet: ["#dea484", "#efc59c", "#cb715e", "#eb9692", "#a85c4c", "#f2c0b5", "#c96374", "#dd956c"], indicatingSet: ["#ffe5c6", "#f4bb9d", "#e57660"], gradientSet: ["#efc59c", "#cb715e"], accentColor: "#cb715e" }, violet: { simpleSet: ["#d1a1d1", "#eeacc5", "#7b5685", "#7e7cad", "#a13d73", "#5b41ab", "#e287e2", "#689cc1"], indicatingSet: ["#d8e2f6", "#d0b2da", "#d56a8a"], gradientSet: ["#eeacc5", "#7b5685"], accentColor: "#7b5685" }, carmine: { simpleSet: ["#fb7764", "#73d47f", "#fed85e", "#d47683", "#dde392", "#757ab2"], indicatingSet: ["#5cb85c", "#f0ad4e", "#d9534f"], gradientSet: ["#fb7764", "#73d47f"], accentColor: "#f05b41" }, "dark moon": { simpleSet: ["#4ddac1", "#f4c99a", "#80dd9b", "#f998b3", "#4aaaa0", "#a5aef1"], indicatingSet: ["#59d8a4", "#f0ad4e", "#f9517e"], gradientSet: ["#4ddac1", "#f4c99a"], accentColor: "#3debd3" }, "soft blue": { simpleSet: ["#7ab8eb", "#97da97", "#facb86", "#e78683", "#839bda", "#4db7be"], indicatingSet: ["#5cb85c", "#f0ad4e", "#d9534f"], gradientSet: ["#7ab8eb", "#97da97"], accentColor: "#7ab8eb" }, "dark violet": { simpleSet: ["#9c63ff", "#64c064", "#eead51", "#d2504b", "#4b6bbf", "#2da7b0"], indicatingSet: ["#5cb85c", "#f0ad4e", "#d9534f"], gradientSet: ["#9c63ff", "#64c064"], accentColor: "#9c63ff" }, "green mist": { simpleSet: ["#3cbab2", "#8ed962", "#5b9d95", "#efcc7c", "#f1929f", "#4d8dab"], indicatingSet: ["#72d63c", "#ffc852", "#f74a5e"], gradientSet: ["#3cbab2", "#8ed962"], accentColor: "#3cbab2" } }; let currentPaletteName; export function currentPalette(name) { if (void 0 === name) { return currentPaletteName || "material" } else { name = normalizeEnum(name); currentPaletteName = name in palettes ? name : void 0 } } export function generateColors(palette, count) { let options = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : { keepLastColorInEnd: false }; options.type = options.baseColorSet; options.extensionMode = options.paletteExtensionMode; return createPalette(palette, options).generateColors(count) } export function getPalette(palette, parameters) { parameters = parameters || {}; palette = palette || (void 0 === currentPaletteName ? parameters.themeDefault : currentPalette()); let result; const type = parameters.type; if (_isArray(palette)) { return palette.slice(0) } else { if (_isString(palette)) { result = palettes[normalizeEnum(palette)] } if (!result) { result = palettes[currentPalette()] } } return type ? result[type].slice(0) : result } export function registerPalette(name, palette) { const item = {}; let paletteName; if (_isArray(palette)) { item.simpleSet = palette.slice(0) } else if (palette) { item.simpleSet = _isArray(palette.simpleSet) ? palette.simpleSet.slice(0) : void 0; item.indicatingSet = _isArray(palette.indicatingSet) ? palette.indicatingSet.slice(0) : void 0; item.gradientSet = _isArray(palette.gradientSet) ? palette.gradientSet.slice(0) : void 0; item.accentColor = palette.accentColor } if (!item.accentColor) { item.accentColor = item.simpleSet && item.simpleSet[0] } if (item.simpleSet || item.indicatingSet || item.gradientSet) { paletteName = normalizeEnum(name); extend(palettes[paletteName] = palettes[paletteName] || {}, item) } } export function getAccentColor(palette, themeDefault) { palette = getPalette(palette, { themeDefault: themeDefault }); return palette.accentColor || palette[0] } function RingBuf(buf) { let ind = 0; this.next = function() { const res = buf[ind++]; if (ind === buf.length) { this.reset() } return res }; this.reset = function() { ind = 0 } } function getAlternateColorsStrategy(palette, parameters) { const stepHighlight = parameters.useHighlight ? 50 : 0; const paletteSteps = new RingBuf([0, stepHighlight, -stepHighlight]); let currentPalette = []; function reset() { const step = paletteSteps.next(); currentPalette = step ? getAlteredPalette(palette, step) : palette.slice(0) } return { getColor: function(index) { const color = currentPalette[index % palette.length]; if (index % palette.length === palette.length - 1) { reset() } return color }, generateColors: function(count) { const colors = []; count = count || parameters.count; for (let i = 0; i < count; i++) { colors.push(this.getColor(i)) } return colors }, reset: function() { paletteSteps.reset(); reset() } } } function getExtrapolateColorsStrategy(palette, parameters) { return { getColor: function(index, count) { const paletteCount = palette.length; const cycles = _floor((count - 1) / paletteCount + 1); const color = palette[index % paletteCount]; if (cycles > 1) { return function(color, cycleIndex, cycleCount) { const hsl = new _Color(color).hsl; let l = hsl.l / 100; const diapason = cycleCount - 1 / cycleCount; let minL = l - .5 * diapason; let maxL = l + .5 * diapason; const cycleMiddle = (cycleCount - 1) / 2; const cycleDiff = cycleIndex - cycleMiddle; if (minL < Math.min(.5, .9 * l)) { minL = Math.min(.5, .9 * l) } if (maxL > Math.max(.8, l + .15 * (1 - l))) { maxL = Math.max(.8, l + .15 * (1 - l)) } if (cycleDiff < 0) { l -= (minL - l) * cycleDiff / cycleMiddle } else { l += cycleDiff / cycleMiddle * (maxL - l) } hsl.l = 100 * l; return _Color.prototype.fromHSL(hsl).toHex() }(color, _floor(index / paletteCount), cycles) } return color }, generateColors: function(count) { const colors = []; count = count || parameters.count; for (let i = 0; i < count; i++) { colors.push(this.getColor(i, count)) } return colors }, reset: function() {} } } function getColorMixer(palette, parameters) { const paletteCount = palette.length; let extendedPalette = []; function distributeColors(count, colorsCount, startIndex, distribution) { const groupSize = Math.floor(count / colorsCount); let extraItems = count - colorsCount * groupSize; let i = startIndex; let middleIndex; let size; while (i < startIndex + count) { size = groupSize; if (extraItems > 0) { size += 1; extraItems-- } middleIndex = size > 2 ? Math.floor(size / 2) : 0; distribution.push(i + middleIndex); i += size } return distribution.sort((function(a, b) { return a - b })) } function getColorAndDistance(arr, startIndex, count) { startIndex = (count + startIndex) % count; let distance = 0; for (let i = startIndex; i < 2 * count; i += 1) { const index = (count + i) % count; if (arr[index]) { return [arr[index], distance] } distance++ } } function extendPalette(count) { if (count <= paletteCount) { return palette } let result = []; const colorInGroups = paletteCount - 2; let currentColorIndex = 0; let cleanColorIndices = []; if (parameters.keepLastColorInEnd) { cleanColorIndices = distributeColors(count - 2, colorInGroups, 1, [0, count - 1]) } else { cleanColorIndices = distributeColors(count - 1, paletteCount - 1, 1, [0]) } for (let i = 0; i < count; i++) { if (cleanColorIndices.indexOf(i) > -1) { result[i] = palette[currentColorIndex++] } } result = function(paletteWithEmptyColors, paletteLength) { for (let i = 0; i < paletteLength; i++) { const color = paletteWithEmptyColors[i]; if (!color) { let color1 = paletteWithEmptyColors[i - 1]; if (!color1) { continue } else { const c2 = getColorAndDistance(paletteWithEmptyColors, i, paletteLength); const color2 = new _Color(c2[0]); color1 = new _Color(color1); for (let j = 0; j < c2[1]; j++, i++) { paletteWithEmptyColors[i] = color1.blend(color2, (j + 1) / (c2[1] + 1)).toHex() } } } } return paletteWithEmptyColors }(result, count); return result } return { getColor: function(index, count) { count = count || parameters.count || paletteCount; if (extendedPalette.length !== count) { extendedPalette = extendPalette(count) } return extendedPalette[index % count] }, generateColors: function(count, repeat) { count = count || parameters.count || paletteCount; if (repeat && count > paletteCount) { const colors = extendPalette(paletteCount); for (let i = 0; i < count - paletteCount; i++) { colors.push(colors[i]) } return colors } else { return paletteCount > 0 ? extendPalette(count).slice(0, count) : [] } }, reset: function() {} } } export function createPalette(palette, parameters, themeDefaultPalette) { const paletteObj = { dispose() { this._extensionStrategy = null }, getNextColor(count) { return this._extensionStrategy.getColor(this._currentColor++, count) }, generateColors(count, parameters) { return this._extensionStrategy.generateColors(count, (parameters || {}).repeat) }, reset() { this._currentColor = 0; this._extensionStrategy.reset(); return this } }; parameters = parameters || {}; const extensionMode = (parameters.extensionMode || "").toLowerCase(); const colors = getPalette(palette, { type: parameters.type || "simpleSet", themeDefault: themeDefaultPalette }); if ("alternate" === extensionMode) { paletteObj._extensionStrategy = getAlternateColorsStrategy(colors, parameters) } else if ("extrapolate" === extensionMode) { paletteObj._extensionStrategy = getExtrapolateColorsStrategy(colors, parameters) } else { paletteObj._extensionStrategy = getColorMixer(colors, parameters) } paletteObj.reset(); return paletteObj } function getAlteredPalette(originalPalette, step) { const palette = []; let i; const ii = originalPalette.length; for (i = 0; i < ii; ++i) { palette.push(getNewColor(originalPalette[i], step)) } return palette } function getNewColor(currentColor, step) { let newColor = new _Color(currentColor).alter(step); const lightness = getLightness(newColor); if (lightness > 200 || lightness < 55) { newColor = new _Color(currentColor).alter(-step / 2) } return newColor.toHex() } function getLightness(color) { return .3 * color.r + .59 * color.g + .11 * color.b } export function getDiscretePalette(source, size, themeDefaultPalette) { const palette = size > 0 ? createDiscreteColors(getPalette(source, { type: "gradientSet", themeDefault: themeDefaultPalette }), size) : []; return { getColor: function(index) { return palette[index] || null } } } function createDiscreteColors(source, count) { const colorCount = count - 1; const sourceCount = source.length - 1; const colors = []; const gradient = []; let i; function addColor(pos) { const k = sourceCount * pos; const kl = _floor(k); const kr = _ceil(k); gradient.push(colors[kl].blend(colors[kr], k - kl).toHex()) } for (i = 0; i <= sourceCount; ++i) { colors.push(new _Color(source[i])) } if (colorCount > 0) { for (i = 0; i <= colorCount; ++i) { addColor(i / colorCount) } } else { addColor(.5) } return gradient } export function getGradientPalette(source, themeDefaultPalette) { const palette = getPalette(source, { type: "gradientSet", themeDefault: themeDefaultPalette }); const color1 = new _Color(palette[0]); const color2 = new _Color(palette[1]); return { getColor: function(ratio) { return 0 <= ratio && ratio <= 1 ? color1.blend(color2, ratio).toHex() : null } } }