website

source code for the ttmaths.uk website
Log | Files | Refs | README

commit c239a3998e2a80e0694dcc0fef868ff878c11454
parent 1d5b7631571ea6d474bb48d99df9c4a507439362
Author: hrryt <harry@mayesfield.uk>
Date:   Fri,  7 Aug 2026 20:53:42 +0100

Add SVG graphing

Diffstat:
ATODO.md | 9+++++++++
Msrc/components/Equation.jsx | 2+-
Dsrc/components/Graph.jsx | 13-------------
Asrc/components/graphing/Graph.jsx | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/graphing/Label.jsx | 27+++++++++++++++++++++++++++
Asrc/components/graphing/Quadratic.jsx | 3+++
Msrc/pages/Home.jsx | 13+++++++++++--
Dsrc/scripts/Plane.js | 13-------------
Msrc/styles/index.css | 1+
Asrc/styles/svg.css | 34++++++++++++++++++++++++++++++++++
10 files changed, 152 insertions(+), 29 deletions(-)

diff --git a/TODO.md b/TODO.md @@ -0,0 +1,9 @@ +# General +- Use KaTeX_Math font outside MathML + +# Questions +- Improve existing questions + - Negative b toggle +- Add more questions + - Add graphing questions +- Drop-down for finer controls diff --git a/src/components/Equation.jsx b/src/components/Equation.jsx @@ -8,5 +8,5 @@ export default function Equation({ equation, displayMode = false }) { render(equation, ref.current, { displayMode: displayMode }); }, [equation]); - return <div ref={ref} >{equation}</div>; + return <div ref={ref}>{equation}</div>; } diff --git a/src/components/Graph.jsx b/src/components/Graph.jsx @@ -1,13 +0,0 @@ -import { useRef, useEffect } from 'preact/hooks'; -import Plane from '../scripts/Plane.js'; - -export default function Graph({ procedure }) { - const ref = useRef(null); - - useEffect(() => { - const plane = new Plane(ref.current); - procedure(plane) - }, [procedure]); - - return <canvas ref={ref} width="150" height="150"/>; -} diff --git a/src/components/graphing/Graph.jsx b/src/components/graphing/Graph.jsx @@ -0,0 +1,66 @@ +import Label from './Label.jsx'; + +function Grid({ viewBox }) { + const [ minX, minY, width, height ] = viewBox; + const d = `M ${minX} ${minY} h ${width} ${`m -${width} 1 h ${width}`.repeat(height)} v -${height} ${`m -1 ${height} v -${height}`.repeat(width)}`; + return <path id="grid" d={d} />; +} + +function Axes({ viewBox }) { + const [ minX, minY, width, height ] = viewBox; + return ( + <g> + <path id="axes" d={`M ${minX} 0 h ${width-.1}`} marker-end="url(#arrow)"/> + <path id="axes" d={`M 0 ${minY} v ${height-.1}`} marker-end="url(#arrow)"/> + </g> + ); +} + +function AxisTicks({ viewBox }) { + const [ minX, minY, width, height ] = viewBox; + return ( + <g> + <g> + {Array.from({ length: width - 1 }).map((_, i) => { + const x = i + minX + 1; + if (x == 0) { return; } + return <Label x={x} y="0" position="bottom" d={.1}>{x}</Label>; + })} + </g> + <g> + {Array.from({ length: height - 1 }).map((_, i) => { + const y = i + minY + 1; + if (y == 0) { return; } + return <Label x="0" y={y} position="left" d={.1}>{y}</Label>; + })} + </g> + <Label x="0" y="0" position="bottom-left" d={.1}>0</Label> + </g> + ) +} + +export default function Graph({ children, width="500", height="500", viewBox=[-5, -5, 10, 10] }) { + return ( + <svg + width={width} height={height} viewBox={viewBox.join(' ')} + transform="scale(1 -1)" xmlns="http://www.w3.org/2000/svg" + > + <defs> + <marker + id='arrow' + viewBox="0 0 4 4" + refX="3" refY="2" + markerWidth="4" markerHeight="4" + markerUnits="strokeWidth" + orient="auto" + > + <path d="M 0 0 L 4 2 L 0 4 z" id='arrowhead' /> + </marker> + </defs> + <Grid viewBox={viewBox} /> + <Axes viewBox={viewBox} /> + {children} + <AxisTicks viewBox={viewBox} /> + </svg> + ); +} diff --git a/src/components/graphing/Label.jsx b/src/components/graphing/Label.jsx @@ -0,0 +1,27 @@ +function getPosition(position) { + switch (position) { + case 'left': + return ['central', 'end', -1, 0]; + case 'right': + return ['central', 'start', 1, 0]; + case 'top': + return ['auto', 'middle', 0, 1]; + case 'bottom': + return ['hanging', 'middle', 0, -1]; + case 'bottom-left': + return ['hanging', 'end', -1, -1]; + } +} + +export default function Label({ x, y, position, d=.15, children }) { + const [dominantBaseline, textAnchor, dx, dy] = getPosition(position); + return ( + <text + x={x} y={-y} dx={d*dx} dy={-d*dy} transform="scale(1 -1)" + dominant-baseline={dominantBaseline} + text-anchor={textAnchor} + > + {children} + </text> + ); +} diff --git a/src/components/graphing/Quadratic.jsx b/src/components/graphing/Quadratic.jsx @@ -0,0 +1,3 @@ +export default function Quadratic() { + return <path d="M -3 9 Q 0 -9 3 9" />; +} diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx @@ -1,12 +1,21 @@ import Window from '../components/Window.jsx'; -import Graph from '../components/Graph.jsx'; +import Graph from '../components/graphing/Graph.jsx'; +import Quadratic from '../components/graphing/Quadratic.jsx'; +import Label from '../components/graphing/Label.jsx'; export default function Home() { return ( <main> <Window title="Home"> <p>Hello World!</p> - <Graph procedure={p => p.draw_test()}/> + <div class="field-border kern"> + <Graph viewBox={[-5, -1, 7, 6]}> + <Quadratic /> + <Label x="-2" y="4" position="left">x²</Label> + <polygon points="1,2 -1,1, -4,3" /> + <Label x="-4" y="3" position="top">(-4, 3)</Label> + </Graph> + </div> </Window> </main> ); diff --git a/src/scripts/Plane.js b/src/scripts/Plane.js @@ -1,13 +0,0 @@ -export default class Plane { - constructor(canvas) { - this.ctx = canvas.getContext("2d"); - } - - draw_test() { - this.ctx.fillStyle = "rgb(200 0 0)"; - this.ctx.fillRect(10, 10, 50, 50); - - this.ctx.fillStyle = "rgb(0 0 200 / 50%)"; - this.ctx.fillRect(30, 30, 50, 50); - } -} diff --git a/src/styles/index.css b/src/styles/index.css @@ -1,3 +1,4 @@ @import 'kern-typ/kern.css'; @import '98.css'; @import './main.css'; +@import './svg.css'; diff --git a/src/styles/svg.css b/src/styles/svg.css @@ -0,0 +1,34 @@ +path,polygon,circle,ellipse,line,polyline { + stroke: #066; + stroke-width: 0.06; + stroke-linejoin: round; + stroke-linecap: round; + fill: transparent; +} + +polygon { + fill: #0668; +} + +path#grid { + stroke: black; + stroke-width: 0.01; +} + +path#axes { + stroke: black; + stroke-width: 0.08; + stroke-linecap: butt; +} + +path#arrowhead { + stroke: black; + fill: black; +} + +text { + font-size: 0.02em; + stroke: white; + stroke-width: 0.02; + paint-order: stroke; +}