commit 1a6a42e93e63b8a1f1c7db46217b3435b62f755a
parent f698541c423e06e8a6d72e3b3ae5c0e0817c9700
Author: hrryt <harry@mayesfield.uk>
Date: Sat, 15 Aug 2026 18:19:42 +0100
Restrict bounds of polynomials
Diffstat:
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];
+}