345 lines
11 KiB
TypeScript
345 lines
11 KiB
TypeScript
/**
|
||
* 图形符号配对专用的图形定义
|
||
* 所有图形保持大小一致
|
||
*/
|
||
|
||
export interface ShapeSymbolShape {
|
||
id: string;
|
||
name: string;
|
||
}
|
||
|
||
export const SHAPE_SYMBOL_SHAPES: ShapeSymbolShape[] = [
|
||
{ id: 'circle', name: '圆形' },
|
||
{ id: 'square', name: '正方形' },
|
||
{ id: 'triangle', name: '三角形' },
|
||
{ id: 'diamond', name: '菱形' },
|
||
{ id: 'trapezoid', name: '梯形' },
|
||
{ id: 'ellipse', name: '椭圆' },
|
||
{ id: 'star', name: '五角星' },
|
||
{ id: 'pentagon', name: '五边形' },
|
||
{ id: 'hexagon', name: '六边形' },
|
||
{ id: 'octagon', name: '八边形' },
|
||
{ id: 'cross', name: '十字形' },
|
||
{ id: 'rectangle', name: '长方形' },
|
||
{ id: 'sector', name: '扇形' },
|
||
{ id: 'heart', name: '心形' },
|
||
];
|
||
|
||
/**
|
||
* 绘制图形选项
|
||
*/
|
||
export interface DrawShapeOptions {
|
||
/** 填充颜色,如果为 null 则不填充 */
|
||
fillColor?: string | null;
|
||
/** 边框颜色,如果为 null 则不绘制边框 */
|
||
strokeColor?: string | null;
|
||
/** 边框宽度,默认为 1.5 */
|
||
strokeWidth?: number;
|
||
}
|
||
|
||
/**
|
||
* 绘制图形(支持边框和填充控制)
|
||
* 所有图形保持相同的大小
|
||
* @overload 旧的方式:drawShapeSymbolShape(ctx, shapeId, x, y, size, fillColor)
|
||
*/
|
||
export function drawShapeSymbolShape(
|
||
ctx: RenderingContext,
|
||
shapeId: string,
|
||
x: number,
|
||
y: number,
|
||
size: number,
|
||
fillColor: string,
|
||
options?: DrawShapeOptions,
|
||
): void;
|
||
/**
|
||
* 绘制图形(支持边框和填充控制)
|
||
* 所有图形保持相同的大小
|
||
* @overload 新的方式:drawShapeSymbolShape(ctx, shapeId, x, y, size, options)
|
||
*/
|
||
export function drawShapeSymbolShape(
|
||
ctx: RenderingContext,
|
||
shapeId: string,
|
||
x: number,
|
||
y: number,
|
||
size: number,
|
||
options: DrawShapeOptions,
|
||
): void;
|
||
/**
|
||
* 绘制图形(支持边框和填充控制)
|
||
* 所有图形保持相同的大小
|
||
*/
|
||
export function drawShapeSymbolShape(
|
||
ctx: RenderingContext,
|
||
shapeId: string,
|
||
x: number,
|
||
y: number,
|
||
size: number,
|
||
fillColorOrOptions: string | DrawShapeOptions,
|
||
options?: DrawShapeOptions,
|
||
): void {
|
||
ctx.save();
|
||
ctx.translate(x, y);
|
||
|
||
const radius = size / 2;
|
||
|
||
// 兼容旧的调用方式:drawShapeSymbolShape(ctx, shapeId, x, y, size, fillColor)
|
||
let fillColor: string | null = null;
|
||
let drawOptions: DrawShapeOptions = {};
|
||
|
||
if (typeof fillColorOrOptions === 'string') {
|
||
// 旧的方式:fillColor 是字符串
|
||
fillColor = fillColorOrOptions;
|
||
drawOptions = options || {};
|
||
} else {
|
||
// 新的方式:第一个参数是 options
|
||
drawOptions = fillColorOrOptions || {};
|
||
fillColor = drawOptions.fillColor || null;
|
||
}
|
||
|
||
// 设置填充和描边样式
|
||
if (fillColor) {
|
||
ctx.fillStyle = fillColor;
|
||
}
|
||
if (drawOptions.strokeColor) {
|
||
ctx.strokeStyle = drawOptions.strokeColor;
|
||
ctx.lineWidth = drawOptions.strokeWidth || 1.5;
|
||
}
|
||
|
||
// 统一的填充和描边处理函数
|
||
const drawPath = () => {
|
||
if (fillColor) {
|
||
ctx.fill();
|
||
}
|
||
if (drawOptions.strokeColor) {
|
||
ctx.stroke();
|
||
}
|
||
};
|
||
|
||
switch (shapeId) {
|
||
case 'circle':
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, radius, 0, Math.PI * 2);
|
||
drawPath();
|
||
break;
|
||
|
||
case 'square':
|
||
// 正方形缩小为原来的0.9倍
|
||
const squareSize = size * 0.9;
|
||
const squareRadius = squareSize / 2;
|
||
if (fillColor) {
|
||
ctx.fillRect(
|
||
-squareRadius,
|
||
-squareRadius,
|
||
squareSize,
|
||
squareSize,
|
||
);
|
||
}
|
||
if (drawOptions.strokeColor) {
|
||
ctx.strokeRect(
|
||
-squareRadius,
|
||
-squareRadius,
|
||
squareSize,
|
||
squareSize,
|
||
);
|
||
}
|
||
break;
|
||
|
||
case 'triangle':
|
||
// 三角形缩小为原来的0.9倍,整体中心提高
|
||
// const triangleRadius = radius * 0.9;
|
||
const bottomY = radius * 0.7; // 减少底部Y坐标,使整体中心提高
|
||
ctx.beginPath();
|
||
ctx.moveTo(0, -radius);
|
||
ctx.lineTo(-radius, bottomY);
|
||
ctx.lineTo(radius, bottomY);
|
||
ctx.closePath();
|
||
drawPath();
|
||
break;
|
||
|
||
case 'diamond':
|
||
ctx.beginPath();
|
||
ctx.moveTo(0, -radius);
|
||
ctx.lineTo(radius, 0);
|
||
ctx.lineTo(0, radius);
|
||
ctx.lineTo(-radius, 0);
|
||
ctx.closePath();
|
||
drawPath();
|
||
break;
|
||
|
||
case 'trapezoid':
|
||
ctx.beginPath();
|
||
const topWidth = radius * 0.6;
|
||
ctx.moveTo(-topWidth, -radius * 0.7);
|
||
ctx.lineTo(topWidth, -radius * 0.7);
|
||
ctx.lineTo(radius, radius * 0.7);
|
||
ctx.lineTo(-radius, radius * 0.7);
|
||
ctx.closePath();
|
||
drawPath();
|
||
break;
|
||
|
||
// 椭圆
|
||
case 'ellipse':
|
||
ctx.beginPath();
|
||
ctx.ellipse(0, 0, radius, radius * 0.8, 0, 0, Math.PI * 2);
|
||
drawPath();
|
||
break;
|
||
|
||
case 'star':
|
||
// 五角星
|
||
ctx.beginPath();
|
||
for (let i = 0; i < 5; i++) {
|
||
const angle1 = (i * 2 * Math.PI) / 5 - Math.PI / 2;
|
||
const angle2 = ((i + 0.5) * 2 * Math.PI) / 5 - Math.PI / 2;
|
||
const r1 = radius;
|
||
const r2 = radius * 0.4;
|
||
const x1 = Math.cos(angle1) * r1;
|
||
const y1 = Math.sin(angle1) * r1;
|
||
const x2 = Math.cos(angle2) * r2;
|
||
const y2 = Math.sin(angle2) * r2;
|
||
if (i === 0) {
|
||
ctx.moveTo(x1, y1);
|
||
} else {
|
||
ctx.lineTo(x1, y1);
|
||
}
|
||
ctx.lineTo(x2, y2);
|
||
}
|
||
ctx.closePath();
|
||
drawPath();
|
||
break;
|
||
|
||
case 'pentagon':
|
||
ctx.beginPath();
|
||
for (let i = 0; i < 5; i++) {
|
||
const angle = (i * 2 * Math.PI) / 5 - Math.PI / 2;
|
||
const px = Math.cos(angle) * radius;
|
||
const py = Math.sin(angle) * radius;
|
||
if (i === 0) ctx.moveTo(px, py);
|
||
else ctx.lineTo(px, py);
|
||
}
|
||
ctx.closePath();
|
||
drawPath();
|
||
break;
|
||
|
||
case 'hexagon':
|
||
// 六边形
|
||
ctx.beginPath();
|
||
for (let i = 0; i < 6; i++) {
|
||
const angle = (i * 2 * Math.PI) / 6 - Math.PI / 2;
|
||
const px = Math.cos(angle) * radius;
|
||
const py = Math.sin(angle) * radius;
|
||
if (i === 0) ctx.moveTo(px, py);
|
||
else ctx.lineTo(px, py);
|
||
}
|
||
ctx.closePath();
|
||
drawPath();
|
||
break;
|
||
|
||
case 'octagon':
|
||
// 八边形
|
||
ctx.beginPath();
|
||
for (let i = 0; i < 8; i++) {
|
||
const angle = (i * 2 * Math.PI) / 8 - Math.PI / 2;
|
||
const px = Math.cos(angle) * radius;
|
||
const py = Math.sin(angle) * radius;
|
||
if (i === 0) ctx.moveTo(px, py);
|
||
else ctx.lineTo(px, py);
|
||
}
|
||
ctx.closePath();
|
||
drawPath();
|
||
break;
|
||
|
||
case 'cross':
|
||
// 十字形
|
||
const crossWidth = radius * 0.3; // 十字的宽度
|
||
if (fillColor) {
|
||
// 竖条
|
||
ctx.fillRect(-crossWidth, -radius, crossWidth * 2, size);
|
||
// 横条
|
||
ctx.fillRect(-radius, -crossWidth, size, crossWidth * 2);
|
||
}
|
||
if (drawOptions.strokeColor) {
|
||
// 竖条
|
||
ctx.strokeRect(-crossWidth, -radius, crossWidth * 2, size);
|
||
// 横条
|
||
ctx.strokeRect(-radius, -crossWidth, size, crossWidth * 2);
|
||
}
|
||
break;
|
||
|
||
case 'rectangle':
|
||
// 长方形(横向,宽度是高度的1.2倍)
|
||
const rectWidth = radius * 1.1;
|
||
const rectHeight = radius * 0.7;
|
||
if (fillColor) {
|
||
ctx.fillRect(
|
||
-rectWidth,
|
||
-rectHeight,
|
||
rectWidth * 2,
|
||
rectHeight * 2,
|
||
);
|
||
}
|
||
if (drawOptions.strokeColor) {
|
||
ctx.strokeRect(
|
||
-rectWidth,
|
||
-rectHeight,
|
||
rectWidth * 2,
|
||
rectHeight * 2,
|
||
);
|
||
}
|
||
break;
|
||
|
||
// 扇形
|
||
case 'sector':
|
||
const sectorRadius = radius * 1.5;
|
||
// 扇形(圆弧在正上方,圆点在正下方)
|
||
const sectorCenterX = 0; // 圆心在水平中心
|
||
const sectorCenterY = sectorRadius * 0.55; // 圆心在正下方
|
||
ctx.beginPath();
|
||
ctx.moveTo(sectorCenterX, sectorCenterY);
|
||
// 从左侧上方到右侧上方绘制圆弧(135度到225度)
|
||
ctx.arc(
|
||
sectorCenterX,
|
||
sectorCenterY,
|
||
sectorRadius,
|
||
(-3 * Math.PI) / 4,
|
||
-Math.PI / 4,
|
||
// (Math.PI * 3) / 4, // 左侧上方(135度)
|
||
// (Math.PI * 5) / 4, // 右侧上方(225度)
|
||
false,
|
||
);
|
||
ctx.lineTo(sectorCenterX, sectorCenterY);
|
||
ctx.closePath();
|
||
drawPath();
|
||
break;
|
||
|
||
case 'heart':
|
||
// 爱心路径(参考 shapes.ts 中的 SVG 路径)
|
||
// SVG路径:M50,35 C35,20 20,35 25,50 C30,65 50,80 50,80 C50,80 70,65 75,50 C80,35 65,20 50,35Z
|
||
// SVG中心为(50,50),高度约为60(从y=20到y=80)
|
||
// 使用scale=60使心形高度约为size,与其他图形保持一致
|
||
const scale = 60;
|
||
function tx(px: number) {
|
||
return ((px - 50) / scale) * size;
|
||
}
|
||
function ty(py: number) {
|
||
return ((py - 50) / scale) * size;
|
||
}
|
||
ctx.beginPath();
|
||
ctx.moveTo(tx(50), ty(35));
|
||
ctx.bezierCurveTo(tx(35), ty(20), tx(20), ty(35), tx(25), ty(50));
|
||
ctx.bezierCurveTo(tx(30), ty(65), tx(50), ty(80), tx(50), ty(80));
|
||
ctx.bezierCurveTo(tx(50), ty(80), tx(70), ty(65), tx(75), ty(50));
|
||
ctx.bezierCurveTo(tx(80), ty(35), tx(65), ty(20), tx(50), ty(35));
|
||
ctx.closePath();
|
||
drawPath();
|
||
break;
|
||
|
||
default:
|
||
// 默认绘制圆形
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, radius, 0, Math.PI * 2);
|
||
drawPath();
|
||
}
|
||
|
||
ctx.restore();
|
||
}
|