Process mimic A schematic of the physical plant with live values on it. The layout is the operator's mental model, so it must not be rearranged for visual balance the way a dashboard grid can be. Tokens this needs: --foreground, --card, --muted, --muted-foreground, --border, --status-warn, --status-critical, --status-unknown The status, chart, scale, state and direction names are an extension, not a rename. shadcn has --destructive and five --chart-* and nothing else in this territory. ──────────────────────────────────────────────────────────────────────── // ProcessMimic.tsx ──────────────────────────────────────────────────────────────────────── import { cn } from "@/lib/utils"; /** The closed set. Nothing else may be styled as a status. */ export type Status = "nominal" | "warn" | "critical" | "unknown"; /** Colour only leaves neutral for an abnormal state, and never alone: the glyph goes with it. */ const STATUS: Record = { nominal: { stroke: "stroke-border", text: "fill-foreground", glyph: null }, warn: { stroke: "stroke-status-warn", text: "fill-status-warn", glyph: "▲" }, critical: { stroke: "stroke-status-critical", text: "fill-status-critical", glyph: "▲" }, unknown: { stroke: "stroke-status-unknown", text: "fill-status-unknown", glyph: "?" }, }; export type Kind = "vessel" | "valve" | "pump" | "instrument" | "outlet"; /** A piece of plant, placed where the operator expects it. The tag is the one printed on the equipment. */ export type Equipment = { tag: string; kind: Kind; x: number; y: number; w?: number; h?: number }; export type Reading = { value: number; unit: string; status: Status; stale?: boolean }; const fmt = (r: Reading) => (r.unit === "%" ? `${r.value}%` : `${r.value} ${r.unit}`); const Value = ({ x, y, r, className, anchor = "middle" }: { x: number; y: number; r?: Reading; className?: string; anchor?: "start" | "middle" }) => r ? ( {STATUS[r.status].glyph && {STATUS[r.status].glyph} } {fmt(r)} {r.stale && stale} ) : null; /** * Flat and grey, with live values where the instruments are. The layout is * the operator's mental model, so it arrives as coordinates and is drawn as * given, never rearranged for balance. A vessel's level is its fill height. */ export function ProcessMimic({ equipment, pipes, readings, width = 440, height = 230 }: { equipment: Equipment[]; /** Polylines between equipment, in the same coordinate space. */ pipes: [number, number][][]; readings: Record; width?: number; height?: number; }) { return ( {pipes.map((p, i) => ( `${x},${y}`).join(" ")} fill="none" className="stroke-border" strokeWidth={2} /> ))} {equipment.map((e) => { const r = readings[e.tag]; const st = r?.stale ? "stroke-border" : STATUS[r?.status ?? "nominal"].stroke; const dash = r?.stale ? "4 3" : undefined; const tag = {e.tag}; switch (e.kind) { case "vessel": { const w = e.w ?? 70, h = e.h ?? 92; const level = r ? Math.min(1, Math.max(0, r.value / 100)) * h : 0; return ( {e.tag} ); } case "valve": return ( {tag} ); case "pump": return ( {tag} ); case "outlet": return ( {e.tag} ); case "instrument": return ( {e.tag} ); } })} ); } ──────────────────────────────────────────────────────────────────────── // demo.tsx ──────────────────────────────────────────────────────────────────────── import { ProcessMimic, type Equipment, type Reading } from "./ProcessMimic"; /** Two vessels, a valve, a pump and two field instruments. V-102 is above its level alarm. */ const EQUIPMENT: Equipment[] = [ { tag: "V-101", kind: "vessel", x: 26, y: 46 }, { tag: "FV-12", kind: "valve", x: 126, y: 92 }, { tag: "V-102", kind: "vessel", x: 156, y: 62 }, { tag: "P-204", kind: "pump", x: 286, y: 108 }, { tag: "to unit 3", kind: "outlet", x: 328, y: 182 }, { tag: "TI-07", kind: "instrument", x: 26, y: 172 }, { tag: "PI-19", kind: "instrument", x: 156, y: 188 }, ]; const PIPES: [number, number][][] = [ [[96, 92], [156, 92]], [[226, 108], [286, 108]], [[312, 108], [360, 108], [360, 182]], ]; const READINGS: Record = { "V-101": { value: 44, unit: "%", status: "nominal" }, "V-102": { value: 96, unit: "%", status: "warn" }, "P-204": { value: 312, unit: "rpm", status: "nominal" }, "TI-07": { value: 84, unit: "°C", status: "nominal" }, "PI-19": { value: 2.1, unit: "bar", status: "nominal" }, }; export default function Demo() { return (
); }