commit 25690356695b993ea05ed3e2d64479d9daf996f6
parent ff67737a89305a0c837cbdc855c299b15536a6a3
Author: fen123456 <fenwthompson@gmail.com>
Date: Thu, 13 Aug 2026 12:22:54 +0100
Merge branch 'main' of https://github.com/hrryt/website
Diffstat:
15 files changed, 162 insertions(+), 91 deletions(-)
diff --git a/src/components/Equation.jsx b/src/components/Equation.jsx
@@ -8,5 +8,5 @@ export default function Equation({ equation, displayMode = false }) {
render(equation, ref.current, { displayMode: displayMode });
}, [equation]);
- return <div ref={ref}>{equation}</div>;
+ return <span ref={ref}>{equation}</span>;
}
diff --git a/src/components/QuestionWindow.jsx b/src/components/QuestionWindow.jsx
@@ -3,6 +3,8 @@ import QuestionList from './QuestionList.jsx';
import { getRandomInt, getRandomNumber, getRandomChoice } from '../scripts/utils.js';
import Window from './Window.jsx';
+const seedDiff = 100;
+
function resolveParameters(question, variables) {
const parameters = question.constants ?? { };
const variableKeys = question.variables ?? { };
@@ -16,7 +18,7 @@ function renderQuestion(seed, showAnswer, variables) {
return (question, i) => {
const QuestionComponent = question.type;
const parameters = resolveParameters(question, variables);
- return <QuestionComponent key={i} seed={seed+i} showAnswer={showAnswer} {...parameters} />;
+ return <QuestionComponent key={i} seed={seed+seedDiff*i} showAnswer={showAnswer} {...parameters} />;
};
}
@@ -61,7 +63,7 @@ export default function QuestionWindow({ data, title }) {
const { id: id, n: n, questions: unique_questions } = questionList;
const questions = shuffleArray(unique_questions, n, current_seed);
const randomQuestionList = getQuestionList(id, questions, showAnswers, current_seed, variables);
- current_seed += n;
+ current_seed += seedDiff * n;
return randomQuestionList;
});
@@ -76,7 +78,7 @@ export default function QuestionWindow({ data, title }) {
<fieldset>
<legend>Options</legend>
<button onClick={revealAnswers}>
- {showAnswers ? "Hide" : "Show"} Answers
+ {showAnswers ? "Show Questions" : "Show Answers"}
</button>
<button onClick={triggerRefresh}>
Refresh Questions
diff --git a/src/components/graphing/CoordinateLabel.jsx b/src/components/graphing/CoordinateLabel.jsx
@@ -1,5 +0,0 @@
-import Label from './Label.jsx';
-
-export default function CoordinateLabel({x, y, position}) {
- return <Label x={x} y={y} position={position}>{`(${x},${y})`}</Label>;
-}
diff --git a/src/components/graphing/Cubic.jsx b/src/components/graphing/Cubic.jsx
@@ -1,6 +1,6 @@
-import { getSymmetricBounds } from '/src/scripts/utils.js';
+import { getSymmetricBounds, getColourClass } from '../../scripts/utils.js';
-export default function Cubic({ a=1, b=0, c=0, d=0, viewBox }) {
+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; }
@@ -10,5 +10,6 @@ export default function Cubic({ a=1, b=0, c=0, d=0, viewBox }) {
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)}`} />
+ 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/graphing/Graph.jsx b/src/components/graphing/Graph.jsx
@@ -10,27 +10,27 @@ function Axes({ viewBox }) {
const [ minX, minY, width, height ] = viewBox;
return (
<g>
- <path id="axes" d={`M ${minX},0 h ${width-.1}`} marker-end="url(#arrow)"/>
- <path id="axes" d={`M 0,${minY} v ${height-.1}`} marker-end="url(#arrow)"/>
+ <path class="axis" d={`M ${minX},0 h ${width-.1}`} marker-end="url(#arrow)"/>
+ <path class="axis" d={`M 0,${minY} v ${height-.1}`} marker-end="url(#arrow)"/>
</g>
);
}
-function AxisTicks({ viewBox }) {
+function AxisTicks({ every = 1, viewBox }) {
const [ minX, minY, width, height ] = viewBox;
return (
<g>
<g>
{Array.from({ length: width - 1 }).map((_, i) => {
const x = i + minX + 1;
- if (x == 0) { return; }
+ if (x == 0 || x % every) { return; }
return <Label x={x} y="0" position="bottom" d={.1}>{x}</Label>;
})}
</g>
<g>
{Array.from({ length: height - 1 }).map((_, i) => {
const y = i + minY + 1;
- if (y == 0) { return; }
+ if (y == 0 || y % every) { return; }
return <Label x="0" y={y} position="left" d={.1}>{y}</Label>;
})}
</g>
@@ -39,7 +39,13 @@ function AxisTicks({ viewBox }) {
)
}
-export default function Graph({ children, viewBox, width="500", height="500" }) {
+export default function Graph({ children, viewBox, width="300", height="300" }) {
+ const scaleFactor = Math.max(viewBox[2] / width, viewBox[3] / height);
+ const style = `
+ text {
+ font-size: ${scaleFactor}em;
+ }
+ `;
return (
<svg
width={width} height={height} viewBox={viewBox.join(' ')}
@@ -54,13 +60,25 @@ export default function Graph({ children, viewBox, width="500", height="500" })
markerUnits="strokeWidth"
orient="auto"
>
- <path d="M 0,0 L 4,2 L 0,4 z" id='arrowhead' />
+ <polygon class='marker' points="0,0 4,2 0,4" />
</marker>
+ <marker
+ id="cross"
+ viewBox="0 0 6 6"
+ refX="3" refY="3"
+ markerWidth="6" markerHeight="6"
+ markerUnits="strokeWidth"
+ >
+ <polygon class='marker' points="0,1 1,0 3,2 5,0 6,1 4,3 6,5 5,6 3,4 1,6 0,5 2,3" />
+ </marker>
+ <style>
+ {style}
+ </style>
</defs>
<Grid viewBox={viewBox} />
<Axes viewBox={viewBox} />
+ <AxisTicks every={Math.floor(50 * scaleFactor)} viewBox={viewBox} />
{children}
- <AxisTicks viewBox={viewBox} />
</svg>
);
}
diff --git a/src/components/graphing/Label.jsx b/src/components/graphing/Label.jsx
@@ -1,5 +1,9 @@
+import { getColourClass } from '../../scripts/utils.js';
+
function getPosition(position) {
switch (position) {
+ case 'central':
+ return ['central', 'middle', 0, 0];
case 'left':
return ['central', 'end', -1, 0];
case 'right':
@@ -13,13 +17,14 @@ function getPosition(position) {
}
}
-export default function Label({ x, y, position, d=.15, children }) {
+export default function Label({ x, y, position='central', colour='auto', d=.15, children }) {
const [dominantBaseline, textAnchor, dx, dy] = getPosition(position);
return (
<text
- x={x} y={-y} dx={d*dx} dy={-d*dy} transform="scale(1 -1)"
+ x={x} y={-y} dx={d*dx} dy={-d*dy} transform='scale(1 -1)'
dominant-baseline={dominantBaseline}
text-anchor={textAnchor}
+ class={getColourClass(colour)}
>
{children}
</text>
diff --git a/src/components/graphing/Point.jsx b/src/components/graphing/Point.jsx
@@ -0,0 +1,11 @@
+import Label from './Label.jsx';
+import { getColourClass } from '../../scripts/utils.js';
+
+export default function Point({ x, y, label=null, colour="auto", position="top" }) {
+ return (
+ <g>
+ <path marker-start="url(#cross)" d={`M ${x},${y}`} class={getColourClass(colour)}/>
+ {label && <Label x={x} y={y} position={position} d={.3} colour={colour}>{label}</Label>}
+ </g>
+ );
+}
diff --git a/src/components/graphing/Polygon.jsx b/src/components/graphing/Polygon.jsx
@@ -0,0 +1,27 @@
+import Label from './Label.jsx';
+import { getColourClass } from '../../scripts/utils.js';
+
+function getMean(arr) {
+ return arr.reduce((a, b) => a + b) / arr.length;
+}
+
+function getMiddle(points) {
+ const x = points.map(point => point[0]);
+ const y = points.map(point => point[1]);
+ return [getMean(x), getMean(y)];
+}
+
+function PolygonLabel({ children, points, colour }) {
+ const [x, y] = getMiddle(points);
+ return <Label x={x} y={y} colour={colour}>{children}</Label>;
+}
+
+export default function Polygon({ points, label=null, colour="auto" }) {
+ const pointsString = points.map((point) => point.join(",")).join(" ");
+ return (
+ <g>
+ <polygon points={pointsString} class={getColourClass(colour)}/>
+ {label && <PolygonLabel points={points} colour={colour}>{label}</PolygonLabel>}
+ </g>
+ );
+}
diff --git a/src/components/graphing/Quadratic.jsx b/src/components/graphing/Quadratic.jsx
@@ -1,6 +1,6 @@
-import { getSymmetricBounds } from '/src/scripts/utils.js';
+import { getSymmetricBounds, getColourClass } from '../../scripts/utils.js';
-export default function Quadratic({ a=1, b=0, c=0, viewBox }) {
+export default function Quadratic({ a=1, b=0, c=0, colour="auto", viewBox }) {
function f(x) { return a*x*x + b*x + c; }
const turnX = -b / (2*a);
@@ -9,5 +9,6 @@ export default function Quadratic({ a=1, b=0, c=0, viewBox }) {
const endY = f(endX);
const controlY = 2*f(turnX) - endY;
- return <path d={`M ${startX},${endY} Q ${turnX},${controlY} ${endX},${endY}`} />;
+ const d = `M ${startX},${endY} Q ${turnX},${controlY} ${endX},${endY}`;
+ return <path d={d} class={getColourClass(colour)} />;
}
diff --git a/src/components/questions/AddingFractionsQuestion.jsx b/src/components/questions/AddingFractionsQuestion.jsx
@@ -12,21 +12,21 @@ function simplifyFraction(num, denom) {
}
function getRandomFraction(seed) {
- const num0 = getRandomInt(1, 5, seed);
- const denom0 = getRandomInt(2, 10, seed + .05);
+ const num0 = getRandomInt(1, 5, seed++);
+ const denom0 = getRandomInt(2, 10, seed++);
const [num, denom] = simplifyFraction(num0, denom0);
- if (denom == 1) { return getRandomFraction(seed + .1); }
+ if (denom == 1) { return getRandomFraction(seed++); }
return [num, denom];
}
function getRandomFractions(seed) {
- const [num1, denom1] = getRandomFraction(seed);
- const [num2, denom2] = getRandomFraction(seed + .5);
+ const [num1, denom1] = getRandomFraction(seed++);
+ const [num2, denom2] = getRandomFraction(seed++);
return [num1, denom1, num2, denom2];
}
export default function AddingFractionsQuestion({ seed, showAnswer }) {
- const [num1, denom1, num2, denom2] = getRandomFractions(seed);
+ const [num1, denom1, num2, denom2] = getRandomFractions(seed++);
const question = formatFraction(num1, denom1) + ' + ' + formatFraction(num2, denom2);
diff --git a/src/components/questions/DoubleTransformationQuestion.jsx b/src/components/questions/DoubleTransformationQuestion.jsx
@@ -1,11 +1,9 @@
import { getRandomChoice, getRandomInt } from "../../scripts/utils";
-import CoordinateLabel from "../graphing/CoordinateLabel";
+import Point from "../graphing/Point";
+import Polygon from "../graphing/Polygon";
import Graph from "../graphing/Graph";
import Question from "../Question";
-
-function shapeToString(shape) {
- return shape.map((point) => point.join(",")).join(" ")
-}
+import Equation from "../Equation";
function rotatePoint(point, angle, axis=[0, 0]) {
const diff = [point[0] - axis[0], point[1] - axis[1]]
@@ -14,34 +12,60 @@ function rotatePoint(point, angle, axis=[0, 0]) {
return [axis[0] + c*diff[0] + s*diff[1], axis[1] + c*diff[1] - s*diff[0]]
}
-
export default function DoubleTransformationQuestion({ seed, showAnswer }) {
const viewBox = [-7, -7, 14, 14]
const originalShape = [[0, 1], [2, 1], [0, 2]]
- const axis1 = [getRandomInt(-3, 3, seed), getRandomInt(-3, 3, seed + .1)]
- const axis2 = [getRandomInt(-3, 3, seed + .2), getRandomInt(-3, 3, seed + .3)]
- const angle = getRandomChoice([Math.PI/2, Math.PI, 3*Math.PI/2], seed + .4)
+ const axis1 = [getRandomInt(-3, 3, seed++), getRandomInt(-3, 3, seed++)]
+ let axis2 = axis1;
+ while (axis2.every((x, i) => x === axis1[i])) {
+ axis2 = [getRandomInt(-3, 3, seed++), getRandomInt(-3, 3, seed++)]
+ }
+ const angleDegrees = getRandomChoice([90, 180, -90], seed++)
+ const angle = angleDegrees / 180 * Math.PI
// Rotation then translation!. could do other way around if really needed.
const midShape = originalShape.map((point) => rotatePoint(point, angle, axis1))
const rotatedAxis = rotatePoint(axis2, angle, axis1)
- const translationVector = axis2.map((value, i) => value - rotatedAxis[i])
+ const translationVector = axis2.map((value, i) => Math.round(value - rotatedAxis[i]))
const endShape = midShape.map((point) => point.map((value, i) => value + translationVector[i]))
+ let angleDirection = ""
+ if (angleDegrees !== 180) {
+ if (angleDegrees < 0) {
+ angleDirection = "counterclockwise"
+ } else {
+ angleDirection = "clockwise"
+ }
+ }
+
+ const angleExpression = (
+ <span>
+ <Equation equation={`${Math.abs(angleDegrees)} degree`} /> {angleDirection}
+ </span>
+ )
+
const question = (<>
- <p>
- What single transformation..?
- </p>
- <Graph viewBox={viewBox} width="300" height="300">
- <polygon className="color-1" points={shapeToString(originalShape)} />
- <polygon className="color-2" points={shapeToString(midShape)} />
- <polygon className="color-3" points={shapeToString(endShape)} />
- <CoordinateLabel x={axis2[0]} y={axis2[1]} position="top" />
- <CoordinateLabel x={axis1[0]} y={axis1[1]} position="top" />
+ <p>Shape P is rotated {angleExpression} about point Q to form shape R.</p>
+ <p>Shape R is translated by <Equation equation={`vec(${translationVector[0]}, ${translationVector[1]})`} /> to form shape S.</p>
+ <p>What single transformation maps shape P onto shape S?</p>
+ <Graph viewBox={viewBox}>
+ <Polygon points={originalShape} label="P" colour="1" />
+ <Point x={axis1[0]} y={axis1[1]} label="Q" colour="2" />
+ </Graph>
+ </>)
+
+ const answer = (<>
+ <p>Rotation {angleExpression} about <Equation equation={`(${axis2[0]}, ${axis2[1]})`} />.</p>
+ <Graph viewBox={viewBox}>
+ <Polygon points={originalShape} label="P" colour="1" />
+ <Point x={axis1[0]} y={axis1[1]} label="Q" colour="2" />
+ <Polygon points={ midShape} label="R" colour="3" />
+ <Polygon points={ endShape} label="S" colour="4" />
+ <Point x={axis2[0]} y={axis2[1]} />
</Graph>
</>)
- return <Question question={question} answer="" showAnswer={showAnswer} />
+ return <Question question={question} answer={answer} showAnswer={showAnswer} />
}
diff --git a/src/components/questions/QuadraticEquationQuestion.jsx b/src/components/questions/QuadraticEquationQuestion.jsx
@@ -10,7 +10,7 @@ function formatQuadraticExpression(a, b, c) {
function formatFraction(num, denom) {
const hcf = getHCF(Math.abs(num), Math.abs(denom))
- const prefix = num / denom > 0 ? "" : "-"
+ const prefix = num / denom > 0 ? "" : "-"
num = Math.abs(num / hcf)
denom = Math.abs(denom / hcf)
@@ -22,7 +22,7 @@ function formatFraction(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)}`;
}
@@ -38,36 +38,29 @@ function formatFactors(l, m, n, o) {
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);
+ let l = getRandomChoice(aChoices, seed++);
+ let n = getRandomChoice(aChoices, seed++);
const factoriseChoices = Array();
framing.includes("Solve") && factoriseChoices.push(false);
framing.includes("Factorise") && factoriseChoices.push(true);
- const factorise = getRandomChoice(factoriseChoices, seed + .2);
+ const factorise = getRandomChoice(factoriseChoices, seed++);
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)
+ const mSign = getRandomChoice([-1, 1], seed++)
+ const cSign = getRandomChoice(cSignChoices, seed++)
- let m = getRandomInt(1, 9, seed + .5) * mSign
- let o = getRandomInt(1, 9, seed + .6) * mSign * cSign
+ let m = getRandomInt(1, 9, seed++) * mSign
+ let o = getRandomInt(1, 9, seed++) * mSign * cSign
if (-m/l > -o/n) {
[l, m, n, o] = [n, o, l, m]
diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx
@@ -2,7 +2,7 @@ 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';
+import Polygon from '../components/graphing/Polygon.jsx';
export default function Home() {
const viewBox = [-5, -4, 8, 8];
@@ -12,11 +12,9 @@ export default function Home() {
<p>Hello World!</p>
<div class="field-border kern">
<Graph viewBox={viewBox}>
- <Quadratic a={2} b={1} c={-3} viewBox={viewBox} />
- <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}/>
+ <Quadratic a={2} b={1} 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}/>
</Graph>
</div>
</Window>
diff --git a/src/scripts/utils.js b/src/scripts/utils.js
@@ -35,3 +35,8 @@ export function getSymmetricBounds(midX, 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}`;
+}
diff --git a/src/styles/svg.css b/src/styles/svg.css
@@ -1,13 +1,12 @@
-path,polygon,circle,ellipse,line,polyline {
- stroke: #066;
+path,polygon,line,polyline,circle,ellipse {
stroke-width: 0.06;
stroke-linejoin: round;
stroke-linecap: round;
- fill: transparent;
+ fill-opacity: 0;
}
polygon {
- fill: #0668;
+ fill-opacity: 0.5;
}
path#grid {
@@ -15,46 +14,39 @@ path#grid {
stroke-width: 0.01;
}
-path#axes {
+path.axis {
stroke: black;
stroke-width: 0.08;
stroke-linecap: butt;
}
-path#arrowhead {
- stroke: black;
- fill: black;
+polygon.marker {
+ fill-opacity: 1;
}
text {
- font-size: 0.02em;
- stroke: white;
- stroke-width: 0.03;
+ stroke: white !important;
+ stroke-width: 0.1;
paint-order: stroke;
}
.color-1 {
fill: var(--graph-1);
stroke: var(--graph-1);
- fill-opacity: 0.5;
}
.color-2 {
fill: var(--graph-2);
stroke: var(--graph-2);
- fill-opacity: 0.5;
}
.color-3 {
fill: var(--graph-3);
stroke: var(--graph-3);
- fill-opacity: 0.5;
}
.color-4 {
fill: var(--graph-4);
stroke: var(--graph-4);
- fill-opacity: 0.5;
}
.color-5 {
fill: var(--graph-5);
stroke: var(--graph-5);
- fill-opacity: 0.5;
-}
-\ No newline at end of file
+}