I want to create a worksheet generator that can export to pdf. I want the graphs to be created in slope intercept form with at least two visible coordinates on the graph that align with x and y axis values that are labeled and a y-intercept that goes through a visibly labeled whole number. The intervals should change for each graph. currently there are 20 gridlines on each axis, i want to be able to create smaller graphs with 10 as well. The user should be able to select 10 or 20. I attached an example image.
import React, { useState } from "react";
import { VictoryChart, VictoryLine, VictoryAxis } from "victory";
import jsPDF from "jspdf";
import html2canvas from "html2canvas";
function WorksheetGenerator() {
const [numQuestions, setNumQuestions] = useState(5);
const [displayMode, setDisplayMode] = useState("both");
const [worksheetOutput, setWorksheetOutput] = useState([]);
const [selectedItems, setSelectedItems] = useState([]);
const generateWorksheet = () => {
const output = [];
for (let i = 0; i < numQuestions; i++) {
const slope = randomSlope(); // Allows negative slope
const intervalX = randomInterval(); // Interval for x-axis
const intervalY = randomInterval(); // Interval for y-axis
const intercept = randomVisibleIntercept(intervalY);
if (
displayMode === "tables" ||
displayMode === "both" ||
displayMode === "matching"
) {
const table = generateTable(slope, intercept, intervalX);
output.push(
<div key={`table-${i}`} id={`table-${i}`}>
{table}
<label>
<input
type="checkbox"
onChange={(e) => handleCheckboxChange(e, `table-${i}`)}
/>{" "}
Include this table in worksheet
</label>
</div>
);
}
if (
displayMode === "graphs" ||
displayMode === "both" ||
displayMode === "matching"
) {
const graph = generateGraph(slope, intercept, intervalX, intervalY);
output.push(
<div key={`graph-${i}`} id={`graph-${i}`}>
{graph}
<label>
<input
type="checkbox"
onChange={(e) => handleCheckboxChange(e, `graph-${i}`)}
/>{" "}
Include this graph in worksheet
</label>
</div>
);
}
}
setWorksheetOutput(output);
};
const handleCheckboxChange = (event, id) => {
if (event.target.checked) {
setSelectedItems((prevItems) => [...prevItems, id]);
} else {
setSelectedItems((prevItems) => prevItems.filter((item) => item !== id));
}
};
// Updated function to allow for negative slopes
const randomSlope = () => {
return Math.random() > 0.5
? Math.floor(Math.random() * 5) + 1 // positive slope
: -1 * (Math.floor(Math.random() * 5) + 1); // negative slope
};
// Function to select an intercept that aligns with the tick marks on the y-axis
const randomVisibleIntercept = (intervalY) => {
const visibleIntercepts = [];
const range = 10 * intervalY;
for (let i = -range; i <= range; i += intervalY) {
visibleIntercepts.push(i);
}
const randomIndex = Math.floor(Math.random() * visibleIntercepts.length);
return visibleIntercepts[randomIndex]; // Return a visible intercept
};
const randomInterval = () => {
const intervals = [1, 2, 3, 4, 5, 10];
return intervals[Math.floor(Math.random() * intervals.length)];
};
const generateTable = (slope, intercept, intervalX) => {
const rows = [];
const usedXValues = new Set();
while (usedXValues.size < 5) {
const x = Math.floor(Math.random() * 10) - 5;
if (!usedXValues.has(x) && x !== 0) {
usedXValues.add(x);
}
}
[...usedXValues].forEach((x) => {
const y =
typeof slope === "string"
? eval(slope) * x + intercept
: slope * x + intercept;
rows.push(
<tr key={x}>
<td style={{ border: "1px solid black", padding: "5px" }}>{x}</td>
<td style={{ border: "1px solid black", padding: "5px" }}>{y}</td>
</tr>
);
});
return (
<table style={{ width: "100%", borderCollapse: "collapse" }}>
<thead>
<tr>
<th style={{ border: "1px solid black", padding: "5px" }}>x</th>
<th style={{ border: "1px solid black", padding: "5px" }}>y</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
};
const generateGraph = (slope, intercept, intervalX, intervalY) => {
const xValues = [];
const yValues = [];
for (let x = -100; x <= 100; x += intervalX) {
xValues.push(x);
yValues.push(
typeof slope === "string"
? eval(slope) * x + intercept
: slope * x + intercept
);
}
// Calculate additional points based on the slope and intercept
const additionalPoints = [];
for (let i = 1; i <= 2; i++) {
const newX = i * intervalX;
const newY = slope * newX + intercept;
if (Math.abs(newY) <= 10 * intervalY) {
// Ensure it falls within the domain
additionalPoints.push({ x: newX, y: newY });
}
}
const data = xValues
.map((x, index) => ({
x: x,
y: yValues[index],
}))
.concat(additionalPoints);
const xDomain = { x: [-10 * intervalX, 10 * intervalX] };
const yDomain = { y: [-10 * intervalY, 10 * intervalY] };
const xTicks = getTicks(xDomain.x, 20, intervalX);
const yTicks = getTicks(yDomain.y, 20, intervalY);
return (
<VictoryChart
domain={{ x: xDomain.x, y: yDomain.y }}
height={300}
width={300}
>
<VictoryLine
data={data}
style={{
data: { stroke: "black", strokeWidth: 1.25 },
}}
/>
<VictoryAxis
tickValues={xTicks}
style={{
tickLabels: { fontSize: 5, padding: 5 },
grid: { stroke: "lightgray" },
axis: { stroke: "black", strokeWidth: 1 },
}}
/>
<VictoryAxis
dependentAxis
tickValues={yTicks}
style={{
tickLabels: { fontSize: 5, padding: 5 },
grid: { stroke: "lightgray" },
axis: { stroke: "black", strokeWidth: 1 },
}}
/>
</VictoryChart>
);
};
const getTicks = (domain, numTicks, interval) => {
const ticks = [];
for (let i = -10; i <= 10; i++) {
ticks.push(i * interval);
}
return ticks;
};
const generatePDF = async () => {
const doc = new jsPDF();
let yOffset = 10;
let xOffset = 10;
const columnWidth = 90;
const rowHeight = 100;
const itemsPerRow = 2;
for (const [index, elementId] of selectedItems.entries()) {
const element = document.getElementById(elementId);
if (element) {
const canvas = await html2canvas(element, { useCORS: true });
const imgData = canvas.toDataURL("image/png");
doc.addImage(imgData, "PNG", xOffset, yOffset, columnWidth, rowHeight);
if ((index + 1) % itemsPerRow === 0) {
yOffset += rowHeight;
xOffset = 10;
} else {
xOffset += columnWidth + 10;
}
}
}
doc.save("worksheet.pdf");
};
return (
<div>
<form>
<label>
Number of questions:
<input
type="number"
min="1"
value={numQuestions}
onChange={(e) => setNumQuestions(parseInt(e.target.value))}
/>
</label>
<br />
<label>
Display:
<select
value={displayMode}
onChange={(e) => setDisplayMode(e.target.value)}
>
<option value="both">Both Tables and Graphs</option>
<option value="tables">Tables Only</option>
<option value="graphs">Graphs Only</option>
<option value="matching">Matching Tables and Graphs</option>
</select>
</label>
<br />
<button type="button" onClick={generateWorksheet}>
Generate Worksheet
</button>
<button type="button" onClick={generatePDF}>
Download PDF
</button>
</form>
<div>{worksheetOutput}</div>
</div>
);
}
export default WorksheetGenerator;```