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

@bicharts/chart-host

v0.5.19

Published

Run a BIC-generated D3 chart in any web host: compiles the generated render() function, applies the shared option defaults, resolves mark clicks (through tooltip overlays), owns the selection affordance, and translates row indices between cross-filtered c

Readme

@bicharts/chart-host

Run a D3 chart generated by Business Intelligence Champions (BIC) in any web page.

A BIC chart is generated source you own — a plain render(container, data, options) function you commit to your repo. There is no API key at run time, no per-render cost, and no network call. This package is the host side: the ~80 lines every integrator would otherwise write, and the handful they would get subtly wrong.

It is the same contract the BIC Power BI visual implements, minus Power BI — so a chart looks and behaves the same wherever it runs.

What it does for you

  • Compiles the generated code and injects your d3.
  • Applies the shared option defaults/clamps — the same resolveOptions the visual uses.
  • Resolves mark clicks — including through a chart's own tooltip overlay, and by geometry when a row has no hittable shape.
  • Multi-select on Ctrl / Cmd / Shift — the same gesture Power BI uses. Modifier-clicking toggles each row, so clicking a selected mark again removes it.
  • Owns the selection affordance — dims unselected marks via the lch-* class grammar.
  • Translates row indices between cross-filtered charts (see the hazard below).
  • Pins typography at the container boundary so your page's CSS can't slice chart labels.
  • Tears down cleanly — StrictMode-safe, stops animation timers.

Install

npm install @bicharts/chart-host
npm install d3@7                      # peer requirement of the CHART, not of this package
npm install -D @types/d3              # TypeScript only — d3 ships no types

This package itself has no runtime dependencies — the shape profiler is bundled in, and the map geometry loads on demand.

React is an optional peer — a vanilla host never resolves it.

React

import * as d3 from "d3";
import { BicChart, BicChartGroup } from "@bicharts/chart-host/react";
import code from "./charts/revenue/chart.js?raw";      // Vite; any raw-text import works
import payload from "./charts/revenue/data.sample.json";

// data.sample.json rows are POSITIONAL arrays; the group wants row OBJECTS.
const columns = payload.columns;
const rows = payload.rows.map(r =>
  Object.fromEntries(columns.map((c, i) => [c.name, r[i]])));

<BicChartGroup columns={columns} rows={rows}>
  <BicChart id="revenue" code={code} d3={d3} options={{ width, height }} />
  <BicChart id="detail"  code={detailCode} d3={d3} options={{ width, height }} />
</BicChartGroup>

Clicking a mark in one chart filters the other. The group owns the source table and the payload→source row mapping — which is the part you must not hand-roll:

The row-index hazard. __rowIdx__ is a position within the payload a chart received, not an id in your table. Re-render a chart with filtered rows and its payload renumbers from zero, so the same integer now denotes a different record. Comparing indices across two charts does not throw — it quietly filters to the wrong thing.

The coordinated dashboard: one chart filters, the other highlights

The pairing almost every dashboard actually wants. A table filters down to the selection; a map keeps every bubble and dims the rest, because filtering a map to one city throws the geography away and leaves nothing to click to change the selection.

<BicChartGroup columns={columns} rows={rows} point={{ city: "City", state: "StateOrProvince" }}>
  <BicChart id="map"   code={mapCode}   d3={d3} geoKind="north-america" respondsWith="highlight" />
  <BicChart id="table" code={tableCode} d3={d3} />
</BicChartGroup>

That is the whole recipe. respondsWith="highlight" keeps the full payload and paints the sibling's selection onto the marks, so the map is re-painted, never re-rendered — it holds its projection, zoom and basemap while the table filters beside it. The rest follows from rules the group already enforces:

  • A chart never filters itself. The origin keeps all its marks with the selected ones lit, so the gesture stays reversible.
  • Mutual by default. Click a bubble → the table filters; click a row → the map highlights. Add filteredBy="table" only when you want one-way wiring.
  • Clearing is group.clear(), or clicking the selected mark again.

Outside React it is host.selection.highlight(rowIdxs) on the chart that should dim, and setData() with a filtered payload on the one that should filter. Both notify subscribers with source "host" — that tag is what stops two linked charts republishing each other's selection and fighting.

Do not reach for the lch-* classes to do this by hand. They are an implementation detail of the affordance, they are re-applied on every render (so a manual classList poke is lost at the next data change), and getting row indices right across a filtered payload is the hazard above.

Vanilla

import { createChartHost } from "@bicharts/chart-host";

const host = createChartHost(el, { code, data, d3, options: { width: 800, height: 500 } });
host.render();
host.selection.onChange(rowIdxs => { /* cross-filter your other panels */ });
host.setOptions({ colorScaleLow: "#eee" });   // live restyle — never a regeneration
host.destroy();                               // call on unmount

Sizing is yours

width/height have no defaults. A chart draws once at whatever it is handed and does not re-measure, so a container measured at 0 draws a 0px chart forever. Measure synchronously, and use a ResizeObserver only for later resizes — gating the first paint on one costs seconds in a background or headless tab, where frames are barely produced.

useLayoutEffect(() => setW(ref.current.getBoundingClientRect().width), []);

Maps

Geometry is not bundled into the entry — the three assets are ~1.3 MB raw, and a bar chart should not pay for a basemap. Choose one:

import { loadGeo } from "@bicharts/chart-host";              // per-asset dynamic import (own chunk)
await loadGeo("us-state-code");
createChartHost(el, { code, data, d3, geoKind: "us-state-code" });

import { geoForKind } from "@bicharts/chart-host/geo";       // or bundle it yourself
createChartHost(el, { code, data, d3, geoKind, geoProvider: geoForKind });

createChartHost(el, { code, data, d3, options: { geo } });   // or pass data.geo.json directly

The React <BicChart> handles this itself: give it geoKind and it fetches the geometry when the cache is cold, re-rendering with the basemap when it lands. await loadGeo(kind) before mounting still works and skips the one-frame pop-in. Outside React, render() is synchronous by contract, so setting geoKind with no geometry available warns and draws without a basemap — load or provide geometry first.

D3 plugins

Some charts call d3.sankey(), d3.hexbin() or the voronoi family. Those are separate packages that attach onto the d3 object — install them and augment the same d3 you pass in:

import * as d3 from "d3";
import { sankey, sankeyLinkHorizontal } from "d3-sankey";
Object.assign(d3, { sankey, sankeyLinkHorizontal });

The integration contract returned by generate_chart names exactly which ones your chart needs. If one is missing, the host throws a message naming the package instead of a bare d3.sankey is not a function.

You can also ask the code itself, before rendering it — which is what you want when the chart arrives at run time and a blank frame is not an acceptable answer:

import { requiredD3Plugins } from "@bicharts/chart-host";

requiredD3Plugins(code);            // -> ["d3-sankey"]  (sorted, deduped, [] for core-only d3)

It is a static scan, not a trial render: synchronous, side-effect free, and safe to run on generated code you have not executed yet. It errs toward naming a plugin the chart may not reach — a needless install is cheaper than a blank chart.

Selection styling

Generated code emits marks and data-row-idx; it never styles "selected", because only the host knows what is selected. This package injects the rules, using the same grammar as the Power BI visual — override them if you want a different look:

.bic-chart-host { --lch-dim-opacity: 0.25; }
.bic-chart-host.lch-has-selection .d3-mark:not(.lch-mark-selected) { opacity: var(--lch-dim-opacity); }

Architecture — three layers, and why it matters if you contribute

Read this before adding code. The package has three concerns, and they are independent. Most well-meaning changes that damage it do so by collapsing two of them.

| layer | knows about | must not know about | | --- | --- | --- | | 1 · Core | rows, marks as a concept, selection state, the cross-filter protocol, option resolution | any charting library, any host | | 2 · Renderer adapter | how one library expresses a binding, and where its marks live | the host it is running in | | 3 · Host glue | one host's lifecycle, identity model and settings | a library's internals |

Layer 3 is not in this package — it is the integrator's. createChartHost is the glue for a plain web page; the BIC Power BI visual has its own, because a report host's lifecycle is genuinely different. That separation is deliberate: merging them would drag host concepts into a package whose entire value is not having them.

Layer 2 is the one that is easy to get wrong, because today only one adapter exists (D3) and its assumptions are easy to mistake for universal truths. They are not. Two examples from charts BIC already generates:

  • Marks are not always DOM elements. D3 tags them in the DOM (.d3-mark + data-row-idx). Other libraries carry the row binding in their data model instead, on the trace or point. An abstraction phrased as querySelectorAll(markClass) is D3's implementation wearing the costume of an interface.
  • A renderer may support no interaction at all. Some chart types render to a raster image — no DOM, no marks, nothing to hit-test. A null adapter has to be legal.

So: if you are adding support for another charting library, you should be implementing layer 2 and touching nothing else. If your change needs edits in the core to make one library work, the seam is in the wrong place — please open an issue rather than widening the core.

Known wart, and it is ours, not yours. contract.ts currently mixes layers 1 and 2: MARK_CLASS ("d3-mark") and ROW_IDX_ATTR are D3's binding mechanism, while XFILTER_REFRESH_EVENT, the lch-* selection classes and DIM_OPACITY_VAR are genuinely renderer-neutral. The names are historical. Do not take the file's current shape as licence to add more renderer-specific constants to it.

Versioning

HOST_CONTRACT_VERSION is the grammar version (mark classes, container slots, the cross-filter event). generate_chart stamps it into every artifact it writes; this package warns if it runs a chart from a different major. Package SemVer tracks the JS API; the contract version tracks the DOM/interaction grammar, and they move independently.

Licence and attribution

Apache-2.0 (see LICENSE).

The package embeds geographic reference data, and one source carries a condition, not a courtesy: city and administrative-region coordinates come from GeoNames under CC BY 4.0, which requires attribution to travel with any redistribution — including an application that bundles this package. The credit is in NOTICE and in a banner comment at the top of every built file, so keeping either satisfies it. Country polygons (Natural Earth) and US state/ZIP boundaries (US Census/TIGER) are public domain.

If your build strips comments and you do not ship NOTICE, reproduce this line somewhere a recipient can find it:

This work includes data from GeoNames (https://www.geonames.org/), licensed under CC BY 4.0.

Building from source

npm install && npm run build     # esbuild bundle (+ .d.ts) into dist/
npm test

The build inlines @bicharts/shape-core, which lives outside this package root — that is deliberate, so the repo keeps one copy of the geo detection logic and the published artifact still stands alone. npm pack rebuilds first (prepack); do not publish a stale dist/.