Interactive Math Plotting with React
A showcase of a small project I've made.
This is a showcase of a small React library I’ve made to create interactive math plots, and similar visualizations.
Example Scene
Here is an example of what can be done with this library. We can have interactive elements that update as we change the variables, move the points around etc. Try moving the points and changing the slider to see how the area under the curve changes.
The library itself is barebones, just wrapping some SVG elements to create interactive components, which can then be used in a declarative way, which is nice for React.
Code (Click to expand)
Code (Click to expand)
Here is the code for the above scene:
const gridOptions: Partial<GridOptions> = { grid: { visible: true, stroke: "#ccc", strokeWidth: 0.5, gap: 1, }, axes: { visible: true, stroke: "#000", strokeWidth: 2, },};
export function DemoScene() { const [a, setA] = useState(0); const [b, setB] = useState(5); const [c, setC] = useState(1);
const f = (x: number) => Math.E ** (-x / c); const integral = (x: number) => -c * Math.E ** (-x / c);
return ( <Board className="w-full h-[500px] border rounded-md bg-white"> <Grid options={gridOptions} /> <FunctionPlot f={f} options={{ strokeWidth: 2, stroke: "blue" }}> <AreaUnder interval={[a, b]} options={{ fill: "rgba(0, 0, 255, 0.2)", strokeWidth: 2, stroke: "blue", }} /> </FunctionPlot> <Point x={a} y={0} options={{ draggable: "x", fill: "red" }} onDrag={(x) => setA(x)} /> <Point x={b} y={0} options={{ draggable: "x", fill: "red" }} onDrag={(x) => setB(x)} /> <Overlay className="p-4 flex flex-col gap-2"> <div className="flex flex-col gap-2 bg-white/10 backdrop-blur-xs border p-2 rounded-md max-w-48"> <InlineMath className="text-black" latex={`c = ${c.toFixed(1)}`} /> <input type="range" min={1} max={10} step={0.1} value={c} onChange={(e) => setC(+e.target.value)} /> </div> <div className="flex gap-2 mt-auto bg-white/10 backdrop-blur-xs border p-4 rounded-md max-w-fit"> <InlineMath className="text-black [&_.katex-display]:!m-0 [&_.katex]:!p-2" latex={String.raw`\int_a^b f(x) \ dx = ${( integral(b) - integral(a) ).toFixed(2)}`} options={{ displayMode: true, }} /> </div> </Overlay> </Board> );}
Explanation and Usage
The Board
component is the root of any scene you’ll create. It defines the coordinate system with the unit
property, which is in pixels. Everything inside the board is relative to this unit.
For example, setting the unit
to 50px and then creating a vector like will have the lengths 50px horizontal and 100px vertical.
The board accepts all properties of a div
element, so you can pass in any props you want to customize the look.
The Grid
component is used to create the grid lines. It has options to control the range of the grid (infinite if no range is provided),
the stroke and stroke width of the grid lines, and the axis lines. Again, the units here are relative to the board’s unit
.
Here is a simple example start:
function Scene() { return ( <Board className="w-full h-[300px] border rounded-md bg-white"> <Grid options={{ grid: { stroke: "#ccc", strokeWidth: 0.5, }, axes: { stroke: "#000", strokeWidth: 2, }, }}/> </Board> );}
Then you can insert the math components like Point
, Line
, FunctionPlot
, Vector
and customize its options similarly.
Some components like AreaUnder
and Glider
can only be used inside a FunctionPlot
component.
<Board className="..."> <Grid options={/* ... */} /> <Point x={0} y={0} options={{ draggable: "x", fill: "red", snapToGrid: true }} /> <Line from={{x: 0, y: 0}} to={{x: 1, y: 1}} options={{ stroke: "blue", strokeWidth: 2 }} /> <FunctionPlot f={(x) => x} options={{ stroke: "green", strokeWidth: 2 }}> <AreaUnder interval={[0, 1]} options={{ fill: "red", strokeWidth: 2, stroke: "blue" }} /> <Glider x={a} onDrag={(x) => setA(x)} /> </FunctionPlot></Board>
The library itself doesn’t provide any relationship between elements. We use the power of React to manage the state of the components. Here is a scene that has two draggable points, and a line between them. The line updates as we move the points around. It’s all just React state:
function Scene() { const [a, setA] = useState({x: 0, y: 0}); const [b, setB] = useState({x: 1, y: 1});
return ( <Board className="..."> <Grid options={/* ... */} /> <Line from={a} to={b} /> <Point x={a.x} y={a.y} onDrag={(x, y) => setA({x, y})} /> <Point x={b.x} y={b.y} onDrag={(x, y) => setB({x, y})} /> </Board> );}
Lastly, the Overlay
component is used to display things on top of the board.
The overlay element is just a div
element that covers the whole board, so you can again
pass in any props you want to customize the look.
Combined with input
elements and the InlineMath
component (which is just a Katex wrapper),
you can display all the information you want:
function Scene() { return ( <Board className="..."> <Grid options={/* ... */} /> <Overlay className="..."> <h2>Hello World!</h2> <InlineMath latex="\int_0^1 x^2 \ dx = \frac{1}{3}" /> </Overlay> </Board> );}
This project was mostly inspired by JSXGraph. This library is not as powerful nor performant as JSXGraph, so if you want a library for real-world projects, check that out. If you want a similar thing for non-web, check out Manim.
I made this library mostly for learning, and to create small visualizations for myself. I also like the declarative approach and managing the state using React, which is a lot more familiar to me.