commit 1d2caaf2fa5bf75b8657687ee9cf9bc44e4191e1
parent 43e995d5b1aac79a4bd450462e4ed6b49b2c73ce
Author: hrryt <harry@mayesfield.uk>
Date: Sat, 8 Aug 2026 17:55:21 +0100
Add cubic graphing component
Diffstat:
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];
+}