@tangle-network/charts 0.6.0 → 0.8.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.
@@ -1,6 +1,6 @@
1
- import { d as hatchPath, i as eventsGap, m as markerPath, o as isRefusal } from "../events-Cw6abvgR.js";
1
+ import { F as hatchPath, R as markerPath, l as markName, o as eventsGap, r as MARK_HEADS, t as timeline, u as isRefusal } from "../timeline-CCWFvLxg.js";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { Fragment as Fragment$1, useEffect, useId, useRef, useState } from "react";
3
+ import { Fragment as Fragment$1, useEffect, useId, useMemo, useRef, useState } from "react";
4
4
  //#region src/react/sparkline.tsx
5
5
  const DEFAULT_SPARKLINE_WIDTH = 96;
6
6
  const DEFAULT_SPARKLINE_HEIGHT = 24;
@@ -247,7 +247,7 @@ function StackedBarChart(props) {
247
247
  validate(props);
248
248
  const bySeries = new Map(series.map((entry) => [entry.id, entry]));
249
249
  const formatted = (value) => isValue(value) ? formatValue(value) : unavailableLabel;
250
- const summary = (bucket) => `${bucket.label}: total ${formatted(bucket.total)}` + (hasGap(bucket) ? ` — ${unavailableLabel}` : "") + (bucket.segments.length ? ` — ${bucket.segments.map((segment) => `${bySeries.get(segment.seriesId).label} ${formatted(segment.value)}`).join(", ")}` : "");
250
+ const summary = (bucket) => `${bucket.label}: total ${formatted(bucket.total)}` + (hasGap(bucket) ? ` — ${unavailableLabel}` : "") + (bucket.segments.length ? ` — ${bucket.segments.map((segment) => `${bySeries.get(segment.seriesId).label} ${formatted(segment.value)}`).join(", ")}` : "") + (bucket.description ? ` — ${bucket.description}` : "");
251
251
  const activeIndex = selectedBucketId === null ? -1 : buckets.findIndex(({ id }) => id === selectedBucketId);
252
252
  const activeBucket = activeIndex >= 0 ? buckets[activeIndex] : null, marginTop = 12, marginRight = 16, marginLeft = 56;
253
253
  const chartHeight = 220;
@@ -490,7 +490,8 @@ function StackedBarChart(props) {
490
490
  bySeries.get(segment.seriesId).label,
491
491
  ": ",
492
492
  formatted(segment.value)
493
- ] }, segment.seriesId))
493
+ ] }, segment.seriesId)),
494
+ activeBucket.description && /* @__PURE__ */ jsx("div", { children: activeBucket.description })
494
495
  ]
495
496
  }),
496
497
  /* @__PURE__ */ jsx("div", {
@@ -1267,13 +1268,17 @@ function Band({ band, x, y, width, height }) {
1267
1268
  ]
1268
1269
  });
1269
1270
  }
1270
- /** A vertical event rule with its label; kinds differ by stroke pattern and the died cross as well as colour. */
1271
+ /** A vertical event rule with its label; kinds differ by stroke pattern, the died cross and the release, steer, ruling
1272
+ * and wake head shapes as well as colour, and a recorded verdict colours the rule. */
1271
1273
  function Mark({ mark, x, top, bottom, row, em, flip }) {
1272
1274
  const s = Math.max(3, em / 4);
1275
+ const head = MARK_HEADS[mark.kind];
1273
1276
  return /* @__PURE__ */ jsxs("g", {
1274
1277
  "data-mark": mark.kind,
1278
+ "data-verdict": mark.verdict,
1279
+ className: mark.verdict ? `ov-verdict-${mark.verdict}` : void 0,
1275
1280
  children: [
1276
- /* @__PURE__ */ jsx("title", { children: mark.label }),
1281
+ /* @__PURE__ */ jsx("title", { children: markName(mark) }),
1277
1282
  /* @__PURE__ */ jsx("line", {
1278
1283
  x1: x,
1279
1284
  x2: x,
@@ -1285,12 +1290,16 @@ function Mark({ mark, x, top, bottom, row, em, flip }) {
1285
1290
  d: `M${x - s} ${bottom - 2 * s}L${x + s} ${bottom}M${x - s} ${bottom}L${x + s} ${bottom - 2 * s}`,
1286
1291
  className: "ov-mark ov-mark-died"
1287
1292
  }),
1293
+ head && /* @__PURE__ */ jsx("path", {
1294
+ d: markerPath(head, x, top + s, s),
1295
+ className: "ov-mark-head"
1296
+ }),
1288
1297
  /* @__PURE__ */ jsx("text", {
1289
1298
  x: flip ? x - em * .3 : x + em * .3,
1290
1299
  y: top + em * (.9 + row),
1291
1300
  textAnchor: flip ? "end" : "start",
1292
1301
  className: "ov-mark-label",
1293
- children: mark.label
1302
+ children: markName(mark)
1294
1303
  })
1295
1304
  ]
1296
1305
  });
@@ -1301,7 +1310,7 @@ function EventList({ marks, bands, format }) {
1301
1310
  const items = [...marks.map((m) => ({
1302
1311
  at: m.at,
1303
1312
  kind: m.kind,
1304
- text: `${m.label} · ${format(m.at)}`
1313
+ text: `${markName(m)} · ${format(m.at)}${m.verdict ? ` · ${m.verdict}` : ""}`
1305
1314
  })), ...bands.map((b) => ({
1306
1315
  at: b.from,
1307
1316
  kind: b.kind,
@@ -1682,4 +1691,46 @@ function FigureView({ figure, presentation = "chart" }) {
1682
1691
  });
1683
1692
  }
1684
1693
  //#endregion
1685
- export { DEFAULT_SPARKLINE_EMPTY_LABEL, DEFAULT_SPARKLINE_HEIGHT, DEFAULT_SPARKLINE_LABEL, DEFAULT_SPARKLINE_UNAVAILABLE_LABEL, DEFAULT_SPARKLINE_WIDTH, DayBars, FigureView, Gantt, HBars, Histogram, LIVE_SERIES, Lines, Meter, Spark, Sparkline, StackedBarChart, WaterfallBar, WaterfallHeader, WaterfallRow, WaterfallTimeAxis, formatCompact, formatSparklineValue, formatWaterfallDuration, sparklineGeometry, sparklineLabel, sparklinePointsAttribute, sparklineReadings, waterfallSpanGeometry, waterfallTicks };
1694
+ //#region src/react/timeline-view.tsx
1695
+ /** The static `timeline` in React, with lanes that fold by subtree. A parent lane's label is a toggle: a click, Enter
1696
+ * or Space folds its descendants away, and the folded lane's ticks and counts then total its subtree. */
1697
+ function TimelineView({ lanes, options, defaultCollapsed = [], presentation }) {
1698
+ const [collapsed, setCollapsed] = useState(defaultCollapsed);
1699
+ const box = useRef(null);
1700
+ const refocus = useRef(null);
1701
+ const figure = useMemo(() => timeline(lanes, {
1702
+ ...options,
1703
+ collapsed
1704
+ }), [
1705
+ lanes,
1706
+ options,
1707
+ collapsed
1708
+ ]);
1709
+ useEffect(() => {
1710
+ const id = refocus.current;
1711
+ refocus.current = null;
1712
+ if (id === null || !box.current) return;
1713
+ Array.from(box.current.querySelectorAll("[data-toggle]")).find((element) => element.getAttribute("data-toggle") === id)?.focus();
1714
+ }, [figure]);
1715
+ const fold = (target, keyboard) => {
1716
+ const id = target instanceof Element ? target.closest("[data-toggle]")?.getAttribute("data-toggle") : null;
1717
+ if (!id) return false;
1718
+ if (keyboard) refocus.current = id;
1719
+ setCollapsed((list) => list.includes(id) ? list.filter((other) => other !== id) : [...list, id]);
1720
+ return true;
1721
+ };
1722
+ return /* @__PURE__ */ jsx("div", {
1723
+ ref: box,
1724
+ className: "tgc-timeline-view",
1725
+ onClick: (event) => fold(event.target, false),
1726
+ onKeyDown: (event) => {
1727
+ if ((event.key === "Enter" || event.key === " ") && fold(event.target, true)) event.preventDefault();
1728
+ },
1729
+ children: /* @__PURE__ */ jsx(FigureView, {
1730
+ figure,
1731
+ presentation
1732
+ })
1733
+ });
1734
+ }
1735
+ //#endregion
1736
+ export { DEFAULT_SPARKLINE_EMPTY_LABEL, DEFAULT_SPARKLINE_HEIGHT, DEFAULT_SPARKLINE_LABEL, DEFAULT_SPARKLINE_UNAVAILABLE_LABEL, DEFAULT_SPARKLINE_WIDTH, DayBars, FigureView, Gantt, HBars, Histogram, LIVE_SERIES, Lines, Meter, Spark, Sparkline, StackedBarChart, TimelineView, WaterfallBar, WaterfallHeader, WaterfallRow, WaterfallTimeAxis, formatCompact, formatSparklineValue, formatWaterfallDuration, sparklineGeometry, sparklineLabel, sparklinePointsAttribute, sparklineReadings, waterfallSpanGeometry, waterfallTicks };
@@ -31,9 +31,23 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
31
31
  .ov-mark-deadline { stroke: var(--chart-live-fg, var(--chart-ink)); stroke-dasharray: 5 3; }
32
32
  .ov-mark-died { stroke: var(--chart-live-danger, var(--chart-breach, var(--status-error))); stroke-width: 2; }
33
33
  .ov-mark-event { stroke: var(--chart-live-muted, var(--chart-ink-muted)); stroke-dasharray: 1 3; stroke-linecap: round; }
34
+ .ov-mark-release { stroke: var(--chart-live-muted, var(--chart-ink-muted)); }
35
+ .ov-mark-steer { stroke: var(--chart-live-accent, var(--chart-accent)); stroke-dasharray: 1 3; stroke-linecap: round; }
36
+ .ov-mark-ruling { stroke: var(--chart-live-fg, var(--chart-ink)); stroke-dasharray: 6 2 1 2; }
37
+ .ov-mark-wake { stroke: var(--chart-live-muted, var(--chart-ink-muted)); stroke-dasharray: 1 3; stroke-linecap: round; }
38
+ .ov-mark-head { fill: var(--chart-live-muted, var(--chart-ink-muted)); stroke: var(--chart-live-surface, var(--chart-canvas)); stroke-width: 1; }
39
+ [data-mark="steer"] .ov-mark-head { fill: var(--chart-live-accent, var(--chart-accent)); }
40
+ [data-mark="ruling"] .ov-mark-head { fill: var(--chart-live-fg, var(--chart-ink)); }
41
+ .ov-verdict-pass .ov-mark { stroke: var(--chart-live-pass, var(--chart-pass, var(--status-running))); }
42
+ .ov-verdict-pass .ov-mark-head { fill: var(--chart-live-pass, var(--chart-pass, var(--status-running))); }
43
+ .ov-verdict-fail .ov-mark { stroke: var(--chart-live-danger, var(--chart-breach, var(--status-error))); }
44
+ .ov-verdict-fail .ov-mark-head { fill: var(--chart-live-danger, var(--chart-breach, var(--status-error))); }
45
+ .ov-verdict-unknown .ov-mark { stroke: var(--chart-live-faint, var(--chart-muted)); }
46
+ .ov-verdict-unknown .ov-mark-head { fill: var(--chart-live-faint, var(--chart-muted)); }
34
47
  .ov-mark-label { fill: var(--chart-live-fg, var(--chart-ink)); font-size: .75em; paint-order: stroke; stroke: var(--chart-live-surface, var(--chart-canvas)); stroke-width: 3px; stroke-linejoin: round; }
35
48
  .ov-band-after-pass { fill: color-mix(in srgb, var(--chart-live-pass, var(--chart-pass, var(--status-running))) 12%, transparent); }
36
49
  .ov-band-idle { fill: var(--chart-live-hover, var(--chart-shade)); }
50
+ .ov-band-blocked { fill: color-mix(in srgb, var(--chart-live-danger, var(--chart-breach, var(--status-error))) 9%, transparent); }
37
51
  /* Missing data is hatched, never drawn as zero. */
38
52
  .ov-hatch-bg { fill: color-mix(in srgb, var(--chart-live-faint, var(--chart-muted)) 10%, transparent); }
39
53
  .ov-hatch { fill: none; stroke: var(--chart-live-faint, var(--chart-muted)); stroke-width: 1; }
@@ -49,7 +63,10 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
49
63
  .ov-event-pass { border-left: 2px solid var(--chart-live-pass, var(--chart-pass, var(--status-running))); border-radius: 0; width: 0; }
50
64
  .ov-event-deadline { border-left: 2px dashed var(--chart-live-fg, var(--chart-ink)); border-radius: 0; width: 0; }
51
65
  .ov-event-died { border-left: 2px solid var(--chart-live-danger, var(--chart-breach, var(--status-error))); border-radius: 0; width: 0; }
52
- .ov-event-event { border-left: 2px dotted var(--chart-live-muted, var(--chart-ink-muted)); border-radius: 0; width: 0; }
66
+ .ov-event-event, .ov-event-steer, .ov-event-wake { border-left: 2px dotted var(--chart-live-muted, var(--chart-ink-muted)); border-radius: 0; width: 0; }
67
+ .ov-event-release { border-left: 2px solid var(--chart-live-muted, var(--chart-ink-muted)); border-radius: 0; width: 0; }
68
+ .ov-event-ruling { border-left: 2px dashed var(--chart-live-fg, var(--chart-ink)); border-radius: 0; width: 0; }
69
+ .ov-event-blocked { background: color-mix(in srgb, var(--chart-live-danger, var(--chart-breach, var(--status-error))) 25%, transparent); }
53
70
  .ov-event-after-pass { background: color-mix(in srgb, var(--chart-live-pass, var(--chart-pass, var(--status-running))) 25%, transparent); }
54
71
  .ov-event-idle { background: var(--chart-live-hover, var(--chart-shade)); }
55
72
  .ov-event-missing { background: repeating-linear-gradient(135deg, var(--chart-live-faint, var(--chart-muted)) 0 1px, transparent 1px 4px); }
@@ -133,15 +133,25 @@ interface Refusal {
133
133
  declare function isRefusal(value: Figure | Refusal): value is Refusal;
134
134
  //#endregion
135
135
  //#region src/events.d.ts
136
- /** `pass` is solid, `deadline` dashed, `died` ends in a cross and `event` is dotted, so kinds read without colour. */
137
- type MarkKind = "pass" | "deadline" | "died" | "event";
136
+ /**
137
+ * `pass` is solid, `deadline` dashed, `died` ends in a cross and `event` is dotted. `release`, `steer`, `ruling` and
138
+ * `wake` carry a head shape (diamond, triangle, square, circle), so every kind reads without colour.
139
+ */
140
+ type MarkKind = "pass" | "deadline" | "died" | "event" | "release" | "steer" | "ruling" | "wake";
141
+ /** A recorded verdict, such as a release tag's check: pass and fail take the status tones, unknown the muted tone. */
142
+ type MarkVerdict = "pass" | "fail" | "unknown";
138
143
  interface ChartMark {
139
144
  at: number;
140
145
  label: string;
141
146
  kind: MarkKind;
147
+ verdict?: MarkVerdict;
148
+ /** Who made the event, such as the operator or the keeper for a steer. Shown with the label and in the table. */
149
+ source?: string;
150
+ /** `timeline` only: the lane the event belongs to. It is drawn on that lane instead of across every lane. */
151
+ lane?: string;
142
152
  }
143
- /** `missing` is hatched: a span with no data is never drawn as zero. */
144
- type BandKind = "after-pass" | "idle" | "missing";
153
+ /** `missing` is hatched: a span with no data is never drawn as zero. `blocked` is a system-wide wall, such as a quota window. */
154
+ type BandKind = "after-pass" | "idle" | "missing" | "blocked";
145
155
  interface ChartBand {
146
156
  from: number;
147
157
  to: number;
@@ -157,14 +167,33 @@ interface TimelineSpan {
157
167
  state: string;
158
168
  note?: string;
159
169
  }
170
+ /** How one recorded attempt ended. `ok` succeeded; every other outcome counts as failed. */
171
+ type TickOutcome = "ok" | "quota" | "rate-limit" | "oom" | "refused" | "aborted" | "other";
172
+ /** One recorded attempt on a lane, such as a driver turn, at the time it ended. `note` (an error line) shows on hover. */
173
+ interface TimelineTick {
174
+ at: number;
175
+ outcome: TickOutcome;
176
+ note?: string;
177
+ }
160
178
  interface TimelineLane {
161
179
  id: string;
162
180
  label: string;
163
181
  spans: TimelineSpan[];
182
+ /** The lane that started this one. The lane is drawn under its parent with a tree connector. */
183
+ parent?: string;
184
+ /** Tree depth: 0 for a root. Defaults to the parent's depth plus one, or 0 without a parent. */
185
+ depth?: number;
186
+ /** Recorded attempts, drawn as ticks with ok and failed counts at the lane's end. Omit when attempts are not recorded. */
187
+ ticks?: TimelineTick[];
188
+ /** Caller-composed facts, one per hover line on the lane and each of its spans: profile, model, status, attempts, cost, top error. */
189
+ detail?: string[];
164
190
  }
165
191
  interface TimelineOptions {
166
192
  measure: string;
167
- /** The recorded window; defaults to the extent of spans, marks, bands and `now`. Everything must fall inside it. */
193
+ /**
194
+ * The recorded window; defaults to the extent of the data (spans, ticks and `now`). Spans, ticks and `now` must fall
195
+ * inside it. Marks outside it, such as a later deadline, are named at the edge they fall past; bands are clipped.
196
+ */
168
197
  start?: number;
169
198
  end?: number;
170
199
  /** The time an open span is drawn to. Required when any span is open. */
@@ -175,9 +204,15 @@ interface TimelineOptions {
175
204
  hatch?: string[];
176
205
  /** States drawn in the muted tone, such as idle. Default: idle. */
177
206
  muted?: string[];
207
+ /**
208
+ * Lane ids whose subtrees are folded: descendants are hidden and the lane's ticks and counts include them. Passing
209
+ * it, even empty, makes each parent lane's label a fold toggle (`data-toggle`), which React `TimelineView` wires up.
210
+ * Ids that name no lane are ignored, so a fold state can outlive the lanes it was made for.
211
+ */
212
+ collapsed?: string[];
178
213
  formatTime?: (ms: number) => string;
179
214
  id?: string;
180
215
  }
181
216
  declare function timeline(lanes: TimelineLane[], o: TimelineOptions): Figure | Refusal;
182
217
  //#endregion
183
- export { isRefusal as C, TierCounts as S, MatrixCell as _, BandKind as a, Setup as b, MarkKind as c, CostBasis as d, Estimate as f, IntervalMethod as g, Interval as h, timeline as i, BreakdownInput as l, Figure as m, TimelineOptions as n, ChartBand as o, Exclusion as p, TimelineSpan as r, ChartMark as s, TimelineLane as t, ComparisonRow as u, RateRow as v, Tier as x, Refusal as y };
218
+ export { Setup as C, isRefusal as E, Refusal as S, TierCounts as T, Figure as _, TimelineTick as a, MatrixCell as b, ChartBand as c, MarkVerdict as d, BreakdownInput as f, Exclusion as g, Estimate as h, TimelineSpan as i, ChartMark as l, CostBasis as m, TimelineLane as n, timeline as o, ComparisonRow as p, TimelineOptions as r, BandKind as s, TickOutcome as t, MarkKind as u, Interval as v, Tier as w, RateRow as x, IntervalMethod as y };