import cloneDeep from 'lodash-es/cloneDeep';
import { INDICATOR_COLOR_PALETTE } from '@src/theme';
import { IndicatorConfig, IndicatorSerie, MASource, SerieOptionsWithColor } from '@src/types';
import { normalizeColor } from '@src/utils/normalizeColor';
export function getMASource(value: unknown): MASource {
return value === 'open' || value === 'high' || value === 'low' || value === 'close' ? value : 'close';
}
export function applyNextIndicatorColors(config: IndicatorConfig, usedColors: readonly string[]): IndicatorConfig {
const nextConfig = cloneDeep(config);
const reservedColors = new Set(usedColors.map(normalizeColor));
const colorizableSeriesCount = nextConfig.series.filter(shouldUsePaletteColor).length;
const singleSeriesStartIndex = nextConfig.paletteStartIndex ?? 0;
const groupedSeriesStartIndex =
nextConfig.paletteStartIndex ?? getNextFreePaletteIndex(reservedColors);
let colorizableSeriesIndex = 0;
nextConfig.series = nextConfig.series.map((serie) => {
if (!shouldUsePaletteColor(serie)) {
return serie;
}
const color =
colorizableSeriesCount > 1
? getGroupedPaletteColor(
reservedColors,
groupedSeriesStartIndex,
colorizableSeriesIndex,
colorizableSeriesCount,
)
: getPaletteColorFromIndex(reservedColors, singleSeriesStartIndex);
colorizableSeriesIndex += 1;
reservedColors.add(normalizeColor(color));
return {
...serie,
seriesOptions: {
...serie.seriesOptions,
color,
},
};
});
return nextConfig;
}
export function getIndicatorColors(config: IndicatorConfig): string[] {
return config.series.reduce<string[]>((colors, serie) => {
if (!shouldUsePaletteColor(serie)) {
return colors;
}
const color = getSerieColor(serie);
if (color) {
colors.push(color);
}
return colors;
}, []);
}
function getGroupedPaletteColor(
usedColors: Set<string>,
startIndex: number,
seriesIndex: number,
seriesCount: number,
): string {
const step = Math.max(1, Math.floor(INDICATOR_COLOR_PALETTE.length / seriesCount));
return getPaletteColorFromIndex(usedColors, startIndex + seriesIndex * step);
}
function getPaletteColorFromIndex(usedColors: Set<string>, startIndex: number): string {
for (let offset = 0; offset < INDICATOR_COLOR_PALETTE.length; offset += 1) {
const color = INDICATOR_COLOR_PALETTE[(startIndex + offset) % INDICATOR_COLOR_PALETTE.length];
if (!usedColors.has(normalizeColor(color))) {
return color;
}
}
return createFallbackColor(usedColors.size);
}
function getNextFreePaletteIndex(usedColors: Set<string>): number {
const index = INDICATOR_COLOR_PALETTE.findIndex((color) => !usedColors.has(normalizeColor(color)));
return index === -1 ? 0 : index;
}
function createFallbackColor(index: number): string {
const hue = Math.round((index * 137.508) % 360);
return `hsl(${hue}deg 72% 56%)`;
}
function shouldUsePaletteColor(serie: IndicatorSerie): boolean {
const options = serie.seriesOptions as SerieOptionsWithColor | undefined;
return serie.name === 'Line' && options?.visible !== false;
}
function getSerieColor(serie: IndicatorSerie): string | null {
const options = serie.seriesOptions as SerieOptionsWithColor | undefined;
const color = options?.color;
return typeof color === 'string' && color.trim() ? color : null;
}