multi-line-chart.ts (1760B)
1 export interface LineData { 2 label: string; 3 color: string; 4 points: { x: number; y: number }[]; 5 } 6 7 export function renderMultiLineChart( 8 lines: LineData[], 9 xLabels: string[], 10 opts: { width?: number; height?: number; yLabel?: string } = {} 11 ): string { 12 const w = opts.width || 600; 13 const h = opts.height || 220; 14 const pad = { l: 50, r: 20, t: 15, b: 30 }; 15 const chartW = w - pad.l - pad.r; 16 const chartH = h - pad.t - pad.b; 17 18 const xScale = (i: number) => pad.l + (i / Math.max(xLabels.length - 1, 1)) * chartW; 19 const yScale = (v: number) => pad.t + chartH - (v / 100) * chartH; 20 21 // Grid + labels 22 let svg = ''; 23 for (let v = 0; v <= 100; v += 25) { 24 svg += `<text x="${pad.l - 8}" y="${yScale(v) + 4}" text-anchor="end">${v}%</text>`; 25 svg += `<line class="grid-line" x1="${pad.l}" x2="${w - pad.r}" y1="${yScale(v)}" y2="${yScale(v)}" stroke-dasharray="3"/>`; 26 } 27 for (let i = 0; i < xLabels.length; i++) { 28 svg += `<text x="${xScale(i)}" y="${h - 5}" text-anchor="middle">${xLabels[i]}</text>`; 29 } 30 31 // Lines 32 for (const line of lines) { 33 if (line.points.length < 2) continue; 34 const pts = line.points.map(p => `${xScale(p.x)},${yScale(p.y)}`).join(' '); 35 svg += `<polyline points="${pts}" fill="none" stroke="${line.color}" stroke-width="2"/>`; 36 for (const p of line.points) { 37 svg += `<circle cx="${xScale(p.x)}" cy="${yScale(p.y)}" r="3" fill="${line.color}"/>`; 38 } 39 } 40 41 // Legend 42 const legend = lines.map(l => 43 `<span class="chart-legend-item"><span class="chart-legend-swatch" style="background:${l.color}"></span>${l.label}</span>` 44 ).join(''); 45 46 return `<svg viewBox="0 0 ${w} ${h}" style="width:100%;max-width:${w}px">${svg}</svg> 47 <div class="chart-legend">${legend}</div>`; 48 }