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


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

import type { VolumeProfile, VolumeProfileKind } from './volumeProfile';

const UI = {
  rowGap: 1,
  pocLineWidth: 2,
};

type DrawDirection = 'leftToRight' | 'rightToLeft';

export class VolumeProfilePaneRenderer implements IPrimitivePaneRenderer {
  private readonly volumeProfile: VolumeProfile;

  constructor(volumeProfile: VolumeProfile) {
    this.volumeProfile = volumeProfile;
  }

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

    if (!data) {
      return;
    }

    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();

      context.fillStyle = data.areaFillColor;
      context.fillRect(left, top, right - left, bottom - top);

      const direction: DrawDirection = data.profileKind === 'visibleRange' ? 'rightToLeft' : 'leftToRight';
      const rowStartX = data.profileKind === 'visibleRange' ? right : left;

      data.rows.forEach((row) => {
        const rowTop = row.top * verticalPixelRatio;
        const rowHeight = row.height * verticalPixelRatio;
        const buyWidth = row.buyWidth * horizontalPixelRatio;
        const sellWidth = row.sellWidth * horizontalPixelRatio;

        drawVolumeRow(
          context,
          rowStartX,
          rowTop,
          rowHeight,
          buyWidth,
          sellWidth,
          pixelRatio,
          data.buyFillColor,
          data.sellFillColor,
          direction,
        );
      });

      if (data.pocY !== null) {
        const pocY = data.pocY * verticalPixelRatio;

        drawPocLine(context, data.profileKind, left, right, pocY, pixelRatio, data.pocLineColor);
      }

      context.restore();
    });
  }
}

function drawVolumeRow(
  context: CanvasRenderingContext2D,
  startX: number,
  top: number,
  height: number,
  buyWidth: number,
  sellWidth: number,
  pixelRatio: number,
  buyFillColor: string,
  sellFillColor: string,
  direction: DrawDirection,
): void {
  const safeHeight = Math.max(1, height - UI.rowGap * pixelRatio);

  if (buyWidth <= 0 && sellWidth <= 0) {
    return;
  }

  if (direction === 'rightToLeft') {
    const buyLeft = startX - buyWidth;
    const sellLeft = buyLeft - sellWidth;

    if (sellWidth > 0) {
      context.fillStyle = sellFillColor;
      context.fillRect(sellLeft, top, sellWidth, safeHeight);
    }

    if (buyWidth > 0) {
      context.fillStyle = buyFillColor;
      context.fillRect(buyLeft, top, buyWidth, safeHeight);
    }

    return;
  }

  if (buyWidth > 0) {
    context.fillStyle = buyFillColor;
    context.fillRect(startX, top, buyWidth, safeHeight);
  }

  if (sellWidth > 0) {
    context.fillStyle = sellFillColor;
    context.fillRect(startX + buyWidth, top, sellWidth, safeHeight);
  }
}

function drawPocLine(
  context: CanvasRenderingContext2D,
  profileKind: VolumeProfileKind,
  left: number,
  right: number,
  y: number,
  pixelRatio: number,
  color: string,
): void {
  const lineLeft = profileKind === 'visibleRange' ? 0 : left;
  const lineRight = profileKind === 'visibleRange' ? context.canvas.width : right;

  context.strokeStyle = color;
  context.lineWidth = UI.pocLineWidth * pixelRatio;

  context.beginPath();
  context.moveTo(lineLeft, y);
  context.lineTo(lineRight, y);
  context.stroke();
}