跳到正文
NNeumorphism UI
文档样式图表模板
—
中文
English한국어简体中文日本語
EXACT DATA · VISUAL ENCODING

不隐藏数据的图表。

展示产品分析与运维信号,同时每个 recipe 都保留精确数据表。

PRODUCT ANALYTICS

产品分析

revenue、channel、conversion 共用同一分析模型和时间范围控制。

示例数据 · 2026年9月 · USD。未连接实际服务。

收入与目标

每日收入按货币显示。虚线表示目标。

收入: US$60,644.00 · 目标: US$75,810.00

查看精确数据
收入与目标
日期收入目标
2026-09-17US$3,682.00US$5,064.00
2026-09-18US$3,842.00US$5,118.00
2026-09-19US$4,292.00US$5,172.00
2026-09-20US$4,542.00US$5,226.00
2026-09-21US$3,640.00US$5,280.00
2026-09-22US$4,040.00US$5,334.00
2026-09-23US$4,142.00US$5,388.00
2026-09-24US$4,642.00US$5,442.00
2026-09-25US$4,842.00US$5,496.00
2026-09-26US$3,890.00US$5,550.00
2026-09-27US$4,340.00US$5,604.00
2026-09-28US$4,550.00US$5,658.00
2026-09-29US$5,000.00US$5,712.00
2026-09-30US$5,200.00US$5,766.00

渠道比较

所选期间的渠道总计。柱形从零开始。

访问: 23,778

查看精确数据
渠道比较
流量渠道访问转化访问转化率
Organic9,4703864.08%
Direct8,0114205.24%
Referral6,2974046.42%

访问转化率

转化访问数除以访问数。纵轴固定为0–100%。

5.09% · 1,210 / 23,778

查看精确数据
访问转化率
日期访问转化访问转化率
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%

缺失日期显示为空白而非零。访问量为零时转化率未定义(—)。

运维与开发图表

与产品分析分开,展示构建、延迟、发布、交付和安装阶段等运行指标。

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
ReleaseInstallsUpdatesUpdate 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%
REGISTRY SOURCE

安装参考

只安装需要的 recipe 源码,实施代码与 Registry 源码可按需展开。

01产品分析

收入与目标

Daily revenue with exact values and shared range controls.

@neumorphism-ui/chart-revenue
Usage
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="zh" />;
}
Source
@components/blocks/revenue-chart.tsx
"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>;
}
02产品分析

渠道比较

Channel contribution without hiding the source totals.

@neumorphism-ui/chart-channel
Usage
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="zh" />;
}
Source
@components/blocks/channel-chart.tsx
"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>;
}
03产品分析

访问转化率

Daily conversion with missing values distinct from zero.

@neumorphism-ui/chart-conversion
Usage
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="zh" />;
}
Source
@components/blocks/conversion-chart.tsx
"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>;
}
04运维与开发

Build duration

Cold and cached build duration with an explicit budget.

@neumorphism-ui/chart-build-duration
Usage
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} />;
}
Source
@components/blocks/build-duration-chart.tsx
"use client";

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

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

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

export function BuildDurationChart({
  data,
  budgetSeconds = 220,
}: {
  data: readonly BuildDurationPoint[];
  budgetSeconds?: number;
}) {
  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="Build duration"
      description="Paired cold and cached build runs in seconds."
      summary={data.length ? `Mean cold ${coldMean.toFixed(1)} s · cached ${cachedMean.toFixed(1)} s · ${breaches}/${data.length} cold runs over budget` : "No build runs."}
      actions={
        <label className="grid gap-1.5 text-xs">
          Visible series
          <Select value={series} onChange={(event) => setSeries(event.target.value as typeof series)}>
            <SelectItem value="both">Both</SelectItem>
            <SelectItem value="cold">Cold only</SelectItem>
            <SelectItem value="cached">Cached only</SelectItem>
          </Select>
        </label>
      }
      tableLabel="View build data"
      empty={!data.length}
      table={
        <table>
          <caption className="sr-only">Build duration data</caption>
          <thead><tr><th>Build</th><th>Cold (s)</th><th>Cached (s)</th><th>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} />
        <ReferenceLine y={budgetSeconds} stroke="var(--destructive)" strokeDasharray="4 4" />
        <ChartTooltip formatter={(value) => `${Number(value)} s`} />
        {series !== "cached" && <Line dataKey="coldSeconds" name="Cold build" stroke="var(--foreground)" strokeWidth={2.5} dot={{ r: 3 }} isAnimationActive={false} />}
        {series !== "cold" && <Line dataKey="cachedSeconds" name="Cached build" 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() {
  return <BuildDurationChart data={exampleBuilds} />;
}
05运维与开发

Service latency

p50 and p95 latency against a visible p95 budget.

@neumorphism-ui/chart-service-latency
Usage
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} />;
}
Source
@components/blocks/service-latency-chart.tsx
"use client";

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

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

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

export function ServiceLatencyChart({
  data,
  budgetMs = 300,
}: {
  data: readonly ServiceLatencyPoint[];
  budgetMs?: number;
}) {
  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="Service latency"
      description="Daily p50 and p95 response time against a p95 budget."
      summary={data.length ? `Worst p95 ${worst} ms · ${breaches.length}/${data.length} periods over budget` : "No latency samples."}
      tableLabel="View latency data"
      empty={!data.length}
      table={
        <table>
          <caption className="sr-only">Service latency data</caption>
          <thead><tr><th>Period</th><th>p50 (ms)</th><th>p95 (ms)</th><th>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 ? "Over" : "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} />
        <ReferenceLine y={budgetMs} stroke="var(--destructive)" strokeDasharray="4 4" />
        <ChartTooltip formatter={(value) => `${Number(value)} ms`} />
        <Line dataKey="p50Ms" name="p50" stroke="var(--muted-foreground)" strokeWidth={2} strokeDasharray="7 4" dot={false} isAnimationActive={false} />
        <Line dataKey="p95Ms" name="p95" 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() {
  return <ServiceLatencyChart data={exampleLatency} />;
}
06运维与开发

Release activity

New installs and updates in counts or per-release share.

@neumorphism-ui/chart-release-activity
Usage
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} />;
}
Source
@components/blocks/release-activity-chart.tsx
"use client";

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

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

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

export function ReleaseActivityChart({
  data,
}: {
  data: readonly ReleaseActivityPoint[];
}) {
  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="Release activity"
      description="New installs and updates by release. Counts are events, not unique users."
      summary={data.length ? `Total activity ${total.toLocaleString()} · update share ${total ? ((updates / total) * 100).toFixed(1) : "0.0"}%` : "No release activity."}
      actions={
        <label className="grid gap-1.5 text-xs">
          View
          <Select value={view} onChange={(event) => setView(event.target.value as typeof view)}>
            <SelectItem value="count">Event counts</SelectItem>
            <SelectItem value="share">Share per release</SelectItem>
          </Select>
        </label>
      }
      tableLabel="View release data"
      empty={!data.length}
      table={
        <table>
          <caption className="sr-only">Release activity data</caption>
          <thead><tr><th>Release</th><th>Installs</th><th>Updates</th><th>Update share</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} />
        <ChartTooltip formatter={(value) => Number(value).toLocaleString()} />
        <Area dataKey="installs" name="New installs" stackId="activity" stroke="var(--foreground)" fill="var(--primary)" fillOpacity={0.22} strokeWidth={2} isAnimationActive={false} />
        <Area dataKey="updates" name="Updates" 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() {
  return <ReleaseActivityChart data={exampleActivity} />;
}
07运维与开发

Delivery capacity

Planned versus delivered items and signed variance.

@neumorphism-ui/chart-delivery-capacity
Usage
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} />;
}
Source
@components/blocks/delivery-capacity-chart.tsx
"use client";

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

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

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

export function DeliveryCapacityChart({
  data,
}: {
  data: readonly DeliveryCapacityPoint[];
}) {
  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="Delivery capacity"
      description="Planned and delivered item counts. This is not a productivity score."
      summary={data.length ? `Planned ${planned} · delivered ${delivered} · net variance ${delivered - planned >= 0 ? "+" : ""}${delivered - planned}` : "No delivery records."}
      actions={
        <label className="grid gap-1.5 text-xs">
          View
          <Select value={view} onChange={(event) => setView(event.target.value as typeof view)}>
            <SelectItem value="compare">Planned vs delivered</SelectItem>
            <SelectItem value="variance">Signed variance</SelectItem>
          </Select>
        </label>
      }
      tableLabel="View delivery data"
      empty={!data.length}
      table={
        <table>
          <caption className="sr-only">Delivery capacity data</caption>
          <thead><tr><th>Period</th><th>Planned</th><th>Delivered</th><th>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)" />
        <ChartTooltip />
        {view === "variance" ? (
          <Bar dataKey="variance" name="Delivered − planned" fill="var(--primary)" maxBarSize={52} isAnimationActive={false} />
        ) : (
          <>
            <Bar dataKey="planned" name="Planned" fill="var(--muted)" stroke="var(--muted-foreground)" strokeDasharray="4 3" maxBarSize={40} isAnimationActive={false} />
            <Bar dataKey="delivered" name="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() {
  return <DeliveryCapacityChart data={exampleCapacity} />;
}
08运维与开发

Install diagnostics

Sequential install-stage timings with unit switching.

@neumorphism-ui/chart-install-diagnostics
Usage
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} />;
}
Source
@components/blocks/install-diagnostics-chart.tsx
"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";

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

export function InstallDiagnosticsChart({
  data,
}: {
  data: readonly InstallDiagnosticPoint[];
}) {
  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="Installation diagnostics"
      description="Sequential stage durations for one installation trace."
      summary={largest ? `Total ${format(total)} · longest stage ${largest.stage} (${format(largest.durationMs)})` : "No installation trace."}
      actions={
        <label className="grid gap-1.5 text-xs">
          Unit
          <Select value={unit} onChange={(event) => setUnit(event.target.value as typeof unit)}>
            <SelectItem value="ms">Milliseconds</SelectItem>
            <SelectItem value="s">Seconds</SelectItem>
          </Select>
        </label>
      }
      tableLabel="View installation data"
      empty={!data.length}
      table={
        <table>
          <caption className="sr-only">Installation diagnostic data</caption>
          <thead><tr><th>Stage</th><th>Duration</th><th>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={72} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <ChartTooltip formatter={(value) => format(Number(value))} />
        <Bar dataKey="durationMs" name="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() {
  return <InstallDiagnosticsChart data={exampleTrace} />;
}
Neumorphism UI56 UI · 16 blocks · 80 registry items

兼容 shadcn 的源码 Registry

GitHubCredits