Загрузка данных
import { CanvasRenderingTarget2D } from 'fancy-canvas';
import { IPrimitivePaneRenderer } from 'lightweight-charts';
import { getThemeStore } from '@src/theme';
import type { ParallelChannel } from './parallelChannel';
import type { Point } from '@core/Drawings/types';
const UI = {
lineWidth: 2,
middleLineWidth: 1,
middleLineDash: 6,
middleLineGap: 4,
handleRadius: 5,
handleBorderWidth: 2,
textLineHeightMultiplier: 1.2,
textOffset: 6,
};
export class ParallelChannelPaneRenderer implements IPrimitivePaneRenderer {
private parallelChannel: ParallelChannel;
constructor(parallelChannel: ParallelChannel) {
this.parallelChannel = parallelChannel;
}
public draw(target: CanvasRenderingTarget2D): void {
const data = this.parallelChannel.getRenderData();
if (!data) {
return;
}
target.useBitmapCoordinateSpace(
({
context,
horizontalPixelRatio,
verticalPixelRatio,
}) => {
const pixelRatio = Math.max(
horizontalPixelRatio,
verticalPixelRatio,
);
const startPoint = scalePoint(
data.startPoint,
horizontalPixelRatio,
verticalPixelRatio,
);
const mainMiddlePoint = scalePoint(
data.mainMiddlePoint,
horizontalPixelRatio,
verticalPixelRatio,
);
const endPoint = scalePoint(
data.endPoint,
horizontalPixelRatio,
verticalPixelRatio,
);
const parallelStartPoint = scalePoint(
data.parallelStartPoint,
horizontalPixelRatio,
verticalPixelRatio,
);
const parallelMiddlePoint = scalePoint(
data.parallelMiddlePoint,
horizontalPixelRatio,
verticalPixelRatio,
);
const parallelEndPoint = scalePoint(
data.parallelEndPoint,
horizontalPixelRatio,
verticalPixelRatio,
);
const middleStartPoint = scalePoint(
data.middleStartPoint,
horizontalPixelRatio,
verticalPixelRatio,
);
const middleEndPoint = scalePoint(
data.middleEndPoint,
horizontalPixelRatio,
verticalPixelRatio,
);
context.save();
drawChannelFill(
context,
[
startPoint,
endPoint,
parallelEndPoint,
parallelStartPoint,
],
data.backgroundColor,
);
context.strokeStyle = data.lineColor;
context.lineWidth = UI.lineWidth * pixelRatio;
drawLine(
context,
startPoint,
endPoint,
);
drawLine(
context,
parallelStartPoint,
parallelEndPoint,
);
if (data.showMiddleLine) {
context.save();
context.lineWidth =
UI.middleLineWidth *
pixelRatio;
context.setLineDash([
UI.middleLineDash * pixelRatio,
UI.middleLineGap * pixelRatio,
]);
drawLine(
context,
middleStartPoint,
middleEndPoint,
);
context.restore();
}
if (data.text.trim()) {
const channelCenterPoint = getMiddlePoint(
mainMiddlePoint,
parallelMiddlePoint,
);
const isMainLineAbove =
mainMiddlePoint.y <=
parallelMiddlePoint.y;
const textStartPoint = isMainLineAbove
? startPoint
: parallelStartPoint;
const textEndPoint = isMainLineAbove
? endPoint
: parallelEndPoint;
const textMiddlePoint = isMainLineAbove
? mainMiddlePoint
: parallelMiddlePoint;
drawTextAlongLine(context, {
startPoint: textStartPoint,
endPoint: textEndPoint,
middlePoint: textMiddlePoint,
channelCenterPoint,
text: data.text,
fontSize: data.fontSize,
isBold: data.isBold,
isItalic: data.isItalic,
textColor: data.textColor,
verticalPixelRatio,
});
}
if (data.showHandles) {
const { colors } = getThemeStore();
context.fillStyle =
colors.chartBackground;
context.strokeStyle =
colors.chartLineColor;
context.lineWidth =
UI.handleBorderWidth *
pixelRatio;
const radius =
UI.handleRadius *
pixelRatio;
drawHandle(
context,
startPoint,
radius,
);
drawHandle(
context,
mainMiddlePoint,
radius,
);
drawHandle(
context,
endPoint,
radius,
);
drawHandle(
context,
parallelStartPoint,
radius,
);
drawHandle(
context,
parallelMiddlePoint,
radius,
);
drawHandle(
context,
parallelEndPoint,
radius,
);
}
context.restore();
},
);
}
}
function scalePoint(
point: Point,
horizontalPixelRatio: number,
verticalPixelRatio: number,
): Point {
return {
x:
point.x *
horizontalPixelRatio,
y:
point.y *
verticalPixelRatio,
};
}
function drawChannelFill(
context: CanvasRenderingContext2D,
points: Point[],
color: string,
): void {
const [
startPoint,
endPoint,
parallelEndPoint,
parallelStartPoint,
] = points;
context.save();
context.fillStyle = color;
context.beginPath();
context.moveTo(
startPoint.x,
startPoint.y,
);
context.lineTo(
endPoint.x,
endPoint.y,
);
context.lineTo(
parallelEndPoint.x,
parallelEndPoint.y,
);
context.lineTo(
parallelStartPoint.x,
parallelStartPoint.y,
);
context.closePath();
context.fill();
context.restore();
}
function drawLine(
context: CanvasRenderingContext2D,
startPoint: Point,
endPoint: Point,
): void {
context.beginPath();
context.moveTo(
startPoint.x,
startPoint.y,
);
context.lineTo(
endPoint.x,
endPoint.y,
);
context.stroke();
}
function drawHandle(
context: CanvasRenderingContext2D,
point: Point,
radius: number,
): void {
context.beginPath();
context.arc(
point.x,
point.y,
radius,
0,
Math.PI * 2,
);
context.fill();
context.stroke();
}
function drawTextAlongLine(
context: CanvasRenderingContext2D,
params: {
startPoint: Point;
endPoint: Point;
middlePoint: Point;
channelCenterPoint: Point;
text: string;
fontSize: number;
isBold: boolean;
isItalic: boolean;
textColor: string;
verticalPixelRatio: number;
},
): void {
const {
startPoint,
endPoint,
middlePoint,
channelCenterPoint,
text,
fontSize,
isBold,
isItalic,
textColor,
verticalPixelRatio,
} = params;
const lines = text.split('\n');
const safeFontSize = Math.max(1, fontSize);
const fontSizePx =
safeFontSize *
verticalPixelRatio;
const lineHeight =
safeFontSize *
UI.textLineHeightMultiplier *
verticalPixelRatio;
const directionX =
endPoint.x -
startPoint.x;
const directionY =
endPoint.y -
startPoint.y;
let angle = Math.atan2(
directionY,
directionX,
);
if (
angle > Math.PI / 2 ||
angle < -Math.PI / 2
) {
angle += Math.PI;
}
const outwardX =
middlePoint.x -
channelCenterPoint.x;
const outwardY =
middlePoint.y -
channelCenterPoint.y;
const outwardLength = Math.hypot(
outwardX,
outwardY,
);
const normalX = outwardLength
? outwardX / outwardLength
: 0;
const normalY = outwardLength
? outwardY / outwardLength
: -1;
const blockHeight =
lines.length *
lineHeight;
const distance =
blockHeight / 2 +
UI.textOffset *
verticalPixelRatio;
const textCenterX =
middlePoint.x +
normalX *
distance;
const textCenterY =
middlePoint.y +
normalY *
distance;
const fontWeight = isBold
? '700 '
: '';
const fontStyle = isItalic
? 'italic '
: '';
context.save();
context.translate(
textCenterX,
textCenterY,
);
context.rotate(angle);
context.font =
`${fontStyle}${fontWeight}` +
`${fontSizePx}px Inter, sans-serif`;
context.fillStyle = textColor;
context.textAlign = 'center';
context.textBaseline = 'middle';
const firstLineY =
-(
lines.length -
1
) *
lineHeight /
2;
lines.forEach(
(
line,
index,
) => {
context.fillText(
line,
0,
firstLineY +
index *
lineHeight,
);
},
);
context.restore();
}
function getMiddlePoint(
startPoint: Point,
endPoint: Point,
): Point {
return {
x:
(
startPoint.x +
endPoint.x
) /
2,
y:
(
startPoint.y +
endPoint.y
) /
2,
};
}