website

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

commit c01935b6086ba220d716b5b73046bad1308efc02
parent 1a6a42e93e63b8a1f1c7db46217b3435b62f755a
Author: fen123456 <fenwthompson@gmail.com>
Date:   Sat, 15 Aug 2026 22:22:56 +0100

split utils up

Diffstat:
Msrc/components/QuestionWindow.jsx | 2+-
Msrc/components/RotationTextElement.jsx | 2+-
Msrc/components/plotting/Angle.jsx | 2+-
Msrc/components/plotting/Cubic.jsx | 2+-
Msrc/components/plotting/Label.jsx | 2+-
Msrc/components/plotting/Line.jsx | 2+-
Msrc/components/plotting/Point.jsx | 2+-
Msrc/components/plotting/Polygon.jsx | 2+-
Msrc/components/plotting/Quadratic.jsx | 2+-
Msrc/components/plotting/Segment.jsx | 2+-
Msrc/components/plotting/Sinusoid.jsx | 3++-
Msrc/components/questions/AddingFractionsQuestion.jsx | 4+++-
Msrc/components/questions/DoubleReflectionQuestion.jsx | 15++-------------
Msrc/components/questions/QuadraticEquationQuestion.jsx | 4+++-
Msrc/components/questions/RotationTranslationQuestion.jsx | 2+-
Asrc/scripts/formatUtils.js | 39+++++++++++++++++++++++++++++++++++++++
Asrc/scripts/graphUtils.js | 46++++++++++++++++++++++++++++++++++++++++++++++
Asrc/scripts/mathsUtils.js | 14++++++++++++++
Asrc/scripts/randomUtils.js | 16++++++++++++++++
Dsrc/scripts/utils.js | 116-------------------------------------------------------------------------------
20 files changed, 136 insertions(+), 143 deletions(-)

diff --git a/src/components/QuestionWindow.jsx b/src/components/QuestionWindow.jsx @@ -1,7 +1,7 @@ import { useState } from 'preact/hooks'; import QuestionList from './QuestionList.jsx'; -import { getRandomInt, getRandomNumber, getRandomChoice } from '../scripts/utils.js'; import Window from './Window.jsx'; +import { getRandomChoice, getRandomNumber } from '../scripts/randomUtils.js'; const seedDiff = 100; diff --git a/src/components/RotationTextElement.jsx b/src/components/RotationTextElement.jsx @@ -1,4 +1,4 @@ -import { trueMod } from "../scripts/utils" +import { trueMod } from "../scripts/mathsUtils" import Equation from "./Equation" export default function RotationTextElement({ angle }) { diff --git a/src/components/plotting/Angle.jsx b/src/components/plotting/Angle.jsx @@ -1,7 +1,7 @@ import Label from './Label.jsx'; import { useContext } from 'preact/hooks'; import { StrokeWidthContext } from '../../scripts/contexts.js'; -import { getColourClass, getMidpoint, interpolateDistance, sumVectors, getVector } from '../../scripts/utils.js'; +import { getColourClass, getMidpoint, interpolateDistance, sumVectors, getVector } from '../../scripts/graphUtils.js'; function getArc(points, radius) { const start = interpolateDistance(points[1], points[0], radius); diff --git a/src/components/plotting/Cubic.jsx b/src/components/plotting/Cubic.jsx @@ -1,4 +1,4 @@ -import { getColourClass, getSymmetricBounds } from '../../scripts/utils.js'; +import { getColourClass, getSymmetricBounds } from '../../scripts/graphUtils.js'; function getDepressedCubicRoots(a, b, c, d) { // https://en.wikipedia.org/wiki/Cubic_equation#Depressed_cubic diff --git a/src/components/plotting/Label.jsx b/src/components/plotting/Label.jsx @@ -1,6 +1,6 @@ import { useContext } from 'preact/hooks'; import { StrokeWidthContext } from '../../scripts/contexts.js'; -import { getColourClass } from '../../scripts/utils.js'; +import { getColourClass } from '../../scripts/graphUtils.js'; function getPosition(position) { switch (position) { diff --git a/src/components/plotting/Line.jsx b/src/components/plotting/Line.jsx @@ -1,4 +1,4 @@ -import { getColourClass } from "../../scripts/utils" +import { getColourClass } from "../../scripts/graphUtils" export default function Line({ line, viewBox, label=null, colour='auto' }) { const [ minX, minY, width, height ] = viewBox diff --git a/src/components/plotting/Point.jsx b/src/components/plotting/Point.jsx @@ -1,5 +1,5 @@ import Label from './Label.jsx'; -import { getColourClass } from '../../scripts/utils.js'; +import { getColourClass } from '../../scripts/graphUtils.js'; export default function Point({ point, label=null, colour="auto", position="top" }) { return ( diff --git a/src/components/plotting/Polygon.jsx b/src/components/plotting/Polygon.jsx @@ -1,5 +1,5 @@ +import { getColourClass } from '../../scripts/graphUtils.js'; import Label from './Label.jsx'; -import { getColourClass } from '../../scripts/utils.js'; function getMean(arr) { return arr.reduce((a, b) => a + b) / arr.length; diff --git a/src/components/plotting/Quadratic.jsx b/src/components/plotting/Quadratic.jsx @@ -1,4 +1,4 @@ -import { getColourClass, getSymmetricBounds } from '../../scripts/utils.js'; +import { getColourClass, getSymmetricBounds } from "../../scripts/graphUtils"; export default function Quadratic({ a=1, b=0, c=0, colour="auto", viewBox }) { function F(x) { return a*x*x + b*x + c; } diff --git a/src/components/plotting/Segment.jsx b/src/components/plotting/Segment.jsx @@ -1,4 +1,4 @@ -import { getColourClass, getMidpoint } from '../../scripts/utils.js'; +import { getColourClass, getMidpoint } from "../../scripts/graphUtils"; export default function Segment({ points, marker = null, colour = "auto" }) { if (!marker) { diff --git a/src/components/plotting/Sinusoid.jsx b/src/components/plotting/Sinusoid.jsx @@ -1,5 +1,6 @@ +import { getColourClass } from '../../scripts/graphUtils.js'; +import { trueMod } from '../../scripts/mathsUtils.js'; import Point from './Point.jsx'; -import { getColourClass, trueMod } from '../../scripts/utils.js'; export default function Sinusoid({ period=1, amp=1, dx=0, dy=0, colour="auto", viewBox }) { const [minX, minY, width, height] = viewBox; diff --git a/src/components/questions/AddingFractionsQuestion.jsx b/src/components/questions/AddingFractionsQuestion.jsx @@ -1,5 +1,7 @@ +import { formatFraction } from '../../scripts/formatUtils.js'; +import { simplifyFraction, getHCF } from '../../scripts/mathsUtils.js'; +import { getRandomInt } from '../../scripts/randomUtils.js'; import Question from '../Question.jsx'; -import { getRandomInt, getHCF, formatFraction, simplifyFraction } from '../../scripts/utils.js'; function getRandomFraction(seed) { const num0 = getRandomInt(1, 5, seed++); diff --git a/src/components/questions/DoubleReflectionQuestion.jsx b/src/components/questions/DoubleReflectionQuestion.jsx @@ -1,4 +1,3 @@ -import { formatLine, getRandomChoice, getRandomInt } from "../../scripts/utils"; import Plot from "../plotting/Plot"; import Line from "../plotting/Line"; import Polygon from "../plotting/Polygon"; @@ -6,10 +5,8 @@ import Question from "../Question"; import Point from "../plotting/Point"; import Equation from "../Equation"; import RotationTextElement from "../RotationTextElement"; - -function shapeToString(shape) { - return shape.map((point) => point.join(",")).join(" ") -} +import { getRandomChoice, getRandomInt } from "../../scripts/randomUtils"; +import { formatLine } from "../../scripts/formatUtils"; function reflectPoint(point, line = {a: 1, b: 1, c: 0}) { const [px, py] = point @@ -22,14 +19,6 @@ function getLineAngle(line) { return -Math.atan(line.b/line.a) } -function rotationText(angle) { - angle = ((angle + Math.PI) % 2*Math.PI) - Math.PI // range -pi to pi - angle = Math.round(angle * 180 / Math.PI) - if (angle == 0) { return '180degree' } - if (angle < 0) { return `${-angle}degree anticlockwise`} - return `${angle}degree clockwise` -} - function findIntersect(line1, line2) { const k = line1.a*line2.b - line2.a*line1.b // parallel diff --git a/src/components/questions/QuadraticEquationQuestion.jsx b/src/components/questions/QuadraticEquationQuestion.jsx @@ -1,5 +1,7 @@ +import { formatCoefficient, formatFraction } from '../../scripts/formatUtils.js'; +import { getHCF } from '../../scripts/mathsUtils.js'; +import { getRandomChoice, getRandomInt } from '../../scripts/randomUtils.js'; import Question from '../Question.jsx'; -import { getRandomInt, getRandomBoolean, getRandomChoice, formatCoefficient, getHCF, formatFraction } from '../../scripts/utils.js'; function formatQuadraticExpression(a, b, c) { const fa = formatCoefficient(a, { showPlus: false, showOne: false, showZero: false, suffix: 'x^2' }); diff --git a/src/components/questions/RotationTranslationQuestion.jsx b/src/components/questions/RotationTranslationQuestion.jsx @@ -1,10 +1,10 @@ -import { getRandomChoice, getRandomInt } from "../../scripts/utils"; import Point from "../plotting/Point"; import Polygon from "../plotting/Polygon"; import Plot from "../plotting/Plot"; import Question from "../Question"; import Equation from "../Equation"; import RotationTextElement from "../RotationTextElement"; +import { getRandomChoice, getRandomInt } from "../../scripts/randomUtils"; function rotatePoint(point, angle, axis=[0, 0]) { const diff = [point[0] - axis[0], point[1] - axis[1]] diff --git a/src/scripts/formatUtils.js b/src/scripts/formatUtils.js @@ -0,0 +1,39 @@ +import { getHCF, simplifyFraction } from './mathsUtils' + +export function formatCoefficient(x, parameters = { showPlus: false, showOne: true, showZero: true, suffix: '' }) { + if (!parameters.showZero && x == 0) { return ''; } + const plus = parameters.showPlus ? '+' : ''; + const sign = x < 0 ? '-' : plus; + x = Math.abs(x); + parameters.showOne || x == 1 && (x = ''); + parameters.suffix || (parameters.suffix = '') // if suffix undefined + return sign + x + parameters.suffix; +} + +// this thing is probably begging for bugs from simplification and zeroes. +export function formatFraction(num, denom, parameters = { showPlus: false, showOne: true, showZero: true, simplify: false, suffix: '' }) { + if (!parameters.showZero && num == 0) { return ''; } + if (getHCF(num, denom) == denom && parameters.simplify) { return formatCoefficient(simplifyFraction(num, denom)[0], parameters)} + const [numOriginal, denomOriginal] = [num, denom] + const plus = parameters.showPlus ? '+' : '' + const sign = num / denom < 0 ? '-' : plus + + num = Math.abs(num) + denom = Math.abs(denom); + [num, denom] = parameters.simplify ? simplifyFraction(num, denom) : [num, denom] + parameters.suffix || (parameters.suffix = '') + + let x = `frac(${num}, ${denom})` + parameters.showOne || num / denom == 1 && (x = ''); + return sign + x + parameters.suffix +} + +export function formatLine(line) { + if (line.b == 0) { return `x = ${formatFraction(-line.c, line.a, { showPlus: false, showOne: true, showZero: true, simplify: true })}`; } + let mxTerm = formatFraction(-line.a, line.b, { showPlus: false, showOne: false, showZero: false, simplify: true, suffix: 'x' }) + const cTerm = formatFraction(-line.c, line.b, { showPlus: false, showOne: true, showZero: false, simplify: true }) + if (!mxTerm && !cTerm) { + mxTerm = '0' + } + return 'y = ' + mxTerm + cTerm +} diff --git a/src/scripts/graphUtils.js b/src/scripts/graphUtils.js @@ -0,0 +1,46 @@ +function scale(vector, factor) { + return vector.map(a => a * factor); +} + +function sum(arr) { + return arr.reduce((a, b) => a + b); +} + +export function sumVectors(a, b) { + return a.map((ai, i) => b[i] + ai); +} + +export function getVector(start, end) { + return start.map((a, i) => end[i] - a); +} + +function normalise(vector, scaleFactor = 1) { + const length = Math.sqrt(sum(vector.map(a => a*a))); + return scale(vector, scaleFactor / length); +} + +function interpolate(start, end, along) { + return sumVectors(start, scale(getVector(start, end), along)); +} + +export function interpolateDistance(start, end, distance) { + return sumVectors(start, normalise(getVector(start, end), distance)); +} + +export function getMidpoint(points) { + return interpolate(points[0], points[1], 0.5); +} + +export function getSymmetricBounds(midX, dx, viewBox) { + const [minX, minY, width, height] = viewBox; + const maxX = minX + width; + + const maxDx = Math.max(midX - minX, maxX - midX); + const dx0 = Math.min(maxDx, dx); + return [midX - dx0, midX + dx0]; +} + +export function getColourClass(colour) { + if (colour == 'auto') { return ''; } + return `color-${colour}`; +} diff --git a/src/scripts/mathsUtils.js b/src/scripts/mathsUtils.js @@ -0,0 +1,14 @@ +export function getHCF(a, b) { + if (a == 1 && b == 1) { return 1 } + return b == 0 ? a : getHCF(b, a % b); +} + +export function simplifyFraction(num, denom) { + const hcf = getHCF(num, denom); + return [num / hcf, denom / hcf]; +} + +export function trueMod(a, b) { + // a mod b, but with expected behaviour for negatives. + return a >= 0 ? a % b : b - ((-a % b) || b); +} diff --git a/src/scripts/randomUtils.js b/src/scripts/randomUtils.js @@ -0,0 +1,16 @@ +export function getRandomInt(min, max, seed) { + return min + Math.floor((max - min + 1) * getRandomNumber(seed)); +} + +export function getRandomChoice(arr, seed) { + return arr[getRandomInt(0, arr.length - 1, seed)]; +} + +export function getRandomBoolean(seed) { + return getRandomNumber(seed) < 0.5; +} + +export function getRandomNumber(seed) { + const x = 1e4 * Math.sin(7 * seed); + return x - Math.floor(x); +} diff --git a/src/scripts/utils.js b/src/scripts/utils.js @@ -1,116 +0,0 @@ -export function getRandomInt(min, max, seed) { - return min + Math.floor((max - min + 1) * getRandomNumber(seed)); -} - -export function getRandomChoice(arr, seed) { - return arr[getRandomInt(0, arr.length - 1, seed)]; -} - -export function getRandomBoolean(seed) { - return getRandomNumber(seed) < 0.5; -} - -export function getRandomNumber(seed) { - const x = 1e4 * Math.sin(7 * seed); - return x - Math.floor(x); -} - -export function getHCF(a, b) { - if (a == 1 && b == 1) { return 1 } - return b == 0 ? a : getHCF(b, a % b); -} - -export function formatCoefficient(x, parameters = { showPlus: false, showOne: true, showZero: true, suffix: '' }) { - if (!parameters.showZero && x == 0) { return ''; } - const plus = parameters.showPlus ? '+' : ''; - const sign = x < 0 ? '-' : plus; - x = Math.abs(x); - parameters.showOne || x == 1 && (x = ''); - parameters.suffix || (parameters.suffix = '') // if suffix undefined - return sign + x + parameters.suffix; -} - -export function simplifyFraction(num, denom) { - const hcf = getHCF(num, denom); - return [num / hcf, denom / hcf]; -} - -// this thing is probably begging for bugs from simplification and zeroes. -export function formatFraction(num, denom, parameters = { showPlus: false, showOne: true, showZero: true, simplify: false, suffix: '' }) { - if (!parameters.showZero && num == 0) { return ''; } - if (getHCF(num, denom) == denom && parameters.simplify) { return formatCoefficient(simplifyFraction(num, denom)[0], parameters)} - const [numOriginal, denomOriginal] = [num, denom] - const plus = parameters.showPlus ? '+' : '' - const sign = num / denom < 0 ? '-' : plus - - num = Math.abs(num) - denom = Math.abs(denom); - [num, denom] = parameters.simplify ? simplifyFraction(num, denom) : [num, denom] - parameters.suffix || (parameters.suffix = '') - - let x = `frac(${num}, ${denom})` - parameters.showOne || num / denom == 1 && (x = ''); - return sign + x + parameters.suffix -} - -export function formatLine(line) { - if (line.b == 0) { return `x = ${formatFraction(-line.c, line.a, { showPlus: false, showOne: true, showZero: true, simplify: true })}`; } - let mxTerm = formatFraction(-line.a, line.b, { showPlus: false, showOne: false, showZero: false, simplify: true, suffix: 'x' }) - const cTerm = formatFraction(-line.c, line.b, { showPlus: false, showOne: true, showZero: false, simplify: true }) - if (!mxTerm && !cTerm) { - mxTerm = '0' - } - return 'y = ' + mxTerm + cTerm -} - -export function getColourClass(colour) { - if (colour == 'auto') { return ''; } - return `color-${colour}`; -} - -function scale(vector, factor) { - return vector.map(a => a * factor); -} - -function sum(arr) { - return arr.reduce((a, b) => a + b); -} - -export function sumVectors(a, b) { - return a.map((ai, i) => b[i] + ai); -} - -export function getVector(start, end) { - return start.map((a, i) => end[i] - a); -} - -function normalise(vector, scaleFactor = 1) { - const length = Math.sqrt(sum(vector.map(a => a*a))); - return scale(vector, scaleFactor / length); -} - -function interpolate(start, end, along) { - return sumVectors(start, scale(getVector(start, end), along)); -} - -export function interpolateDistance(start, end, distance) { - return sumVectors(start, normalise(getVector(start, end), distance)); -} - -export function getMidpoint(points) { - return interpolate(points[0], points[1], 0.5); -} - -export function trueMod(a, b) { - // a mod b, but with expected behaviour for negatives. - return a >= 0 ? a % b : b - ((-a % b) || b); -} - -export function getSymmetricBounds(midX, dx, viewBox) { - const [minX, minY, width, height] = viewBox; - const maxX = minX + width; - - const maxDx = Math.max(midX - minX, maxX - midX); - const dx0 = Math.min(maxDx, dx); - return [midX - dx0, midX + dx0]; -}