Загрузка данных
import { getThemeStore } from '@src/theme';
import { SettingField, SettingsTab, SettingsValues } from '@src/types';
export interface RectangleStyle {
borderColor: string;
fillColor: string;
}
export interface RectangleTextStyle {
text: string;
fontSize: number;
isBold: boolean;
isItalic: boolean;
textColor: string;
}
export type RectangleSettings = RectangleStyle & RectangleTextStyle & SettingsValues;
export function createDefaultSettings(): RectangleSettings {
const { colors } = getThemeStore();
return {
borderColor: colors.rectangleBorderFill,
fillColor: colors.rectangleAreaFill,
text: '',
fontSize: 14,
isBold: false,
isItalic: false,
textColor: colors.chartPriceLineText,
};
}
export function getRectangleSettingsTabs(settings: RectangleSettings): SettingsTab[] {
const styleFields: SettingField[] = [
{
key: 'borderColor',
label: 'Цвет границы',
type: 'color',
defaultValue: settings.borderColor,
},
{
key: 'fillColor',
label: 'Цвет фона',
type: 'color',
defaultValue: settings.fillColor,
},
];
const textFields: SettingField[] = [
{
key: 'fontSize',
label: 'Размер текста',
type: 'number',
defaultValue: settings.fontSize,
min: 8,
max: 24,
},
{
key: 'text',
label: 'Текст',
type: 'textarea',
defaultValue: settings.text,
placeholder: 'Введите текст',
},
{
key: 'isBold',
label: 'Жирный текст',
type: 'boolean',
defaultValue: settings.isBold,
},
{
key: 'isItalic',
label: 'Курсив',
type: 'boolean',
defaultValue: settings.isItalic,
},
{
key: 'textColor',
label: 'Цвет текста',
type: 'color',
defaultValue: settings.textColor,
},
];
return [
{
key: 'style',
label: 'Стиль',
fields: styleFields,
},
{
key: 'text',
label: 'Текст',
fields: textFields,
},
];
}
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: 8,
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,
right,
top,
text: data.text,
fontSize: data.fontSize,
isBold: data.isBold,
isItalic: data.isItalic,
textColor: data.textColor,
pixelRatio,
});
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;
right: number;
top: number;
text: string;
fontSize: number;
isBold: boolean;
isItalic: boolean;
textColor: string;
pixelRatio: number;
},
): void {
const { left, right, top, text, fontSize, isBold, isItalic, textColor, pixelRatio } = params;
if (!text.trim()) {
return;
}
const lines = text.split('\n');
const safeFontSize = Math.max(1, fontSize);
const fontSizePx = safeFontSize * pixelRatio;
const lineHeight = safeFontSize * UI.textLineHeightMultiplier * pixelRatio;
const fontWeight = isBold ? '700 ' : '';
const fontStyle = isItalic ? 'italic ' : '';
const textOffset = UI.textOffset * pixelRatio;
const centerX = (left + right) / 2;
const blockHeight = lines.length * lineHeight;
const firstLineY = top - textOffset - blockHeight + lineHeight / 2;
context.save();
context.font = `${fontStyle}${fontWeight}${fontSizePx}px Inter, sans-serif`;
context.fillStyle = textColor;
context.textAlign = 'center';
context.textBaseline = 'middle';
lines.forEach((line, index) => {
context.fillText(line, centerX, 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();
}