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

@vit-foundation/chart

v0.2.0

Published

Charts with a pluggable mark seam, the scales behind them, and the legends that describe them — a plot frame, marks, class bands and ramp legends.

Readme

@vit-foundation/chart

Charts, the scales behind them, and the legends that describe them.

Two ideas hold the package together.

A renderer that paints classed data and a legend that explains it must agree on what a class is. classColors / classOpacities are that agreement for the colour channel, and weightRange is the same agreement for a thickness channel. Hand both sides the same function and they cannot drift.

A chart's geometry and its appearance are different things. A chart resolves its box into a plot frame — margins, scales, tick counts, which labels fit — and a mark decides what the series looks like in it. Adding a visual style is one small component, never a branch inside a chart.

Extracted from the National Geographic Food for Tomorrow explore globe, where the same arithmetic already had four readers — a GPU renderer's stepped visual variables, a CSS hover mirror, the legend bar and the tier dots — and drift between them was the standing hazard.

It knows nothing about maps and nothing about any dataset. Two dependencies (chroma-js, d3-scale) and one peer (svelte, for the components).

Install

npm install @vit-foundation/chart

A chart

<script>
	import SeriesPlot from '@vit-foundation/chart/SeriesPlot.svelte';

	const rows = [
		{ x: 2000, y: 326121011, weight: 4804500 },
		{ x: 2001, y: 344292922, weight: 4957900 }
		// …
	];
</script>

<div style="height: 50vh">
	<SeriesPlot
		{rows}
		mark="ribbon"
		markStyle={{ color: '#FFD700', outlineColor: '#d9b700', outlineWidth: 2 }}
		yLabel="↑ million tonnes"
		xLabel="year →"
		legend={{ title: 'Area harvested' }}
	/>
</div>

Rows are { x, y, weight } — deliberately not a dataset's column names. The chart draws what it is given and fetches nothing, so the same component serves a CSV, an API and a fixture.

Marks

mark takes a name from the built-in table or a component of your own:

| name | draws | | -------- | ---------------------------------------- | | ribbon | a curve whose thickness follows weight | | line | a plain constant-width curve | | area | a filled area down to the baseline |

A mark is an ordinary Svelte component handed { frame, rows, extents, style }. It renders SVG into the plotting area and derives nothing: the frame already resolved every measurement. Nothing in the package enumerates the marks, so a new style never edits a file you do not own:

<!-- Dots.svelte -->
<script>
	let { frame, rows, style } = $props();
</script>

{#each rows as r (r.x)}
	<circle cx={frame.x(r.x)} cy={frame.y(r.y)} r="3" fill={style.color} />
{/each}
<SeriesPlot {rows} mark={Dots} />

Hover

The chart owns hit-testing, because it is the only thing that knows which row a pointer is over. It does not own the card — onhover reports the row and the point, and the host draws whatever its design system draws.

<SeriesPlot {rows} onhover={(row, point) => (tip = { row, point })} onleave={() => (tip = null)} />

The scale

import { classColors, classOpacities } from '@vit-foundation/chart/scale';

const k = breaks.length + 1;
classColors(['#fff7bc', '#fec44f', '#d95f0e'], k); // one hex per class, light→dark
classOpacities(k, 0.2, 1); // one alpha per class, low→high

Hand the same ramp and the same k to whatever paints the data, and the legend and the rendering cannot disagree.

The legend

<script>
	import PercentLegend from '@vit-foundation/chart/PercentLegend.svelte';
</script>

<PercentLegend colors={RAMP} max={72} breaks={[6.6, 18.7, 35.6, 58.4]} />

breaks classes the colour channel; opacity classes (or ramps) the alpha channel; the two composite independently, so mismatched break sets still render correctly. flat is the opacity-only variant — the bar holds the darkest ramp colour and lets alpha carry the value. mode="ticks" draws break marks instead of hard-edged bands.

The arithmetic is exported separately, so you can build your own bar:

import { bandEdges, barGradient, pickLabels, ticksOf } from '@vit-foundation/chart/legend';

Theming

The component ships plain CSS with custom-property hooks, so it renders standalone and restyles without a CSS framework:

| property | default | | ------------------------------ | -------------- | | --vit-legend-font | inherit | | --vit-legend-label-color | currentColor | | --vit-legend-label-size | 0.75rem | | --vit-legend-tick-size | 0.625rem | | --vit-legend-stroke | #424c5c | | --vit-legend-bar-height | 0.375rem | | --vit-legend-max-label-width | 3.5rem |

The chart components read their own set:

| property | default | | ---------------------------- | -------------- | | --vit-chart-font | inherit | | --vit-chart-axis-color | currentColor | | --vit-chart-grid-color | #cacaca | | --vit-chart-grid-opacity | 0.5 | | --vit-chart-hatch-color | #00000020 | | --vit-chart-highlight-ring | #ffffff |

Subpaths

Import the narrow subpath, not the barrel:

| subpath | holds | | -------------------------------------------- | -------------------------------------- | | @vit-foundation/chart/plot | the plot frame + axis rules, no DOM | | @vit-foundation/chart/series | the row contract and extents, no DOM | | @vit-foundation/chart/marks | the mark contract, registry and kernel | | @vit-foundation/chart/scale | classColors, classOpacities | | @vit-foundation/chart/legend | the bar arithmetic, no Svelte, no DOM | | @vit-foundation/chart/SeriesPlot.svelte | the chart | | @vit-foundation/chart/PercentLegend.svelte | the colour-scale legend | | @vit-foundation/chart/WeightWedge.svelte | the thickness key | | @vit-foundation/chart/PlotAxes.svelte | grid, ticks and axis labels |

The two .ts subpaths run in plain Node. The barrel (@vit-foundation/chart) re-exports the component, so it needs a Svelte-aware bundler — reach for it only from an app that has one.

Scope

In: charts built on the plot-frame + mark seam, colour-scale arithmetic, and the legends that depict either.

Out: anything that describes a symbol rather than a scale or a mark (size rings, alpha tiers — those are map vocabulary), and anything that knows where data came from. Loading, parsing and a dataset's column names stay in the app.

This section used to read "a chart you built for one story is not a library", which was the right rule for a chart with no seam in it: generalising one story's graphic by adding props gives you a component with thirty of them and one caller. The line moved on 2026-09-21 because the mark seam changes the economics — the package holds the frame and the contract, and a style is a component anyone can write without touching this repo.

Documentation

The full site lives in docs/ and is served by npm run dev:docs:

  • Getting started — install, the subpaths, a legend on screen
  • The scale — class bands, and the contract they hold
  • The legend — the two channels, the modes, theming
  • Reference — every entry point and what it exports
  • Changelog — one page per version, newest first

CONTRIBUTING has the setup, the rules the tooling enforces, and the release ritual.

Package name

This package was published as @vit-foundation/vizvit-chart up to 0.0.3, which contained only the ExampleChart scaffold. It is @vit-foundation/chart from 0.1.0. ExampleChart is still exported from the barrel; nothing was removed.

License

Apache-2.0