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 branchPoint,
9 evalPoints,
10 singularities,
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);
20 useEffect(() => {
21 const canvas = canvasRef.current;
22 if (!canvas) return;
23 const draw = () => {
24 const size = 360;
25 const dpr = window.devicePixelRatio || 1;
26 canvas.width = size * dpr;
27 canvas.height = size * dpr;
28 canvas.style.width = `${size}px`;
29 canvas.style.height = `${size}px`;
30 const ctx = canvas.getContext("2d");
31 if (!ctx) return;
32 ctx.scale(dpr, dpr);
33 ctx.clearRect(0, 0, size, size);
35 const extent = 1 + Math.abs(inst.a) + inst.d + 0.25;
36 const s = size / (2 * extent);
37 const X = (x: number) => size / 2 + x * s;
38 const Y = (y: number) => size / 2 - y * s;
40 // domain fill + boundary
41 ctx.beginPath();
42 for (let i = 0; i <= 512; i++) {
43 const t = (2 * Math.PI * i) / 512;
44 const p = boundaryPoint(inst, t);
45 if (i === 0) ctx.moveTo(X(p.x), Y(p.y));
46 else ctx.lineTo(X(p.x), Y(p.y));
47 }
48 ctx.closePath();
49 ctx.fillStyle = token("--surface-2");
50 ctx.fill();
51 ctx.strokeStyle = token("--text");
52 ctx.lineWidth = 2;
53 ctx.stroke();
55 // evaluation points
56 ctx.fillStyle = token("--text-2");
57 for (const p of evalPoints(inst)) {
58 ctx.beginPath();
59 ctx.arc(X(p.x), Y(p.y), 1.4, 0, 2 * Math.PI);
60 ctx.fill();
61 }
63 // the exact solution's singularities
64 ctx.strokeStyle = token("--series-2");
65 ctx.lineWidth = 2;
66 for (const src of singularities(inst)) {
67 const cx = X(src.x);
68 const cy = Y(src.y);
69 ctx.beginPath();
70 ctx.moveTo(cx - 5, cy - 5);
71 ctx.lineTo(cx + 5, cy + 5);
72 ctx.moveTo(cx - 5, cy + 5);
73 ctx.lineTo(cx + 5, cy - 5);
74 ctx.stroke();
75 }
77 // branch family: dashed segment along the cut (radially outward)
78 if (inst.family === "branch-point") {
79 const b = branchPoint(inst);
80 ctx.strokeStyle = token("--text-3");
81 ctx.lineWidth = 1.5;
82 ctx.setLineDash([4, 3]);
83 ctx.beginPath();
84 ctx.moveTo(X(b.x), Y(b.y));
85 ctx.lineTo(
86 X(b.x + 0.3 * Math.cos(b.theta0)),
87 Y(b.y + 0.3 * Math.sin(b.theta0))
88 );
89 ctx.stroke();
90 ctx.setLineDash([]);
91 }
92 };
93 draw();
94 const mq = window.matchMedia("(prefers-color-scheme: dark)");
95 mq.addEventListener("change", draw);
96 return () => mq.removeEventListener("change", draw);
97 }, [inst]);
99 return (
100 <figure style={{ margin: 0 }}>
101 <canvas ref={canvasRef} />
102 <figcaption className="field-caption" style={{ maxWidth: 360 }}>
103 The domain, the evaluation points where solutions are scored
104 (dots), and the exact solution's{" "}
105 {inst.family === "branch-point"
106 ? "branch point a distance " + inst.d + " outside the boundary " +
107 "(cross), with its cut running radially outward (dashed)"
108 : "sources a distance " + inst.d + " outside the boundary (crosses)"}
109 .
110 </figcaption>
111 </figure>
112 );
113}