運行図表StringlineExperimental
停車地点と時刻を結ぶ運行図表を表示し、計画と実績、上下方向、現在時刻を重ねて確認できます。
プレビュー
712T
下り+8分新宿 12番線
新宿 7:12 → 八王子 8:14
- 計画
- 新宿 7:12 → 八王子 8:06
- 運行担当
- 中央線運行指令
状態とバリエーション
計画と実績
actual を渡すと計画線を破線、実績を実線で重ねます。
712T
下り+8分新宿 12番線
新宿 7:12 → 八王子 8:14
- 計画
- 新宿 7:12 → 八王子 8:06
- 運行担当
- 中央線運行指令
選択中の運行
selectedRunId で1本を強調し、他を淡色化します。
712T
下り+8分新宿 12番線
新宿 7:12 → 八王子 8:14
- 計画
- 新宿 7:12 → 八王子 8:06
- 運行担当
- 中央線運行指令
短い区間
停車地点や運行本数を絞ったプレビューにも使えます。
712T
下り+8分新宿 12番線
新宿 7:12 → 八王子 8:14
- 計画
- 新宿 7:12 → 八王子 8:06
- 運行担当
- 中央線運行指令
プロパティ
表は横にスクロールできます
| プロパティ | 型 | 初期値 | 説明 |
|---|---|---|---|
| stops | StringlineStop[] | - | 距離軸の停車地点です。distance が縦位置を決めます。 |
| runs | StringlineRun[] | - | 時刻と停車地点を結ぶ運行線です。 |
| startTime / endTime | number | - | 時間軸の範囲です。既定単位は0時からの分です。 |
| actual | StringlineRunPoint[] | - | 実績点です。計画は破線、実績は実線で重ねます。 |
| now | number | - | 現在時刻の縦線です。SSR安全のため値を渡します。 |
| runLabel / directionLabels | string / object | - | 選択可能な運行の読み上げ文言をローカライズします。 |
| selectedRunId | string | number | - | 1本の運行を強調し、他を淡色化します。 |
| height | number | 320 | プロット高さです。 |
| formatTime | (time: number) => string | - | 軸ラベルの時刻を整形します(既定は0時からの分を HH:MM)。関数propのため Client Component からのみ渡すこと(Server Component から渡すと next build が落ちる)。ドメイン値のため serializable な代替は無く、RSC からは "use client" ラッパーで包む。(#338) |
使い方
import * as React from "react";
import { Badge, Stringline, type StringlineRun, type StringlineStop } from "@gunjo/ui";
const stops: StringlineStop[] = [
{ id: "sjk", label: "新宿", distance: 0 },
{ id: "mtk", label: "三鷹", distance: 14.3 },
{ id: "ttc", label: "立川", distance: 27.2 },
{ id: "hco", label: "八王子", distance: 37.1 },
];
const runs: StringlineRun[] = [
{
id: "712T",
label: "712T",
direction: "down",
tone: "primary",
points: [
{ stopId: "sjk", time: 432 },
{ stopId: "mtk", time: 451 },
{ stopId: "ttc", time: 469 },
{ stopId: "hco", time: 486 },
],
actual: [
{ stopId: "sjk", time: 432 },
{ stopId: "mtk", time: 455 },
{ stopId: "ttc", time: 477 },
{ stopId: "hco", time: 494 },
],
},
{
id: "809M",
label: "809M",
direction: "up",
tone: "info",
points: [
{ stopId: "hco", time: 438 },
{ stopId: "ttc", time: 456 },
{ stopId: "mtk", time: 474 },
{ stopId: "sjk", time: 494 },
],
},
{
id: "615A",
label: "615A",
direction: "down",
tone: "warning",
points: [
{ stopId: "sjk", time: 450 },
{ stopId: "mtk", time: 466 },
{ stopId: "ttc", time: 490 },
{ stopId: "hco", time: 510 },
],
},
];
const details = {
"712T": {
direction: "下り",
planned: "新宿 7:12 → 八王子 8:06",
actual: "新宿 7:12 → 八王子 8:14",
delay: "+8分",
platform: "新宿 12番線",
crew: "中央線運行指令",
},
"809M": {
direction: "上り",
planned: "八王子 7:18 → 新宿 8:14",
actual: "計画通り",
delay: "定時",
platform: "八王子 3番線",
crew: "中央線運行指令",
},
"615A": {
direction: "下り",
planned: "新宿 7:30 → 八王子 8:30",
actual: "立川で調整中",
delay: "+12分見込み",
platform: "新宿 10番線",
crew: "運転整理担当",
},
};
export function TrainDiagram() {
const [selectedRunId, setSelectedRunId] = React.useState("712T");
const selectableRuns = runs.map((run) => ({ ...run, onSelect: () => setSelectedRunId(String(run.id)) }));
const selectedRun = selectableRuns.find((run) => run.id === selectedRunId)!;
const detail = details[selectedRunId as keyof typeof details];
return (
<div className="flex w-full max-w-4xl flex-col gap-3 rounded-lg border bg-card p-4">
<Stringline
stops={stops}
runs={selectableRuns}
startTime={420}
endTime={520}
now={462}
tickInterval={20}
height={300}
selectedRunId={selectedRunId}
ariaLabel="中央線の運行図表"
/>
<section
className="grid min-w-0 gap-3 rounded-md border bg-background p-3"
aria-live="polite"
aria-label="選択中運行の詳細"
>
<div className="min-w-0 space-y-1.5">
<div className="flex flex-wrap items-center gap-2">
<h3 className="text-sm font-semibold text-foreground">{selectedRun.label}</h3>
<Badge variant="secondary">{detail.direction}</Badge>
<Badge variant={detail.delay.includes("+") ? "destructive" : "secondary"}>{detail.delay}</Badge>
</div>
<p className="text-xs text-muted-foreground">{detail.platform}</p>
<p className="text-sm text-foreground">{detail.actual}</p>
</div>
<dl className="grid min-w-0 gap-2 rounded-md bg-muted/40 p-3 text-xs">
<div>
<dt className="text-muted-foreground">計画</dt>
<dd className="font-medium text-foreground">
{detail.planned}
</dd>
</div>
<div>
<dt className="text-muted-foreground">運行担当</dt>
<dd className="font-medium text-foreground">
{detail.crew}
</dd>
</div>
</dl>
</section>
</div>
);
}