Code — how this page is built

Under the hood

The actual source of the components on this page, read straight from the repo. Copy it, read it, or open it on GitHub.


Compiles the expression with math.js, samples it per pixel across the x-range, maps to canvas coordinates and strokes the curve; non-finite/huge values break the path.

'use client';

import { compile } from 'mathjs';
import * as React from 'react';

const W = 480;
const H = 360;
const XR = 10; // x ∈ [-10, 10]
const YR = 6; // y ∈ [-6, 6]

export function PlotterGame() {
  const canvasRef = React.useRef<HTMLCanvasElement>(null);
  const [expr, setExpr] = React.useState('sin(x) * x / 3');
  const [error, setError] = React.useState(false);

  React.useEffect(() => {
    const ctx = canvasRef.current?.getContext('2d');
    if (!ctx) return;
    ctx.fillStyle = '#faf7ef';
    ctx.fillRect(0, 0, W, H);

    const px = (x: number) => ((x + XR) / (2 * XR)) * W;
    const py = (y: number) => H - ((y + YR) / (2 * YR)) * H;

    // grid
    ctx.strokeStyle = 'rgba(20,17,11,0.08)';
    ctx.lineWidth = 1;
    for (let x = -XR; x <= XR; x++) {
      ctx.beginPath();
      ctx.moveTo(px(x), 0);
      ctx.lineTo(px(x), H);
      ctx.stroke();
    }
    for (let y = -YR; y <= YR; y++) {
      ctx.beginPath();
      ctx.moveTo(0, py(y));
      ctx.lineTo(W, py(y));
      ctx.stroke();
    }
    // axes
    ctx.strokeStyle = 'rgba(20,17,11,0.4)';
    ctx.beginPath();
    ctx.moveTo(0, py(0));
    ctx.lineTo(W, py(0));
    ctx.moveTo(px(0), 0);
    ctx.lineTo(px(0), H);
    ctx.stroke();

    try {
      const fn = compile(expr);
      ctx.strokeStyle = '#c2453a';
      ctx.lineWidth = 2;
      ctx.beginPath();
      let started = false;
      for (let i = 0; i <= W; i++) {
        const x = (i / W) * 2 * XR - XR;
        const y = fn.evaluate({ x });
        if (typeof y !== 'number' || !Number.isFinite(y) || Math.abs(y) > YR * 3) {
          started = false;
          continue;
        }
        const sy = py(y);
        if (!started) {
          ctx.moveTo(px(x), sy);
          started = true;
        } else ctx.lineTo(px(x), sy);
      }
      ctx.stroke();
      setError(false);
    } catch {
      setError(true);
    }
  }, [expr]);

  return (
    <div className="flex w-full max-w-[520px] flex-col items-center gap-4">
      <canvas ref={canvasRef} width={W} height={H} className="w-full max-w-full rounded-md border border-foreground/15" />
      <div className="flex w-full max-w-md items-center gap-2 font-mono">
        <span className="text-sm opacity-60">y =</span>
        <input
          value={expr}
          onChange={(e) => setExpr(e.target.value)}
          spellCheck={false}
          className={`flex-1 border-b bg-transparent px-1 py-2 text-sm outline-none ${error ? 'border-[var(--accent-red)]' : 'border-foreground/30'}`}
        />
      </div>
      <div className="font-mono text-[11px] uppercase tracking-[0.12em] opacity-45">
        {error ? "can't parse that" : 'try sin(x), x^2/4, tan(x), abs(x)-3…'}
      </div>
    </div>
  );
}
↖ kaspirius