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


import { getThemeStore } from '@src/theme';
import { SettingField, SettingsTab, SettingsValues } from '@src/types';

export interface AxisLineStyle {
  lineColor: string;
}

export interface AxisLineTextStyle {
  text: string;
  fontSize: number;
  isBold: boolean;
  isItalic: boolean;
  textColor: string;
}

export type AxisLineSettings = AxisLineStyle & AxisLineTextStyle & SettingsValues;

export function createDefaultSettings(): AxisLineSettings {
  const { colors } = getThemeStore();

  return {
    lineColor: colors.chartLineColor,
    text: '',
    fontSize: 14,
    isBold: false,
    isItalic: false,
    textColor: colors.chartPriceLineText,
  };
}

export function getAxisLineSettingsTabs(settings: AxisLineSettings): SettingsTab[] {
  const styleFields: SettingField[] = [
    {
      key: 'lineColor',
      label: 'Цвет линии',
      type: 'color',
      defaultValue: settings.lineColor,
    },
  ];

  const textFields: SettingField[] = [
    {
      key: 'fontSize',
      label: 'Размер текста',
      type: 'number',
      defaultValue: settings.fontSize,
      min: 8,
      max: 24,
    },
    {
      key: 'text',
      label: 'Текст',
      type: 'textarea',
      defaultValue: settings.text,
      placeholder: 'Введите текст',
    },
    {
      key: 'isBold',
      label: 'Жирный текст',
      type: 'boolean',
      defaultValue: settings.isBold,
    },
    {
      key: 'isItalic',
      label: 'Курсив',
      type: 'boolean',
      defaultValue: settings.isItalic,
    },
    {
      key: 'textColor',
      label: 'Цвет текста',
      type: 'color',
      defaultValue: settings.textColor,
    },
  ];

  return [
    { key: 'style', label: 'Стиль', fields: styleFields },
    { key: 'text', label: 'Текст', fields: textFields },
  ];
}



import { CanvasRenderingTarget2D } from 'fancy-canvas';
import { IPrimitivePaneRenderer } from 'lightweight-charts';

import { getThemeStore } from '@src/theme';

import { AxisLine } from './axisLine';

const UI = {
  lineWidth: 1,
  handleSize: 10,
  handleBorderWidth: 1,
  textLineHeightMultiplier: 1.2,
  textOffset: 6,
};

export class AxisLinePaneRenderer implements IPrimitivePaneRenderer {
  private readonly axisLine: AxisLine;

  constructor(axisLine: AxisLine) {
    this.axisLine = axisLine;
  }

  public draw(target: CanvasRenderingTarget2D): void {
    const data = this.axisLine.getRenderData();

    if (!data) {
      return;
    }

    const { colors } = getThemeStore();

    target.useBitmapCoordinateSpace(({ context, bitmapSize, horizontalPixelRatio, verticalPixelRatio }) => {
      const pixelRatio = Math.max(horizontalPixelRatio, verticalPixelRatio);

      context.save();
      context.fillStyle = data.lineColor;

      if (data.direction === 'vertical') {
        const x = Math.round(data.coordinate * horizontalPixelRatio);
        const width = Math.max(1, UI.lineWidth * pixelRatio);

        context.fillRect(x - width / 2, 0, width, bitmapSize.height);
      }

      if (data.direction === 'horizontal') {
        const y = Math.round(data.coordinate * verticalPixelRatio);
        const height = Math.max(1, UI.lineWidth * pixelRatio);

        context.fillRect(0, y - height / 2, bitmapSize.width, height);
      }

      drawTextAlongAxisLine(context, {
        direction: data.direction,
        coordinate: data.coordinate,
        text: data.text,
        fontSize: data.fontSize,
        isBold: data.isBold,
        isItalic: data.isItalic,
        textColor: data.textColor,
        bitmapWidth: bitmapSize.width,
        bitmapHeight: bitmapSize.height,
        horizontalPixelRatio,
        verticalPixelRatio,
      });

      if (data.showHandle) {
        drawHandle(
          context,
          data.handle.x * horizontalPixelRatio,
          data.handle.y * verticalPixelRatio,
          horizontalPixelRatio,
          verticalPixelRatio,
          colors.chartLineColor,
          colors.chartBackground,
        );
      }

      context.restore();
    });
  }
}

function drawTextAlongAxisLine(
  context: CanvasRenderingContext2D,
  params: {
    direction: 'vertical' | 'horizontal';
    coordinate: number;
    text: string;
    fontSize: number;
    isBold: boolean;
    isItalic: boolean;
    textColor: string;
    bitmapWidth: number;
    bitmapHeight: number;
    horizontalPixelRatio: number;
    verticalPixelRatio: number;
  },
): void {
  const {
    direction,
    coordinate,
    text,
    fontSize,
    isBold,
    isItalic,
    textColor,
    bitmapWidth,
    bitmapHeight,
    horizontalPixelRatio,
    verticalPixelRatio,
  } = params;

  if (!text.trim()) {
    return;
  }

  const lines = text.split('\n');
  const safeFontSize = Math.max(1, fontSize);
  const fontSizePx = safeFontSize * verticalPixelRatio;
  const lineHeight = safeFontSize * UI.textLineHeightMultiplier * verticalPixelRatio;
  const fontWeight = isBold ? '700 ' : '';
  const fontStyle = isItalic ? 'italic ' : '';
  const textOffset = UI.textOffset * Math.max(horizontalPixelRatio, verticalPixelRatio);

  const x = direction === 'vertical' ? coordinate * horizontalPixelRatio : bitmapWidth / 2;
  const y = direction === 'horizontal' ? coordinate * verticalPixelRatio : bitmapHeight / 2;
  const angle = direction === 'vertical' ? -Math.PI / 2 : 0;

  context.save();
  context.translate(x, y);
  context.rotate(angle);

  context.font = `${fontStyle}${fontWeight}${fontSizePx}px Inter, sans-serif`;
  context.fillStyle = textColor;
  context.textAlign = 'center';
  context.textBaseline = 'middle';

  const blockHeight = lines.length * lineHeight;
  const textCenterY = -(blockHeight / 2 + textOffset);
  const startLineY = textCenterY - blockHeight / 2 + lineHeight / 2;

  lines.forEach((line, index) => {
    context.fillText(line, 0, startLineY + index * lineHeight);
  });

  context.restore();
}

function drawHandle(
  context: CanvasRenderingContext2D,
  x: number,
  y: number,
  horizontalPixelRatio: number,
  verticalPixelRatio: number,
  strokeColor: string,
  fillColor: string,
): void {
  const width = UI.handleSize * horizontalPixelRatio;
  const height = UI.handleSize * verticalPixelRatio;
  const left = x - width / 2;
  const top = y - height / 2;

  context.save();
  context.fillStyle = fillColor;
  context.strokeStyle = strokeColor;
  context.lineWidth = UI.handleBorderWidth * Math.max(horizontalPixelRatio, verticalPixelRatio);

  context.beginPath();
  context.rect(left, top, width, height);
  context.fill();
  context.stroke();

  context.restore();
}