website

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

commit 1d2caaf2fa5bf75b8657687ee9cf9bc44e4191e1
parent 43e995d5b1aac79a4bd450462e4ed6b49b2c73ce
Author: hrryt <harry@mayesfield.uk>
Date:   Sat,  8 Aug 2026 17:55:21 +0100

Add cubic graphing component

Diffstat:
Asrc/components/graphing/Cubic.jsx | 14++++++++++++++
Msrc/components/graphing/Quadratic.jsx | 16++++------------
Dsrc/components/questions/AddingFractionsQuestion.jsx | 41-----------------------------------------
Dsrc/components/questions/QuadraticEquationQuestion.jsx | 86-------------------------------------------------------------------------------
Msrc/pages/Home.jsx | 4+++-
Msrc/scripts/utils.js | 6++++++
6 files changed, 27 insertions(+), 140 deletions(-)

diff --git a/src/components/graphing/Cubic.jsx b/src/components/graphing/Cubic.jsx @@ -0,0 +1,14 @@ +import { getSymmetricBounds } from '/src/scripts/utils.js'; + +export default function Cubic({ a=1, b=0, c=0, d=0, viewBox }) { + // https://math.stackexchange.com/questions/3356084/to-construct-a-polynomial-using-b%C3%A9zier-curves + // http://graphics.stanford.edu/courses/cs164-09-spring/Handouts/handout19.pdf + function g(u, v, w) { return a*u*v*w + b*(v*w + w*u + u*v)/3 + c*(u + v + w)/3 + d; } + + const [i, f] = getSymmetricBounds(-b / (3*a), viewBox); + // https://math.stackexchange.com/questions/2414459/draw-cubic-polynomial-using-2d-cubic-bezier-curve + const x1 = 2/3 * i + 1/3 * f; + const x2 = 1/3 * i + 2/3 * f; + + return <path d={`M ${i},${g(i,i,i)} C ${x1},${g(i,i,f)} ${x2},${g(i,f,f)} ${f},${g(f,f,f)}`} /> +} diff --git a/src/components/graphing/Quadratic.jsx b/src/components/graphing/Quadratic.jsx @@ -1,18 +1,10 @@ -export default function Quadratic({ a, b, c, viewBox }) { - const [ minX, minY, width, height ] = viewBox; +import { getSymmetricBounds } from '/src/scripts/utils.js'; + +export default function Quadratic({ a=1, b=0, c=0, viewBox }) { function f(x) { return a*x*x + b*x + c; } const turnX = -b / (2*a); - - const maxX = minX + width; - // Assumes turnX within viewBox! - const halfWidth = Math.max( - turnX - minX, - maxX - turnX - ); - - const startX = turnX - halfWidth; - const endX = turnX + halfWidth; + const [startX, endX] = getSymmetricBounds(turnX, viewBox); const endY = f(endX); const controlY = 2*f(turnX) - endY; diff --git a/src/components/questions/AddingFractionsQuestion.jsx b/src/components/questions/AddingFractionsQuestion.jsx @@ -1,41 +0,0 @@ -import Question from '../Question.jsx'; -import { getRandomInt, getHCF } from '../../scripts/utils.js'; - -function formatFraction(num, denom) { - if (denom == 1) { return `${num}`; } - return num + '/' + denom; -} - -function simplifyFraction(num, denom) { - const hcf = getHCF(num, denom); - return [num / hcf, denom / hcf]; -} - -function getRandomFraction(seed) { - const num0 = getRandomInt(1, 5, seed); - const denom0 = getRandomInt(2, 10, seed + .05); - const [num, denom] = simplifyFraction(num0, denom0); - if (denom == 1) { return getRandomFraction(seed + .1); } - return [num, denom]; -} - -function getRandomFractions(seed) { - const [num1, denom1] = getRandomFraction(seed); - const [num2, denom2] = getRandomFraction(seed + .5); - return [num1, denom1, num2, denom2]; -} - -export default function AddingFractionsQuestion({ seed, showAnswer }) { - const [num1, denom1, num2, denom2] = getRandomFractions(seed); - - const question = formatFraction(num1, denom1) + ' + ' + formatFraction(num2, denom2); - - const hcf = getHCF(denom1, denom2); - const sum = (num1 * denom2 + num2 * denom1) / hcf; - const lcm = denom1 * denom2 / hcf; - const [num, denom] = simplifyFraction(sum, lcm); - - const answer = formatFraction(num, denom); - - return <Question question={question} answer={answer} showAnswer={showAnswer} />; -} diff --git a/src/components/questions/QuadraticEquationQuestion.jsx b/src/components/questions/QuadraticEquationQuestion.jsx @@ -1,85 +0,0 @@ -import Question from '../Question.jsx'; -import { getRandomInt, getRandomBoolean, getRandomChoice, formatCoefficient, getHCF } from '../../scripts/utils.js'; - -function formatQuadraticExpression(a, b, c) { - const fa = formatCoefficient(a, { showPlus: false, showOne: false, showZero: false, suffix: 'x^2' }); - const fb = formatCoefficient(b, { showPlus: true, showOne: false, showZero: false, suffix: 'x' }); - const fc = formatCoefficient(c, { showPlus: true, showOne: true, showZero: false }); - return `${fa} ${fb} ${fc}`; -} - -function formatFraction(num, denom) { - const hcf = getHCF(Math.abs(num), Math.abs(denom)) - num = num / hcf - denom = denom / hcf - if (denom == 1) { return `${num}`; } - return `frac(${num}, ${denom})` -} - -function formatRoots(l, m, n, o) { - if (m / l == o / n) { return `x = ${formatFraction(-m, l)}`; } - if (-m / l == o / n) { return `x = plus.minus ${formatFraction(Math.abs(-m), l)}`; } - - return `x = ${formatFraction(-m, l)} "or" x = ${formatFraction(-o, n)}`; -} - -function formatFactor(x_coeff, const_coeff) { - if (const_coeff == 0) { return "x"; } - const fx_coeff = formatCoefficient(x_coeff, { showPlus: false, showOne: false, showZero: false, suffix: 'x'}) - const fconst_coeff = formatCoefficient(const_coeff, { showPlus: true, showOne: true, showZero: false }) - return `(${fx_coeff}${fconst_coeff})`; -} - -function formatFactors(l, m, n, o) { - if (l == n && m == o) { return `${formatFactor(l, m)}^2`} - return `${formatFactor(l, m)}${formatFactor(n, o)}` -} - -function getRandomRoots(seed) { - return [ - getRandomInt(-9, 9, seed), - getRandomInt(-9, 9, seed + .1), - ].sort((a, b) => a - b); -} - -export default function QuadraticEquationQuestion({ seed, showAnswer, aPlurality, framing, cSigns }) { - // (lx + m)(nx + o) - - const aChoices = Array(); - aPlurality.includes("a=1") && aChoices.push(1); - aPlurality.includes("a>1") && aChoices.push(2, 3); - let l = getRandomChoice(aChoices, seed); - let n = getRandomChoice(aChoices, seed + .1); - - const factoriseChoices = Array(); - framing.includes("Solve") && factoriseChoices.push(false); - framing.includes("Factorise") && factoriseChoices.push(true); - const factorise = getRandomChoice(factoriseChoices, seed + .2); - - const cSignChoices = Array(); - cSigns.includes("Positive") && cSignChoices.push(1) - cSigns.includes("Negative") && cSignChoices.push(-1) - - const mSign = getRandomChoice([-1, 1], seed + .3) - const cSign = getRandomChoice(cSignChoices, seed + .4) - - let m = getRandomInt(1, 9, seed + .5) * mSign - let o = getRandomInt(1, 9, seed + .6) * mSign * cSign - - if (-m/l > -o/n) { - [l, m, n, o] = [n, o, l, m] - } - - // lnx^2 + (lo + nm)x + mo - const a = l * n - const b = (l * o) + (n * m) - const c = m * o - - const quadratic = formatQuadraticExpression(a, b, c); - const question = factorise ? `"Factorise" ${quadratic}` : `"Solve" ${quadratic} = 0`; - - const answer = factorise ? formatFactors(l, m, n, o) : formatRoots(l, m, n, o); - - return <Question question={question} answer={answer} showAnswer={showAnswer} />; - -} -\ No newline at end of file diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx @@ -1,10 +1,11 @@ import Window from '../components/Window.jsx'; import Graph from '../components/graphing/Graph.jsx'; import Quadratic from '../components/graphing/Quadratic.jsx'; +import Cubic from '../components/graphing/Cubic.jsx'; import CoordinateLabel from '../components/graphing/CoordinateLabel.jsx'; export default function Home() { - const viewBox = [-5, -4, 7, 8]; + const viewBox = [-5, -4, 8, 8]; return ( <main> <Window title="Home"> @@ -15,6 +16,7 @@ export default function Home() { <CoordinateLabel x={-.25} y={-3.125} position="bottom" /> <polygon points="1,2 -1,1, -4,3" /> <CoordinateLabel x={-4} y={3} position="top" /> + <Cubic a={1} b={-3} c={3} d={-1} viewBox={viewBox}/> </Graph> </div> </Window> diff --git a/src/scripts/utils.js b/src/scripts/utils.js @@ -29,3 +29,9 @@ export function formatCoefficient(x, parameters = {showPlus: false, showOne: tru parameters.suffix || (parameters.suffix = '') // if suffix undefined return sign + x + parameters.suffix; } + +export function getSymmetricBounds(midX, viewBox) { + const [ minX, minY, width, height ] = viewBox; + const halfWidth = Math.max(midX - minX, minX + width - midX); + return [midX - halfWidth, midX + halfWidth]; +}