/** * 图形符号配对专用的图形定义 * 所有图形保持大小一致 */ 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(); }