SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%
1.9 KB · 48 lines tsx
Raw Blame History
1import { fmtValue } from '@/lib/format';23export interface BarDatum {4  label: string;5  value: number;6  href?: string;7  muted?: boolean;8}910/**11 * Horizontal bar chart in pure SVG (no chart library). Single hue; labels carry the meaning.12 * Values are formatted with the metric unit; bars never start anywhere but zero.13 */14export function BarChart({ data, unit, maxBars = 15, ariaLabel }: { data: BarDatum[]; unit?: string | null; maxBars?: number; ariaLabel: string }) {15  const rows = data.slice(0, maxBars);16  if (rows.length === 0) return null;17  const max = Math.max(...rows.map((r) => Math.abs(r.value)), Number.EPSILON);18  const rowH = 22;19  const labelW = 190;20  const valueW = 74;21  const width = 640;22  const barW = width - labelW - valueW - 8;23  const height = rows.length * rowH + 4;24  return (25    <figure className="w-full overflow-x-auto">26      <svg viewBox={`0 0 ${width} ${height}`} width="100%" role="img" aria-label={ariaLabel} className="block min-w-[480px] text-ink" style={{ maxHeight: height }}>27        <title>{ariaLabel}</title>28        {rows.map((r, i) => {29          const y = i * rowH + 2;30          const w = Math.max(1, (Math.abs(r.value) / max) * barW);31          const fill = r.muted ? 'var(--color-ink-4)' : 'var(--color-accent)';32          return (33            <g key={`${r.label}-${i}`}>34              <text x={labelW - 8} y={y + rowH / 2 + 4} textAnchor="end" fontSize="12" fill="var(--color-ink-2)">35                {r.label.length > 30 ? `${r.label.slice(0, 29)}…` : r.label}36              </text>37              <rect x={labelW} y={y + 4} width={w} height={rowH - 8} fill={fill} />38              <text x={labelW + w + 6} y={y + rowH / 2 + 4} fontSize="12" fill="var(--color-ink)" style={{ fontVariantNumeric: 'tabular-nums' }}>39                {fmtValue(r.value, unit)}40              </text>41            </g>42          );43        })}44      </svg>45    </figure>46  );47}48