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


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

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

import { Rectangle } from './rectangle';

const UI = {
  borderWidth: 1,
  handleSize: 10,
  handleBorderWidth: 1,
  textOffset: 4,
  textLineHeightMultiplier: 1.2,
};

export class RectanglePaneRenderer implements IPrimitivePaneRenderer {
  private readonly rectangle: Rectangle;

  constructor(rectangle: Rectangle) {
    this.rectangle = rectangle;
  }

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

    if (!data) {
      return;
    }

    const { colors } = getThemeStore();

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

      const left = data.left * horizontalPixelRatio;
      const right = data.right * horizontalPixelRatio;
      const top = data.top * verticalPixelRatio;
      const bottom = data.bottom * verticalPixelRatio;

      context.save();

      if (data.showFill) {
        context.fillStyle = data.fillColor;
        context.fillRect(left, top, right - left, bottom - top);
      }

      context.lineWidth = UI.borderWidth * pixelRatio;
      context.strokeStyle = data.borderColor;
      context.strokeRect(left, top, right - left, bottom - top);

      drawRectangleText(context, {
        left,
        top,
        text: data.text,
        fontSize: data.fontSize,
        isBold: data.isBold,
        isItalic: data.isItalic,
        textColor: data.textColor,
        horizontalPixelRatio,
        verticalPixelRatio,
      });

      if (data.showHandles) {
        for (const handle of Object.values(data.handles)) {
          drawHandle(
            context,
            handle.x * horizontalPixelRatio,
            handle.y * verticalPixelRatio,
            horizontalPixelRatio,
            verticalPixelRatio,
            colors.chartLineColor,
            colors.chartBackground,
          );
        }
      }

      context.restore();
    });
  }
}

function drawRectangleText(
  context: CanvasRenderingContext2D,
  params: {
    left: number;
    top: number;
    text: string;
    fontSize: number;
    isBold: boolean;
    isItalic: boolean;
    textColor: string;
    horizontalPixelRatio: number;
    verticalPixelRatio: number;
  },
): void {
  const { left, top, text, fontSize, isBold, isItalic, textColor, 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 offsetX = UI.textOffset * horizontalPixelRatio;
  const offsetY = UI.textOffset * verticalPixelRatio;

  const fontWeight = isBold ? '700 ' : '';
  const fontStyle = isItalic ? 'italic ' : '';

  const textX = left + offsetX;
  const blockHeight = lines.length * lineHeight;
  const firstLineY = top - offsetY - blockHeight + lineHeight / 2;

  context.save();

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

  lines.forEach((line, index) => {
    context.fillText(line, textX, firstLineY + 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();
}