@bitbaum/chartkit

中文文档
0.7.0发布于 2026/10/30MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 4431

One chart for every product: token-driven SVG charts that render on the server, read as a table, and never cut a label — line, ranked bars, share bars, sparkline, meter, timeline, stat tiles.

原始使用文档
文档目录7 节
  1. Install
  2. Use
  3. What every chart guarantees
  4. The audit
  5. Develop
  6. App language
  7. Reference intervals, stacking and proportions

chartkit

One chart for every bitbaum product. A line, ranked bars, a share bar, a sparkline, a meter, a timeline and a strip of figures — drawn on the server from the app's own tokens, each with a table behind it and no label that clips, overlaps or shrinks — so a fix made once reaches every product.

Why it exists: on 2026-10-23 the owner said visualisations are "super important", a profile must be "more useful than Bloomberg", and "it's packages what makes us special". Substrata had one hand-rolled line chart and nothing else; every other product had none. This is the one place charts now live. (Colour and type come from design-tokens; chartkit is the drawing.)

Install

pnpm add @bitbaum/chartkit@^0.7.0

Install releases from npm, never a commit or a branch, for the reason chatkit's README gives: a semver range is what lets a fix reach every product.

Use

import { LineChart, BarChart, ColumnChart, ShareBars, Sparkline, Meter, Timeline, StatTiles } from "@bitbaum/chartkit/react";
import "@bitbaum/chartkit/styles.css";

<LineChart
  series={[{ id: "backlog", label: "Backlog", points: [{ x: "2025-Q1", y: 29, href: "…" }] }]}
  unit="GW"
  caption="GE Vernova Gas Power equipment backlog, GW, by quarter."
  yFrom="zero"                       // or "data"; left out, zero only when the data sits near it
  height={208}                       // CSS px; the width is the container's
/>
<LineChart                           // a year of daily closes: a clean line with a light fill
  series={[{ id: "close", label: "Close", points: closes }]}
  unit="USD"
  yFrom="data"
  markers="auto"                     // "all" (default) | "none" | "ends" | "auto"
  area                               // faint fill under the line, in the series' token colour
  caption="NVIDIA daily closes, USD, last 12 months."
/>
<BarChart rows={[{ label: "China", value: 900000, share: 0.99, estimated: true }, { label: "Other", value: null }]} unit="kg" caption="…" highlight="China" />
<ShareBars rows={[{ label: "BlackRock", share: 0.074 }]} other={0.926} otherLabel="Others, each below 5%" caption="…" />
<ColumnChart series={[{ id: "rev", label: "Revenue", points: [{ x: "FY2024", y: 61 }, { x: "FY2025", y: -4, href: "…" }] }]} unit="USD bn" caption="…" />
<Sparkline points={points} label="Backlog over six quarters" />
<Sparkline                           // a null breaks the line; a tone colours that point's dot
  points={[{ x: "2026-10-01", y: 4, tone: "positive" }, { x: "2026-10-02", y: null }, { x: "2026-10-03", y: 1, tone: "negative" }]}
  label="Mood over three days: one good, one unrecorded, one bad"
/>
<Meter value={12} max={12} label="Severity" href="/methods/severity" />
<Meter value={3} max={10} label="Risk" tone="negative" showValue={false} />  // the caller prints "3/10"
<Timeline events={[{ date: "2026-07-17", label: "…", tone: "down", href: "…" }]} caption="…" />
<StatTiles tiles={[{ label: "Bottlenecks mapped", value: 33, href: "/bottlenecks" }]} />

x is an ISO period (2025, 2025-Q2, 2025-03, 2025-03-31) or a number. Several series share one axis — there is no second y-axis and never will be; two measures of different scale are two charts. BarChart defaults to ranking its rows (largest first, no number last) whatever order they arrive in, draws a null as "—" and an estimate with "≈". ShareBars names its remainder otherLabel (left out, "Other") — say what the rest is, never a bare "Other 87%". ColumnChart draws periods left to right in the order given (fiscal labels are not re-sorted), several series side by side, a loss below the zero line, and a missing figure as no column and "—" in the table. Every point of a LineChart and every event of a Timeline is an anchor when it has an href, with its value, period and note on hover and on keyboard focus.

A dated LineChart picks its ticks for whatever span it is given: days for a week of data, Mondays for a month, months for a year, years for decades — at most five, the coarsest step that still gives three, the first label and the first of each new year naming the year. An axis is never left bare: a span no step ticks inside is labelled at its two ends.

ColumnChart prints every period label whole. When there are more periods than the plot has room for, every second (third, …) label steps back so the rest fit, and nothing is cut: "2015 2017 2019 …" on a phone, every year on a desktop. The step is planned on the server for bands of plot width and applied by a container query on the axis (no measurement, no effect); the table behind the chart still holds every period. Only a label wider than the whole plot is ever shortened.

markers says which values of a LineChart carry a dot: all (the default, as before) every one; none the line alone; ends only each series' last value; auto every one up to 60 points on a series and none above — 250 daily closes drawn as 250 beads is a string of pearls, not a price line. A value whose dot is hidden is still there: its target is a full-height strip of the plot (the strips tile it, so a pointer or a finger anywhere over the line finds the nearest value), and hover or keyboard focus shows its dot, a hairline cursor and the value. The table behind the chart holds every value either way. area adds a light fill under each line in that series' own --ck-series-* colour — never a literal.

Meter takes tone — accent (the default), neutral, positive, warning or negative, each a --ck-* token — and showValue={false} to draw the track alone when the caller already prints the figure; the value stays in the meter's role (aria-valuenow), name and title. A Sparkline point may be y: null, which breaks the line there instead of drawing a zero (a value alone between two gaps is a dot), and may carry a tone (positive, warning, negative, neutral) that colours its dot. Colour is never the only sign: say in the sparkline's label what the tones mean.

What every chart guarantees

  • It renders on the server. Pure SVG and HTML, no effect, no measurement. The drawing is an SVG stretched to its box (preserveAspectRatio="none", non-scaling strokes); every label is HTML in rem laid over it in per cent, so a 320px plot has the same type as a 1440px one.
  • Nothing is reachable only by pointing. Each chart is a <figure class="ck-chart"> with its caption and a visually hidden <table> of the same values; the decorative parts are aria-hidden, the anchors are not.
  • No label clips or overlaps. Long names truncate with an ellipsis and carry the full text in title; column periods thin out instead, so a year or a month is never cut; bar labels have a least column; timeline events that would collide take a second lane; the y gutter is as wide as its widest number. @bitbaum/chartkit/audit measures it (below).
  • Colour and type come only from --ck-* variables, which default to design-tokens and can be set once on :root: --ck-ink, --ck-ink-muted, --ck-line, --ck-bg, --ck-accent, --ck-up, --ck-down, --ck-series-1 … -6, --ck-font, --ck-mono. Series are by index, never cycled, and each after the first has a dash as well as a colour, so the set survives a monochrome theme and a print. The prefix is shared with chatkit on purpose: --ck-line, --ck-accent, --ck-bg, --ck-font and --ck-mono mean the same in both, so an app that themed its chat has themed its charts. Light and dark both work by token.

The audit

import { CHART_DEFECTS_SOURCE } from "@bitbaum/chartkit/audit";
const defects = await page.evaluate(`(${CHART_DEFECTS_SOURCE})(document.body)`);
if (defects.length) throw new Error(defects.join("\n"));

findChartDefects(root) runs inside the page and returns every piece of chart text whose box leaves its chart or overlaps another piece of text in the same chart, one line each. Reading the CSS finds none of these; laying the words out with the real font at the real width does. test/browser.mjs runs it over the demo at 320, 390 and 1280px in light and dark, and first over a deliberately broken figure, to prove the gate can fail.

Develop

pnpm install
pnpm run verify       # format:check, lint, typecheck, build, test, test:browser
pnpm run demo         # builds demo/dist; open demo/index.html

test:browser needs Chromium: pnpm exec playwright install chromium, or set CHROMIUM_PATH. dist/ is committed so a github: install needs no build step; pnpm run check:dist says whether it matches the source.

For proportions, pass the whole as max: <BarChart rows={[{ label: "Share", value: 1.6 }]} max={100} unit="%" caption="Share of the whole" /> draws a 1.6% bar. Without max, bars are relative to the largest row. The scale always expands to fit larger values; invalid or non-positive maxima fall back to the largest row. Accessible tables retain the original values.

App language

LineChart, ColumnChart and Timeline accept an explicit BCP 47 locale (such as de-CH); numbers, dates, axes, tooltips and accessible tables use it. Dates are formatted in UTC so a server timezone cannot move a reporting date. Omitting the locale preserves the existing English output. Pass translations from the app's dictionary with periodLabel on line/column charts, preliminaryLabel on line charts, and tableLabels: { date, event, effect } on timelines. These labels remain English unless supplied: a locale formats data, it does not guess product copy.

<LineChart series={series} caption="Umsatz" locale="de-CH"
  periodLabel="Zeitraum" preliminaryLabel="vorläufig" />

Reference intervals, stacking and proportions

LineChart accepts referenceBands={[{ from: 10, to: 20, label: "Expected interval" }]}. The app owns the interval, label and unit; the chart extends its scale to include the bounds and exposes the exact interval as text. Invalid/reversed bounds fail rather than silently presenting a different interval. This is a display feature, not a source of clinical ranges or medical advice.

ColumnChart stacked stacks positives upward and negatives downward from zero. The axis includes accumulated totals; the table keeps each original value, including the distinction between missing and zero. The default remains grouped.

DoughnutChart takes rows: { label, value, href? }[], caption, unit?, locale?, labelHeading? and valueHeading?. It renders a proportional ring, linked legend and exact-value accessible table. Values must be finite and non-negative. An empty/zero total draws no invented shares. Colours use app tokens; legend links remain keyboard reachable without depending on a slice.

For ordinal scales, use order="input" to preserve the row sequence. Optional row tone values (positive, warning, negative) use theme colors rather than a hardcoded palette. Tone takes precedence over highlight color; keep meaningful text labels so color is never the only indication.

BarChart also accepts locale for visual and accessible number formatting, and labels (row, value, share, note, estimated) for table headings and estimate text. Defaults preserve existing English ranked charts. For a satisfaction distribution, pass the total as max, ordinal rows and translated scale labels; each bar then shows its share of all responses.

版本与发布记录

@bitbaum/chartkit 共发布 8 个版本,首次发布于 2026/10/26;过去 12 个月发布了 8 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.7.0latest2026/10/309 天前
0.6.02026/10/2910 天前
0.5.02026/10/2811 天前
0.4.02026/10/2811 天前
0.3.02026/10/2711 天前
0.2.02026/10/2712 天前

关于 @bitbaum/chartkit 的常见问题

@bitbaum/chartkit 是什么?

One chart for every product: token-driven SVG charts that render on the server, read as a table, and never cut a label — line, ranked bars, share bars, sparkline, meter, timeline, stat tiles.

如何安装 @bitbaum/chartkit?

在项目目录运行 npm install @bitbaum/chartkit,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@bitbaum/chartkit 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bitbaum/chartkit 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@bitbaum/chartkit 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@bitbaum/chartkit 可以商用吗?

@bitbaum/chartkit 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bitbaum/chartkit 的下载量有多少?

根据 npm 官方下载统计,@bitbaum/chartkit 在 2026/10/08 – 2026/11/06 这 30 天内被下载 4,431 次,日均约 148 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bitbaum/chartkit 还在维护吗?

最新版本 0.7.0 发布于 2026/10/30(9 天前);至今共发布 8 个版本,其中过去 12 个月发布了 8 个;首次发布于 2026/10/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。