1// Draws the problem geometry: boundary curve, evaluation points, and the
2// exact solution's sources.
4import { useEffect, useRef } from "react";
5import type { Laplace2dInstance } from "../../problems/laplace2d/spec";
6import {
7 boundaryPoint,
8 evalPoints,
9 maxRadius,
10 sources,
11} from "../../problems/laplace2d/exact";
13function token(name: string): string {
14 return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
15}
17export function DomainView({ inst }: { inst: Laplace2dInstance }) {
18 const canvasRef = useRef<HTMLCanvasElement>(null);
19 // The count varies by instance: 289 normally, and more where a
20 // near-corner or near-boundary set is added.
21 const nEval = evalPoints(inst).length;
23 useEffect(() => {
24 const canvas = canvasRef.current;
25 if (!canvas) return;
26 const draw = () => {
27 const size = 360;
28 const dpr = window.devicePixelRatio || 1;
29 canvas.width = size * dpr;
30 canvas.height = size * dpr;
31 canvas.style.width = `${size}px`;
32 canvas.style.height = `${size}px`;
33 const ctx = canvas.getContext("2d");
34 if (!ctx) return;
35 ctx.scale(dpr, dpr);
36 ctx.clearRect(0, 0, size, size);
38 const extent = maxRadius(inst) + inst.d + 0.25;
39 const s = size / (2 * extent);
40 const X = (x: number) => size / 2 + x * s;
41 const Y = (y: number) => size / 2 - y * s;
43 // domain fill + boundary
44 ctx.beginPath();
45 const nOutline = 4096;
46 for (let i = 0; i <= nOutline; i++) {
47 const t = (2 * Math.PI * i) / nOutline;
48 const p = boundaryPoint(inst, t);
49 if (i === 0) ctx.moveTo(X(p.x), Y(p.y));
50 else ctx.lineTo(X(p.x), Y(p.y));
51 }
52 ctx.closePath();
53 ctx.fillStyle = token("--surface-2");
54 ctx.fill();
55 ctx.strokeStyle = token("--text");
56 ctx.lineWidth = 2;
57 ctx.stroke();
59 // evaluation points
60 ctx.fillStyle = token("--text-2");
61 for (const p of evalPoints(inst)) {
62 ctx.beginPath();
63 ctx.arc(X(p.x), Y(p.y), 1.4, 0, 2 * Math.PI);
64 ctx.fill();
65 }
67 // sources
68 ctx.strokeStyle = token("--series-2");
69 ctx.lineWidth = 2;
70 for (const src of sources(inst)) {
71 const cx = X(src.x);
72 const cy = Y(src.y);
73 ctx.beginPath();
74 ctx.moveTo(cx - 5, cy - 5);
75 ctx.lineTo(cx + 5, cy + 5);
76 ctx.moveTo(cx - 5, cy + 5);
77 ctx.lineTo(cx + 5, cy - 5);
78 ctx.stroke();
79 }
80 };
81 draw();
82 const mq = window.matchMedia("(prefers-color-scheme: dark)");
83 mq.addEventListener("change", draw);
84 return () => mq.removeEventListener("change", draw);
85 }, [inst]);
87 return (
88 <figure style={{ margin: 0 }}>
89 <canvas ref={canvasRef} />
90 <figcaption className="field-caption" style={{ maxWidth: 360 }}>
91 The domain, the {nEval} evaluation points where solutions are
92 scored (dots), and the exact solution's sources a distance {inst.d}{" "}
93 outside the boundary (crosses).
94 </figcaption>
95 </figure>
96 );
97}