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

@kgdiff/renderer

v0.12.0

Published

High-DPI Canvas 2D renderer for kgdiff scenes: smooth camera, LOD, label placement, hit testing and minimap.

Readme

@kgdiff/renderer

A high-DPI Canvas 2D renderer for @kgdiff/scene frames: smooth camera (pan, zoom, fly-to), level of detail, collision-free label placement, hit testing, swipe clipping and a minimap. It draws pixels and knows nothing about diffs, so the same frames can also go to @kgdiff/g6 or your own renderer.

Install

pnpm add @kgdiff/renderer @kgdiff/scene @kgdiff/layout @kgdiff/core

ESM only. Runs in evergreen browsers (it needs a DOM and Canvas 2D).

Quick start

import { diff } from '@kgdiff/core';
import { layoutDiff } from '@kgdiff/layout';
import { createMinimap, createRenderer } from '@kgdiff/renderer';
import { buildScene, createViewState, createVisualResolver, getTheme } from '@kgdiff/scene';

const result = await diff(before, after);
const scene = buildScene(result, await layoutDiff(result), { theme: 'dark' });
const resolver = createVisualResolver(scene);

// The container needs a size (CSS); the canvas follows it through a ResizeObserver.
const container = document.getElementById('graph') as HTMLElement;
const renderer = createRenderer(container, { theme: getTheme('dark') });
let frame = resolver.resolve(createViewState());
renderer.setFrame(frame); // the first frame with content is fitted automatically

renderer.on('click', ({ pick }) => {
  if (pick?.type !== 'node') return;
  frame = resolver.resolve(createViewState({ selectedNode: pick.index }), frame);
  renderer.setFrame(frame);
});

createMinimap(document.getElementById('minimap') as HTMLElement, renderer).update(frame);

setFrame only marks the view dirty; drawing happens on the next animation frame and the loop sleeps when nothing changes. Pass { split, before, after } to setFrame for a swipe view.

Labels keep their pixel size at any zoom and move beside their node when the spot below is taken, so fitting node positions alone can clip the labels of the outermost nodes. labelFootprint(frame, theme).at(k) runs the renderer's label placement at zoom k and returns the world rect the discs, labels and badges cover, for fits that keep every label in view.

Layers: drawing under and over the graph

What only your application knows about (a background behind a group of nodes, a certification mark, a count on a node) you draw yourself, on the renderer's own canvas, in two places: under the graph (after the background and the dot grid, before everything else) and over it (after the labels). No second canvas to keep in step with the camera.

const removeBackground = renderer.addLayer({
  order: 'under',
  draw(ctx, view) {
    // Positions are the layout's (world units); the context is in CSS pixels.
    const corner = view.worldToScreen({ x: group.x, y: group.y });
    const k = view.camera.k;
    ctx.fillStyle = 'rgba(56, 189, 248, 0.12)'; // keep it soft, see below
    ctx.beginPath();
    ctx.roundRect(corner.x, corner.y, group.width * k, group.height * k, 12);
    ctx.fill();
  },
});

renderer.addLayer({
  order: 'over',
  draw(ctx, view) {
    for (const mark of marks) {
      const p = view.worldToScreen(mark); // { x, y } of a node
      ctx.fillStyle = mark.color;
      ctx.beginPath();
      ctx.arc(p.x + 8, p.y - 8, 4, 0, Math.PI * 2); // 4 px at every zoom
      ctx.fill();
    }
  },
});

removeBackground(); // `addLayer` returns the function that removes the layer
  • draw is called once per painted frame; in a swipe view once per side, clipped to it, with view.side ('before' or 'after'). view.viewport is the world rectangle that call can show, for culling. When your own data changes, call renderer.requestRender().
  • The renderer saves the context before each layer and restores it after, so nothing a layer sets leaks into the graph. On entry the transform is the pixel ratio, globalAlpha is 1 and the line dash is empty; set every other property you use.
  • Layers are part of the frame: toDataURL includes them, and their time counts in the frame event's ms. With no layers the renderer paints exactly as before.
  • Layers are not picked, and their text takes no part in label placement. Hit-test your own marks with renderer.camera.screenToWorld and the click event's world.
  • Known limit: labels carry a halo in the canvas background color, which shows as a faint outline over a tinted under area. Soft tints, such as the theme's soft colors (about 10% alpha), keep it unnoticeable.

addLayer is experimental in 0.9 (RFC 0003, part 4a): it may change in a minor release before it is stable. <kgdiff-stage> of @kgdiff/viewer has the same method for the graph inside the viewer.

Learn more

License

MIT