devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
472 lines (458 loc) • 17.1 kB
JavaScript
/**
* 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
}
}
}