graphspace
v0.1.0
Published
A production-ready educational graph editor engine for React applications. Infinite canvas, multi-graph plotting, equation parsing, and interactive tools.
Maintainers
Readme
graphspace
A traditional school-style coordinate graph editor for React. SVG-based, interactive, and fully customizable.
npm i graphspaceimport { SchoolGraphEditor } from "graphspace";
export default function App() {
return <SchoolGraphEditor />;
}Features
- School-style graph paper — dense grid with minor/major lines, centered X/X'/Y/Y' axes with arrows, proper numbering
- Interactive — drag to pan, scroll to zoom, auto-fit data, reset view
- Configurable scale — set 1cm = N units independently for X and Y axes (e.g., 1cm = 2 units)
- Multiple datasets — add/remove/toggle datasets with independent point styles
- Point connection — render each dataset as dots, connected lines, or smooth curves
- Coordinate labels — show/hide numeric labels on each plotted point
- Editable data table — X/Y table with add/remove/reorder rows, paste from Excel/CSV
- Export — PNG (2x), SVG, JSON, and Print
- JSON import/export — full graph state as
.jsonfiles - SVG-based — crisp rendering at any resolution, print-friendly
- Fully composable — use the full editor or compose your own UI from primitives
Quick Start
import React, { useEffect } from "react";
import { SchoolGraphEditor, useGraphStore } from "graphspace";
export default function App() {
return (
<div style={{ height: "100vh", display: "flex", flexDirection: "column" }}>
<SchoolGraphEditor />
</div>
);
}Loading data programmatically
import { SchoolGraphEditor, useGraphStore } from "graphspace";
function App() {
const load = () => {
const store = useGraphStore.getState();
const id = store.addDataSet("My Points");
store.setDataPoints(id, [
{ x: 1, y: 2 },
{ x: 3, y: 5 },
{ x: 5, y: 8 },
]);
store.updateDataSet(id, { connectPoints: "line", color: "#2563eb" });
store.autoFit();
};
return (
<div style={{ height: "100vh", display: "flex", flexDirection: "column" }}>
<button onClick={load}>Load Data</button>
<SchoolGraphEditor />
</div>
);
}Components
<SchoolGraphEditor />
The all-in-one editor with toolbar, graph canvas, and settings panel.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| toolbar | ReactNode \| false | built-in toolbar | Custom toolbar content. false hides it. |
| settingsPanel | ReactNode \| false | <SettingsPanel /> | Custom right panel. false hides it. |
// Just the graph, no chrome
<SchoolGraphEditor toolbar={false} settingsPanel={false} />
// Custom toolbar, default settings
<SchoolGraphEditor toolbar={<MyToolbar />} />
// Custom everything
<SchoolGraphEditor toolbar={<MyToolbar />} settingsPanel={<MyPanel />} /><GraphCanvas />
The interactive graph area (pan + zoom + SVG render). No toolbar, no settings panel.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| style | CSSProperties | — | Override wrapper styles |
| paperStyle | CSSProperties | — | Override paper (white board) styles |
| children | ReactNode | — | Extra content overlaid on the graph |
<GraphCanvas /><GraphPaper />
The pure SVG renderer. No interaction logic, no wrapper. Renders grid, axes, labels, and plotted points.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| width | number | 900 | SVG viewBox width |
| height | number | 700 | SVG viewBox height |
Normally you don't need this directly — <GraphCanvas /> wraps it with a ResizeObserver to pass the actual container dimensions.
<SettingsPanel />
The right sidebar with scale controls, axis range, display toggles, and dataset management.
<SettingsPanel /><DataTable />
An editable X/Y coordinate table with add, remove, reorder, and paste support.
| Prop | Type | Description |
|------|------|-------------|
| points | DataRow[] | Current points array |
| onChange | (points: DataRow[]) => void | Called when points change |
| color | string | Dataset color (used for accent) |
<DataTable points={points} onChange={setPoints} color="#c62828" /><ExportButtons />
Export toolbar buttons (JSON, Open, PNG, SVG, Print).
| Prop | Type | Description |
|------|------|-------------|
| containerRef | RefObject<HTMLDivElement \| null> | Ref to a parent element containing the SVG |
<ExportButtons containerRef={myContainerRef} />Store API
The Zustand store is accessible via useGraphStore for both React hooks and imperative access.
import { useGraphStore } from "graphspace";
// React hook
const datasets = useGraphStore((s) => s.datasets);
const scale = useGraphStore((s) => s.scale);
// Imperative (outside components, thunks, etc.)
const store = useGraphStore.getState();State
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| datasets | DataSet[] | [] | All datasets and their points |
| scale | { xUnitsPerCm, yUnitsPerCm } | { x: 1, y: 1 } | Grid scale |
| axisRange | { xMin, xMax, yMin, yMax } | { -3, 15, -5, 12 } | Visible coordinate range |
| showGrid | boolean | true | Show/hide grid lines |
| showLabels | boolean | true | Show/hide axis numbers and coordinate labels |
| margin | number | 50 | Padding around the drawing area (SVG units) |
| canvasWidth | number | 900 | Current canvas width (set by ResizeObserver) |
| canvasHeight | number | 700 | Current canvas height |
Methods
| Method | Description |
|--------|-------------|
| addDataSet(label?) | Add a new empty dataset. Returns its id. |
| removeDataSet(id) | Remove a dataset by id. |
| updateDataSet(id, partial) | Update fields on a dataset (label, color, connectPoints, pointSize, visible, showCoordinates). |
| setDataPoints(id, points) | Replace all points for a dataset. |
| addDataPoint(id, row) | Append a single point { x, y }. |
| removeDataPoint(id, index) | Remove a point by index. |
| toggleDataSet(id) | Toggle dataset visibility. |
| clearDataSets() | Remove all datasets. |
| setScale(partial) | Update xUnitsPerCm and/or yUnitsPerCm. |
| setAxisRange(partial) | Update any/all of xMin, xMax, yMin, yMax. |
| setShowGrid(v) | Show/hide grid. |
| setShowLabels(v) | Show/hide axis numbering and coordinate labels. |
| pan(dxPx, dyPx) | Pan the view by pixel deltas. |
| zoom(factor, cxPx, cyPx) | Zoom by factor around a pixel point. |
| resetView() | Reset axis range and scale to defaults. |
| autoFit() | Adjust axis range to fit all visible data points with 15% padding. |
| exportJSON() | Serialize full graph state to a GraphJSON object. |
| importJSON(data) | Restore graph state from a GraphJSON object. |
Types
interface Point {
x: number;
y: number;
}
interface DataRow {
x: number;
y: number;
}
type ConnectType = "none" | "line" | "smooth";
interface DataSet {
id: string;
label: string;
color: string;
points: DataRow[];
visible: boolean;
connectPoints: ConnectType;
pointSize: number;
showCoordinates: boolean;
}
interface ScaleConfig {
xUnitsPerCm: number;
yUnitsPerCm: number;
}
interface AxisRange {
xMin: number;
xMax: number;
yMin: number;
yMax: number;
}
interface GraphJSON {
version: 1;
datasets: {
label: string;
color: string;
points: DataRow[];
visible: boolean;
connectPoints: ConnectType;
pointSize: number;
showCoordinates: boolean;
}[];
scale: ScaleConfig;
axisRange: AxisRange;
showGrid: boolean;
showLabels: boolean;
}Constants
COLORS // ["#1f4e8a", "#c62828", "#2e7d32", "#e65100", ...]
PRESETS // { default, symmetric, quadrant1, wide }JSON Import / Export
Export the full graph state:
const data = useGraphStore.getState().exportJSON();
console.log(data);Import from JSON:
const data = {
version: 1,
datasets: [
{
label: "y = x²",
color: "#c62828",
points: [{ x: -2, y: 4 }, { x: -1, y: 1 }, { x: 0, y: 0 }],
visible: true,
connectPoints: "smooth",
pointSize: 4,
showCoordinates: true,
},
],
scale: { xUnitsPerCm: 1, yUnitsPerCm: 1 },
axisRange: { xMin: -3, xMax: 5, yMin: -1, yMax: 6 },
showGrid: true,
showLabels: true,
};
useGraphStore.getState().importJSON(data);The built-in toolbar also has JSON (export) and Open (import) buttons that handle file download/upload.
Custom Layouts
Compose your own UI using the exported primitives:
import {
GraphCanvas,
SettingsPanel,
ExportButtons,
useGraphStore,
} from "graphspace";
function MyGraphPage() {
return (
<div style={{ display: "flex", height: "100%" }}>
<div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
<MyToolbar />
<GraphCanvas />
</div>
<SettingsPanel />
</div>
);
}
function MyToolbar() {
return (
<div style={{ display: "flex", alignItems: "center", gap: 8, padding: 8 }}>
<button onClick={() => useGraphStore.getState().autoFit()}>
Fit
</button>
<button onClick={() => useGraphStore.getState().resetView()}>
Reset
</button>
</div>
);
}Styling
The package has no CSS dependencies. All styles are inline JavaScript style objects. You can:
- Override
styleandpaperStyleon<GraphCanvas /> - Style your own wrapper elements around
<SchoolGraphEditor /> - Build your own
<SettingsPanel />using the Zustand store
Dark mode example
<GraphCanvas
style={{ background: "#1e1e2e" }}
paperStyle={{ background: "#181825", borderColor: "#45475a" }}
/>Development
git clone https://github.com/sachinabs/graphspace.git
cd graphspace
npm install
# Build the package
npm run build
# Development (watch mode)
npm run dev
# Run test app
cd test-app && npm install && npm run devCommands
| Command | Description |
|---------|-------------|
| npm run build | Build ESM + CJS + DTS |
| npm run dev | Watch mode rebuild |
| npm run typecheck | TypeScript check |
| npm run lint | TypeScript check (alias) |
| npm run clean | Remove dist/ |
License
MIT © Sachin ABS
GitHub: sachinabs/graphspace
