本文へ移動
NNeumorphism UI
ドキュメントスタイリングチャートテンプレート
—
日本語
English한국어简体中文日本語
EXACT DATA · VISUAL ENCODING

データを隠さないチャート。

プロダクト分析と運用シグナルを可視化しながら、すべての recipe が正確なデータ表を併記します。

PRODUCT ANALYTICS

プロダクト分析

revenue、channel、conversion を同じ analytics model と期間制御で確認します。

サンプルデータ · 2026年9月 · USD。実際のサービスには接続されていません。

売上と目標

日別売上を通貨で表示します。破線は目標です。

売上: $60,644.00 · 目標: $75,810.00

正確なデータを見る
売上と目標
日付売上目標
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

流入経路の比較

選択期間の経路別合計。棒はゼロから始まります。

訪問: 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 だけを source として導入し、実装と Registry source は必要なときだけ展開できます。

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="ja" />;
}
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="ja" />;
}
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="ja" />;
}
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