SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
3.0 KB · 77 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import { Bar, BarChart, CartesianGrid, Cell, LabelList, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";4import { AXIS_TICK, CHART_COLORS } from "./chart-theme";5import { ChartTooltip } from "./chart-tooltip";67export type HorizontalBarFormat = "number" | "percent" | "ms" | "usd";89const FORMATTERS: Record<HorizontalBarFormat, (v: number) => string> = {10  number: (v) => v.toLocaleString("en-US"),11  percent: (v) => `${v.toFixed(1)}%`,12  ms: (v) => (v < 1000 ? `${Math.round(v)} ms` : `${(v / 1000).toFixed(2)} s`),13  usd: (v) => (v < 0.01 && v > 0 ? `$${v.toFixed(4)}` : `$${v.toFixed(2)}`),14};1516export interface HorizontalBarDatum {17  label: string;18  value: number;19  /** Optional per-entity color (defaults to accent). */20  color?: string;21  /** Optional secondary text shown in the tooltip (e.g. request count). */22  hint?: string;23}2425/**26 * Single-series horizontal bars (one color for all bars unless the entity has a fixed color).27 * Used for success rate by network and error-code breakdowns.28 */29export function HorizontalBarChart({30  data,31  color = CHART_COLORS.accent,32  max,33  format = "number",34  rowHeight = 34,35  labelWidth = 120,36}: {37  data: HorizontalBarDatum[];38  color?: string;39  max?: number;40  /** Serializable formatter key (server components cannot pass functions to client components). */41  format?: HorizontalBarFormat;42  rowHeight?: number;43  labelWidth?: number;44}) {45  const valueFormatter = FORMATTERS[format];46  const height = Math.max(rowHeight * Math.max(data.length, 1) + 16, 80);47  return (48    <ResponsiveContainer width="100%" height={height}>49      <BarChart data={data} layout="vertical" margin={{ top: 4, right: 56, bottom: 4, left: 0 }} barCategoryGap="28%">50        <CartesianGrid horizontal={false} stroke={CHART_COLORS.grid} />51        <XAxis type="number" hide domain={[0, max ?? "auto"]} />52        <YAxis type="category" dataKey="label" width={labelWidth} tick={{ ...AXIS_TICK, fill: "var(--fg-muted)", fontSize: 12 }} axisLine={false} tickLine={false} interval={0} />53        <Tooltip54          cursor={{ fill: CHART_COLORS.cursor, opacity: 0.6 }}55          content={56            <ChartTooltip57              labelFormatter={(l) => String(l)}58              valueFormatter={(v) => valueFormatter(v)}59              names={{ value: "Value" }}60              footer={(p) => {61                const hint = (p[0]?.payload as HorizontalBarDatum | undefined)?.hint;62                return hint ? <span>{hint}</span> : null;63              }}64            />65          }66        />67        <Bar dataKey="value" radius={[0, 3, 3, 0]} maxBarSize={18} isAnimationActive={false} background={{ fill: "var(--bg-muted)", radius: 3 }}>68          {data.map((d) => (69            <Cell key={d.label} fill={d.color ?? color} />70          ))}71          <LabelList dataKey="value" position="right" formatter={(v: unknown) => valueFormatter(Number(v))} style={{ fill: "var(--fg-muted)", fontSize: 11.5, fontFamily: "var(--font-mono)" }} />72        </Bar>73      </BarChart>74    </ResponsiveContainer>75  );76}77