@tangle-network/charts 0.3.0 → 0.4.0

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
@@ -2,6 +2,8 @@
2
2
 
3
3
  Benchmark figures as static SVG and HTML tables, plus optional React timing primitives.
4
4
 
5
+ The React entry also exports `DayBars`, `HBars`, `Histogram`, `Lines`, `Meter`, `Spark`, and `Gantt` for live operational pages. Import `@tangle-network/charts/react/live.css` for their layout and set `--chart-live-*` theme tokens on an ancestor. Data, state colors, destination links, labels, and interpretation remain with the caller. `DayBars` accepts an `onOpen(index)` action with keyboard activation.
6
+
5
7
  `WaterfallHeader`, `WaterfallTimeAxis`, `WaterfallBar` and `WaterfallRow` from
6
8
  `@tangle-network/charts/react` own the timing presentation extracted from
7
9
  Intelligence's trace viewer. Supply actual span start/end milliseconds and the
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) {
@@ -131,4 +131,91 @@ interface WaterfallHeaderProps extends WaterfallTimeAxisProps {
131
131
  }
132
132
  export declare function WaterfallHeader({ label, attribution, durationLabel, ...axis }: WaterfallHeaderProps): import("react").JSX.Element;
133
133
  //#endregion
134
+ //#region src/react/live.d.ts
135
+ export declare const LIVE_SERIES: readonly ['#8b7cf6', '#0ea5a0', '#c98500', '#e2508f', '#4a90e2'];
136
+ export declare const formatCompact: (value: number | null | undefined) => string;
137
+ /** Bars per day (or per any label: `tick` shortens it on the axis), stacked by series, on one axis; hovering a bar lists
138
+ * every series' value for it, and `onOpen` makes a bar a link. */
139
+ export declare function DayBars({ days, series, format, rows, missing, tick, onOpen }: {
140
+ days: string[];
141
+ series: {
142
+ name: string;
143
+ color: string;
144
+ values: (number | null)[];
145
+ }[];
146
+ format?: (v: number) => string;
147
+ rows?: number;
148
+ missing?: string;
149
+ tick?: (day: string) => string;
150
+ onOpen?: (index: number) => void;
151
+ }): import("react").JSX.Element;
152
+ /** Ranked horizontal bars with the value and an optional note on each row; past `limit` rows the rest are counted. */
153
+ export declare function HBars({ rows, format, color, max, limit }: {
154
+ rows: {
155
+ label: string;
156
+ value: number;
157
+ note?: string;
158
+ color?: string;
159
+ href?: string;
160
+ }[];
161
+ format?: (v: number) => string;
162
+ color?: string;
163
+ max?: number;
164
+ limit?: number;
165
+ }): import("react").JSX.Element;
166
+ /** A histogram of counts per bin; each bar's tooltip names its range and count. */
167
+ export declare function Histogram({ bins, format, color, unit, rows }: {
168
+ bins: {
169
+ lo: number;
170
+ hi: number;
171
+ n: number;
172
+ }[];
173
+ format?: (v: number) => string;
174
+ color?: string;
175
+ unit: string;
176
+ rows?: number;
177
+ }): import("react").JSX.Element;
178
+ /** Lines over time on one axis (at most four series); the crosshair readout lists each series at the nearest time. A
179
+ * `dots` series draws its points as marks (checkpoints) instead of a line; `axis` labels the time axis (default MM-DD). */
180
+ export declare function Lines({ series, format, rows, axis }: {
181
+ series: {
182
+ name: string;
183
+ color: string;
184
+ points: [number, number][];
185
+ dots?: boolean;
186
+ label?: (t: number) => string;
187
+ }[];
188
+ format?: (v: number) => string;
189
+ rows?: number;
190
+ axis?: (t: number) => string;
191
+ }): import("react").JSX.Element;
192
+ /** A used-of-total meter with its numbers. */
193
+ export declare function Meter({ used, total, format, tone, text }: {
194
+ used: number | null | undefined;
195
+ total: number | null | undefined;
196
+ format?: (v: number) => string;
197
+ tone?: string;
198
+ text?: string;
199
+ }): import("react").JSX.Element;
200
+ /** A small line of one series for a row in a list (one hue; the row's label carries identity). */
201
+ export declare function Spark({ points, max, color }: {
202
+ points: [number, number][];
203
+ max?: number;
204
+ color?: string;
205
+ }): import("react").JSX.Element;
206
+ /** One bar per row on a shared time axis (who worked when): each row's bar spans its start to its end, coloured by its
207
+ * state; hovering a row names it, its state and its span. Times are epoch ms; `axis` labels the time axis. */
208
+ export declare function Gantt({ rows, colors, axis, span }: {
209
+ rows: {
210
+ label: string;
211
+ start: number;
212
+ end: number;
213
+ state: string;
214
+ note?: string;
215
+ }[];
216
+ colors: Record<string, string>;
217
+ axis: (t: number) => string;
218
+ span: (start: number, end: number) => string;
219
+ }): import("react").JSX.Element;
220
+ //#endregion
134
221
  export type { SparklineDirection, SparklineGeometry, SparklineGeometryOptions, SparklineLabelOptions, SparklinePoint, SparklineProps, SparklineValues, StackedBarBucket, StackedBarChartProps, StackedBarSegment, StackedBarSeries, WaterfallBarProps, WaterfallHeaderProps, WaterfallRowProps, WaterfallSpanGeometry, WaterfallTimeAxisProps, WaterfallWindow };
@@ -1,5 +1,5 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { Fragment, useId, useRef, useState } from "react";
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment as Fragment$1, useEffect, useId, useRef, useState } from "react";
3
3
  //#region src/react/sparkline.tsx
4
4
  const DEFAULT_SPARKLINE_WIDTH = 96;
5
5
  const DEFAULT_SPARKLINE_HEIGHT = 24;
@@ -515,7 +515,7 @@ function StackedBarChart(props) {
515
515
  children: "Value"
516
516
  })
517
517
  ] }) }),
518
- /* @__PURE__ */ jsx("tbody", { children: buckets.map((bucket) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("tr", { children: [
518
+ /* @__PURE__ */ jsx("tbody", { children: buckets.map((bucket) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("tr", { children: [
519
519
  /* @__PURE__ */ jsx("th", {
520
520
  scope: "row",
521
521
  children: bucket.label
@@ -755,4 +755,561 @@ function WaterfallHeader({ label = "Span", attribution, durationLabel = "Duratio
755
755
  });
756
756
  }
757
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 };
758
+ //#region src/react/live.tsx
759
+ /** Reusable, em-scaled charts for live operational series. Values, labels and status colors belong to callers. */
760
+ const LIVE_SERIES = [
761
+ "#8b7cf6",
762
+ "#0ea5a0",
763
+ "#c98500",
764
+ "#e2508f",
765
+ "#4a90e2"
766
+ ];
767
+ const formatCompact = (value) => value === null || value === void 0 || !Number.isFinite(value) ? "—" : new Intl.NumberFormat("en-US", {
768
+ notation: "compact",
769
+ maximumFractionDigits: 1
770
+ }).format(value);
771
+ const formatShare = (value) => value === null || !Number.isFinite(value) ? "—" : `${Math.round(value * 100)}%`;
772
+ /** The rendered width and font size of an element, so an SVG chart draws at its real size and its geometry follows
773
+ * the page's type. */
774
+ function useWidth() {
775
+ const [element, ref] = useState(null);
776
+ const [size, setSize] = useState({
777
+ width: 0,
778
+ em: 16
779
+ });
780
+ useEffect(() => {
781
+ if (!element) return;
782
+ const observer = new ResizeObserver(([entry]) => setSize({
783
+ width: Math.floor(entry.contentRect.width),
784
+ em: parseFloat(getComputedStyle(element).fontSize) || 16
785
+ }));
786
+ observer.observe(element);
787
+ return () => observer.disconnect();
788
+ }, [element]);
789
+ return [
790
+ ref,
791
+ size.width,
792
+ size.em
793
+ ];
794
+ }
795
+ /** A nice ceiling for an axis and its three gridline values. */
796
+ function ticksFor(max) {
797
+ if (!(max > 0)) return {
798
+ top: 1,
799
+ ticks: [
800
+ 0,
801
+ .5,
802
+ 1
803
+ ]
804
+ };
805
+ const power = 10 ** Math.floor(Math.log10(max));
806
+ const step = [
807
+ 1,
808
+ 2,
809
+ 2.5,
810
+ 5,
811
+ 10
812
+ ].map((m) => m * power).find((s) => s * 4 >= max) ?? power * 10;
813
+ const top = Math.ceil(max / step) * step;
814
+ return {
815
+ top,
816
+ ticks: [
817
+ 0,
818
+ top / 2,
819
+ top
820
+ ]
821
+ };
822
+ }
823
+ function Tooltip({ tip }) {
824
+ if (!tip) return null;
825
+ return /* @__PURE__ */ jsxs("div", {
826
+ className: "ov-tip",
827
+ role: "tooltip",
828
+ style: {
829
+ left: tip.x,
830
+ top: tip.y
831
+ },
832
+ children: [/* @__PURE__ */ jsx("strong", { children: tip.title }), tip.lines.map((line) => /* @__PURE__ */ jsxs("span", {
833
+ className: "ov-tip-line",
834
+ children: [
835
+ line.color && /* @__PURE__ */ jsx("i", { style: { background: line.color } }),
836
+ /* @__PURE__ */ jsx("span", { children: line.label }),
837
+ /* @__PURE__ */ jsx("b", { children: line.value })
838
+ ]
839
+ }, line.label))]
840
+ });
841
+ }
842
+ /** Bars per day (or per any label: `tick` shortens it on the axis), stacked by series, on one axis; hovering a bar lists
843
+ * every series' value for it, and `onOpen` makes a bar a link. */
844
+ function DayBars({ days, series, format = formatCompact, rows = 13, missing = "No runs started", tick = (day) => day.slice(5), onOpen }) {
845
+ const [ref, width, em] = useWidth();
846
+ const height = Math.round(rows * em);
847
+ const [tip, setTip] = useState(null);
848
+ const totals = days.map((_, i) => series.reduce((sum, s) => sum + (s.values[i] ?? 0), 0));
849
+ const { top, ticks } = ticksFor(Math.max(...totals));
850
+ const left = Math.round(3.4 * em);
851
+ const bottom = Math.round(1.8 * em);
852
+ const plotW = Math.max(0, width - left - 8);
853
+ const plotH = height - bottom - 8;
854
+ const slot = days.length ? plotW / days.length : 0;
855
+ const bar = Math.max(3, slot * .72);
856
+ const labelEvery = Math.max(1, Math.ceil(days.length / Math.max(1, Math.floor(plotW / (4.5 * em)))));
857
+ const labelGap = Math.ceil(4.5 * em / Math.max(slot, 1));
858
+ const y = (v) => 8 + plotH - v / top * plotH;
859
+ return /* @__PURE__ */ jsxs("div", {
860
+ ref,
861
+ className: "ov-plot",
862
+ onMouseLeave: () => setTip(null),
863
+ children: [width > 0 && /* @__PURE__ */ jsxs("svg", {
864
+ width,
865
+ height,
866
+ role: "img",
867
+ "aria-label": `${series.map((s) => s.name).join(", ")} per day`,
868
+ children: [ticks.map((t) => /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("line", {
869
+ x1: left,
870
+ x2: width - 8,
871
+ y1: y(t),
872
+ y2: y(t),
873
+ className: "ov-gridline"
874
+ }), /* @__PURE__ */ jsx("text", {
875
+ x: left - em * .4,
876
+ y: y(t),
877
+ dominantBaseline: "middle",
878
+ className: "ov-axis",
879
+ textAnchor: "end",
880
+ children: format(t)
881
+ })] }, t)), days.map((day, i) => {
882
+ let base = 0;
883
+ const x = left + i * slot + (slot - bar) / 2;
884
+ const showTip = () => setTip({
885
+ x: Math.min(left + i * slot + slot + 8, width - em * 14),
886
+ y: 8,
887
+ title: `${day} · ${series.every((s) => s.values[i] === null) ? missing : format(totals[i] ?? 0)}`,
888
+ lines: series.map((s) => ({
889
+ label: s.name,
890
+ value: s.values[i] === null ? missing : format(s.values[i] ?? 0),
891
+ color: s.color
892
+ }))
893
+ });
894
+ return /* @__PURE__ */ jsxs("g", {
895
+ role: onOpen ? "button" : void 0,
896
+ tabIndex: onOpen ? 0 : void 0,
897
+ "aria-label": onOpen ? `Open ${day}` : void 0,
898
+ onFocus: onOpen ? showTip : void 0,
899
+ onBlur: onOpen ? () => setTip(null) : void 0,
900
+ onKeyDown: onOpen ? (event) => {
901
+ if (event.key === "Enter" || event.key === " ") {
902
+ event.preventDefault();
903
+ onOpen(i);
904
+ }
905
+ } : void 0,
906
+ children: [
907
+ series.map((s) => {
908
+ const v = s.values[i] ?? 0;
909
+ if (!v) return null;
910
+ const y0 = y(base);
911
+ base += v;
912
+ const y1 = y(base);
913
+ return /* @__PURE__ */ jsx("rect", {
914
+ x,
915
+ y: y1,
916
+ width: bar,
917
+ height: Math.max(2, y0 - y1 - 2),
918
+ rx: 2,
919
+ fill: s.color
920
+ }, s.name);
921
+ }),
922
+ (i === days.length - 1 || i % labelEvery === 0 && i + labelGap <= days.length - 1) && /* @__PURE__ */ jsx("text", {
923
+ x: x + bar / 2,
924
+ y: height - em * .35,
925
+ className: "ov-axis",
926
+ textAnchor: "middle",
927
+ children: tick(day)
928
+ }),
929
+ /* @__PURE__ */ jsx("rect", {
930
+ x: left + i * slot,
931
+ y: 8,
932
+ width: slot,
933
+ height: plotH,
934
+ fill: "transparent",
935
+ style: onOpen ? { cursor: "pointer" } : void 0,
936
+ onClick: onOpen ? () => onOpen(i) : void 0,
937
+ onMouseEnter: showTip
938
+ })
939
+ ]
940
+ }, day);
941
+ })]
942
+ }), /* @__PURE__ */ jsx(Tooltip, { tip })]
943
+ });
944
+ }
945
+ /** Ranked horizontal bars with the value and an optional note on each row; past `limit` rows the rest are counted. */
946
+ function HBars({ rows, format = formatCompact, color = LIVE_SERIES[0], max, limit = 8 }) {
947
+ const top = max ?? Math.max(1, ...rows.map((row) => row.value));
948
+ if (!rows.length) return /* @__PURE__ */ jsx("p", {
949
+ className: "ov-empty",
950
+ children: "Nothing recorded."
951
+ });
952
+ const rest = rows.slice(limit);
953
+ return /* @__PURE__ */ jsxs("ol", {
954
+ className: "ov-hbars",
955
+ children: [rows.slice(0, limit).map((row) => {
956
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
957
+ /* @__PURE__ */ jsx("span", {
958
+ className: "ov-hbar-label",
959
+ title: row.label,
960
+ children: row.label
961
+ }),
962
+ /* @__PURE__ */ jsx("span", {
963
+ className: "ov-hbar-track",
964
+ children: /* @__PURE__ */ jsx("span", { style: {
965
+ width: `${Math.max(1, row.value / top * 100)}%`,
966
+ background: row.color ?? color
967
+ } })
968
+ }),
969
+ /* @__PURE__ */ jsx("span", {
970
+ className: "ov-hbar-value",
971
+ children: format(row.value)
972
+ }),
973
+ row.note && /* @__PURE__ */ jsx("span", {
974
+ className: "ov-hbar-note",
975
+ children: row.note
976
+ })
977
+ ] });
978
+ return /* @__PURE__ */ jsx("li", { children: row.href ? /* @__PURE__ */ jsx("a", {
979
+ href: row.href,
980
+ children: content
981
+ }) : /* @__PURE__ */ jsx("div", { children: content }) }, row.label);
982
+ }), rest.length > 0 && /* @__PURE__ */ jsxs("li", {
983
+ className: "ov-hbars-more",
984
+ children: [
985
+ "+",
986
+ rest.length,
987
+ " more · ",
988
+ format(rest.reduce((a, row) => a + row.value, 0))
989
+ ]
990
+ })]
991
+ });
992
+ }
993
+ /** A histogram of counts per bin; each bar's tooltip names its range and count. */
994
+ function Histogram({ bins, format = formatCompact, color = LIVE_SERIES[0], unit, rows = 12 }) {
995
+ const [ref, width, em] = useWidth();
996
+ const height = Math.round(rows * em);
997
+ const [tip, setTip] = useState(null);
998
+ const first = bins.findIndex((b) => b.n > 0);
999
+ const last = bins.length - 1 - [...bins].reverse().findIndex((b) => b.n > 0);
1000
+ const shown = first < 0 ? [] : bins.slice(first, last + 1);
1001
+ const { top, ticks } = ticksFor(Math.max(...shown.map((b) => b.n), 1));
1002
+ const left = Math.round(3 * em);
1003
+ const bottom = Math.round(1.8 * em);
1004
+ const plotW = Math.max(0, width - left - 8);
1005
+ const plotH = height - bottom - 8;
1006
+ const slot = shown.length ? plotW / shown.length : 0;
1007
+ const y = (v) => 8 + plotH - v / top * plotH;
1008
+ if (!shown.length) return /* @__PURE__ */ jsx("p", {
1009
+ className: "ov-empty",
1010
+ children: "Nothing recorded."
1011
+ });
1012
+ return /* @__PURE__ */ jsxs("div", {
1013
+ ref,
1014
+ className: "ov-plot",
1015
+ onMouseLeave: () => setTip(null),
1016
+ children: [width > 0 && /* @__PURE__ */ jsxs("svg", {
1017
+ width,
1018
+ height,
1019
+ role: "img",
1020
+ "aria-label": `Distribution of ${unit}`,
1021
+ children: [ticks.map((t) => /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("line", {
1022
+ x1: left,
1023
+ x2: width - 8,
1024
+ y1: y(t),
1025
+ y2: y(t),
1026
+ className: "ov-gridline"
1027
+ }), /* @__PURE__ */ jsx("text", {
1028
+ x: left - em * .4,
1029
+ y: y(t),
1030
+ dominantBaseline: "middle",
1031
+ className: "ov-axis",
1032
+ textAnchor: "end",
1033
+ children: formatCompact(t)
1034
+ })] }, t)), shown.map((bin, i) => /* @__PURE__ */ jsxs("g", { children: [
1035
+ /* @__PURE__ */ jsx("rect", {
1036
+ x: left + i * slot + 2,
1037
+ y: y(bin.n),
1038
+ width: Math.max(3, slot - 4),
1039
+ height: Math.max(bin.n ? 2 : 0, 8 + plotH - y(bin.n)),
1040
+ rx: 2,
1041
+ fill: color
1042
+ }),
1043
+ (i % Math.max(1, Math.ceil(shown.length / 6)) === 0 || i === shown.length - 1) && /* @__PURE__ */ jsx("text", {
1044
+ x: left + i * slot + slot / 2,
1045
+ y: height - em * .35,
1046
+ className: "ov-axis",
1047
+ textAnchor: "middle",
1048
+ children: format(bin.lo)
1049
+ }),
1050
+ /* @__PURE__ */ jsx("rect", {
1051
+ x: left + i * slot,
1052
+ y: 8,
1053
+ width: slot,
1054
+ height: plotH,
1055
+ fill: "transparent",
1056
+ onMouseEnter: () => setTip({
1057
+ x: Math.min(left + i * slot + slot + 6, width - em * 14),
1058
+ y: 8,
1059
+ title: `${format(bin.lo)} to ${format(bin.hi)} ${unit}`,
1060
+ lines: [{
1061
+ label: "count",
1062
+ value: String(bin.n)
1063
+ }]
1064
+ })
1065
+ })
1066
+ ] }, i))]
1067
+ }), /* @__PURE__ */ jsx(Tooltip, { tip })]
1068
+ });
1069
+ }
1070
+ /** Lines over time on one axis (at most four series); the crosshair readout lists each series at the nearest time. A
1071
+ * `dots` series draws its points as marks (checkpoints) instead of a line; `axis` labels the time axis (default MM-DD). */
1072
+ function Lines({ series, format = formatCompact, rows = 13, axis }) {
1073
+ const [ref, width, em] = useWidth();
1074
+ const height = Math.round(rows * em);
1075
+ const [hover, setHover] = useState(null);
1076
+ const all = series.flatMap((s) => s.points);
1077
+ if (!all.length) return /* @__PURE__ */ jsx("p", {
1078
+ className: "ov-empty",
1079
+ children: "No history yet."
1080
+ });
1081
+ const t0 = Math.min(...all.map((p) => p[0]));
1082
+ const t1 = Math.max(...all.map((p) => p[0]));
1083
+ const { top, ticks } = ticksFor(Math.max(...all.map((p) => p[1])));
1084
+ const left = Math.round(3.4 * em);
1085
+ const bottom = Math.round(1.8 * em);
1086
+ const plotW = Math.max(0, width - left - 10);
1087
+ const plotH = height - bottom - 8;
1088
+ const x = (t) => left + (t1 > t0 ? (t - t0) / (t1 - t0) * plotW : 0);
1089
+ const y = (v) => 8 + plotH - v / top * plotH;
1090
+ const nearest = (points, t) => points.reduce((best, p) => Math.abs(p[0] - t) < Math.abs(best[0] - t) ? p : best, points[0]);
1091
+ const day = (t) => new Date(t).toISOString().slice(5, 16).replace("T", " ");
1092
+ return /* @__PURE__ */ jsxs("div", {
1093
+ ref,
1094
+ className: "ov-plot",
1095
+ onMouseMove: (event) => {
1096
+ const box = event.currentTarget.getBoundingClientRect();
1097
+ const px = event.clientX - box.left;
1098
+ setHover(px >= left ? t0 + (px - left) / Math.max(1, plotW) * (t1 - t0) : null);
1099
+ },
1100
+ onMouseLeave: () => setHover(null),
1101
+ children: [width > 0 && /* @__PURE__ */ jsxs("svg", {
1102
+ width,
1103
+ height,
1104
+ role: "img",
1105
+ "aria-label": series.map((s) => s.name).join(", "),
1106
+ children: [
1107
+ ticks.map((t) => /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("line", {
1108
+ x1: left,
1109
+ x2: width - 10,
1110
+ y1: y(t),
1111
+ y2: y(t),
1112
+ className: "ov-gridline"
1113
+ }), /* @__PURE__ */ jsx("text", {
1114
+ x: left - em * .4,
1115
+ y: y(t),
1116
+ dominantBaseline: "middle",
1117
+ className: "ov-axis",
1118
+ textAnchor: "end",
1119
+ children: format(t)
1120
+ })] }, t)),
1121
+ [
1122
+ t0,
1123
+ (t0 + t1) / 2,
1124
+ t1
1125
+ ].map((t, i) => /* @__PURE__ */ jsx("text", {
1126
+ x: x(t),
1127
+ y: height - em * .35,
1128
+ className: "ov-axis",
1129
+ textAnchor: i === 0 ? "start" : i === 2 ? "end" : "middle",
1130
+ children: axis ? axis(t) : day(t).slice(0, 5)
1131
+ }, i)),
1132
+ series.map((s) => s.dots ? /* @__PURE__ */ jsx("g", { children: s.points.map((p) => /* @__PURE__ */ jsx("circle", {
1133
+ cx: x(p[0]),
1134
+ cy: y(p[1]),
1135
+ r: Math.max(3, em / 5),
1136
+ fill: s.color,
1137
+ stroke: "var(--chart-live-surface, #fff)",
1138
+ strokeWidth: 1.5
1139
+ }, p[0])) }, s.name) : /* @__PURE__ */ jsx("polyline", {
1140
+ fill: "none",
1141
+ stroke: s.color,
1142
+ strokeWidth: Math.max(2, em / 8),
1143
+ strokeLinejoin: "round",
1144
+ points: s.points.map((p) => `${x(p[0])},${y(p[1])}`).join(" ")
1145
+ }, s.name)),
1146
+ hover !== null && /* @__PURE__ */ jsx("line", {
1147
+ x1: x(hover),
1148
+ x2: x(hover),
1149
+ y1: 8,
1150
+ y2: 8 + plotH,
1151
+ className: "ov-crosshair"
1152
+ }),
1153
+ hover !== null && series.map((s) => {
1154
+ const p = s.points.length ? nearest(s.points, hover) : null;
1155
+ return p ? /* @__PURE__ */ jsx("circle", {
1156
+ cx: x(p[0]),
1157
+ cy: y(p[1]),
1158
+ r: Math.max(4, em / 4),
1159
+ fill: s.color,
1160
+ stroke: "var(--chart-live-surface, #fff)",
1161
+ strokeWidth: 2
1162
+ }, s.name) : null;
1163
+ })
1164
+ ]
1165
+ }), hover !== null && /* @__PURE__ */ jsx(Tooltip, { tip: {
1166
+ x: Math.min(x(hover) + 10, width - em * 14),
1167
+ y: 8,
1168
+ title: day(hover),
1169
+ lines: series.filter((s) => s.points.length).map((s) => {
1170
+ const p = nearest(s.points, hover);
1171
+ return {
1172
+ label: s.label ? s.label(p[0]) : s.name,
1173
+ value: format(p[1]),
1174
+ color: s.color
1175
+ };
1176
+ })
1177
+ } })]
1178
+ });
1179
+ }
1180
+ /** A used-of-total meter with its numbers. */
1181
+ function Meter({ used, total, format = formatCompact, tone, text }) {
1182
+ const share = used !== null && used !== void 0 && total ? Math.min(1, used / total) : null;
1183
+ return /* @__PURE__ */ jsxs("div", {
1184
+ className: "ov-meter",
1185
+ children: [/* @__PURE__ */ jsx("span", {
1186
+ className: "ov-meter-track",
1187
+ children: /* @__PURE__ */ jsx("span", { style: {
1188
+ width: `${(share ?? 0) * 100}%`,
1189
+ background: tone ?? (share !== null && share > .9 ? "var(--chart-live-danger, #d85050)" : share !== null && share > .7 ? "var(--chart-live-warning, #c98500)" : LIVE_SERIES[0])
1190
+ } })
1191
+ }), /* @__PURE__ */ jsx("span", {
1192
+ className: "ov-meter-text",
1193
+ children: text ?? `${format(used ?? NaN)} of ${format(total ?? NaN)} · ${formatShare(share)}`
1194
+ })]
1195
+ });
1196
+ }
1197
+ /** A small line of one series for a row in a list (one hue; the row's label carries identity). */
1198
+ function Spark({ points, max = 1, color = LIVE_SERIES[0] }) {
1199
+ if (points.length < 2) return /* @__PURE__ */ jsx("span", {
1200
+ className: "ov-spark-empty",
1201
+ children: "collecting"
1202
+ });
1203
+ const t0 = points[0][0];
1204
+ const t1 = points.at(-1)[0];
1205
+ const w = 160;
1206
+ const h = 32;
1207
+ const domain = Math.max(1, max);
1208
+ const d = points.map((p) => `${(p[0] - t0) / Math.max(1, t1 - t0) * w},${30 - Math.min(domain, p[1]) / domain * 28}`).join(" ");
1209
+ return /* @__PURE__ */ jsx("svg", {
1210
+ viewBox: `0 0 ${w} ${h}`,
1211
+ className: "ov-spark",
1212
+ "aria-hidden": "true",
1213
+ children: /* @__PURE__ */ jsx("polyline", {
1214
+ fill: "none",
1215
+ stroke: color,
1216
+ strokeWidth: 1.6,
1217
+ points: d
1218
+ })
1219
+ });
1220
+ }
1221
+ /** One bar per row on a shared time axis (who worked when): each row's bar spans its start to its end, coloured by its
1222
+ * state; hovering a row names it, its state and its span. Times are epoch ms; `axis` labels the time axis. */
1223
+ function Gantt({ rows, colors, axis, span }) {
1224
+ const [ref, width, em] = useWidth();
1225
+ const [tip, setTip] = useState(null);
1226
+ if (!rows.length) return /* @__PURE__ */ jsx("p", {
1227
+ className: "ov-empty",
1228
+ children: "No agent has started."
1229
+ });
1230
+ const rowH = Math.round(1.25 * em);
1231
+ const labelW = Math.min(Math.round(13 * em), Math.round(width * .32));
1232
+ const top = 4;
1233
+ const bottom = Math.round(1.8 * em);
1234
+ const height = top + rows.length * rowH + bottom;
1235
+ const t0 = Math.min(...rows.map((row) => row.start));
1236
+ const t1 = Math.max(...rows.map((row) => row.end), t0 + 6e4);
1237
+ const plotW = Math.max(0, width - labelW - 10);
1238
+ const x = (t) => labelW + (t - t0) / (t1 - t0) * plotW;
1239
+ const fallback = colors.unknown ?? "#8e8e8e";
1240
+ return /* @__PURE__ */ jsxs("div", {
1241
+ ref,
1242
+ className: "ov-plot",
1243
+ onMouseLeave: () => setTip(null),
1244
+ children: [width > 0 && /* @__PURE__ */ jsxs("svg", {
1245
+ width,
1246
+ height,
1247
+ role: "img",
1248
+ "aria-label": `${rows.length} agents over time`,
1249
+ children: [[
1250
+ 0,
1251
+ .25,
1252
+ .5,
1253
+ .75,
1254
+ 1
1255
+ ].map((f) => /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("line", {
1256
+ x1: labelW + f * plotW,
1257
+ x2: labelW + f * plotW,
1258
+ y1: top,
1259
+ y2: top + rows.length * rowH,
1260
+ className: "ov-gridline"
1261
+ }), /* @__PURE__ */ jsx("text", {
1262
+ x: labelW + f * plotW,
1263
+ y: height - em * .35,
1264
+ className: "ov-axis",
1265
+ textAnchor: f === 0 ? "start" : f === 1 ? "end" : "middle",
1266
+ children: axis(t0 + f * (t1 - t0))
1267
+ })] }, f)), rows.map((row, i) => {
1268
+ const y = top + i * rowH;
1269
+ const color = colors[row.state] ?? fallback;
1270
+ return /* @__PURE__ */ jsxs("g", {
1271
+ onMouseEnter: () => setTip({
1272
+ x: Math.min(x(row.end) + 8, width - em * 16),
1273
+ y: Math.max(0, y - em),
1274
+ title: row.label,
1275
+ lines: [{
1276
+ label: row.state,
1277
+ value: span(row.start, row.end),
1278
+ color
1279
+ }, ...row.note ? [{
1280
+ label: row.note,
1281
+ value: ""
1282
+ }] : []]
1283
+ }),
1284
+ children: [
1285
+ /* @__PURE__ */ jsx("rect", {
1286
+ x: 0,
1287
+ y,
1288
+ width,
1289
+ height: rowH,
1290
+ fill: "transparent"
1291
+ }),
1292
+ /* @__PURE__ */ jsx("text", {
1293
+ x: labelW - em * .5,
1294
+ y: y + rowH / 2,
1295
+ dominantBaseline: "middle",
1296
+ textAnchor: "end",
1297
+ className: "ov-axis ov-gantt-label",
1298
+ children: row.label.length > 26 ? `${row.label.slice(0, 25)}…` : row.label
1299
+ }),
1300
+ /* @__PURE__ */ jsx("rect", {
1301
+ x: x(row.start),
1302
+ y: y + rowH * .18,
1303
+ width: Math.max(2, x(row.end) - x(row.start)),
1304
+ height: rowH * .64,
1305
+ rx: 2,
1306
+ fill: color
1307
+ })
1308
+ ]
1309
+ }, `${row.label}-${i}`);
1310
+ })]
1311
+ }), /* @__PURE__ */ jsx(Tooltip, { tip })]
1312
+ });
1313
+ }
1314
+ //#endregion
1315
+ export { DEFAULT_SPARKLINE_EMPTY_LABEL, DEFAULT_SPARKLINE_HEIGHT, DEFAULT_SPARKLINE_LABEL, DEFAULT_SPARKLINE_UNAVAILABLE_LABEL, DEFAULT_SPARKLINE_WIDTH, DayBars, Gantt, HBars, Histogram, LIVE_SERIES, Lines, Meter, Spark, Sparkline, StackedBarChart, WaterfallBar, WaterfallHeader, WaterfallRow, WaterfallTimeAxis, formatCompact, formatSparklineValue, formatWaterfallDuration, sparklineGeometry, sparklineLabel, sparklinePointsAttribute, sparklineReadings, waterfallSpanGeometry, waterfallTicks };
@@ -0,0 +1,27 @@
1
+ /* Shared live charts. Consumers may set --chart-live-* tokens on an ancestor. */
2
+ .ov-empty { margin: 0; color: var(--chart-live-faint, #777); font-size: .9em; }
3
+ .ov-plot { position: relative; min-width: 0; }
4
+ .ov-plot svg { display: block; overflow: visible; }
5
+ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%, transparent); stroke-width: 1; }
6
+ .ov-axis { fill: var(--chart-live-muted, #666); font: .8em var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; }
7
+ .ov-crosshair { stroke: var(--chart-live-faint, #777); stroke-dasharray: 3 3; }
8
+ .ov-tip { position: absolute; z-index: 4; display: grid; gap: .25em; min-width: 12em; max-width: 18em; padding: .6em .75em; background: var(--chart-live-raised, #fff); border: 1px solid var(--chart-live-border, #aaa); border-radius: 8px; box-shadow: 0 10px 24px -12px #000; font-size: .85em; pointer-events: none; }
9
+ .ov-tip-line { display: grid; grid-template-columns: auto 1fr auto; gap: .6em; align-items: center; color: var(--chart-live-muted, #666); }
10
+ .ov-tip-line b { color: var(--chart-live-fg, #222); font-variant-numeric: tabular-nums; }
11
+ .ov-hbars { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, max-content) minmax(33%, 1fr) max-content; gap: .25em .7em; }
12
+ .ov-hbars li { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; }
13
+ .ov-hbars li > a, .ov-hbars li > div { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; row-gap: .15em; align-items: center; padding: .3em .4em; border-radius: 6px; color: inherit; text-decoration: none; }
14
+ .ov-hbars-more { grid-column: 1 / -1; padding: .1em .4em; font-size: .8em; color: var(--chart-live-faint, #777); }
15
+ .ov-hbars li > a:hover, .ov-hbars li > div:hover { background: var(--chart-live-hover, #eee); text-decoration: none; }
16
+ .ov-hbar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .95em; color: var(--chart-live-fg, #222); }
17
+ .ov-hbar-track { height: .7em; border-radius: 3px; background: color-mix(in srgb, var(--chart-live-faint, #777) 14%, transparent); overflow: hidden; }
18
+ .ov-hbar-track > span { display: block; height: 100%; border-radius: 3px; }
19
+ .ov-hbar-value { font: .9em var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; text-align: right; color: var(--chart-live-fg, #222); }
20
+ .ov-hbar-note { grid-column: 1 / -1; font-size: .78em; color: var(--chart-live-faint, #777); }
21
+ .ov-meter { display: grid; gap: .3em; min-width: 0; }
22
+ .ov-meter-track { display: block; height: .7em; border-radius: 3px; background: color-mix(in srgb, var(--chart-live-faint, #777) 14%, transparent); overflow: hidden; }
23
+ .ov-meter-track > span { display: block; height: 100%; border-radius: 3px; }
24
+ .ov-meter-text { font: .85em var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; color: var(--chart-live-muted, #666); }
25
+ .ov-spark { display: block; width: 8em; height: 1.6em; }
26
+ .ov-spark-empty { font-size: .8em; color: var(--chart-live-faint, #777); }
27
+ .ov-tip-line i { display: inline-block; width: .7em; height: .7em; border-radius: 3px; margin-right: .4em; vertical-align: -.05em; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/charts",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
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",
@@ -14,7 +14,8 @@
14
14
  },
15
15
  "homepage": "https://github.com/tangle-network/brand/tree/main/packages/charts",
16
16
  "sideEffects": [
17
- "./dist/figure.css"
17
+ "./dist/figure.css",
18
+ "./dist/react/live.css"
18
19
  ],
19
20
  "files": [
20
21
  "dist",
@@ -27,6 +28,7 @@
27
28
  "default": "./dist/index.js"
28
29
  },
29
30
  "./figure.css": "./dist/figure.css",
31
+ "./react/live.css": "./dist/react/live.css",
30
32
  "./react": {
31
33
  "types": "./dist/react/index.d.ts",
32
34
  "import": "./dist/react/index.js",
@@ -53,6 +55,6 @@
53
55
  ],
54
56
  "scripts": {
55
57
  "gen:css": "node scripts/gen-css.mjs",
56
- "build": "node scripts/gen-css.mjs --check && tsdown && cp src/figure.css dist/figure.css"
58
+ "build": "node scripts/gen-css.mjs --check && tsdown && cp src/figure.css dist/figure.css && cp src/react/live.css dist/react/live.css"
57
59
  }
58
60
  }