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


export function applyNextIndicatorColors(config: IndicatorConfig, usedColors: readonly string[]): IndicatorConfig {
  const reservedColors = new Set(usedColors.map(normalizeColor));
  const colorizableSeriesCount = config.series.filter(shouldUsePaletteColor).length;
  const startIndex = getNextFreePaletteIndex(reservedColors);
  const step = colorizableSeriesCount > 1 ? Math.max(1, Math.floor(indicatorColorPalette.length / colorizableSeriesCount)) : 1;

  let colorizableSeriesIndex = 0;

  return {
    ...config,
    settings: config.settings ? [...config.settings] : undefined,
    seriesLabels: config.seriesLabels ? { ...config.seriesLabels } : undefined,
    series: config.series.map((serie) => {
      const nextSerie = cloneSerie(serie);

      if (!shouldUsePaletteColor(serie)) {
        return nextSerie;
      }

      const color =
        colorizableSeriesCount > 1
          ? getPaletteColorFromIndex(reservedColors, startIndex + colorizableSeriesIndex * step)
          : getNextPaletteColor(reservedColors);

      colorizableSeriesIndex += 1;
      reservedColors.add(normalizeColor(color));

      return {
        ...nextSerie,
        seriesOptions: {
          ...nextSerie.seriesOptions,
          color,
        },
      };
    }),
  };
}

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 getNextPaletteColor(usedColors: Set<string>): string {
  return getPaletteColorFromIndex(usedColors, 0);
}

function getPaletteColorFromIndex(usedColors: Set<string>, startIndex: number): string {
  for (let offset = 0; offset < indicatorColorPalette.length; offset += 1) {
    const color = indicatorColorPalette[(startIndex + offset) % indicatorColorPalette.length];

    if (!usedColors.has(normalizeColor(color))) {
      return color;
    }
  }

  return createFallbackColor(usedColors.size);
}

function getNextFreePaletteIndex(usedColors: Set<string>): number {
  const index = indicatorColorPalette.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;
}

function cloneSerie(serie: IndicatorSerie): IndicatorSerie {
  return {
    ...serie,
    seriesOptions: serie.seriesOptions ? { ...serie.seriesOptions } : undefined,
    priceScaleOptions: serie.priceScaleOptions ? { ...serie.priceScaleOptions } : undefined,
  };
}