Загрузка данных


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;
}