@tangle-network/charts 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,17 @@
1
1
  # @tangle-network/charts
2
2
 
3
- Benchmark figures as static SVG and HTML tables.
3
+ Benchmark figures as static SVG and HTML tables, plus optional React timing primitives.
4
+
5
+ `WaterfallHeader`, `WaterfallTimeAxis`, `WaterfallBar` and `WaterfallRow` from
6
+ `@tangle-network/charts/react` own the timing presentation extracted from
7
+ Intelligence's trace viewer. Supply actual span start/end milliseconds and the
8
+ same `{ startMs, endMs }` window to every row. Hierarchy, selection, attribution,
9
+ formatting and failure interpretation stay with the consumer. The original
10
+ minimum/clipped marker remains visible; its tooltip reports the actual duration.
11
+ Set `--waterfall-timing-width`, `--waterfall-duration-width` and the existing
12
+ `--intel-waterfall-*` colors for the consumer's layout and theme. No stylesheet,
13
+ router or React DOM dependency is required.
14
+
4
15
  Five figures cover the decisions a benchmark reader makes:
5
16
 
6
17
  | Figure | Question |
package/dist/index.js CHANGED
@@ -1273,7 +1273,7 @@ function plot$1(costed, unplaced, drawLine, o) {
1273
1273
  /** The figure block and mark styles (src/figure.css), for documents that inline their CSS. */
1274
1274
  const figureCss = "/* @tangle-network/charts — figure block and mark styles.\n *\n * Every colour is a --chart-* token from @tangle-network/brand tokens.css, so a\n * figure follows the page theme. A standalone document without brand tokens\n * embeds `chartTokensCss` first. Fonts fall back to Inter and JetBrains Mono.\n *\n * Each figure carries a 720 px and a 358 px render. The swap follows the\n * figure's own width (a container query), so a narrow column shows the phone\n * render even on a wide screen. */\n\n.tgc-figure {\n margin: 32px 0 0;\n padding: 0;\n max-width: 720px;\n color: var(--chart-ink);\n font-family: var(--font-sans, Inter, ui-sans-serif, system-ui, sans-serif);\n}\n\n.tgc-finding {\n margin: 0 0 8px;\n font-size: 16px;\n line-height: 24px;\n font-weight: 600;\n color: var(--chart-ink);\n}\n\n.tgc-noteref {\n margin-left: 2px;\n font-size: 12px;\n font-weight: 400;\n color: var(--chart-ink-muted);\n text-decoration: none;\n font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n}\n.tgc-noteref:hover,\n.tgc-noteref:focus-visible {\n color: var(--chart-accent);\n text-decoration: underline;\n}\n\n.tgc-lede {\n margin: 0 0 16px;\n font-size: 14px;\n line-height: 22px;\n color: var(--text-secondary, var(--chart-ink));\n}\n\n.tgc-plot {\n container-type: inline-size;\n}\n.tgc-wide svg,\n.tgc-narrow svg {\n display: block;\n overflow: visible;\n}\n.tgc-narrow {\n display: none;\n}\n.tgc-narrow svg {\n max-width: 100%;\n height: auto;\n}\n@container (max-width: 719.98px) {\n .tgc-wide {\n display: none;\n }\n .tgc-narrow {\n display: block;\n }\n}\n\n.tgc-read {\n margin: 8px 0 0;\n font-size: 12px;\n line-height: 16px;\n color: var(--chart-ink-muted);\n}\n\n.tgc-data {\n margin: 8px 0 0;\n font-size: 12px;\n line-height: 16px;\n}\n.tgc-data > summary {\n cursor: pointer;\n color: var(--chart-ink-muted);\n width: fit-content;\n}\n.tgc-data > summary:hover {\n color: var(--chart-ink);\n}\n.tgc-data[open] > summary {\n margin-bottom: 8px;\n}\n\n.tgc-scroll {\n overflow-x: auto;\n}\n.tgc-scroll:focus-visible {\n outline: 2px solid var(--chart-accent);\n outline-offset: 2px;\n}\n\n.tgc-table {\n border-collapse: collapse;\n min-width: 100%;\n font-size: 12px;\n line-height: 16px;\n color: var(--chart-ink);\n /* A host page's table rules must not restyle the figure's table. */\n border: 0;\n border-radius: 0;\n background: none;\n}\n.tgc-table caption {\n text-align: left;\n padding: 0 0 8px;\n color: var(--chart-ink-muted);\n}\n.tgc-table th,\n.tgc-table td {\n padding: 6px 12px 6px 0;\n border-bottom: 1px solid var(--chart-rule);\n text-align: left;\n vertical-align: top;\n font-weight: 400;\n font-size: inherit;\n color: inherit;\n background: none;\n text-transform: none;\n letter-spacing: normal;\n}\n.tgc-table thead th {\n color: var(--chart-ink-muted);\n white-space: nowrap;\n}\n.tgc-table tbody th {\n position: sticky;\n left: 0;\n background: var(--chart-canvas, var(--depth-1, transparent));\n}\n.tgc-table .tgc-n {\n font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n.tgc-table thead .tgc-n {\n font-family: inherit;\n}\n.tgc-table tbody tr:hover {\n background: var(--chart-shade);\n}\n.tgc-narrow-table .tgc-table {\n display: block;\n overflow-x: auto;\n}\n\n.tgc-notes {\n margin: 16px 0 0;\n padding-left: 20px;\n font-size: 12px;\n line-height: 16px;\n color: var(--chart-ink-muted);\n}\n.tgc-notes li {\n margin: 0 0 8px;\n padding-left: 4px;\n}\n.tgc-notes li:target {\n color: var(--chart-ink);\n}\n.tgc-backlink {\n color: var(--chart-ink-muted);\n text-decoration: none;\n}\n\n.tgc-refusals {\n margin: 8px 0 0;\n padding-left: 20px;\n font-size: 14px;\n line-height: 22px;\n color: var(--chart-ink-muted);\n}\n\n/* Marks */\n.tgc-svg {\n font-family: var(--font-sans, Inter, ui-sans-serif, system-ui, sans-serif);\n}\n.tgc-svg .tgc-num {\n font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n font-variant-numeric: tabular-nums;\n}\n.tgc-ink {\n fill: var(--chart-ink);\n}\n.tgc-ink-muted {\n fill: var(--chart-ink-muted);\n}\n.tgc-bar,\n.tgc-ci,\n.tgc-pt-front {\n fill: var(--chart-accent);\n}\n.tgc-gridline {\n stroke: var(--chart-grid);\n stroke-width: 1;\n}\n.tgc-rule {\n stroke: var(--chart-rule);\n stroke-width: 1;\n}\n.tgc-axis,\n.tgc-zero {\n stroke: var(--chart-muted);\n stroke-width: 1;\n}\n.tgc-whisker,\n.tgc-tick {\n stroke: var(--chart-ink);\n stroke-width: 1.5;\n stroke-linecap: round;\n}\n.tgc-threshold {\n stroke: var(--chart-ink);\n stroke-width: 1.5;\n stroke-dasharray: 3 3;\n}\n.tgc-front-line {\n fill: none;\n stroke: var(--chart-ink);\n stroke-width: 1.5;\n stroke-linejoin: round;\n}\n.tgc-pt,\n.tgc-open {\n fill: none;\n stroke: var(--chart-muted);\n stroke-width: 1.5;\n}\n.tgc-pt-est {\n fill: none;\n stroke: var(--chart-accent);\n stroke-width: 2;\n}\n.tgc-pt-ci,\n.tgc-strike {\n stroke: var(--chart-muted);\n stroke-width: 1.5;\n}\n.tgc-leader {\n stroke: var(--chart-muted);\n stroke-width: 1;\n}\n.tgc-ci-exact,\n.tgc-dot {\n fill: var(--chart-muted);\n}\n.tgc-dashed {\n stroke-dasharray: 2 2;\n}\n.tgc-tier-c {\n fill: var(--chart-tier-c);\n}\n.tgc-tier-b {\n fill: var(--chart-tier-b);\n}\n.tgc-tier-a {\n fill: var(--chart-tier-a);\n}\n.tgc-tier-s {\n fill: var(--chart-tier-s);\n}\n/* Neutral tint for counts: never the accent, capped at half strength so ink keeps 6:1. */\n.tgc-tint {\n fill: color-mix(in srgb, var(--chart-muted) calc(var(--s, 0) * 50%), transparent);\n}\n\n@media print {\n .tgc-plot {\n container-type: normal;\n }\n .tgc-wide {\n display: block;\n }\n .tgc-narrow {\n display: none;\n }\n .tgc-wide svg {\n width: 100%;\n height: auto;\n }\n .tgc-figure {\n break-inside: avoid;\n }\n}\n\n/* Chart-led surfaces keep supporting text in the native disclosure. */\n.tgc-figure-chart { max-width: none; margin: 0; }\n.tgc-figure-chart .tgc-wide svg { width: 100%; height: auto; }\n.tgc-wide:has(.tgc-vertical), .tgc-narrow:has(.tgc-vertical) { overflow-x: auto; }\n.tgc-vertical { width: 100%; }\n.tgc-narrow .tgc-vertical { max-width: none; }\n.tgc-column-hit { fill: transparent; stroke: transparent; stroke-width: 2; }\n.tgc-column:focus-visible { outline: none; }\n.tgc-column:focus-visible .tgc-column-hit { stroke: var(--chart-accent); }\n.tgc-vertical-bar { fill: var(--chart-accent); }\n/* Identity assets keep their approved colors on a white plate in either theme. */\n.tgc-logo-plate { fill: #fff; stroke: var(--chart-rule); }\n.tgc-column-tip { opacity: 0; pointer-events: none; }\n.tgc-tip-bg { fill: var(--chart-ink); }\n.tgc-tip-ink { fill: var(--chart-canvas); }\n\n.tgc-series-line { fill: none; stroke: var(--chart-accent); stroke-width: 2; stroke-linejoin: round; }\n.tgc-series-point { fill: var(--chart-accent); stroke: var(--chart-canvas); stroke-width: 1; }\n.tgc-series-hit { fill: transparent; stroke: transparent; }\n.tgc-series-observation:focus-visible { outline: none; }\n.tgc-series-observation:focus-visible .tgc-series-hit { stroke: var(--chart-ink); stroke-width: 2; }\n.tgc-series-tip { opacity: 0; pointer-events: none; }\n\n.tgc-scalar-table { min-width: 620px; }\n.tgc-scalar-table tbody th { min-width: 180px; max-width: 260px; }\n\n/* Plot presentation keeps the same complete data available to assistive technology. */\n.tgc-accessible-data { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }\n.tgc-figure-plot .tgc-wide svg { box-sizing: content-box; width: calc(100% - 48px); padding: 0 24px; }\n.tgc-figure-plot svg text { font-size: 14px; }\n.tgc-figure-plot svg text.tgc-num { font-size: 15px; }\n/* Larger tooltip text needs padding outside the original report backdrop. */\n.tgc-figure-plot .tgc-tip-bg { stroke: var(--chart-ink); stroke-width: 24px; }\n.tgc-figure-plot .tgc-phone-plot svg { min-width: var(--tgc-phone-width); }\n.tgc-figure-plot .tgc-wide, .tgc-figure-plot .tgc-narrow { overflow-x: auto; }\n\n.tgc-figure-plot .tgc-wide-fallback svg { min-width: var(--tgc-fallback-width); }\n.tgc-phone-plot:focus-visible, .tgc-wide-fallback:focus-visible { outline: 2px solid var(--chart-accent); outline-offset: 2px; }\n";
1275
1275
  /** --chart-* resolved per theme, for documents without brand tokens: light, dark by preference or [data-theme="dark"], light in print. */
1276
- const chartTokensCss = ":root{--chart-ink:#2d2d2d;--chart-ink-muted:#686868;--chart-muted:#787878;--chart-grid:#f2f2f2;--chart-rule:#d0d0d0;--chart-shade:rgba(76, 63, 198, 0.06);--chart-accent:hsl(246 54% 51%);--chart-tier-c:#9b9ef0;--chart-tier-b:#7c7fe3;--chart-tier-a:#5b5bd6;--chart-tier-s:#272962;--chart-canvas:#ffffff;color-scheme:light}\n@media (prefers-color-scheme: dark){:root:not([data-theme=\"light\"]){--chart-ink:#e6e6e6;--chart-ink-muted:#a5a5a5;--chart-muted:#8e8e8e;--chart-grid:#353535;--chart-rule:#4c4c4c;--chart-shade:rgba(165, 180, 252, 0.07);--chart-accent:#818CF8;--chart-tier-c:#4a4a95;--chart-tier-b:#5b5bd6;--chart-tier-a:#8b87f0;--chart-tier-s:#b1a9ff;--chart-canvas:#161616;color-scheme:dark}}\n:root[data-theme=\"dark\"]{--chart-ink:#e6e6e6;--chart-ink-muted:#a5a5a5;--chart-muted:#8e8e8e;--chart-grid:#353535;--chart-rule:#4c4c4c;--chart-shade:rgba(165, 180, 252, 0.07);--chart-accent:#818CF8;--chart-tier-c:#4a4a95;--chart-tier-b:#5b5bd6;--chart-tier-a:#8b87f0;--chart-tier-s:#b1a9ff;--chart-canvas:#161616;color-scheme:dark}\n@media print{:root,:root:not([data-theme=\"light\"]),:root[data-theme=\"dark\"]{--chart-ink:#2d2d2d;--chart-ink-muted:#686868;--chart-muted:#787878;--chart-grid:#f2f2f2;--chart-rule:#d0d0d0;--chart-shade:rgba(76, 63, 198, 0.06);--chart-accent:hsl(246 54% 51%);--chart-tier-c:#9b9ef0;--chart-tier-b:#7c7fe3;--chart-tier-a:#5b5bd6;--chart-tier-s:#272962;--chart-canvas:#ffffff;color-scheme:light}}";
1276
+ const chartTokensCss = ":root{--chart-ink:#2d2d2d;--chart-ink-muted:#686868;--chart-muted:#787878;--chart-grid:#f1f2f7;--chart-rule:#c7c6d6;--chart-shade:rgba(76, 63, 198, 0.06);--chart-accent:hsl(246 54% 51%);--chart-tier-c:#9b9ef0;--chart-tier-b:#7c7fe3;--chart-tier-a:#5b5bd6;--chart-tier-s:#272962;--chart-canvas:#ffffff;color-scheme:light}\n@media (prefers-color-scheme: dark){:root:not([data-theme=\"light\"]){--chart-ink:#e6e6e6;--chart-ink-muted:#a5a5a5;--chart-muted:#8e8e8e;--chart-grid:#221f33;--chart-rule:#2a293d;--chart-shade:rgba(165, 180, 252, 0.07);--chart-accent:#818CF8;--chart-tier-c:#4a4a95;--chart-tier-b:#5b5bd6;--chart-tier-a:#8b87f0;--chart-tier-s:#b1a9ff;--chart-canvas:#0a0a14;color-scheme:dark}}\n:root[data-theme=\"dark\"]{--chart-ink:#e6e6e6;--chart-ink-muted:#a5a5a5;--chart-muted:#8e8e8e;--chart-grid:#221f33;--chart-rule:#2a293d;--chart-shade:rgba(165, 180, 252, 0.07);--chart-accent:#818CF8;--chart-tier-c:#4a4a95;--chart-tier-b:#5b5bd6;--chart-tier-a:#8b87f0;--chart-tier-s:#b1a9ff;--chart-canvas:#0a0a14;color-scheme:dark}\n@media print{:root,:root:not([data-theme=\"light\"]),:root[data-theme=\"dark\"]{--chart-ink:#2d2d2d;--chart-ink-muted:#686868;--chart-muted:#787878;--chart-grid:#f1f2f7;--chart-rule:#c7c6d6;--chart-shade:rgba(76, 63, 198, 0.06);--chart-accent:hsl(246 54% 51%);--chart-tier-c:#9b9ef0;--chart-tier-b:#7c7fe3;--chart-tier-a:#5b5bd6;--chart-tier-s:#272962;--chart-canvas:#ffffff;color-scheme:light}}";
1277
1277
  //#endregion
1278
1278
  //#region src/vertical-columns.ts
1279
1279
  function image(mark, x, y, size) {
@@ -1,4 +1,4 @@
1
- import { ReactElement } from "react";
1
+ import { ReactElement, ReactNode } from "react";
2
2
  //#region src/react/sparkline.d.ts
3
3
  type SparklineDirection = "rising" | "falling" | "flat";
4
4
  type SparklineValues = readonly (number | null)[];
@@ -81,4 +81,54 @@ interface StackedBarChartProps {
81
81
  }
82
82
  export declare function StackedBarChart(props: StackedBarChartProps): import("react").JSX.Element;
83
83
  //#endregion
84
- export type { SparklineDirection, SparklineGeometry, SparklineGeometryOptions, SparklineLabelOptions, SparklinePoint, SparklineProps, SparklineValues, StackedBarBucket, StackedBarChartProps, StackedBarSegment, StackedBarSeries };
84
+ //#region src/react/waterfall.d.ts
85
+ interface WaterfallWindow {
86
+ readonly startMs: number;
87
+ readonly endMs: number;
88
+ }
89
+ interface WaterfallSpanGeometry {
90
+ readonly offsetPct: number;
91
+ readonly widthPct: number;
92
+ readonly startOffsetMs: number;
93
+ readonly durationMs: number;
94
+ readonly clipped: boolean;
95
+ }
96
+ /** The Intelligence clipping/minimum-marker policy, applied to relative times. */
97
+ export declare function waterfallSpanGeometry(startMs: number, endMs: number, window: WaterfallWindow): WaterfallSpanGeometry;
98
+ /** Intelligence's round-duration ticks, with the original 208px default. */
99
+ export declare function waterfallTicks(windowMs: number, widthPx?: number, minLabelGapPx?: number): number[];
100
+ export declare function formatWaterfallDuration(ms: number): string;
101
+ interface WaterfallTimeAxisProps {
102
+ windowMs: number;
103
+ originMs?: number;
104
+ widthPx?: number;
105
+ formatDuration?: (ms: number) => string;
106
+ className?: string;
107
+ }
108
+ export declare function WaterfallTimeAxis({ windowMs, originMs, widthPx, formatDuration, className }: WaterfallTimeAxisProps): import("react").JSX.Element | null;
109
+ interface WaterfallBarProps {
110
+ startMs: number;
111
+ endMs: number;
112
+ window: WaterfallWindow;
113
+ tone?: "default" | "error" | "critical";
114
+ formatDuration?: (ms: number) => string;
115
+ className?: string;
116
+ }
117
+ export declare function WaterfallBar({ startMs, endMs, window, tone, formatDuration, className }: WaterfallBarProps): import("react").JSX.Element;
118
+ interface WaterfallRowProps extends Omit<WaterfallBarProps, "className"> {
119
+ label: ReactNode;
120
+ attribution?: ReactNode;
121
+ timingClassName?: string;
122
+ durationClassName?: string;
123
+ className?: string;
124
+ }
125
+ /** Non-interactive row body; its caller owns treeitems, buttons and selection. */
126
+ export declare function WaterfallRow({ label, attribution, startMs, endMs, window, tone, formatDuration, className, timingClassName, durationClassName }: WaterfallRowProps): import("react").JSX.Element;
127
+ interface WaterfallHeaderProps extends WaterfallTimeAxisProps {
128
+ label?: ReactNode;
129
+ attribution?: ReactNode;
130
+ durationLabel?: ReactNode;
131
+ }
132
+ export declare function WaterfallHeader({ label, attribution, durationLabel, ...axis }: WaterfallHeaderProps): import("react").JSX.Element;
133
+ //#endregion
134
+ export type { SparklineDirection, SparklineGeometry, SparklineGeometryOptions, SparklineLabelOptions, SparklinePoint, SparklineProps, SparklineValues, StackedBarBucket, StackedBarChartProps, StackedBarSegment, StackedBarSeries, WaterfallBarProps, WaterfallHeaderProps, WaterfallRowProps, WaterfallSpanGeometry, WaterfallTimeAxisProps, WaterfallWindow };
@@ -535,4 +535,224 @@ function StackedBarChart(props) {
535
535
  });
536
536
  }
537
537
  //#endregion
538
- export { DEFAULT_SPARKLINE_EMPTY_LABEL, DEFAULT_SPARKLINE_HEIGHT, DEFAULT_SPARKLINE_LABEL, DEFAULT_SPARKLINE_UNAVAILABLE_LABEL, DEFAULT_SPARKLINE_WIDTH, Sparkline, StackedBarChart, formatSparklineValue, sparklineGeometry, sparklineLabel, sparklinePointsAttribute, sparklineReadings };
538
+ //#region src/react/waterfall.tsx
539
+ /** The Intelligence clipping/minimum-marker policy, applied to relative times. */
540
+ function waterfallSpanGeometry(startMs, endMs, window) {
541
+ if (![
542
+ startMs,
543
+ endMs,
544
+ window.startMs,
545
+ window.endMs
546
+ ].every(Number.isFinite) || endMs < startMs || window.endMs < window.startMs) throw new RangeError("Waterfall requires finite, ordered span and window times");
547
+ const durationMs = endMs - startMs;
548
+ const startOffsetMs = startMs - window.startMs;
549
+ const windowMs = window.endMs - window.startMs;
550
+ const span = windowMs === 0 ? 1 : windowMs;
551
+ const rawOffsetPct = startOffsetMs / span * 100;
552
+ const endPct = rawOffsetPct + durationMs / span * 100;
553
+ const fullyOutside = endPct <= 0 || rawOffsetPct >= 100;
554
+ const offsetPct = Math.min(99, Math.max(0, rawOffsetPct));
555
+ return {
556
+ offsetPct,
557
+ widthPct: fullyOutside ? .5 : Math.max(.5, Math.min(100 - offsetPct, endPct - offsetPct)),
558
+ startOffsetMs,
559
+ durationMs,
560
+ clipped: fullyOutside || rawOffsetPct < 0 || endPct > 100
561
+ };
562
+ }
563
+ function niceTickMs(spanMs, target = 4) {
564
+ if (!(spanMs > 0)) return 1;
565
+ const rough = spanMs / target;
566
+ const mag = 10 ** Math.floor(Math.log10(rough));
567
+ const norm = rough / mag;
568
+ return (norm >= 5 ? 5 : norm >= 2 ? 2 : 1) * mag;
569
+ }
570
+ /** Intelligence's round-duration ticks, with the original 208px default. */
571
+ function waterfallTicks(windowMs, widthPx = 208, minLabelGapPx = 64) {
572
+ if (!Number.isFinite(windowMs) || windowMs < 0 || !Number.isFinite(widthPx) || widthPx <= 0 || !Number.isFinite(minLabelGapPx) || minLabelGapPx <= 0) throw new RangeError("Waterfall axis requires finite, nonnegative time and positive dimensions");
573
+ if (windowMs === 0) return [];
574
+ const step = niceTickMs(windowMs, Math.max(2, Math.floor(widthPx / minLabelGapPx)));
575
+ const raw = [];
576
+ for (let t = 0; t <= windowMs + 1e-6; t += step) raw.push(t);
577
+ const minGapPct = minLabelGapPx / widthPx * 100;
578
+ const kept = [];
579
+ let lastPct = Number.NEGATIVE_INFINITY;
580
+ for (const t of raw) {
581
+ const pct = t / windowMs * 100;
582
+ if (kept.length === 0 || pct - lastPct >= minGapPct) {
583
+ kept.push(t);
584
+ lastPct = pct;
585
+ }
586
+ }
587
+ return kept;
588
+ }
589
+ function formatWaterfallDuration(ms) {
590
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
591
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
592
+ if (ms < 36e5) return `${(ms / 6e4).toFixed(1)}m`;
593
+ return `${(ms / 36e5).toFixed(1)}h`;
594
+ }
595
+ const timingCell = {
596
+ width: "var(--waterfall-timing-width, 208px)",
597
+ flexShrink: 0
598
+ };
599
+ const rowStyle = {
600
+ display: "flex",
601
+ alignItems: "center",
602
+ gap: "0.5rem",
603
+ width: "100%",
604
+ minWidth: 0
605
+ };
606
+ const labelStyle = {
607
+ display: "flex",
608
+ alignItems: "center",
609
+ gap: "0.5rem",
610
+ flex: "1 1 0%",
611
+ minWidth: 0
612
+ };
613
+ const durationStyle = {
614
+ width: "var(--waterfall-duration-width, 4rem)",
615
+ flexShrink: 0,
616
+ textAlign: "right",
617
+ fontFamily: "var(--font-mono, monospace)",
618
+ fontVariantNumeric: "tabular-nums"
619
+ };
620
+ const ordered = (start, end) => Number.isFinite(start) && Number.isFinite(end) && end >= start;
621
+ function WaterfallTimeAxis({ windowMs, originMs = 0, widthPx = 208, formatDuration = formatWaterfallDuration, className }) {
622
+ if (!ordered(0, windowMs) || !Number.isFinite(originMs)) return null;
623
+ const ticks = waterfallTicks(windowMs, widthPx);
624
+ if (windowMs === 0) return null;
625
+ return /* @__PURE__ */ jsx("div", {
626
+ className,
627
+ "data-waterfall-axis": "",
628
+ "aria-hidden": "true",
629
+ style: {
630
+ ...timingCell,
631
+ position: "relative",
632
+ height: "var(--waterfall-axis-height, 1.5rem)"
633
+ },
634
+ children: ticks.map((t) => {
635
+ const pct = t / windowMs * 100;
636
+ return /* @__PURE__ */ jsxs("span", {
637
+ style: {
638
+ position: "absolute",
639
+ top: 0,
640
+ bottom: 0,
641
+ left: `${pct}%`,
642
+ display: "flex",
643
+ flexDirection: "column",
644
+ alignItems: "start"
645
+ },
646
+ children: [/* @__PURE__ */ jsx("span", { style: {
647
+ height: "0.5rem",
648
+ width: 1,
649
+ background: "var(--intel-chart-grid, var(--color-border, #d1d3e1))"
650
+ } }), /* @__PURE__ */ jsx("span", {
651
+ style: {
652
+ marginTop: "0.25rem",
653
+ whiteSpace: "nowrap",
654
+ fontFamily: "var(--font-mono, monospace)",
655
+ fontSize: "var(--waterfall-axis-font-size, 11px)",
656
+ lineHeight: 1,
657
+ fontVariantNumeric: "tabular-nums",
658
+ transform: pct > 75 ? "translateX(-100%)" : void 0
659
+ },
660
+ children: formatDuration(originMs + t)
661
+ })]
662
+ }, t);
663
+ })
664
+ });
665
+ }
666
+ function WaterfallBar({ startMs, endMs, window, tone = "default", formatDuration = formatWaterfallDuration, className }) {
667
+ if (!ordered(startMs, endMs) || !ordered(window.startMs, window.endMs)) return /* @__PURE__ */ jsx("span", {
668
+ className,
669
+ "data-waterfall-track": "",
670
+ "data-unavailable": "true",
671
+ title: "Timing unavailable",
672
+ style: {
673
+ ...timingCell,
674
+ textAlign: "center"
675
+ },
676
+ children: "—"
677
+ });
678
+ const geometry = waterfallSpanGeometry(startMs, endMs, window);
679
+ const color = tone === "error" ? "var(--intel-waterfall-error, #dc2626)" : tone === "critical" ? "var(--intel-waterfall-critical, #0891b2)" : "var(--intel-waterfall-bar, #2563eb)";
680
+ return /* @__PURE__ */ jsx("span", {
681
+ className,
682
+ "data-waterfall-track": "",
683
+ "data-tone": tone,
684
+ title: `+${formatDuration(geometry.startOffsetMs)} · ${formatDuration(geometry.durationMs)}${tone === "critical" ? " · critical path" : ""}`,
685
+ style: {
686
+ ...timingCell,
687
+ position: "relative",
688
+ height: "var(--waterfall-bar-height, 0.875rem)",
689
+ overflow: "hidden",
690
+ borderRadius: "var(--waterfall-bar-radius, 999px)",
691
+ background: "var(--intel-waterfall-track, rgb(148 163 184 / 22%))"
692
+ },
693
+ children: /* @__PURE__ */ jsx("span", {
694
+ "data-waterfall-bar": "",
695
+ "data-clipped": geometry.clipped || void 0,
696
+ style: {
697
+ position: "absolute",
698
+ top: 0,
699
+ height: "100%",
700
+ borderRadius: "inherit",
701
+ background: color,
702
+ left: `${geometry.offsetPct}%`,
703
+ width: `${geometry.widthPct}%`,
704
+ boxShadow: tone === "default" ? void 0 : `0 0 0 1px ${color}`
705
+ }
706
+ })
707
+ });
708
+ }
709
+ /** Non-interactive row body; its caller owns treeitems, buttons and selection. */
710
+ function WaterfallRow({ label, attribution, startMs, endMs, window, tone, formatDuration = formatWaterfallDuration, className, timingClassName, durationClassName }) {
711
+ return /* @__PURE__ */ jsxs("span", {
712
+ "data-waterfall-row": "",
713
+ className,
714
+ style: rowStyle,
715
+ children: [
716
+ /* @__PURE__ */ jsx("span", {
717
+ "data-waterfall-label": "",
718
+ style: labelStyle,
719
+ children: label
720
+ }),
721
+ attribution,
722
+ /* @__PURE__ */ jsx(WaterfallBar, {
723
+ startMs,
724
+ endMs,
725
+ window,
726
+ tone,
727
+ formatDuration,
728
+ className: timingClassName
729
+ }),
730
+ /* @__PURE__ */ jsx("span", {
731
+ "data-waterfall-duration": "",
732
+ className: durationClassName,
733
+ style: durationStyle,
734
+ children: ordered(startMs, endMs) ? formatDuration(endMs - startMs) : "—"
735
+ })
736
+ ]
737
+ });
738
+ }
739
+ function WaterfallHeader({ label = "Span", attribution, durationLabel = "Duration", ...axis }) {
740
+ return /* @__PURE__ */ jsxs("div", {
741
+ "data-waterfall-header": "",
742
+ style: rowStyle,
743
+ children: [
744
+ /* @__PURE__ */ jsx("span", {
745
+ style: labelStyle,
746
+ children: label
747
+ }),
748
+ attribution,
749
+ /* @__PURE__ */ jsx(WaterfallTimeAxis, { ...axis }),
750
+ /* @__PURE__ */ jsx("span", {
751
+ style: durationStyle,
752
+ children: durationLabel
753
+ })
754
+ ]
755
+ });
756
+ }
757
+ //#endregion
758
+ export { DEFAULT_SPARKLINE_EMPTY_LABEL, DEFAULT_SPARKLINE_HEIGHT, DEFAULT_SPARKLINE_LABEL, DEFAULT_SPARKLINE_UNAVAILABLE_LABEL, DEFAULT_SPARKLINE_WIDTH, Sparkline, StackedBarChart, WaterfallBar, WaterfallHeader, WaterfallRow, WaterfallTimeAxis, formatSparklineValue, formatWaterfallDuration, sparklineGeometry, sparklineLabel, sparklinePointsAttribute, sparklineReadings, waterfallSpanGeometry, waterfallTicks };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/charts",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Benchmark figures as static SVG and HTML tables: ranked rates, cost frontier, comparison intervals, task matrix and breakdown. Draws from producer data and refuses what it cannot draw honestly.",
5
5
  "type": "module",
6
6
  "license": "MIT",