1/** GMRES relative residual against iteration, on a log scale. */
2export function drawConvergence(canvas: HTMLCanvasElement, series: { label: string; res: number[]; color: string }[], eps: number) {
3 const dpr = window.devicePixelRatio || 1;
4 const W = canvas.clientWidth || 300;
5 const H = canvas.clientHeight || 140;
6 canvas.width = Math.round(W * dpr);
7 canvas.height = Math.round(H * dpr);
8 const ctx = canvas.getContext('2d')!;
9 ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
10 ctx.clearRect(0, 0, W, H);
11 const style = getComputedStyle(canvas);
12 const ink = style.getPropertyValue('--ink-2').trim() || '#888';
13 const line = style.getPropertyValue('--line').trim() || '#ccc';
14 const L = 44;
15 const R = 8;
16 const T = 8;
17 const B = 22;
18 const nmax = Math.max(10, ...series.map((s) => s.res.length));
19 const lo = Math.floor(Math.log10(Math.min(eps, ...series.flatMap((s) => s.res)) * 0.5));
20 const hi = 0;
21 const X = (i: number) => L + ((W - L - R) * i) / nmax;
22 const Y = (r: number) => T + ((H - T - B) * (hi - Math.log10(r))) / (hi - lo);
23 ctx.font = '11px system-ui, sans-serif';
24 ctx.fillStyle = ink;
25 ctx.strokeStyle = line;
26 ctx.lineWidth = 1;
27 for (let e = lo; e <= hi; e++) {
28 if ((e - lo) % Math.max(1, Math.ceil((hi - lo) / 5)) !== 0) continue;
29 const y = Y(10 ** e);
30 ctx.beginPath();
31 ctx.moveTo(L, y);
32 ctx.lineTo(W - R, y);
33 ctx.stroke();
34 ctx.textAlign = 'right';
35 ctx.textBaseline = 'middle';
36 ctx.fillText(`1e${e}`, L - 4, y);
37 }
38 ctx.textAlign = 'center';
39 ctx.textBaseline = 'top';
40 ctx.fillText('GMRES iteration', (L + W - R) / 2, H - B + 6);
41 // tolerance
42 ctx.setLineDash([4, 3]);
43 ctx.beginPath();
44 ctx.moveTo(L, Y(eps));
45 ctx.lineTo(W - R, Y(eps));
46 ctx.stroke();
47 ctx.setLineDash([]);
48 for (const s of series) {
49 if (!s.res.length) continue;
50 ctx.strokeStyle = s.color;
51 ctx.lineWidth = 2;
52 ctx.beginPath();
53 ctx.moveTo(X(0), Y(1));
54 s.res.forEach((r, i) => ctx.lineTo(X(i + 1), Y(Math.max(r, 10 ** lo))));
55 ctx.stroke();
56 }
57 let ly = T + 4;
58 for (const s of series) {
59 if (!s.res.length) continue;
60 ctx.fillStyle = s.color;
61 ctx.fillRect(W - R - 110, ly + 3, 10, 3);
62 ctx.fillStyle = ink;
63 ctx.textAlign = 'left';
64 ctx.textBaseline = 'top';
65 ctx.fillText(s.label, W - R - 96, ly - 2);
66 ly += 14;
67 }
68}