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


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

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

import { Traectory } from './traectory';

const UI = {
  lineWidth: 2,
  previewLineWidth: 1,
  handleRadius: 5,
  handleBorderWidth: 2,
  previewDash: [6, 4],
  arrowLength: 14,
  arrowHalfWidth: 6,
};

export class TraectoryPaneRenderer implements IPrimitivePaneRenderer {
  private readonly traectory: Traectory;

  constructor(traectory: Traectory) {
    this.traectory = traectory;
  }

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

    if (!data) {
      return;
    }

    target.useBitmapCoordinateSpace(({ context, horizontalPixelRatio, verticalPixelRatio }) => {
      const pixelRatio = Math.max(horizontalPixelRatio, verticalPixelRatio);
      const lineWidth = UI.lineWidth * pixelRatio;
      const previewLineWidth = UI.previewLineWidth * pixelRatio;
      const handleRadius = UI.handleRadius * pixelRatio;
      const handleBorderWidth = UI.handleBorderWidth * pixelRatio;
      const arrowLength = UI.arrowLength * pixelRatio;
      const arrowHalfWidth = UI.arrowHalfWidth * pixelRatio;

      context.save();
      context.lineJoin = 'round';
      context.lineCap = 'round';
      context.strokeStyle = data.lineColor;
      context.fillStyle = data.lineColor;

      if (data.points.length > 1) {
        context.lineWidth = lineWidth;
        context.beginPath();

        data.points.forEach((point, index) => {
          const x = point.x * horizontalPixelRatio;
          const y = point.y * verticalPixelRatio;

          if (index === 0) {
            context.moveTo(x, y);
            return;
          }

          context.lineTo(x, y);
        });

        context.stroke();
      }

      if (data.showArrow && data.points.length > 1) {
        const previousPoint = data.points[data.points.length - 2];
        const lastPoint = data.points[data.points.length - 1];

        context.lineWidth = lineWidth;
        drawArrowHead(
          context,
          previousPoint.x * horizontalPixelRatio,
          previousPoint.y * verticalPixelRatio,
          lastPoint.x * horizontalPixelRatio,
          lastPoint.y * verticalPixelRatio,
          arrowLength,
          arrowHalfWidth,
        );
      }

      if (data.previewPoint && data.points.length > 0) {
        const lastPoint = data.points[data.points.length - 1];

        context.save();
        context.lineWidth = previewLineWidth;
        context.setLineDash(UI.previewDash.map((value) => value * pixelRatio));
        context.beginPath();
        context.moveTo(lastPoint.x * horizontalPixelRatio, lastPoint.y * verticalPixelRatio);
        context.lineTo(data.previewPoint.x * horizontalPixelRatio, data.previewPoint.y * verticalPixelRatio);
        context.stroke();
        context.restore();
      }

      if (data.showHandles) {
        const { colors } = getThemeStore();

        context.fillStyle = colors.chartBackground;
        context.strokeStyle = colors.chartLineColor;
        context.lineWidth = handleBorderWidth;

        data.points.forEach((point) => {
          drawHandle(context, point.x * horizontalPixelRatio, point.y * verticalPixelRatio, handleRadius);
        });
      }

      context.restore();
    });
  }
}

function drawHandle(context: CanvasRenderingContext2D, x: number, y: number, radius: number): void {
  context.beginPath();
  context.arc(x, y, radius, 0, Math.PI * 2);
  context.fill();
  context.stroke();
}

function drawArrowHead(
  context: CanvasRenderingContext2D,
  fromX: number,
  fromY: number,
  toX: number,
  toY: number,
  arrowLength: number,
  arrowHalfWidth: number,
): void {
  const dx = toX - fromX;
  const dy = toY - fromY;
  const distance = Math.hypot(dx, dy);

  if (distance === 0) {
    return;
  }

  const directionX = dx / distance;
  const directionY = dy / distance;

  const normalX = -directionY;
  const normalY = directionX;

  const leftX = toX - directionX * arrowLength + normalX * arrowHalfWidth;
  const leftY = toY - directionY * arrowLength + normalY * arrowHalfWidth;

  const rightX = toX - directionX * arrowLength - normalX * arrowHalfWidth;
  const rightY = toY - directionY * arrowLength - normalY * arrowHalfWidth;

  context.beginPath();
  context.moveTo(toX, toY);
  context.lineTo(leftX, leftY);
  context.moveTo(toX, toY);
  context.lineTo(rightX, rightY);
  context.stroke();
}



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

export interface TraectoryStyle {
  lineColor: string;
}

export type TraectorySettings = SettingsValues & TraectoryStyle;

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

  return {
    lineColor: colors.chartLineColor,
  };
}

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

  return [{ key: 'style', label: 'Стиль', fields }];
}