Skip to content
Neumorphism UI
DocsStylingChartsTemplates
—
EN
English한국어简体中文日本語

Charts

Compare revenue, conversion, build times and service latency. Each chart includes its underlying data table.

Product analyticsOperations & developmentInstallation reference

Product analytics

Compare revenue, channels and conversion over the same selected period.

Illustrative data · September 2026 · USD. No live service is connected.

Revenue against target

Daily revenue, displayed as currency. The dashed line is the target.

Revenue: $60,644.00 · Target: $75,810.00

View exact data
Revenue against target
DateRevenueTarget
2026-09-17$3,682.00$5,064.00
2026-09-18$3,842.00$5,118.00
2026-09-19$4,292.00$5,172.00
2026-09-20$4,542.00$5,226.00
2026-09-21$3,640.00$5,280.00
2026-09-22$4,040.00$5,334.00
2026-09-23$4,142.00$5,388.00
2026-09-24$4,642.00$5,442.00
2026-09-25$4,842.00$5,496.00
2026-09-26$3,890.00$5,550.00
2026-09-27$4,340.00$5,604.00
2026-09-28$4,550.00$5,658.00
2026-09-29$5,000.00$5,712.00
2026-09-30$5,200.00$5,766.00

Acquisition mix

Channel totals in the selected period. Every bar starts at zero.

Visits: 23,778

View exact data
Acquisition mix
Acquisition channelVisitsConverted visitsConversion rate
Organic9,4703864.08%
Direct8,0114205.24%
Referral6,2974046.42%

Visit conversion

Converted visits divided by visits. The vertical scale stays at 0–100%.

5.09% · 1,210 / 23,778

View exact data
Visit conversion
DateVisitsConverted visitsConversion rate
2026-09-171,557734.69%
2026-09-181,547774.98%
2026-09-191,622865.3%
2026-09-201,612915.65%
2026-09-211,602724.49%
2026-09-221,677804.77%
2026-09-231,667834.98%
2026-09-241,742935.34%
2026-09-251,732975.6%
2026-09-261,722774.47%
2026-09-271,797864.79%
2026-09-281,787915.09%
2026-09-291,8621005.37%
2026-09-301,8521045.62%

Missing days are gaps, not zero. A zero-visit conversion rate is undefined (—).

Operational charts

Separate operational signals from product analytics: builds, latency, releases, delivery and install traces.

Build duration

Paired cold and cached build runs in seconds.

Mean cold 210.2 s · cached 103.7 s · 2/6 cold runs over budget

View build data
Build duration data
BuildCold (s)Cached (s)Saved (s)
B1210108102
B219510293
B3238114124
B4226109117
B520496108
B61889395

Service latency

Daily p50 and p95 response time against a p95 budget.

Worst p95 470 ms · 3/7 periods over budget

View latency data
Service latency data
Periodp50 (ms)p95 (ms)Budget
Mon80210Within
Tue76195Within
Wed95340Over
Thu130470Over
Fri100330Over
Sat82240Within
Sun79215Within

Release activity

New installs and updates by release. Counts are events, not unique users.

Total activity 2,350 · Update share 32.8%

View release data
Release activity data
ReleaseNew installsUpdatesUpdate share
R11486229.5%
R223210431.0%
R31968831.0%
R428413231.7%
R534117634.0%
R637820935.6%

Delivery capacity

Planned and delivered item counts. This is not a productivity score.

Planned 140 · Delivered 140 · Net variance +0

View delivery data
Delivery capacity data
PeriodPlannedDeliveredVariance
S13228-4
S23639+3
S33431-3
S43842+4

Installation diagnostics

Sequential stage durations for one installation trace.

Total 1260 ms · Longest stage Download (840 ms)

View installation data
Installation diagnostic data
StageDurationShare
Resolve120 ms9.5%
Download840 ms66.7%
Transform210 ms16.7%
Write90 ms7.1%

Installation reference

Install an individual chart, then expand its usage example or installed source.

Product analytics

Revenue

Daily revenue and the selected reporting period.

@neumorphism-ui/chart-revenue
Usage
"use client";
import { RevenueChart } from "@/components/blocks/revenue-chart";
import { summarizeAnalytics, type AnalyticsRecord } from "@/lib/analytics-model";

export function Example({ records }: { records: AnalyticsRecord[] }) {
  return <RevenueChart data={summarizeAnalytics(records, 14)} locale="en" />;
}
Source
"use client";

import * as React from "react";
import { Area, CartesianGrid, ComposedChart, Line, XAxis, YAxis } from "recharts";
import { ChartContainer, ChartTooltip } from "@/components/ui/chart";
import { Checkbox } from "@/components/ui/checkbox";
import type { AnalyticsSummary } from "@/lib/analytics-model";
import { analyticsCopy, analyticsFormats, type AnalyticsLocale } from "@/lib/analytics-copy";

export function RevenueChart({ data, locale = "en", currency = "USD" }: {
  data: AnalyticsSummary; locale?: AnalyticsLocale; currency?: string;
}) {
  const [showTarget, setShowTarget] = React.useState(true);
  const t = analyticsCopy[locale];
  const f = analyticsFormats(locale, currency);
  return <ChartContainer
    data-chart="revenue" title={t.revenueTitle} description={t.revenueBody}
    summary={data.observedDays ? <>{t.revenue}: {f.money(data.totals.revenueCents)} · {t.target}: {f.money(data.totals.targetCents)}</> : t.empty}
    actions={<label className="inline-flex items-center gap-2 text-xs"><Checkbox checked={showTarget} onCheckedChange={value => setShowTarget(value === true)} />{t.targetToggle}</label>}
    tableLabel={t.table} empty={data.observedDays === 0} emptyLabel={t.empty}
    table={<table><caption className="sr-only">{t.revenueTitle}</caption><thead><tr><th scope="col">{t.date}</th><th scope="col">{t.revenue}</th><th scope="col">{t.target}</th></tr></thead><tbody>{data.points.map(point => <tr key={point.date}><th scope="row">{point.date}</th><td>{f.money(point.revenueCents)}</td><td>{f.money(point.targetCents)}</td></tr>)}</tbody></table>}
  >
    <ComposedChart data={data.points} accessibilityLayer margin={{ top: 12, right: 12, bottom: 4, left: 0 }}>
      <CartesianGrid vertical={false} stroke="var(--border)" strokeDasharray="3 4" />
      <XAxis dataKey="date" tickFormatter={(value: string) => value.slice(5)} minTickGap={28} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
      <YAxis domain={[0, "auto"]} width={72} tickFormatter={(value: number) => f.compactMoney(value)} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
      <ChartTooltip formatter={value => f.money(typeof value === "number" ? value : null)} />
      <Area type="linear" dataKey="revenueCents" name={t.revenue} stroke="var(--primary)" fill="var(--primary)" fillOpacity={0.1} strokeWidth={2.5} connectNulls={false} isAnimationActive={false} />
      {showTarget && <Line type="linear" dataKey="targetCents" name={t.target} stroke="var(--muted-foreground)" strokeWidth={2} strokeDasharray="6 4" dot={false} connectNulls={false} isAnimationActive={false} />}
    </ComposedChart>
  </ChartContainer>;
}
Product analytics

Channels

Visits and conversions by channel.

@neumorphism-ui/chart-channel
Usage
"use client";
import { ChannelChart } from "@/components/blocks/channel-chart";
import { summarizeAnalytics, type AnalyticsRecord } from "@/lib/analytics-model";

export function Example({ records }: { records: AnalyticsRecord[] }) {
  return <ChannelChart data={summarizeAnalytics(records, 14)} locale="en" />;
}
Source
"use client";

import * as React from "react";
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
import { ChartContainer, ChartTooltip } from "@/components/ui/chart";
import { Select, SelectItem } from "@/components/ui/select";
import type { AnalyticsSummary } from "@/lib/analytics-model";
import { analyticsCopy, analyticsFormats, type AnalyticsLocale } from "@/lib/analytics-copy";

export function ChannelChart({ data, locale = "en" }: { data: AnalyticsSummary; locale?: AnalyticsLocale }) {
  const [metric, setMetric] = React.useState<"visits" | "convertedVisits">("visits");
  const t = analyticsCopy[locale];
  const f = analyticsFormats(locale);
  const label = metric === "visits" ? t.visits : t.conversions;
  return <ChartContainer
    data-chart="channels" title={t.channelsTitle} description={t.channelsBody}
    summary={data.observedDays ? <>{label}: {f.number(data.totals[metric])}</> : t.empty}
    actions={<label className="grid max-w-full gap-1.5 text-xs">{t.metric}<Select value={metric} onChange={event => setMetric(event.target.value === "visits" ? "visits" : "convertedVisits")}><SelectItem value="visits">{t.visits}</SelectItem><SelectItem value="convertedVisits">{t.conversions}</SelectItem></Select></label>}
    tableLabel={t.table} empty={!data.channels.length} emptyLabel={t.empty}
    table={<table><caption className="sr-only">{t.channelsTitle}</caption><thead><tr><th scope="col">{t.channel}</th><th scope="col">{t.visits}</th><th scope="col">{t.conversions}</th><th scope="col">{t.conversion}</th></tr></thead><tbody>{data.channels.map(group => <tr key={group.channel}><th scope="row">{group.channel}</th><td>{f.number(group.visits)}</td><td>{f.number(group.convertedVisits)}</td><td>{f.percent(group.conversion)}</td></tr>)}</tbody></table>}
  >
    <BarChart data={data.channels} accessibilityLayer margin={{ top: 12, right: 12, bottom: 4, left: 0 }}>
      <CartesianGrid vertical={false} stroke="var(--border)" strokeDasharray="3 4" />
      <XAxis tickFormatter={(value: string) => value.length > 14 ? `${value.slice(0, 13)}…` : value} dataKey="channel" minTickGap={16} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
      <YAxis domain={[0, "auto"]} width={52} allowDecimals={false} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
      <ChartTooltip formatter={value => f.number(typeof value === "number" ? value : null)} />
      <Bar dataKey={metric} name={label} fill="var(--primary)" radius={[4, 4, 0, 0]} maxBarSize={56} isAnimationActive={false} />
    </BarChart>
  </ChartContainer>;
}
Product analytics

Conversion

Daily conversion rates; missing values are not shown as zero.

@neumorphism-ui/chart-conversion
Usage
"use client";
import { ConversionChart } from "@/components/blocks/conversion-chart";
import { summarizeAnalytics, type AnalyticsRecord } from "@/lib/analytics-model";

export function Example({ records }: { records: AnalyticsRecord[] }) {
  return <ConversionChart data={summarizeAnalytics(records, 14)} locale="en" />;
}
Source
"use client";

import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
import { ChartContainer, ChartTooltip } from "@/components/ui/chart";
import type { AnalyticsSummary } from "@/lib/analytics-model";
import { analyticsCopy, analyticsFormats, type AnalyticsLocale } from "@/lib/analytics-copy";

export function ConversionChart({ data, locale = "en" }: { data: AnalyticsSummary; locale?: AnalyticsLocale }) {
  const t = analyticsCopy[locale];
  const f = analyticsFormats(locale);
  return <ChartContainer
    data-chart="conversion" title={t.conversionTitle} description={t.conversionBody}
    summary={data.observedDays ? <>{f.percent(data.conversion)} · {f.number(data.totals.convertedVisits)} / {f.number(data.totals.visits)}</> : t.empty}
    tableLabel={t.table} empty={!data.observedDays} emptyLabel={t.empty}
    table={<table><caption className="sr-only">{t.conversionTitle}</caption><thead><tr><th scope="col">{t.date}</th><th scope="col">{t.visits}</th><th scope="col">{t.conversions}</th><th scope="col">{t.conversion}</th></tr></thead><tbody>{data.points.map(point => <tr key={point.date}><th scope="row">{point.date}</th><td>{f.number(point.visits)}</td><td>{f.number(point.convertedVisits)}</td><td>{f.percent(point.conversion)}</td></tr>)}</tbody></table>}
  >
    <LineChart data={data.points} accessibilityLayer margin={{ top: 12, right: 12, bottom: 4, left: 0 }}>
      <CartesianGrid vertical={false} stroke="var(--border)" strokeDasharray="3 4" />
      <XAxis dataKey="date" tickFormatter={(value: string) => value.slice(5)} minTickGap={28} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
      <YAxis domain={[0, 1]} ticks={[0, 0.25, 0.5, 0.75, 1]} width={48} tickFormatter={(value: number) => `${value * 100}%`} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
      <ChartTooltip formatter={value => f.percent(typeof value === "number" ? value : null)} />
      <Line type="linear" dataKey="conversion" name={t.conversion} stroke="var(--primary)" strokeWidth={2.5} dot={{ r: 2 }} activeDot={{ r: 5 }} connectNulls={false} isAnimationActive={false} />
    </LineChart>
  </ChartContainer>;
}
Operations & development

Build duration

Cold and cached builds compared with a time budget.

@neumorphism-ui/chart-build-duration
Usage
"use client";
import { BuildDurationChart, type BuildDurationPoint } from "@/components/blocks/build-duration-chart";

const data: BuildDurationPoint[] = [
  { label: "B1", coldSeconds: 210, cachedSeconds: 108 },
];

export function Example() {
  return <BuildDurationChart data={data} locale="en" />;
}
Source
"use client";

import * as React from "react";
import { CartesianGrid, Line, LineChart, ReferenceLine, XAxis, YAxis } from "recharts";

import { ChartContainer, ChartTooltip, ChartLegend } from "@/components/ui/chart";
import { Select, SelectItem } from "@/components/ui/select";

const copy = {
  "en": {
    "title": "Build duration",
    "description": "Paired cold and cached build runs in seconds.",
    "meanCold": "Mean cold",
    "cached": "cached",
    "overBudget": "cold runs over budget",
    "empty": "No build runs.",
    "series": "Visible series",
    "both": "Both",
    "coldOnly": "Cold only",
    "cachedOnly": "Cached only",
    "table": "View build data",
    "caption": "Build duration data",
    "period": "Build",
    "cold": "Cold",
    "cachedHead": "Cached",
    "saved": "Saved",
    "coldBuild": "Cold build",
    "cachedBuild": "Cached build",
    "budget": "Budget"
  },
  "ko": {
    "title": "빌드 시간",
    "description": "캐시를 사용하지 않은 빌드와 캐시를 사용한 빌드의 소요 시간(초)입니다.",
    "meanCold": "캐시 미사용 평균",
    "cached": "캐시 사용 평균",
    "overBudget": "캐시 미사용 빌드가 목표 초과",
    "empty": "빌드 기록이 없습니다.",
    "series": "표시할 계열",
    "both": "모두",
    "coldOnly": "캐시 미사용",
    "cachedOnly": "캐시 사용",
    "table": "빌드 데이터 보기",
    "caption": "빌드 시간 데이터",
    "period": "빌드",
    "cold": "캐시 미사용",
    "cachedHead": "캐시 사용",
    "saved": "단축 시간",
    "coldBuild": "캐시 미사용",
    "cachedBuild": "캐시 사용",
    "budget": "목표"
  },
  "ja": {
    "title": "ビルド時間",
    "description": "キャッシュ未使用・使用時のビルド所要時間(秒)です。",
    "meanCold": "キャッシュ未使用の平均",
    "cached": "使用時の平均",
    "overBudget": "未使用ビルドが目標超過",
    "empty": "ビルド記録はありません。",
    "series": "表示する系列",
    "both": "両方",
    "coldOnly": "キャッシュ未使用",
    "cachedOnly": "キャッシュ使用",
    "table": "ビルドデータを表示",
    "caption": "ビルド時間データ",
    "period": "ビルド",
    "cold": "未使用",
    "cachedHead": "使用",
    "saved": "短縮時間",
    "coldBuild": "キャッシュ未使用",
    "cachedBuild": "キャッシュ使用",
    "budget": "目標"
  },
  "zh": {
    "title": "构建耗时",
    "description": "比较未使用缓存和使用缓存的构建耗时,单位为秒。",
    "meanCold": "未使用缓存平均",
    "cached": "使用缓存平均",
    "overBudget": "次无缓存构建超出目标",
    "empty": "暂无构建记录。",
    "series": "显示系列",
    "both": "全部",
    "coldOnly": "未使用缓存",
    "cachedOnly": "使用缓存",
    "table": "查看构建数据",
    "caption": "构建耗时数据",
    "period": "构建",
    "cold": "未使用缓存",
    "cachedHead": "使用缓存",
    "saved": "节省时间",
    "coldBuild": "未使用缓存",
    "cachedBuild": "使用缓存",
    "budget": "目标"
  }
} as const;

export type BuildDurationPoint = {
  label: string;
  coldSeconds: number;
  cachedSeconds: number;
};

export function BuildDurationChart({
  data,
  budgetSeconds = 220,
  locale = "en",
}: {
  data: readonly BuildDurationPoint[];
  budgetSeconds?: number;
  locale?: keyof typeof copy;
}) {
  const t = copy[locale];
  const number = new Intl.NumberFormat(locale, { maximumFractionDigits: 1 });
  const [series, setSeries] = React.useState<"both" | "cold" | "cached">("both");
  const coldMean = data.length ? data.reduce((sum, row) => sum + row.coldSeconds, 0) / data.length : 0;
  const cachedMean = data.length ? data.reduce((sum, row) => sum + row.cachedSeconds, 0) / data.length : 0;
  const breaches = data.filter((row) => row.coldSeconds > budgetSeconds).length;

  return (
    <ChartContainer
      data-chart="build-duration"
      title={t.title}
      description={t.description}
      summary={data.length ? `${t.meanCold} ${number.format(coldMean)} s · ${t.cached} ${number.format(cachedMean)} s · ${breaches}/${data.length} ${t.overBudget}` : t.empty}
      actions={
        <label className="grid gap-1.5 text-xs">
          {t.series}
          <Select value={series} onChange={(event) => setSeries(event.target.value as typeof series)}>
            <SelectItem value="both">{t.both}</SelectItem>
            <SelectItem value="cold">{t.coldOnly}</SelectItem>
            <SelectItem value="cached">{t.cachedOnly}</SelectItem>
          </Select>
        </label>
      }
      tableLabel={t.table}
      emptyLabel={t.empty}
      empty={!data.length}
      table={
        <table>
          <caption className="sr-only">{t.caption}</caption>
          <thead><tr><th>{t.period}</th><th>{t.cold} (s)</th><th>{t.cachedHead} (s)</th><th>{t.saved} (s)</th></tr></thead>
          <tbody>{data.map((row) => <tr key={row.label}><th scope="row">{row.label}</th><td>{row.coldSeconds}</td><td>{row.cachedSeconds}</td><td>{row.coldSeconds - row.cachedSeconds}</td></tr>)}</tbody>
        </table>
      }
    >
      <LineChart data={data} accessibilityLayer margin={{ top: 12, right: 12, bottom: 4, left: 0 }}>
        <CartesianGrid vertical={false} stroke="var(--border)" strokeDasharray="3 4" />
        <XAxis dataKey="label" tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <YAxis domain={[0, "auto"]} width={52} tickFormatter={(value: number) => `${value}s`} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <ChartLegend reference={{ value: `${t.budget} ${number.format(budgetSeconds)} s`, color: "var(--destructive)", dashArray: "4 4" }} />
        <ReferenceLine ifOverflow="extendDomain" y={budgetSeconds} stroke="var(--destructive)" strokeDasharray="4 4" />
        <ChartTooltip formatter={(value) => `${Number(value)} s`} />
        {series !== "cached" && <Line dataKey="coldSeconds" name={t.coldBuild} legendType="plainline" stroke="var(--foreground)" strokeWidth={2.5} dot={{ r: 3 }} isAnimationActive={false} />}
        {series !== "cold" && <Line dataKey="cachedSeconds" name={t.cachedBuild} legendType="plainline" stroke="var(--primary)" strokeWidth={2.5} strokeDasharray="7 4" dot={{ r: 3 }} isAnimationActive={false} />}
      </LineChart>
    </ChartContainer>
  );
}

const exampleBuilds: BuildDurationPoint[] = [
  { label: "B1", coldSeconds: 210, cachedSeconds: 108 },
  { label: "B2", coldSeconds: 195, cachedSeconds: 102 },
  { label: "B3", coldSeconds: 238, cachedSeconds: 114 },
  { label: "B4", coldSeconds: 226, cachedSeconds: 109 },
  { label: "B5", coldSeconds: 204, cachedSeconds: 96 },
  { label: "B6", coldSeconds: 188, cachedSeconds: 93 },
];

export function BuildDurationExample({ locale = "en" }: { locale?: keyof typeof copy }) {
  return <BuildDurationChart data={exampleBuilds} locale={locale} />;
}
Operations & development

Service latency

p50 and p95 response times compared with a p95 budget.

@neumorphism-ui/chart-service-latency
Usage
"use client";
import { ServiceLatencyChart, type ServiceLatencyPoint } from "@/components/blocks/service-latency-chart";

const data: ServiceLatencyPoint[] = [
  { label: "Mon", p50Ms: 80, p95Ms: 210 },
];

export function Example() {
  return <ServiceLatencyChart data={data} locale="en" />;
}
Source
"use client";

import { CartesianGrid, Line, LineChart, ReferenceLine, XAxis, YAxis } from "recharts";

import { ChartContainer, ChartTooltip, ChartLegend } from "@/components/ui/chart";

const copy = {
  "en": {
    "title": "Service latency",
    "description": "Daily p50 and p95 response time against a p95 budget.",
    "worst": "Worst p95",
    "overBudget": "periods over budget",
    "empty": "No latency samples.",
    "table": "View latency data",
    "caption": "Service latency data",
    "period": "Period",
    "budget": "Budget",
    "over": "Over",
    "within": "Within",
    "days": [
      "Mon",
      "Tue",
      "Wed",
      "Thu",
      "Fri",
      "Sat",
      "Sun"
    ]
  },
  "ko": {
    "title": "서비스 지연 시간",
    "description": "일별 p50·p95 응답 시간과 p95 목표값을 비교합니다.",
    "worst": "최대 p95",
    "overBudget": "기간에서 목표 초과",
    "empty": "지연 시간 데이터가 없습니다.",
    "table": "지연 시간 데이터 보기",
    "caption": "서비스 지연 시간 데이터",
    "period": "기간",
    "budget": "목표",
    "over": "초과",
    "within": "이내",
    "days": [
      "월",
      "화",
      "수",
      "목",
      "금",
      "토",
      "일"
    ]
  },
  "ja": {
    "title": "サービスの応答時間",
    "description": "日別のp50・p95応答時間をp95の目標値と比較します。",
    "worst": "最大p95",
    "overBudget": "期間で目標超過",
    "empty": "応答時間データはありません。",
    "table": "応答時間データを表示",
    "caption": "サービス応答時間データ",
    "period": "期間",
    "budget": "目標",
    "over": "超過",
    "within": "以内",
    "days": [
      "月",
      "火",
      "水",
      "木",
      "金",
      "土",
      "日"
    ]
  },
  "zh": {
    "title": "服务延迟",
    "description": "比较每日p50、p95响应时间与p95目标值。",
    "worst": "最高p95",
    "overBudget": "个时段超出目标",
    "empty": "暂无延迟数据。",
    "table": "查看延迟数据",
    "caption": "服务延迟数据",
    "period": "时段",
    "budget": "目标",
    "over": "超出",
    "within": "范围内",
    "days": [
      "周一",
      "周二",
      "周三",
      "周四",
      "周五",
      "周六",
      "周日"
    ]
  }
} as const;

export type ServiceLatencyPoint = {
  label: string;
  p50Ms: number;
  p95Ms: number;
};

export function ServiceLatencyChart({
  data,
  budgetMs = 300,
  locale = "en",
}: {
  data: readonly ServiceLatencyPoint[];
  budgetMs?: number;
  locale?: keyof typeof copy;
}) {
  const t = copy[locale];
  const number = new Intl.NumberFormat(locale);
  const worst = data.length ? Math.max(...data.map((row) => row.p95Ms)) : 0;
  const breaches = data.filter((row) => row.p95Ms > budgetMs);

  return (
    <ChartContainer
      data-chart="service-latency"
      title={t.title}
      description={t.description}
      summary={data.length ? `${t.worst} ${number.format(worst)} ms · ${breaches.length}/${data.length} ${t.overBudget}` : t.empty}
      tableLabel={t.table}
      emptyLabel={t.empty}
      empty={!data.length}
      table={
        <table>
          <caption className="sr-only">{t.caption}</caption>
          <thead><tr><th>{t.period}</th><th>p50 (ms)</th><th>p95 (ms)</th><th>{t.budget}</th></tr></thead>
          <tbody>{data.map((row) => <tr key={row.label}><th scope="row">{row.label}</th><td>{row.p50Ms}</td><td>{row.p95Ms}</td><td>{row.p95Ms > budgetMs ? t.over : t.within}</td></tr>)}</tbody>
        </table>
      }
    >
      <LineChart data={data} accessibilityLayer margin={{ top: 12, right: 12, bottom: 4, left: 0 }}>
        <CartesianGrid vertical={false} stroke="var(--border)" strokeDasharray="3 4" />
        <XAxis dataKey="label" tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <YAxis domain={[0, "auto"]} width={52} tickFormatter={(value: number) => `${value}ms`} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <ChartLegend reference={{ value: `${t.budget} ${number.format(budgetMs)} ms`, color: "var(--destructive)", dashArray: "4 4" }} />
        <ReferenceLine ifOverflow="extendDomain" y={budgetMs} stroke="var(--destructive)" strokeDasharray="4 4" />
        <ChartTooltip formatter={(value) => `${Number(value)} ms`} />
        <Line dataKey="p50Ms" name="p50" legendType="plainline" stroke="var(--muted-foreground)" strokeWidth={2} strokeDasharray="7 4" dot={false} isAnimationActive={false} />
        <Line dataKey="p95Ms" name="p95" legendType="plainline" stroke="var(--primary)" strokeWidth={2.5} dot={{ r: 3 }} isAnimationActive={false} />
      </LineChart>
    </ChartContainer>
  );
}

const exampleLatency: ServiceLatencyPoint[] = [
  { label: "Mon", p50Ms: 80, p95Ms: 210 },
  { label: "Tue", p50Ms: 76, p95Ms: 195 },
  { label: "Wed", p50Ms: 95, p95Ms: 340 },
  { label: "Thu", p50Ms: 130, p95Ms: 470 },
  { label: "Fri", p50Ms: 100, p95Ms: 330 },
  { label: "Sat", p50Ms: 82, p95Ms: 240 },
  { label: "Sun", p50Ms: 79, p95Ms: 215 },
];

export function ServiceLatencyExample({ locale = "en" }: { locale?: keyof typeof copy }) {
  const data = exampleLatency.map((point, index) => ({ ...point, label: copy[locale].days[index] }));
  return <ServiceLatencyChart data={data} locale={locale} />;
}
Operations & development

Release activity

New installs and updates, as counts or a share of each release.

@neumorphism-ui/chart-release-activity
Usage
"use client";
import { ReleaseActivityChart, type ReleaseActivityPoint } from "@/components/blocks/release-activity-chart";

const data: ReleaseActivityPoint[] = [
  { release: "R1", installs: 148, updates: 62 },
];

export function Example() {
  return <ReleaseActivityChart data={data} locale="en" />;
}
Source
"use client";

import * as React from "react";
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";

import { ChartContainer, ChartTooltip, ChartLegend } from "@/components/ui/chart";
import { Select, SelectItem } from "@/components/ui/select";

const copy = {
  "en": {
    "title": "Release activity",
    "description": "New installs and updates by release. Counts are events, not unique users.",
    "total": "Total activity",
    "updateShare": "Update share",
    "empty": "No release activity.",
    "view": "View",
    "count": "Event counts",
    "share": "Share per release",
    "table": "View release data",
    "caption": "Release activity data",
    "release": "Release",
    "installs": "New installs",
    "updates": "Updates"
  },
  "ko": {
    "title": "릴리스 활동",
    "description": "릴리스별 신규 설치와 업데이트 건수입니다. 고유 사용자 수가 아닌 이벤트 수입니다.",
    "total": "전체 활동",
    "updateShare": "업데이트 비율",
    "empty": "릴리스 활동이 없습니다.",
    "view": "보기 방식",
    "count": "이벤트 수",
    "share": "릴리스별 비율",
    "table": "릴리스 데이터 보기",
    "caption": "릴리스 활동 데이터",
    "release": "릴리스",
    "installs": "신규 설치",
    "updates": "업데이트"
  },
  "ja": {
    "title": "リリースの利用状況",
    "description": "リリース別の新規インストールと更新件数です。人数ではなくイベント数です。",
    "total": "合計イベント",
    "updateShare": "更新の割合",
    "empty": "リリースの利用記録はありません。",
    "view": "表示方法",
    "count": "イベント数",
    "share": "リリース別の割合",
    "table": "リリースデータを表示",
    "caption": "リリース利用状況データ",
    "release": "リリース",
    "installs": "新規インストール",
    "updates": "更新"
  },
  "zh": {
    "title": "版本活动",
    "description": "各版本的新安装和更新次数。计数为事件数,而非独立用户数。",
    "total": "总活动数",
    "updateShare": "更新占比",
    "empty": "暂无版本活动。",
    "view": "显示方式",
    "count": "事件数",
    "share": "各版本占比",
    "table": "查看版本数据",
    "caption": "版本活动数据",
    "release": "版本",
    "installs": "新安装",
    "updates": "更新"
  }
} as const;

export type ReleaseActivityPoint = {
  release: string;
  installs: number;
  updates: number;
};

export function ReleaseActivityChart({
  data,
  locale = "en",
}: {
  data: readonly ReleaseActivityPoint[];
  locale?: keyof typeof copy;
}) {
  const t = copy[locale];
  const [view, setView] = React.useState<"count" | "share">("count");
  const installs = data.reduce((sum, row) => sum + row.installs, 0);
  const updates = data.reduce((sum, row) => sum + row.updates, 0);
  const total = installs + updates;

  return (
    <ChartContainer
      data-chart="release-activity"
      title={t.title}
      description={t.description}
      summary={data.length ? `${t.total} ${total.toLocaleString(locale)} · ${t.updateShare} ${total ? ((updates / total) * 100).toFixed(1) : "0.0"}%` : t.empty}
      actions={
        <label className="grid gap-1.5 text-xs">
          {t.view}
          <Select value={view} onChange={(event) => setView(event.target.value as typeof view)}>
            <SelectItem value="count">{t.count}</SelectItem>
            <SelectItem value="share">{t.share}</SelectItem>
          </Select>
        </label>
      }
      tableLabel={t.table}
      emptyLabel={t.empty}
      empty={!data.length}
      table={
        <table>
          <caption className="sr-only">{t.caption}</caption>
          <thead><tr><th>{t.release}</th><th>{t.installs}</th><th>{t.updates}</th><th>{t.updateShare}</th></tr></thead>
          <tbody>{data.map((row) => {
            const rowTotal = row.installs + row.updates;
            return <tr key={row.release}><th scope="row">{row.release}</th><td>{row.installs}</td><td>{row.updates}</td><td>{rowTotal ? ((row.updates / rowTotal) * 100).toFixed(1) + "%" : "—"}</td></tr>;
          })}</tbody>
        </table>
      }
    >
      <AreaChart data={data} stackOffset={view === "share" ? "expand" : "none"} accessibilityLayer margin={{ top: 12, right: 12, bottom: 4, left: 0 }}>
        <CartesianGrid vertical={false} stroke="var(--border)" strokeDasharray="3 4" />
        <XAxis dataKey="release" tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <YAxis domain={view === "share" ? [0, 1] : [0, "auto"]} width={52} tickFormatter={(value: number) => view === "share" ? `${Math.round(value * 100)}%` : String(value)} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <ChartLegend />
        <ChartTooltip formatter={(value) => Number(value).toLocaleString(locale)} />
        <Area dataKey="installs" name={t.installs} legendType="plainline" stackId="activity" stroke="var(--foreground)" fill="var(--primary)" fillOpacity={0.22} strokeWidth={2} isAnimationActive={false} />
        <Area dataKey="updates" name={t.updates} legendType="plainline" stackId="activity" stroke="var(--muted-foreground)" fill="var(--muted)" fillOpacity={0.8} strokeWidth={2} strokeDasharray="6 4" isAnimationActive={false} />
      </AreaChart>
    </ChartContainer>
  );
}

const exampleActivity: ReleaseActivityPoint[] = [
  { release: "R1", installs: 148, updates: 62 },
  { release: "R2", installs: 232, updates: 104 },
  { release: "R3", installs: 196, updates: 88 },
  { release: "R4", installs: 284, updates: 132 },
  { release: "R5", installs: 341, updates: 176 },
  { release: "R6", installs: 378, updates: 209 },
];

export function ReleaseActivityExample({ locale = "en" }: { locale?: keyof typeof copy }) {
  return <ReleaseActivityChart data={exampleActivity} locale={locale} />;
}
Operations & development

Delivery capacity

Planned and delivered item counts, with the difference between them.

@neumorphism-ui/chart-delivery-capacity
Usage
"use client";
import { DeliveryCapacityChart, type DeliveryCapacityPoint } from "@/components/blocks/delivery-capacity-chart";

const data: DeliveryCapacityPoint[] = [
  { period: "S1", planned: 32, delivered: 28 },
];

export function Example() {
  return <DeliveryCapacityChart data={data} locale="en" />;
}
Source
"use client";

import * as React from "react";
import { Bar, BarChart, CartesianGrid, ReferenceLine, XAxis, YAxis } from "recharts";

import { ChartContainer, ChartTooltip, ChartLegend } from "@/components/ui/chart";
import { Select, SelectItem } from "@/components/ui/select";

const copy = {
  "en": {
    "title": "Delivery capacity",
    "description": "Planned and delivered item counts. This is not a productivity score.",
    "planned": "Planned",
    "delivered": "Delivered",
    "netVariance": "Net variance",
    "empty": "No delivery records.",
    "view": "View",
    "compare": "Planned vs delivered",
    "signed": "Signed variance",
    "table": "View delivery data",
    "caption": "Delivery capacity data",
    "period": "Period",
    "variance": "Variance",
    "difference": "Delivered − planned"
  },
  "ko": {
    "title": "계획·완료 수량",
    "description": "계획한 항목과 완료한 항목의 수를 비교합니다. 생산성 점수가 아닙니다.",
    "planned": "계획",
    "delivered": "완료",
    "netVariance": "순차이",
    "empty": "완료 기록이 없습니다.",
    "view": "보기 방식",
    "compare": "계획과 완료 비교",
    "signed": "증감 차이",
    "table": "계획·완료 데이터 보기",
    "caption": "계획·완료 수량 데이터",
    "period": "기간",
    "variance": "차이",
    "difference": "완료 − 계획"
  },
  "ja": {
    "title": "計画・完了件数",
    "description": "計画した項目と完了した項目の件数です。生産性スコアではありません。",
    "planned": "計画",
    "delivered": "完了",
    "netVariance": "合計差分",
    "empty": "完了記録はありません。",
    "view": "表示方法",
    "compare": "計画と完了を比較",
    "signed": "増減の差分",
    "table": "計画・完了データを表示",
    "caption": "計画・完了件数データ",
    "period": "期間",
    "variance": "差分",
    "difference": "完了 − 計画"
  },
  "zh": {
    "title": "计划与完成数量",
    "description": "比较计划和已完成的事项数量。这不是生产力评分。",
    "planned": "计划",
    "delivered": "完成",
    "netVariance": "净差额",
    "empty": "暂无完成记录。",
    "view": "显示方式",
    "compare": "计划与完成对比",
    "signed": "增减差额",
    "table": "查看计划与完成数据",
    "caption": "计划与完成数量数据",
    "period": "时段",
    "variance": "差额",
    "difference": "完成 − 计划"
  }
} as const;

export type DeliveryCapacityPoint = {
  period: string;
  planned: number;
  delivered: number;
};

export function DeliveryCapacityChart({
  data,
  locale = "en",
}: {
  data: readonly DeliveryCapacityPoint[];
  locale?: keyof typeof copy;
}) {
  const t = copy[locale];
  const [view, setView] = React.useState<"compare" | "variance">("compare");
  const planned = data.reduce((sum, row) => sum + row.planned, 0);
  const delivered = data.reduce((sum, row) => sum + row.delivered, 0);
  const chartData = data.map((row) => ({ ...row, variance: row.delivered - row.planned }));

  return (
    <ChartContainer
      data-chart="delivery-capacity"
      title={t.title}
      description={t.description}
      summary={data.length ? `${t.planned} ${planned} · ${t.delivered} ${delivered} · ${t.netVariance} ${delivered - planned >= 0 ? "+" : ""}${delivered - planned}` : t.empty}
      actions={
        <label className="grid gap-1.5 text-xs">
          {t.view}
          <Select value={view} onChange={(event) => setView(event.target.value as typeof view)}>
            <SelectItem value="compare">{t.compare}</SelectItem>
            <SelectItem value="variance">{t.signed}</SelectItem>
          </Select>
        </label>
      }
      tableLabel={t.table}
      emptyLabel={t.empty}
      empty={!data.length}
      table={
        <table>
          <caption className="sr-only">{t.caption}</caption>
          <thead><tr><th>{t.period}</th><th>{t.planned}</th><th>{t.delivered}</th><th>{t.variance}</th></tr></thead>
          <tbody>{chartData.map((row) => <tr key={row.period}><th scope="row">{row.period}</th><td>{row.planned}</td><td>{row.delivered}</td><td>{row.variance > 0 ? "+" + row.variance : row.variance}</td></tr>)}</tbody>
        </table>
      }
    >
      <BarChart data={chartData} accessibilityLayer margin={{ top: 12, right: 12, bottom: 4, left: 0 }}>
        <CartesianGrid vertical={false} stroke="var(--border)" strokeDasharray="3 4" />
        <XAxis dataKey="period" tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <YAxis width={44} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <ReferenceLine y={0} stroke="var(--foreground)" />
        <ChartLegend />
        <ChartTooltip />
        {view === "variance" ? (
          <Bar dataKey="variance" name={t.difference} fill="var(--primary)" maxBarSize={52} isAnimationActive={false} />
        ) : (
          <>
            <Bar dataKey="planned" name={t.planned} legendType="plainline" fill="var(--muted-foreground)" fillOpacity={0.14} stroke="var(--muted-foreground)" strokeDasharray="4 3" maxBarSize={40} isAnimationActive={false} />
            <Bar dataKey="delivered" name={t.delivered} fill="var(--primary)" maxBarSize={40} isAnimationActive={false} />
          </>
        )}
      </BarChart>
    </ChartContainer>
  );
}

const exampleCapacity: DeliveryCapacityPoint[] = [
  { period: "S1", planned: 32, delivered: 28 },
  { period: "S2", planned: 36, delivered: 39 },
  { period: "S3", planned: 34, delivered: 31 },
  { period: "S4", planned: 38, delivered: 42 },
];

export function DeliveryCapacityExample({ locale = "en" }: { locale?: keyof typeof copy }) {
  return <DeliveryCapacityChart data={exampleCapacity} locale={locale} />;
}
Operations & development

Install diagnostics

Duration of each installation stage, in milliseconds or seconds.

@neumorphism-ui/chart-install-diagnostics
Usage
"use client";
import { InstallDiagnosticsChart, type InstallDiagnosticPoint } from "@/components/blocks/install-diagnostics-chart";

const data: InstallDiagnosticPoint[] = [
  { stage: "Download", durationMs: 840 },
];

export function Example() {
  return <InstallDiagnosticsChart data={data} locale="en" />;
}
Source
"use client";

import * as React from "react";
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";

import { ChartContainer, ChartTooltip } from "@/components/ui/chart";
import { Select, SelectItem } from "@/components/ui/select";

const copy = {
  "en": {
    "title": "Installation diagnostics",
    "description": "Sequential stage durations for one installation trace.",
    "total": "Total",
    "longest": "Longest stage",
    "empty": "No installation trace.",
    "unit": "Unit",
    "ms": "Milliseconds",
    "seconds": "Seconds",
    "table": "View installation data",
    "caption": "Installation diagnostic data",
    "stage": "Stage",
    "duration": "Duration",
    "share": "Share",
    "stages": [
      "Resolve",
      "Download",
      "Transform",
      "Write"
    ]
  },
  "ko": {
    "title": "설치 단계 진단",
    "description": "한 번의 설치에서 순서대로 실행된 각 단계의 소요 시간입니다.",
    "total": "전체 시간",
    "longest": "가장 오래 걸린 단계",
    "empty": "설치 기록이 없습니다.",
    "unit": "단위",
    "ms": "밀리초",
    "seconds": "초",
    "table": "설치 데이터 보기",
    "caption": "설치 단계 진단 데이터",
    "stage": "단계",
    "duration": "소요 시간",
    "share": "비율",
    "stages": [
      "의존성 확인",
      "다운로드",
      "변환",
      "파일 쓰기"
    ]
  },
  "ja": {
    "title": "インストール診断",
    "description": "1回のインストールで順番に実行された各工程の所要時間です。",
    "total": "合計時間",
    "longest": "最長の工程",
    "empty": "インストール記録はありません。",
    "unit": "単位",
    "ms": "ミリ秒",
    "seconds": "秒",
    "table": "インストールデータを表示",
    "caption": "インストール診断データ",
    "stage": "工程",
    "duration": "所要時間",
    "share": "割合",
    "stages": [
      "依存関係",
      "ダウンロード",
      "変換",
      "書き込み"
    ]
  },
  "zh": {
    "title": "安装诊断",
    "description": "一次安装中按顺序执行的各阶段耗时。",
    "total": "总耗时",
    "longest": "最耗时阶段",
    "empty": "暂无安装记录。",
    "unit": "单位",
    "ms": "毫秒",
    "seconds": "秒",
    "table": "查看安装数据",
    "caption": "安装诊断数据",
    "stage": "阶段",
    "duration": "耗时",
    "share": "占比",
    "stages": [
      "解析依赖",
      "下载",
      "转换",
      "写入"
    ]
  }
} as const;

export type InstallDiagnosticPoint = {
  stage: string;
  durationMs: number;
};

export function InstallDiagnosticsChart({
  data,
  locale = "en",
}: {
  data: readonly InstallDiagnosticPoint[];
  locale?: keyof typeof copy;
}) {
  const t = copy[locale];
  const [unit, setUnit] = React.useState<"ms" | "s">("ms");
  const total = data.reduce((sum, row) => sum + row.durationMs, 0);
  const largest = data.length ? data.reduce((best, row) => row.durationMs > best.durationMs ? row : best) : null;
  const format = (value: number) => unit === "ms" ? `${value} ms` : `${(value / 1000).toFixed(2)} s`;

  return (
    <ChartContainer
      data-chart="install-diagnostics"
      title={t.title}
      description={t.description}
      summary={largest ? `${t.total} ${format(total)} · ${t.longest} ${largest.stage} (${format(largest.durationMs)})` : t.empty}
      actions={
        <label className="grid gap-1.5 text-xs">
          {t.unit}
          <Select value={unit} onChange={(event) => setUnit(event.target.value as typeof unit)}>
            <SelectItem value="ms">{t.ms}</SelectItem>
            <SelectItem value="s">{t.seconds}</SelectItem>
          </Select>
        </label>
      }
      tableLabel={t.table}
      emptyLabel={t.empty}
      empty={!data.length}
      table={
        <table>
          <caption className="sr-only">{t.caption}</caption>
          <thead><tr><th>{t.stage}</th><th>{t.duration}</th><th>{t.share}</th></tr></thead>
          <tbody>{data.map((row) => <tr key={row.stage}><th scope="row">{row.stage}</th><td>{format(row.durationMs)}</td><td>{total ? ((row.durationMs / total) * 100).toFixed(1) + "%" : "—"}</td></tr>)}</tbody>
        </table>
      }
    >
      <BarChart data={data} layout="vertical" accessibilityLayer margin={{ top: 12, right: 12, bottom: 4, left: 0 }}>
        <CartesianGrid horizontal={false} stroke="var(--border)" strokeDasharray="3 4" />
        <XAxis type="number" tickFormatter={(value: number) => format(value)} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <YAxis type="category" dataKey="stage" width={locale === "en" ? 72 : 88} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <ChartTooltip formatter={(value) => format(Number(value))} />
        <Bar dataKey="durationMs" name={t.duration} fill="var(--primary)" radius={[0, 4, 4, 0]} maxBarSize={42} isAnimationActive={false} />
      </BarChart>
    </ChartContainer>
  );
}

const exampleTrace: InstallDiagnosticPoint[] = [
  { stage: "Resolve", durationMs: 120 },
  { stage: "Download", durationMs: 840 },
  { stage: "Transform", durationMs: 210 },
  { stage: "Write", durationMs: 90 },
];

export function InstallDiagnosticsExample({ locale = "en" }: { locale?: keyof typeof copy }) {
  const data = exampleTrace.map((point, index) => ({ ...point, stage: copy[locale].stages[index] }));
  return <InstallDiagnosticsChart data={data} locale={locale} />;
}
Neumorphism UI56 UI · 16 blocks · 80 registry items

shadcn-compatible source Registry

GitHubCredits