e s«# DASGraph — Discrete Relation Practice
Local-only React + TypeScript app for exam-style discrete mathematics practice. The app is organized by relation family (closures, composition, structures) and submode (construct, multiple-choice, review).
src/math/: Pure relation logic. Normalization, closures, composition, transpose, property checks, checking utilities.src/layout/: Deterministic geometry generation (seeded), no relation logic.src/render/: SVG path utilities, arrow markers, export helpers.src/parser/: JSON parsing + runtime validation; outputs normalized, typed data with warnings.src/components/: React UI and diagram composition.src/state/: JSON editor state + study mode defaults.src/examples/: Family + submode examples.
Single source of truth: normalized relation data. Layouts only affect geometry.
npm install
npm run dev- Practice (default): hides solutions by default.
- Review: reveals solutions by default.
{
"family": "closures",
"submode": "construct",
"task": "transitive",
"set": ["1","2","3","4"],
"relation": [["1","2"],["2","3"]],
"layoutPreset": "irregular-exam",
"seed": 12
}{
"family": "closures",
"submode": "multiple-choice",
"task": "transitive",
"set": ["1","2","3","4"],
"relation": [["1","2"],["2","3"]],
"correctOptionId": "B",
"options": [
{ "id": "A", "relation": [["1","2"],["2","3"]] },
{ "id": "B", "relation": [["1","2"],["2","3"],["1","3"]] },
{ "id": "C", "relation": [["1","2"],["2","3"],["3","1"]] },
{ "id": "D", "relation": [["1","2"],["2","3"],["1","4"]] }
],
"layoutPreset": "compact-exam",
"seed": 12
}{
"family": "composition",
"submode": "construct",
"X": ["1","2","3"],
"Y": ["1","2","3"],
"Z": ["1","2","3"],
"R": [["1","2"],["2","3"]],
"S": [["2","1"],["3","2"]],
"allowTraceAssist": true
}{
"family": "composition",
"submode": "multiple-choice",
"X": ["1","2","3"],
"Y": ["1","2","3"],
"Z": ["1","2","3"],
"R": [["1","2"],["2","3"]],
"S": [["2","1"],["3","2"]],
"correctOptionId": "C",
"options": [
{ "id": "A", "relation": [["1","1"]] },
{ "id": "B", "relation": [["2","2"]] },
{ "id": "C", "relation": [["1","1"],["2","2"]] },
{ "id": "D", "relation": [["1","2"],["2","1"]] }
]
}{
"family": "structures",
"submode": "true-false",
"set": ["1","2","3","4","5","6","7","8"],
"relation": [["1","2"],["2","3"],["3","4"]],
"askAboutSquaredRelation": true,
"layoutPreset": "polygon",
"seed": 7
}{
"family": "structures",
"submode": "multiple-choice",
"prompt": "Choose the correct graph for R ∘ R",
"set": ["1","2","3","4"],
"relation": [["1","2"],["2","3"]],
"correctOptionId": "B",
"options": [
{ "id": "A", "relation": [["1","2"],["2","3"]] },
{ "id": "B", "relation": [["1","3"]] },
{ "id": "C", "relation": [["3","1"]] },
{ "id": "D", "relation": [["1","1"],["2","2"]] }
],
"derivedTask": "square-relation"
}- Elements accept strings or numbers (normalized to internal string ids).
- Relation pairs must be 2-element arrays.
- Relations must be subsets of the intended Cartesian product.
- Duplicate labels (after normalization) are rejected.
- Multiple-choice inputs warn if the marked correct option disagrees with computed results.
npm run testCoverage includes:
- closure/composition checking utilities
- property checking
- correct-option validation warnings
- study mode defaults
- deterministic layouts
- opposite-direction edge routing
- label centering defaults