// The polar grid of cymbal2_grid.m, seen from above: real radial lines from // the washer circle (l = 3) to the free edge (l = Nr), virtual lines beyond // both boundaries drawn dashed, and the azimuthal spokes. Sliders reproduce // the n_radial / n_theta parameters of the app. import { useState } from 'react'; import { useCanvas, drawFrame, PLOT_COLORS } from '../../components/plots/canvasUtils'; import { Figure, FigControls, FigSlider } from '../figparts'; const HEIGHT = 340; const A = 0.23; const C = 0.029; export default function GridFig() { const [nr, setNr] = useState(24); const [nth, setNth] = useState(60); const ref = useCanvas( (ctx, w, h) => { drawFrame(ctx, w, h); const cx = w / 2; const cy = h / 2 + 6; const hr = (A - C) / (nr - 3); const rOuterVirtual = C + (nr - 1) * hr; // l = Nr + 2 const scale = (Math.min(w, h) / 2 - 26) / rOuterVirtual; const rl = (l: number) => Math.max(0, C + (l - 3) * hr) * scale; // spokes ctx.strokeStyle = 'rgba(42, 47, 56, 0.9)'; for (let j = 0; j < nth; j++) { const th = (2 * Math.PI * j) / nth; ctx.beginPath(); ctx.moveTo(cx + rl(3) * Math.cos(th), cy + rl(3) * Math.sin(th)); ctx.lineTo(cx + rl(nr) * Math.cos(th), cy + rl(nr) * Math.sin(th)); ctx.stroke(); } // real circles for (let l = 3; l <= nr; l++) { ctx.strokeStyle = l === 3 || l === nr ? PLOT_COLORS.accent : 'rgba(139, 147, 161, 0.35)'; ctx.lineWidth = l === 3 || l === nr ? 2 : 1; ctx.beginPath(); ctx.arc(cx, cy, rl(l), 0, 2 * Math.PI); ctx.stroke(); } ctx.lineWidth = 1; // virtual circles, dashed ctx.setLineDash([4, 4]); ctx.strokeStyle = 'rgba(229, 115, 115, 0.55)'; for (const l of [1, 2, nr + 1, nr + 2]) { const r = rl(l); if (r <= 1) continue; ctx.beginPath(); ctx.arc(cx, cy, r, 0, 2 * Math.PI); ctx.stroke(); } ctx.setLineDash([]); // node dots when sparse enough to read const totalNodes = (nr - 2) * nth; if (totalNodes <= 2600) { ctx.fillStyle = 'rgba(214, 218, 226, 0.7)'; for (let l = 3; l <= nr; l++) { for (let j = 0; j < nth; j++) { const th = (2 * Math.PI * j) / nth; ctx.beginPath(); ctx.arc(cx + rl(l) * Math.cos(th), cy + rl(l) * Math.sin(th), 1.4, 0, 2 * Math.PI); ctx.fill(); } } } // legend and readouts, kept off the disk where they collide ctx.font = '11px system-ui'; ctx.textAlign = 'left'; ctx.fillStyle = PLOT_COLORS.accent; ctx.fillText('amber: washer circle (l = 3) and free edge (l = Nr)', 12, 20); ctx.fillStyle = 'rgba(229, 115, 115, 0.9)'; ctx.fillText('dashed: virtual lines', 12, 38); ctx.fillStyle = PLOT_COLORS.axis; const stepMm = (hr * 1000).toFixed(1); ctx.fillText(`radial spacing h_r = ${stepMm} mm`, 12, h - 26); ctx.fillText( `unknowns per step: ${(nr + 2) * nth} displacement + ${(nr + 2) * nth} stress function`, 12, h - 8, ); }, [nr, nth], HEIGHT, ); return (
The grid of Samejima's Fig. 4 for the paper's shell (23 cm radius, 2.9 cm washer circle). Real nodes live on the radial lines l = 3 … Nr, between the washer circle and the free edge, both drawn amber. The two dashed lines beyond each boundary carry virtual nodes: the fourth-order operators need neighbors two lines deep on each side, and the boundary-condition equations are what determine the values there. The paper's production grid is Nr = 24, Nθ{' '} = 60, drawn here as the default. } > `${Math.round(v)}`} onChange={(v) => setNr(Math.round(v))} /> `${Math.round(v)}`} onChange={(v) => setNth(2 * Math.round(v / 2))} />
); }