import { useId } from 'react';
import type { MappingSpec, TreeNode, TreeSpec, VennSpec } from './types';
import { Arrowheads, COLORS, INK, Label, SERIES, arrowId, mathText } from './svg';
const W = 320;
/* ─── نمودارِ ون ──────────────────────────────────────────────────────────
* ناحیهها با نامِ مجموعههایی که در آناند: «A» فقط A، «AB» اشتراکِ A و B (و نه
* C)، «ABC»، و «out» بیرونِ همه. سایه با برشِ تو در تو (clipPath) و ماسکِ
* «نه-این-مجموعه» ساخته میشود، پس ناحیهی سایهخورده دقیقاً همان ناحیه است. */
export function Venn({ spec }: { spec: VennSpec }) {
const uid = useId().replace(/:/g, '');
const sets = spec.sets.slice(0, 3);
if (sets.length < 1) return null;
const H = sets.length === 3 ? 250 : 190;
const R = sets.length === 3 ? 62 : 70;
const centers: [number, number][] = sets.length === 1 ? [[W / 2, 95]]
: sets.length === 2 ? [[W / 2 - 45, 95], [W / 2 + 45, 95]]
: [[W / 2 - 40, 95], [W / 2 + 40, 95], [W / 2, 160]];
const ids = sets.map((_, i) => `v${uid}-${i}`);
const keyOf = (inside: number[]) => inside.map((i) => sets[i]).join('');
// مرکزِ تقریبیِ هر ناحیه برای نوشتنِ اعضا
const regionCenter = (code: string): [number, number] => {
if (code === 'out') return [22, H - 16];
const inside = sets.map((s, i) => (code.includes(s) ? i : -1)).filter((i) => i >= 0);
const all = centers.map((_, i) => i);
const cxIn = inside.reduce((a, i) => a + centers[i][0], 0) / inside.length, cyIn = inside.reduce((a, i) => a + centers[i][1], 0) / inside.length;
const outside = all.filter((i) => !inside.includes(i));
if (!outside.length || inside.length > 1) {
if (inside.length === sets.length) return [cxIn, cyIn];
const o = outside.length ? centers[outside[0]] : [cxIn, cyIn];
return [cxIn + (cxIn - o[0]) * 0.18, cyIn + (cyIn - o[1]) * 0.18];
}
const ox = outside.reduce((a, i) => a + centers[i][0], 0) / outside.length, oy = outside.reduce((a, i) => a + centers[i][1], 0) / outside.length;
const dx = cxIn - ox, dy = cyIn - oy, n = Math.hypot(dx, dy) || 1;
return [cxIn + (dx / n) * R * 0.42, cyIn + (dy / n) * R * 0.42];
};
const shaded = (spec.shade ?? []).filter((c) => c === 'out' || [...c].every((ch) => sets.includes(ch)));
const shadeRegion = (code: string, idx: number) => {
const fill = `${COLORS.orange}66`;
if (code === 'out') {
return (
{centers.map((c, i) => )}
);
}
const inside = sets.map((s, i) => (code.includes(s) ? i : -1)).filter((i) => i >= 0);
const outside = sets.map((_, i) => i).filter((i) => !inside.includes(i));
const mid = `${ids[0]}-m${idx}`;
let node = (
{outside.length > 0 && {outside.map((i) => )}}
);
for (const i of inside) node = {node};
return {node};
};
return (
);
}
const chunk = (xs: T[], n: number): T[][] => Array.from({ length: Math.ceil(xs.length / n) }, (_, i) => xs.slice(i * n, i * n + n));
/* ─── نمودارِ پیکانی (تابع از A به B) ─────────────────────────────────────── */
export function Mapping({ spec }: { spec: MappingSpec }) {
const L = spec.left.slice(0, 10), Rt = spec.right.slice(0, 10);
const rows = Math.max(L.length, Rt.length, 1);
const H = 56 + rows * 28;
const yOf = (i: number, n: number) => 40 + ((i + 0.5) * (rows * 28)) / n;
// جهتِ نگاشت چپ به راست است، مثلِ کتاب (دامنه سمتِ چپ)
const lx = 78, rx = W - 78;
const col = COLORS.red;
return (
);
}
/* ─── نمودارِ درختی (احتمال و شمارش) ──────────────────────────────────────── */
export function Tree({ spec }: { spec: TreeSpec }) {
interface Laid { node: TreeNode; x: number; y: number; depth: number; parent?: Laid }
const laid: Laid[] = [];
let leaf = 0;
const depthOf = (n: TreeNode): number => 1 + Math.max(0, ...(n.children ?? []).map(depthOf));
const depth = Math.min(4, depthOf(spec.root));
const levelH = 62;
const place = (n: TreeNode, d: number, parent?: Laid): Laid => {
const kids = d < 3 ? (n.children ?? []).slice(0, 6) : [];
const me: Laid = { node: n, x: 0, y: 24 + d * levelH, depth: d, parent };
if (!kids.length) { me.x = leaf++; laid.push(me); return me; }
const placed = kids.map((c) => place(c, d + 1, me));
me.x = (placed[0].x + placed[placed.length - 1].x) / 2;
laid.push(me);
return me;
};
place(spec.root, 0);
const leaves = Math.max(1, leaf);
const H = 24 + (depth - 1) * levelH + 34;
const X = (x: number) => 24 + ((x + 0.5) / leaves) * (W - 48);
return (
);
}