Загрузка данных
import { CanvasRenderingTarget2D } from 'fancy-canvas';
import { drawRoundedRect } from '@core/Drawings/utils';
import { removeAlphaFromHex } from '@src/utils/removeAlphaFromHex';
import type { IPrimitivePaneRenderer } from 'lightweight-charts';
import type { SliderPositionStyle } from './settings';
import type { SliderPosition } from './sliderPosition';
const UI = {
lineHeightMultiplier: 1.2,
mainBoxHeight: 28,
sideBoxHeight: 16,
padding: 4,
boxRadius: 4,
labelOffset: 10,
};
export class SliderPaneRenderer implements IPrimitivePaneRenderer {
private readonly slider: SliderPosition;
constructor(slider: SliderPosition) {
this.slider = slider;
}
public draw(target: CanvasRenderingTarget2D): void {
const data = this.slider.getRenderData();
if (!data) {
return;
}
target.useBitmapCoordinateSpace(
({ context, horizontalPixelRatio, verticalPixelRatio, bitmapSize }) => {
const left = data.leftX * horizontalPixelRatio;
const right = data.rightX * horizontalPixelRatio;
const entryY = data.entryY * verticalPixelRatio;
const stopY = data.stopY * verticalPixelRatio;
const targetY = data.targetY * verticalPixelRatio;
const profitTop = data.profitTop * verticalPixelRatio;
const profitBottom = data.profitBottom * verticalPixelRatio;
const lossTop = data.lossTop * verticalPixelRatio;
const lossBottom = data.lossBottom * verticalPixelRatio;
const centerX = (left + right) / 2;
const labelOffset = UI.labelOffset * verticalPixelRatio;
const targetBoxHeight = getTextBoxHeight(
data.targetText,
data,
UI.sideBoxHeight,
verticalPixelRatio,
);
const centerBoxHeight = getTextBoxHeight(
data.centerText,
data,
UI.mainBoxHeight,
verticalPixelRatio,
);
const stopBoxHeight = getTextBoxHeight(
data.stopText,
data,
UI.sideBoxHeight,
verticalPixelRatio,
);
const targetLabelY = getOuterLabelCenterY(
targetY,
data.targetLabelDirection,
targetBoxHeight,
labelOffset,
);
const stopLabelY = getOuterLabelCenterY(
stopY,
data.stopLabelDirection,
stopBoxHeight,
labelOffset,
);
const centerLabelY = getCenterLabelCenterY(
entryY,
targetY,
stopY,
centerBoxHeight,
labelOffset,
bitmapSize.height,
);
context.save();
context.fillStyle = data.positiveFillColor;
context.fillRect(left, profitTop, right - left, profitBottom - profitTop);
context.fillStyle = data.negativeFillColor;
context.fillRect(left, lossTop, right - left, lossBottom - lossTop);
context.lineWidth =
data.lineWidth * Math.max(horizontalPixelRatio, verticalPixelRatio);
drawHorizontalLine(
context,
left,
right,
targetY,
removeAlphaFromHex(data.positiveFillColor),
);
drawHorizontalLine(context, left, right, entryY, data.lineColor);
drawHorizontalLine(
context,
left,
right,
stopY,
removeAlphaFromHex(data.negativeFillColor),
);
if (data.showLabels) {
drawTextBox(
context,
centerX,
targetLabelY,
data.targetText,
data.positiveFillColor,
data,
horizontalPixelRatio,
verticalPixelRatio,
UI.sideBoxHeight,
);
drawTextBox(
context,
centerX,
centerLabelY,
data.centerText,
data.centerBoxColor,
data,
horizontalPixelRatio,
verticalPixelRatio,
UI.mainBoxHeight,
);
drawTextBox(
context,
centerX,
stopLabelY,
data.stopText,
data.negativeFillColor,
data,
horizontalPixelRatio,
verticalPixelRatio,
UI.sideBoxHeight,
);
}
context.restore();
},
);
}
}
function getOuterLabelCenterY(
coordinate: number,
direction: 'up' | 'down',
boxHeight: number,
labelOffset: number,
): number {
const offset = labelOffset + boxHeight / 2;
return direction === 'up' ? coordinate - offset : coordinate + offset;
}
function getCenterLabelCenterY(
entryY: number,
targetY: number,
stopY: number,
boxHeight: number,
labelOffset: number,
viewportHeight: number,
): number {
const targetSpace = Math.abs(targetY - entryY);
const stopSpace = Math.abs(stopY - entryY);
const direction =
targetSpace < stopSpace
? stopY < entryY
? -1
: 1
: targetY < entryY
? -1
: 1;
return clampTextBoxCenter(
entryY + direction * (labelOffset + boxHeight / 2),
boxHeight,
viewportHeight,
);
}
function clampTextBoxCenter(
coordinate: number,
boxHeight: number,
viewportHeight: number,
): number {
if (viewportHeight <= boxHeight) {
return viewportHeight / 2;
}
const halfHeight = boxHeight / 2;
return Math.max(halfHeight, Math.min(coordinate, viewportHeight - halfHeight));
}
function drawHorizontalLine(
context: CanvasRenderingContext2D,
left: number,
right: number,
y: number,
color: string,
): void {
context.strokeStyle = color;
context.beginPath();
context.moveTo(left, y);
context.lineTo(right, y);
context.stroke();
}
function drawTextBox(
context: CanvasRenderingContext2D,
centerX: number,
centerY: number,
text: string,
fillColor: string,
style: SliderPositionStyle,
horizontalPixelRatio: number,
verticalPixelRatio: number,
fixedHeight: number,
): void {
context.save();
const lines = text.split('\n');
const fontSize = style.fontSize * verticalPixelRatio;
const lineHeight =
Math.round(style.fontSize * UI.lineHeightMultiplier) * verticalPixelRatio;
const paddingX = UI.padding * horizontalPixelRatio;
const boxHeight = getTextBoxHeight(text, style, fixedHeight, verticalPixelRatio);
const radius = UI.boxRadius * Math.max(horizontalPixelRatio, verticalPixelRatio);
context.font = `${fontSize}px Inter, sans-serif`;
context.textAlign = 'center';
context.textBaseline = 'middle';
let maxTextWidth = 0;
for (const line of lines) {
maxTextWidth = Math.max(maxTextWidth, context.measureText(line).width);
}
const width = maxTextWidth + paddingX * 2;
const x = centerX - width / 2;
const y = centerY - boxHeight / 2;
context.fillStyle = fillColor;
context.beginPath();
drawRoundedRect(context, x, y, width, boxHeight, radius);
context.fill();
context.fillStyle = style.textColor;
if (lines.length === 1) {
context.fillText(lines[0], centerX, centerY);
context.restore();
return;
}
const textBlockHeight = lines.length * lineHeight;
const firstLineY = centerY - textBlockHeight / 2 + lineHeight / 2;
lines.forEach((line, index) => {
context.fillText(line, centerX, firstLineY + index * lineHeight);
});
context.restore();
}
function getTextBoxHeight(
text: string,
style: SliderPositionStyle,
fixedHeight: number,
verticalPixelRatio: number,
): number {
const linesCount = text.split('\n').length;
const lineHeight =
Math.round(style.fontSize * UI.lineHeightMultiplier) * verticalPixelRatio;
const paddingY = UI.padding * verticalPixelRatio;
const minHeight = fixedHeight * verticalPixelRatio;
return Math.max(minHeight, linesCount * lineHeight + paddingY * 2);
}