From f8ef83506a4e01cf5715b9d3bf72f99ab9c00748 Mon Sep 17 00:00:00 2001 From: hailin Date: Sun, 5 Apr 2026 21:33:16 -0700 Subject: [PATCH] fix: stretch wordcloud shapes to fill container using ellipse radii Changed from square min(w,h) radius to separate rx/ry that use the full width and height of the container. Shapes now fill the entire chart area instead of being constrained to a small square. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../wordcloudOptionBuilder.ts | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/frontend/src/adapters/gateways/EChartsOptionBuilder/wordcloudOptionBuilder.ts b/frontend/src/adapters/gateways/EChartsOptionBuilder/wordcloudOptionBuilder.ts index b1e252d..ece00be 100644 --- a/frontend/src/adapters/gateways/EChartsOptionBuilder/wordcloudOptionBuilder.ts +++ b/frontend/src/adapters/gateways/EChartsOptionBuilder/wordcloudOptionBuilder.ts @@ -66,25 +66,27 @@ function createShapeMask(shape: string, w: number, h: number): HTMLCanvasElement ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, w, h); - // Draw black shape (word area) — fill as much as possible + // Draw black shape (word area) — stretch to fill container ctx.fillStyle = '#000'; const cx = w / 2; const cy = h / 2; - const r = Math.min(w, h) * 0.48; + const rx = w * 0.48; // horizontal radius + const ry = h * 0.48; // vertical radius switch (shape) { - case 'circle': + case 'circle': { ctx.beginPath(); - ctx.arc(cx, cy, r, 0, Math.PI * 2); + ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2); ctx.fill(); break; + } case 'cardioid': { ctx.beginPath(); for (let angle = 0; angle < Math.PI * 2; angle += 0.01) { - const rr = r * 0.85 * (1 - Math.sin(angle)); - const x = cx + rr * Math.cos(angle); - const y = cy - rr * Math.sin(angle) + r * 0.3; + const scale = 0.85 * (1 - Math.sin(angle)); + const x = cx + rx * scale * Math.cos(angle); + const y = cy - ry * scale * Math.sin(angle) + ry * 0.3; if (angle === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } @@ -95,10 +97,10 @@ function createShapeMask(shape: string, w: number, h: number): HTMLCanvasElement case 'diamond': { ctx.beginPath(); - ctx.moveTo(cx, cy - r); - ctx.lineTo(cx + r, cy); - ctx.lineTo(cx, cy + r); - ctx.lineTo(cx - r, cy); + ctx.moveTo(cx, cy - ry); + ctx.lineTo(cx + rx, cy); + ctx.lineTo(cx, cy + ry); + ctx.lineTo(cx - rx, cy); ctx.closePath(); ctx.fill(); break; @@ -106,9 +108,9 @@ function createShapeMask(shape: string, w: number, h: number): HTMLCanvasElement case 'triangle': { ctx.beginPath(); - ctx.moveTo(cx, cy - r); - ctx.lineTo(cx + r * 0.87, cy + r * 0.5); - ctx.lineTo(cx - r * 0.87, cy + r * 0.5); + ctx.moveTo(cx, cy - ry); + ctx.lineTo(cx + rx, cy + ry); + ctx.lineTo(cx - rx, cy + ry); ctx.closePath(); ctx.fill(); break; @@ -118,9 +120,10 @@ function createShapeMask(shape: string, w: number, h: number): HTMLCanvasElement ctx.beginPath(); for (let i = 0; i < 10; i++) { const angle = (Math.PI / 2) + (i * Math.PI / 5); - const rr = i % 2 === 0 ? r : r * 0.4; - const x = cx + rr * Math.cos(angle); - const y = cy - rr * Math.sin(angle); + const outerX = i % 2 === 0 ? rx : rx * 0.4; + const outerY = i % 2 === 0 ? ry : ry * 0.4; + const x = cx + outerX * Math.cos(angle); + const y = cy - outerY * Math.sin(angle); if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } @@ -133,8 +136,8 @@ function createShapeMask(shape: string, w: number, h: number): HTMLCanvasElement ctx.beginPath(); for (let i = 0; i < 5; i++) { const angle = (Math.PI / 2) + (i * 2 * Math.PI / 5); - const x = cx + r * Math.cos(angle); - const y = cy - r * Math.sin(angle); + const x = cx + rx * Math.cos(angle); + const y = cy - ry * Math.sin(angle); if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } @@ -144,7 +147,6 @@ function createShapeMask(shape: string, w: number, h: number): HTMLCanvasElement } default: - // Fallback: fill entire canvas (rectangle) ctx.fillRect(0, 0, w, h); break; }