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

pluton-2d

v0.2.0

Published

Abstraction over HTML SVG for easy blueprint drawing.

Downloads

390

Readme

pluton-2d

I started working on this library for my own workflow, I needed a simple technical drawing tool based on SVG for crisp output and low DOM churn. The result provides built-in helpers for grids, dimensions, hatching, and camera controls without hiding the SVG/DOM model. It fits my need pretty well for now, sharing it in case someone else needs the same kind of tooling :)

What you get

  • SVG-first rendering → inspectable DOM and sharp output at any zoom
  • Drafting helpers → built-in grid, axes, dimensions, and hatch fill
  • Low DOM churn → reused builders with change-only attribute writes
  • Reactive params → mutate params and redraws are scheduled automatically
  • Opt-in camera → pan/zoom only when enabled, reset anytime
  • CSS-driven styling → control visuals through CSS variables on the root SVG

Getting started

Install the package and import the default stylesheet:

npm install pluton-2d
import "pluton-2d/style.css";
import { Pluton2D } from "pluton-2d";

const svg = document.querySelector("svg")!;
const scene = new Pluton2D(svg, {
  params: { width: 240, height: 120 },
});

scene.enablePan(true);
scene.enableZoom(true);

const geom = scene.geometry.group();

// drawing a rectangle
scene.draw((p) => {
  const path = geom.path();
  path
    .moveToAbs(-p.width / 2, -p.height / 2)
    .lineTo(p.width, 0)
    .lineTo(0, p.height)
    .lineTo(-p.width, 0)
    .close();
});

Mutating params triggers redraws automatically:

scene.params.width = 150;
// or
Object.assign(scene.params, { width: 200, height: 100 });

🚨 Constraints: params must be flat, and top-level reassignment is not supported.

scene.params = { width: 200, height: 100 }; // throws

How rendering works

  1. Reactive params → Param mutation calls scheduleRender()
  2. Frame-capped loop (60 FPS)beginRecord() resets active indexes, draw callbacks run, then commit() applies changes
  3. Group reuse → Create groups once, request builders every frame
const g = scene.geometry.group();

scene.draw(() => {
  const path = g.path();
  path.moveToAbs(0, 0).lineTo(10, 10);
});

Builders are pooled internally. Elements are created only when needed, and attributes are written only when changed.

Coordinate system

Pluton uses a center origin with Y-up coordinates.

  • Origin is the center of the viewport
  • Positive X is right, positive Y is up
  • lineTo(10, 20) moves right 10 units and up 20 units

The viewport layer applies scale(1, -1) for SVG rendering. Use dimension helpers when placing text/annotations so orientation stays correct.

API

This section is the practical surface for day-to-day usage. For exhaustive method-by-method docs, see API Reference.

Construction

const scene = new Pluton2D(svg, {
  params: { width: 240, height: 120 },
});

params can be any flat shape (type inferred from the object). Nested objects are not supported.

ViewBox (coordinate space)

Set an explicit drawing coordinate system:

const scene = new Pluton2D(svg, {
  params: { width: 240, height: 120 },
  viewBox: { width: 200, height: 300 },
});

Viewport priority order:

  1. Constructor viewBox
  2. SVG viewBox attribute
  3. SVG pixel size (getBoundingClientRect)

Migration note:

  • new Pluton2D(svg, params, { filterIntensity }) was removed
  • Use scene.setDisplacementScale(...)

Draw loop

const unsubscribe = scene.draw((params) => {
  // build geometry and dimensions
});

unsubscribe(); // optional
scene.dispose(); // required when tearing down the scene

If all draw callbacks are removed, pending renders stop unless camera/input requests frames.

Controls

scene.enableFilter(true); // default: false
scene.setDisplacementScale(2.75); // default: 2.75
scene.setDisplacementFrequency(0.1); // default: 0.1
scene.setDisplacementOctaves(1); // default: 1

scene.enableMask(false); // default: false
scene.setMaskFrequency(0.03); // default: 0.03
scene.setMaskOctaves(1); // default: 1
scene.setMaskScale(1.6); // default: 1.6

scene.enableFill(true); // default: true

scene.enableGrid(true); // default: true

scene.enableAxes(true); // default: true

The hand-drawn filter has two independent parts:

  • Displacement (setDisplacementScale/Frequency/Octaves) — wobble applied to strokes and fills.
  • Mask (setMaskScale/Frequency/Octaves/Enabled) — incomplete-line masking applied to geometry groups (fills and strokes). Disabled by default.

Safari caveat: SVG filters can be expensive during zoom.

Geometry

Create groups outside draw callbacks, request builders inside draw callbacks:

const g = scene.geometry.group();

scene.draw((p) => {
  g.path({ className: "my-shape" })
    .moveToAbs(0, 0)
    .lineTo(p.width, 0)
    .lineTo(0, p.height)
    .close();
});

Most-used group methods:

group.translate(x, y)
group.scale(x, y)
group.setDrawUsage("static" | "dynamic") // default: "dynamic"
group.clear()

Most-used path methods:

path.moveToAbs(x, y)
path.lineTo(dx, dy)
path.lineToAbs(x, y)
path.arcTo(dx, dy, r, clockwise?, largeArc?)
path.cubicTo(c1dx, c1dy, c2dx, c2dy, dx, dy)
path.close()

Dimensions

Dimensions are a separate layer for annotation primitives:

const d = scene.dimensions.group();

scene.draw(() => {
  d.dimension()
    .moveToAbs(-40, 0)
    .tick(0)
    .lineTo(80, 0)
    .tick(0)
    .textAt(0, -10, "80");
});

Most-used dimension methods:

dim.moveToAbs(x, y)
dim.lineTo(dx, dy)
dim.arc(r, startAngle, endAngle)
dim.arrow(angleRad, size?)
dim.tick(angleRad, size?)
dim.textAt(dx, dy, text, align?, className?)

Text align: "start" | "middle" | "end" (default: "middle").

Camera controls

Pan/zoom are opt-in and can be reset anytime:

scene.enablePan(true); // middle-mouse or shift+left-click
scene.enableZoom(true); // mouse wheel, 1x-20x range
scene.resetCamera();

Responsive view scaling

Use setViewScale(...) to scale visual output without changing coordinate space or camera zoom state:

if (window.innerWidth <= 768) {
  scene.setViewScale(0.75);
} else {
  scene.setViewScale(1.0);
}

Styling

All styling is controlled by CSS variables on .pluton-root:

.pluton-root {
  /* Grid */
  --pluton-grid-minor-stroke: rgba(0, 0, 0, 0.025);
  --pluton-grid-major-stroke: rgba(0, 0, 0, 0.12);
  --pluton-grid-stroke-width: 0.5;

  /* Axes */
  --pluton-axis-color: rgba(0, 0, 0, 0.2);
  --pluton-axis-stroke-width: 1;
  --pluton-axis-dash: 5 5;

  /* Geometry */
  --pluton-geometry-stroke: rgba(0, 0, 0, 0.7);
  --pluton-geometry-stroke-width: 1;

  /* Hatch stroke color used by built-in hatch patterns */
  --pluton-hatch-color: rgba(0, 39, 50, 0.14);

  /* Dimensions */
  --pluton-dim-color: black;
  --pluton-dim-stroke-width: 1;
  --pluton-dim-text-color: rgba(0, 0, 0, 0.6);
  --pluton-dim-font-size: 12px;
  --pluton-dim-font-family: system-ui, sans-serif;
}

Custom classes

g.path({ className: "my-custom-path" });
d.dimension({ className: "highlighted-dim" });

Hatch fill

Fill resolution order when fills are enabled (scene.enableFill(true), default):

  • if path({ fill }) is set, that value is used
  • otherwise, default hatch fill is used
const blueFillId = scene.addHatchFill("#2563eb", 0.35);
g.path({ fill: blueFillId });

Use fill: "none" for stroke-only geometry.

Performance

Prefer one draw callback

One callback keeps ordering explicit. Multiple callbacks are supported and run in registration order.

Mark static groups

Use static groups for geometry that does not change:

const staticGroup = scene.geometry.group();
staticGroup.setDrawUsage("static");

const dynamicGroup = scene.geometry.group();

scene.draw((p) => {
  staticGroup.path().moveToAbs(0, 0).lineTo(100, 0).lineTo(0, 100).close();
  dynamicGroup.path().moveToAbs(0, 0).lineTo(p.width, 0).lineTo(0, p.height).close();
});

Static groups still run through draw callbacks, but commits are skipped after the first commit.

Safari filter performance

If zoom feels laggy on Safari, disable the filter:

scene.enableFilter(false);

When to use

Pluton2D is optimized for technical drawing workflows: crisp SVG, dimensions, hatching, and predictable redraw behavior.

Good fit for Pluton2D

  • Technical drawings, blueprints, engineering diagrams
  • Annotation-heavy scenes (dimensions, ticks, callouts)
  • Workflows where inspectable/exportable SVG matters
  • Interactive scenes with moderate redraw frequency

Prefer charting libraries when

  • Your primary goal is data visualization
  • You need chart primitives, scales, legends, and tooltips out of the box
  • You need chart-specific ecosystem tooling

Prefer Canvas when

  • You need high-frequency animation
  • You render many moving primitives every frame
  • SVG/DOM updates become the bottleneck

Prefer WebGL or WebGPU when

  • You need very large geometry counts or GPU-heavy effects
  • You need shader pipelines or post-processing
  • You need 3D or high-end real-time rendering

Troubleshooting

SVG is blank

  • Check CSS import: import "pluton-2d/style.css"
  • Ensure the SVG has size (CSS width/height or viewBox)

Y-axis feels inverted

  • Pluton uses Y-up coordinates, not screen-style Y-down
  • lineTo(0, 10) moves up

Params changes don't trigger redraw

  • Mutate params: scene.params.width = 100
  • Top-level params object is immutable: scene.params = { ... }
  • Params must be flat; nested objects throw at construction

Dimensions not visible

  • Check layer creation: scene.dimensions.group()
  • Verify draw callback registration
  • Check CSS variable: --pluton-dim-color

Performance issues during zoom (Safari)

  • Disable filter: scene.enableFilter(false)

SSR

Pluton2D is DOM-dependent and does not support SSR. Instantiate it on the client after mount.