npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

Readme

graphspace

A traditional school-style coordinate graph editor for React. SVG-based, interactive, and fully customizable.

npm i graphspace
import { 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 .json files
  • 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 style and paperStyle on <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 dev

Commands

| 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