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

@adnathanail/zxcc

v0.6.1

Published

Framework-agnostic web component for rendering ZX-calculus diagrams

Readme

zxcc - ZX Calculus Components

CI Lit TypeScript Storybook Vite+

Framework-agnostic web component for rendering ZX-calculus diagrams.

| Example basic layout | Example algebraic layout | | ----------------------- | ------------------ | | Basic layout demo | Algebraic layout demo |

Checkout the demo

Usage

npm install @adnathanail/zxcc
<script type="module" src="./node_modules/@adnathanail/zxcc/dist/index.bundle.js"></script>

<zx-diagram id="d"></zx-diagram>

<script type="module">
  document.getElementById('d').diagram = {
    nodes: [
      { id: 0, type: 'input',  ioId: 0 },
      { id: 1, type: 'spider', color: 'Z', phase: 'π/2' },
      { id: 2, type: 'output', ioId: 0 },
    ],
    edges: [
      { src: 0, tgt: 1 },
      { src: 1, tgt: 2 },
    ],
  }
</script>

diagram is a JS property, not an attribute — set it via a DOM reference so the object round-trips without JSON coercion.

Assign a new object to change the diagram; layout runs when the property's identity changes, so mutating the object already assigned paints nothing new. If you must mutate in place, call el.refresh() afterwards. Note that either way the re-layout resets the drawing: dragged nodes return to their laid-out positions and the selection is cleared.

The drawing responds to mouse, pen and touch alike: press a node or a dot to select it and drag to move it. A drawing wider than its box scrolls, and on a touch screen dragging the empty canvas is what scrolls it — so the rubber-band selection over blank canvas is a mouse and pen gesture only.

zxcc can be bundled into more than one script on the same page, as long as every copy is the same version: the first copy to load registers the elements and the rest use them. Loading two different versions on one page throws an error naming both, since a custom element name can only be registered once and one of the two would otherwise be silently drawing with the other's elements.

Diagram shape

interface DiagramData {
  nodes: DiagramNode[]
  edges: DiagramEdge[]
  boxes?: { kind: 'stack' | 'compose'; nodeIds: number[] }[]
  labels?: [number, string][]  // node-id → phase-label override
  pauliWeb?: { src: number; tgt: number; kind: 'X' | 'Y' | 'Z' | 'I' }[]
  scalar?: string        // global scalar, drawn below the diagram
}

interface DiagramNode {
  id: number
  type: 'spider' | 'input' | 'output' | 'hadamard' | 'wire'
      | 'w-input' | 'w-output' | 'z-box'
  color?: 'Z' | 'X'      // spider only
  phase?: string         // pre-formatted (e.g. "π/2", "-π/4")
  ioId?: number          // input/output index
  col?: number           // optional pre-computed column
  qubit?: number         // optional pre-computed qubit row
  ground?: boolean       // draws a ground symbol below the node
  vdata?: [string, unknown][]  // annotations drawn above the node
}

interface DiagramEdge {
  src: number
  tgt: number
  kind?: 'simple' | 'hadamard' | 'w-io' | string   // default 'simple'; see edgeColors
}

If any node carries col, auto-layout is skipped and every node is expected to carry both col and qubit. Otherwise a BFS from the inputs assigns rows and qubits.

An edge with src === tgt renders as a self-loop arc.

Element attributes

These mirror the keyword arguments of pyzx's draw_d3() and control presentation only — graph structure always lives in diagram.

Some can be set as attributes in HTML, some must be set with JS on the element, similarly to diagram above

| Attribute | Property | Default | Meaning | | --- | --- | --- | --- | | show-labels | showLabels | false | Draw node/wire IDs | | color-scheme | colorScheme | original | original / rgb / grayscale | | scale | scale | derived | Pixels per row/qubit; must be a positive number | | view-mode | viewMode | graph | graph / hypergraph / both-vertical / both-horizontal - see Hypergraph view. <zx-diagram> only. | | disable-io-blobs-in-hypergraph | disableIOBlobsInHypergraph | false | Leave out the single-dot blob around each input/output in the hypergraph view. | | — | colors | null | Full palette override (Record<string, string>), overrides color-scheme. | | — | edgeColors | null | Define custom wire 'kinds', to display wires in custom colours, overrides both of the above for the kinds named. |

<zx-diagram show-labels color-scheme="grayscale" scale="40"></zx-diagram>

The palettes are exported too, if you want to build a variant:

import { ORIGINAL_COLORS, RGB_COLORS, GRAYSCALE_COLORS, COLOR_SCHEMES } from '@adnathanail/zxcc'

Wire colours and custom wire kinds

edgeColors maps edge kinds to colours — the kinds the diagram is written in, rather than pyzx's edge / Hedge / Xedge palette entries. Only the kinds you name move; the rest stay on whatever color-scheme or colors decided.

An edge's kind is only a colour: nothing in the layout or the geometry reads it. So it isn't limited to the three built-in kinds — use any string you like and give it a colour here:

const d = document.getElementById('d')
d.diagram = {
  nodes: [/* … */],
  edges: [
    { src: 0, tgt: 1, kind: 'control' },     // a kind of your own
    { src: 1, tgt: 2, kind: 'hadamard' },    // a built-in, recoloured
    { src: 2, tgt: 3 },                      // 'simple', left alone
  ],
}
d.edgeColors = { control: '#00aa55', hadamard: '#ff00aa' }

A kind you don't give a colour draws like a plain wire. Both views read a wire's colour from the same lookup, so the hypergraph view's dot for a wire always comes out the colour of the wire itself.

Hypergraph view

Set view-mode="hypergraph" (or the viewMode property) to draw the diagram's hypergraph dual instead of the diagram — the roles of wires and spiders swap. Every ZX wire becomes a node/dot; every ZX spider becomes a hyperedge, drawn as a blob enclosing the dots of the wires incident to it. An input/output is incident to one wire, so its blob is a circle around that one dot.

<zx-diagram id="d" view-mode="hypergraph"></zx-diagram>

Only Z/X spiders, I/O, and Hadamards are supported in this view, other node types will be rejected.

Input/output blobs

Each input/output gets a blob of its own, a circle around the single dot for its leg. That circle is what tells a boundary leg apart from a self-loop: both are one dot hanging off one spider, and the rule that every dot sits in two blobs — one per end of its wire — only reads off the drawing while the boundaries have blobs too. On a diagram with many boundaries it is also a lot of outline, so it can be turned off:

<zx-diagram id="d" view-mode="hypergraph" disable-io-blobs-in-hypergraph></zx-diagram>
document.getElementById('d').disableIOBlobsInHypergraph = true

The dots stay either way — a boundary leg is still a wire. Only the circle round it goes, and with it goes anything that circle could be pressed or counted for: an input/output can no longer be selected in this view, and a dot near one no longer counts as trespassing into it.

Both views

You can also have both the graph & hypergraph views, by setting the view-mode to both-vertical or both-horizontal. Selections will be synced across the two viewers.

The three elements

<zx-diagram> is the element to reach for when you have a ZX diagram, and it is built out of two others that you can use directly:

| Element | Takes | Draws | | --- | --- | --- | | <zx-graph> | diagram (a DiagramData) | the ZX diagram | | <zx-hypergraph> | hypergraph (a HypergraphInput) | the hypergraph | | <zx-diagram> | diagram (a DiagramData) | mounts one or both of the above, per view-mode |

They all take the presentation properties above, and all announce a zx-selection event carrying what a gesture picked out — ZX node ids and edge indices — which you can also write back onto the element's selection property to pick something out yourself. <zx-diagram> does exactly that between its two children, which is how the pair stays in step.

document.getElementById('g').addEventListener('zx-selection', e => {
  console.log([...e.detail.nodes], [...e.detail.edges])
})

Use <zx-graph> when the picture you want is the diagram and only the diagram — it has no view-mode and knows nothing about the dual. Use <zx-diagram> when you want the dual, or the pair: deriving the one from the other is the thing it does that neither of the others can.

Drawing a hypergraph directly

<zx-diagram> derives its hypergraph from a diagram, which puts every dot on the midpoint of the wire it stands for — that is what makes the two views line up, and it is also the only arrangement it will produce. Handing <zx-hypergraph> a hypergraph yourself is the other way in: you say what the hypergraph is and where every dot goes, and it draws exactly that. Nothing is worked out.

<zx-hypergraph id="h" show-labels></zx-hypergraph>

<script type="module">
  document.getElementById('h').hypergraph = {
    // Hypergraph nodes, drawn as dots. Each stands for a ZX wire, and says which
    // grid square it goes in — the same column/qubit grid a ZX diagram is on.
    wires: [
      { col: 0, qubit: 0 },
      { col: 1, qubit: 0, kind: 'hadamard' },   // an H-wire's dot, in H-wire blue
      { col: 2, qubit: 0 },
    ],
    // Hyperedges, drawn as blobs around the dots they hold, by index into `wires`.
    hyperedges: [
      { kind: 'boundary', name: 'in',  wires: [0] },
      { kind: 'z-spider', phase: 'π/2', wires: [0, 1] },
      { kind: 'x-spider', wires: [1, 2] },
      { kind: 'boundary', name: 'out', wires: [2] },
    ],
  }
</script>

A hyperedge's kind is one of z-spider, x-spider, hadamard or boundary — the same four the derived view draws — and picks the palette entry its blob is filled with. name is written only when show-labels is on and defaults to the letter for its kind (Z, X, H); a boundary has no default, since which end of the diagram it is isn't something a hypergraph knows. phase is pre-formatted and drawn whether labels are on or off.

Every wire must be held by one or two hyperedge ends. Two is the ordinary case — the two ZX nodes its edge would run between, or the same hyperedge listed twice for a self-loop, or a spider and a boundary for a leg hanging out of the diagram. One is what you get by leaving out a hyperedge you don't want drawn, which is how you'd drop the boundary circles. Three is an error naming the wire rather than a drawing: a dot's two ends are read from the hyperedges holding it when you select one, so a third has nowhere to go.

Each wire and hyperedge may carry an id, which is the number a selection names it by — so a zx-selection event can hand you back your own identifiers rather than positions in a list. Left out, it defaults to the position in the list, and ids must be distinct.

Where the dots go

A wire is placed either on the grid (col and qubit) or in pixels (x and y), never both and never neither, and every wire in one input has to use the same pair.

col and qubit are the same column/qubit grid <zx-diagram> lays a diagram out on, drawn two scales to the square — a blob's outline stands off every dot it holds, so the dual needs more room than the diagram would. They may be negative or fractional: the grid is read relative to its own lowest column and qubit, so a dot goes wherever you want it.

x and y are used exactly as given — nothing is subtracted and no padding is added — for a drawing whose spacing is a fact about the picture rather than a multiple of anything. You can give the canvas as width and height alongside them; without it, one is measured around the dots.

Either way the canvas leaves room for the captions above and the wire ids below.

Presentation

The element takes the same presentation properties as <zx-diagram> — show-labels, color-scheme, colors, edgeColors and scale — and behaves the same way under a press or a drag. There is no view-mode: a hypergraph has only the one picture.

scale sets how big a dot is drawn and how far a blob's outline stands off the dots it holds. On the grid it also sets how far apart the dots are, so the whole drawing grows and shrinks with it. In pixels the positions are already fixed, so scale changes the weight of the marks alone — raising it grows the dots without moving them. It defaults to 35, the middle of the 20–50 band a derived scale is clamped to, and anything that isn't a positive number is an error.

Exported constants

The main entry defines a custom element as it loads, so it needs a DOM and won't import in Node. To allow build-time usage of constants by Node, we have a second entry point, @adnathanail/zxcc/constants:

import { VIEW_MODES, COLOR_SCHEMES } from '@adnathanail/zxcc/constants'

if (!VIEW_MODES.includes(mode)) throw new Error(`unknown view-mode: ${mode}`)

Development

Install vite+

Install npm dependencies:

npm install

This also installs a pre-commit hook that runs vp check --fix on the staged files.

Demo

To view the Storybook example usages, and an interactive Playground story with controls:

npm install
npm run storybook

Testing

You can run tests from inside the Storybook web interface. If you want to run them via the terminal:

vp run test

And to run with coverage:

vp run coverage

Then open coverage/index.html in a browser.

Tasks run through vp run are cached: rerunning one whose inputs haven't changed replays its output instead. vp run --no-cache <task> forces a fresh run, and vp cache clean clears the cache.

Building from source

vp run build        # dist/index.bundle.js and dist/constants.js
vp run build-dev    # the same, unminified with inline sourcemaps
vp run watch        # unminified rebuild on every change

The bundle is self-contained with no runtime dependencies.

Analyzing bundle composition

vp run analyze