website

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

commit 1a6a42e93e63b8a1f1c7db46217b3435b62f755a
parent f698541c423e06e8a6d72e3b3ae5c0e0817c9700
Author: hrryt <harry@mayesfield.uk>
Date:   Sat, 15 Aug 2026 18:19:42 +0100

Restrict bounds of polynomials

Diffstat:
Msrc/components/plotting/Cubic.jsx | 47+++++++++++++++++++++++++++++++++++++++++++----
Msrc/components/plotting/Quadratic.jsx | 21++++++++++++++-------
Msrc/pages/Home.jsx | 8+++++---
Msrc/scripts/utils.js | 15+++++++++------
4 files changed, 71 insertions(+), 20 deletions(-)

diff --git a/src/components/plotting/Cubic.jsx b/src/components/plotting/Cubic.jsx @@ -1,15 +1,54 @@ -import { getSymmetricBounds, getColourClass } from '../../scripts/utils.js'; +import { getColourClass, getSymmetricBounds } from '../../scripts/utils.js'; + +function getDepressedCubicRoots(a, b, c, d) { + // https://en.wikipedia.org/wiki/Cubic_equation#Depressed_cubic + const p = (3*a*c - b*b) / (3*a*a); + const q = (2*b*b*b - 9*a*b*c + 27*a*a*d) / (27*a*a*a); + const discriminantish = q*q/4 + p*p*p/27; + + if (discriminantish > 0) { + // https://en.wikipedia.org/wiki/Cubic_equation#Cardano's_formula + const sqrt = Math.sqrt(discriminantish); + const root = Math.cbrt(-q/2 + sqrt) + Math.cbrt(-q/2 - sqrt); + return [root]; + } + + if (discriminantish === 0) { + // https://en.wikipedia.org/wiki/Cubic_equation#Multiple_root + if (p === 0) { return [0,0,0]; } + const doubleRoot = -3*q/(2*p); + return [3*q/p, doubleRoot, doubleRoot]; + } + + // https://en.wikipedia.org/wiki/Cubic_equation#Trigonometric_and_hyperbolic_solutions + const cosCoef = 2*Math.sqrt(-p/3); + const arccosine = 1/3 * Math.acos(3*q/(2*p) * Math.sqrt(-3/p)); + const kCoef = 2*Math.PI / 3; + return [0,1,2].map(k => cosCoef * Math.cos(arccosine - kCoef * k)); +} export default function Cubic({ a=1, b=0, c=0, d=0, colour="auto", 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; } + 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 midX = -b / (3*a); + + const [minX, minY, width, height] = viewBox; + const maxY = minY + height; + const intercepts = [ + ...getDepressedCubicRoots(a,b,c,d - minY), + ...getDepressedCubicRoots(a,b,c,d - maxY) + ]; + console.log(intercepts.map(x => x + midX)); + const dx = Math.max(...intercepts.map(x => Math.abs(x))); + + const [i, f] = getSymmetricBounds(midX, dx, viewBox); - 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; - d = `M ${i},${g(i,i,i)} C ${x1},${g(i,i,f)} ${x2},${g(i,f,f)} ${f},${g(f,f,f)}`; + d = `M ${i},${G(i,i,i)} C ${x1},${G(i,i,f)} ${x2},${G(i,f,f)} ${f},${G(f,f,f)}`; return <path d={d} class={getColourClass(colour)} /> } diff --git a/src/components/plotting/Quadratic.jsx b/src/components/plotting/Quadratic.jsx @@ -1,14 +1,21 @@ -import { getSymmetricBounds, getColourClass } from '../../scripts/utils.js'; +import { getColourClass, getSymmetricBounds } from '../../scripts/utils.js'; export default function Quadratic({ a=1, b=0, c=0, colour="auto", viewBox }) { - function f(x) { return a*x*x + b*x + c; } + function F(x) { return a*x*x + b*x + c; } - const turnX = -b / (2*a); - const [startX, endX] = getSymmetricBounds(turnX, viewBox); + const midX = -b / (2*a); - const endY = f(endX); - const controlY = 2*f(turnX) - endY; + const [minX, minY, width, height] = viewBox; + const maxY = minY + height; + const midY = F(midX); + const curveHeight = a < 0 ? midY - minY : maxY - midY; + const dx = Math.sqrt(curveHeight / Math.abs(a)); - const d = `M ${startX},${endY} Q ${turnX},${controlY} ${endX},${endY}`; + const [i, f] = getSymmetricBounds(midX, dx, viewBox); + + const y0 = F(i); + const y1 = 2*midY - y0; + + const d = `M ${i},${y0} Q ${midX},${y1} ${f},${y0}`; return <path d={d} class={getColourClass(colour)} />; } diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx @@ -11,7 +11,7 @@ import Label from '../components/plotting/Label.jsx'; import Sinusoid from '../components/plotting/Sinusoid.jsx'; export default function Home() { - const viewBox = [-5, -4, 8, 8]; + const viewBox = [-5, -4, 8, 9]; const [A, B, C] = [[1,4], [3,2], [2,1]]; return ( <main> @@ -19,9 +19,11 @@ export default function Home() { <p>Hello World!</p> <div class="field-border kern"> <Plot viewBox={viewBox}> - <Quadratic a={2} b={1} c={-3} colour="1" viewBox={viewBox} /> + <Quadratic a={-2} b={1} c={-3} colour="1" viewBox={viewBox} /> + <Quadratic a={.04} b={.02} c={-.06} colour="1" viewBox={viewBox} /> + <Quadratic a={-.0001} b={2} c={-3} colour="1" viewBox={viewBox} /> <Polygon points={[[1,2], [-1,1], [-4,3]]} label="P" colour="2" /> - <Cubic a={1} b={-3} c={3} d={-1} colour="3" viewBox={viewBox} /> + <Cubic a={2} b={1} c={-1} d={2} colour="3" viewBox={viewBox} /> <Point point={[1,0]} label="Q" colour="3" /> <Line line={{a: -10, b: -9, c: -1}} colour="4" viewBox={viewBox} /> <Sinusoid period={5} amp={0.5} dx={-11} dy={-2} colour="5" viewBox={viewBox} /> diff --git a/src/scripts/utils.js b/src/scripts/utils.js @@ -63,12 +63,6 @@ export function formatLine(line) { return 'y = ' + mxTerm + cTerm } -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]; -} - export function getColourClass(colour) { if (colour == 'auto') { return ''; } return `color-${colour}`; @@ -111,3 +105,12 @@ 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]; +}