@tangle-network/charts 0.4.0 → 0.6.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,3 +1,4 @@
1
+ import { d as hatchPath, i as eventsGap, m as markerPath, o as isRefusal } from "../events-Cw6abvgR.js";
1
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
3
  import { Fragment as Fragment$1, useEffect, useId, useRef, useState } from "react";
3
4
  //#region src/react/sparkline.tsx
@@ -1069,17 +1070,24 @@ function Histogram({ bins, format = formatCompact, color = LIVE_SERIES[0], unit,
1069
1070
  }
1070
1071
  /** Lines over time on one axis (at most four series); the crosshair readout lists each series at the nearest time. A
1071
1072
  * `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
+ function Lines({ series, format = formatCompact, rows = 13, axis, marks = [], bands = [] }) {
1073
1074
  const [ref, width, em] = useWidth();
1074
1075
  const height = Math.round(rows * em);
1075
1076
  const [hover, setHover] = useState(null);
1077
+ const gap = eventsGap(marks, bands);
1078
+ if (gap) throw new RangeError(gap);
1076
1079
  const all = series.flatMap((s) => s.points);
1077
1080
  if (!all.length) return /* @__PURE__ */ jsx("p", {
1078
1081
  className: "ov-empty",
1079
1082
  children: "No history yet."
1080
1083
  });
1081
- const t0 = Math.min(...all.map((p) => p[0]));
1082
- const t1 = Math.max(...all.map((p) => p[0]));
1084
+ const times = [
1085
+ ...all.map((p) => p[0]),
1086
+ ...marks.map((m) => m.at),
1087
+ ...bands.flatMap((b) => [b.from, b.to])
1088
+ ];
1089
+ const t0 = Math.min(...times);
1090
+ const t1 = Math.max(...times);
1083
1091
  const { top, ticks } = ticksFor(Math.max(...all.map((p) => p[1])));
1084
1092
  const left = Math.round(3.4 * em);
1085
1093
  const bottom = Math.round(1.8 * em);
@@ -1098,87 +1106,234 @@ function Lines({ series, format = formatCompact, rows = 13, axis }) {
1098
1106
  setHover(px >= left ? t0 + (px - left) / Math.max(1, plotW) * (t1 - t0) : null);
1099
1107
  },
1100
1108
  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", {
1109
+ children: [
1110
+ width > 0 && /* @__PURE__ */ jsxs("svg", {
1111
+ width,
1112
+ height,
1113
+ role: "img",
1114
+ "aria-label": series.map((s) => s.name).join(", "),
1115
+ children: [
1116
+ ticks.map((t) => /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("line", {
1117
+ x1: left,
1118
+ x2: width - 10,
1119
+ y1: y(t),
1120
+ y2: y(t),
1121
+ className: "ov-gridline"
1122
+ }), /* @__PURE__ */ jsx("text", {
1123
+ x: left - em * .4,
1124
+ y: y(t),
1125
+ dominantBaseline: "middle",
1126
+ className: "ov-axis",
1127
+ textAnchor: "end",
1128
+ children: format(t)
1129
+ })] }, t)),
1130
+ [
1131
+ t0,
1132
+ (t0 + t1) / 2,
1133
+ t1
1134
+ ].map((t, i) => /* @__PURE__ */ jsx("text", {
1135
+ x: x(t),
1136
+ y: height - em * .35,
1137
+ className: "ov-axis",
1138
+ textAnchor: i === 0 ? "start" : i === 2 ? "end" : "middle",
1139
+ children: axis ? axis(t) : day(t).slice(0, 5)
1140
+ }, i)),
1141
+ bands.map((band, i) => /* @__PURE__ */ jsx(Band, {
1142
+ band,
1143
+ x: x(band.from),
1144
+ y: 8,
1145
+ width: x(band.to) - x(band.from),
1146
+ height: plotH
1147
+ }, `band-${i}`)),
1148
+ series.map((s) => s.dots ? /* @__PURE__ */ jsx("g", { children: s.points.map((p) => /* @__PURE__ */ jsx("circle", {
1156
1149
  cx: x(p[0]),
1157
1150
  cy: y(p[1]),
1158
- r: Math.max(4, em / 4),
1151
+ r: Math.max(3, em / 5),
1159
1152
  fill: s.color,
1160
1153
  stroke: "var(--chart-live-surface, #fff)",
1161
- strokeWidth: 2
1162
- }, s.name) : null;
1154
+ strokeWidth: 1.5
1155
+ }, p[0])) }, s.name) : /* @__PURE__ */ jsxs("g", {
1156
+ "data-series": s.name,
1157
+ "data-dash": s.dash ?? "solid",
1158
+ "data-marker": s.marker,
1159
+ children: [/* @__PURE__ */ jsx("polyline", {
1160
+ fill: "none",
1161
+ stroke: s.color,
1162
+ strokeWidth: Math.max(2, em / 8),
1163
+ strokeLinejoin: "round",
1164
+ strokeLinecap: s.dash === "dot" ? "round" : void 0,
1165
+ strokeDasharray: dashArray(s.dash, em),
1166
+ points: s.points.map((p) => `${x(p[0])},${y(p[1])}`).join(" ")
1167
+ }), s.marker && (s.points.length <= 30 ? s.points : s.points.slice(-1)).map((p) => /* @__PURE__ */ jsx("path", {
1168
+ d: markerPath(s.marker, x(p[0]), y(p[1]), Math.max(3, em / 5)),
1169
+ fill: s.color,
1170
+ stroke: "var(--chart-live-surface, var(--chart-canvas))",
1171
+ strokeWidth: 1.5
1172
+ }, p[0]))]
1173
+ }, s.name)),
1174
+ [...marks].sort((a, b) => a.at - b.at).map((mark, i) => /* @__PURE__ */ jsx(Mark, {
1175
+ mark,
1176
+ x: x(mark.at),
1177
+ top: 8,
1178
+ bottom: 8 + plotH,
1179
+ row: i % 3,
1180
+ em,
1181
+ flip: x(mark.at) > left + plotW * .62
1182
+ }, `mark-${i}`)),
1183
+ hover !== null && /* @__PURE__ */ jsx("line", {
1184
+ x1: x(hover),
1185
+ x2: x(hover),
1186
+ y1: 8,
1187
+ y2: 8 + plotH,
1188
+ className: "ov-crosshair"
1189
+ }),
1190
+ hover !== null && series.map((s) => {
1191
+ const p = s.points.length ? nearest(s.points, hover) : null;
1192
+ return p ? /* @__PURE__ */ jsx("circle", {
1193
+ cx: x(p[0]),
1194
+ cy: y(p[1]),
1195
+ r: Math.max(4, em / 4),
1196
+ fill: s.color,
1197
+ stroke: "var(--chart-live-surface, #fff)",
1198
+ strokeWidth: 2
1199
+ }, s.name) : null;
1200
+ })
1201
+ ]
1202
+ }),
1203
+ hover !== null && /* @__PURE__ */ jsx(Tooltip, { tip: {
1204
+ x: Math.min(x(hover) + 10, width - em * 14),
1205
+ y: 8,
1206
+ title: day(hover),
1207
+ lines: series.filter((s) => s.points.length).map((s) => {
1208
+ const p = nearest(s.points, hover);
1209
+ return {
1210
+ label: s.label ? s.label(p[0]) : s.name,
1211
+ value: format(p[1]),
1212
+ color: s.color
1213
+ };
1163
1214
  })
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
- };
1215
+ } }),
1216
+ series.length > 1 && series.some((s) => s.dash || s.marker) && /* @__PURE__ */ jsx("ul", {
1217
+ className: "ov-key",
1218
+ "aria-label": "Series",
1219
+ children: series.map((s) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsxs("svg", {
1220
+ width: em * 2,
1221
+ height: em,
1222
+ "aria-hidden": "true",
1223
+ children: [/* @__PURE__ */ jsx("line", {
1224
+ x1: 0,
1225
+ x2: em * 2,
1226
+ y1: em / 2,
1227
+ y2: em / 2,
1228
+ stroke: s.color,
1229
+ strokeWidth: Math.max(2, em / 8),
1230
+ strokeDasharray: dashArray(s.dash, em),
1231
+ strokeLinecap: s.dash === "dot" ? "round" : void 0
1232
+ }), s.marker && /* @__PURE__ */ jsx("path", {
1233
+ d: markerPath(s.marker, em, em / 2, Math.max(3, em / 5)),
1234
+ fill: s.color
1235
+ })]
1236
+ }), s.name] }, s.name))
1237
+ }),
1238
+ /* @__PURE__ */ jsx(EventList, {
1239
+ marks,
1240
+ bands,
1241
+ format: axis ?? ((t) => day(t))
1176
1242
  })
1177
- } })]
1243
+ ]
1244
+ });
1245
+ }
1246
+ function dashArray(dash, em) {
1247
+ return dash === "dash" ? `${Math.round(em * .55)} ${Math.round(em * .3)}` : dash === "dot" ? `0.1 ${Math.round(em * .35)}` : void 0;
1248
+ }
1249
+ /** A shaded span under the data; a `missing` span is hatched because it has no data. */
1250
+ function Band({ band, x, y, width, height }) {
1251
+ return /* @__PURE__ */ jsxs("g", {
1252
+ "data-band": band.kind,
1253
+ "data-hatched": band.kind === "missing" ? "true" : void 0,
1254
+ children: [
1255
+ /* @__PURE__ */ jsx("title", { children: band.label }),
1256
+ /* @__PURE__ */ jsx("rect", {
1257
+ x,
1258
+ y,
1259
+ width: Math.max(0, width),
1260
+ height,
1261
+ className: band.kind === "missing" ? "ov-hatch-bg" : `ov-band ov-band-${band.kind}`
1262
+ }),
1263
+ band.kind === "missing" && /* @__PURE__ */ jsx("path", {
1264
+ d: hatchPath(x, y, width, height),
1265
+ className: "ov-hatch"
1266
+ })
1267
+ ]
1178
1268
  });
1179
1269
  }
1180
- /** A used-of-total meter with its numbers. */
1181
- function Meter({ used, total, format = formatCompact, tone, text }) {
1270
+ /** A vertical event rule with its label; kinds differ by stroke pattern and the died cross as well as colour. */
1271
+ function Mark({ mark, x, top, bottom, row, em, flip }) {
1272
+ const s = Math.max(3, em / 4);
1273
+ return /* @__PURE__ */ jsxs("g", {
1274
+ "data-mark": mark.kind,
1275
+ children: [
1276
+ /* @__PURE__ */ jsx("title", { children: mark.label }),
1277
+ /* @__PURE__ */ jsx("line", {
1278
+ x1: x,
1279
+ x2: x,
1280
+ y1: top,
1281
+ y2: bottom,
1282
+ className: `ov-mark ov-mark-${mark.kind}`
1283
+ }),
1284
+ mark.kind === "died" && /* @__PURE__ */ jsx("path", {
1285
+ d: `M${x - s} ${bottom - 2 * s}L${x + s} ${bottom}M${x - s} ${bottom}L${x + s} ${bottom - 2 * s}`,
1286
+ className: "ov-mark ov-mark-died"
1287
+ }),
1288
+ /* @__PURE__ */ jsx("text", {
1289
+ x: flip ? x - em * .3 : x + em * .3,
1290
+ y: top + em * (.9 + row),
1291
+ textAnchor: flip ? "end" : "start",
1292
+ className: "ov-mark-label",
1293
+ children: mark.label
1294
+ })
1295
+ ]
1296
+ });
1297
+ }
1298
+ /** Every mark and band as text, in time order: the table form of the events drawn on the plot. */
1299
+ function EventList({ marks, bands, format }) {
1300
+ if (!marks.length && !bands.length) return null;
1301
+ const items = [...marks.map((m) => ({
1302
+ at: m.at,
1303
+ kind: m.kind,
1304
+ text: `${m.label} · ${format(m.at)}`
1305
+ })), ...bands.map((b) => ({
1306
+ at: b.from,
1307
+ kind: b.kind,
1308
+ text: `${b.label} · ${format(b.from)} to ${format(b.to)}${b.kind === "missing" ? " · no data" : ""}`
1309
+ }))].sort((a, b) => a.at - b.at);
1310
+ return /* @__PURE__ */ jsx("ul", {
1311
+ className: "ov-events",
1312
+ "aria-label": "Recorded events",
1313
+ children: items.map((item, i) => /* @__PURE__ */ jsxs("li", {
1314
+ "data-kind": item.kind,
1315
+ children: [/* @__PURE__ */ jsx("i", {
1316
+ className: `ov-event-swatch ov-event-${item.kind}`,
1317
+ "aria-hidden": "true"
1318
+ }), item.text]
1319
+ }, i))
1320
+ });
1321
+ }
1322
+ const SEGMENT_TONES = [
1323
+ "var(--chart-accent)",
1324
+ "var(--chart-tier-c)",
1325
+ "var(--chart-muted)"
1326
+ ];
1327
+ /** A used-of-total meter with its numbers. With `segments` it is a part-of-whole bar: the remainder of `total` that no
1328
+ * segment accounts for is drawn hatched and labelled `untraced`, and a missing total hatches the whole track. */
1329
+ function Meter({ used, total, format = formatCompact, tone, text, segments, untraced = "untraced" }) {
1330
+ if (segments) return /* @__PURE__ */ jsx(SegmentMeter, {
1331
+ segments,
1332
+ total,
1333
+ format,
1334
+ text,
1335
+ untraced
1336
+ });
1182
1337
  const share = used !== null && used !== void 0 && total ? Math.min(1, used / total) : null;
1183
1338
  return /* @__PURE__ */ jsxs("div", {
1184
1339
  className: "ov-meter",
@@ -1194,6 +1349,64 @@ function Meter({ used, total, format = formatCompact, tone, text }) {
1194
1349
  })]
1195
1350
  });
1196
1351
  }
1352
+ function SegmentMeter({ segments, total, format, text, untraced }) {
1353
+ for (const segment of segments) if (segment.value !== null && (!Number.isFinite(segment.value) || segment.value < 0)) throw new RangeError(`${segment.label}: a segment value must be a nonnegative finite number or null.`);
1354
+ const known = segments.reduce((sum, segment) => sum + (segment.value ?? 0), 0);
1355
+ const whole = total !== null && total !== void 0 && Number.isFinite(total) && total > 0 ? total : null;
1356
+ if (whole !== null && known > whole * (1 + 1e-9)) throw new RangeError(`Segments sum to ${known}, more than the total ${whole}.`);
1357
+ const remainder = whole === null ? 0 : whole - known;
1358
+ const parts = [...segments.map((segment, i) => ({
1359
+ ...segment,
1360
+ color: segment.color ?? SEGMENT_TONES[i % SEGMENT_TONES.length],
1361
+ remainder: false
1362
+ })), ...remainder > whole * 1e-9 ? [{
1363
+ label: untraced,
1364
+ value: remainder,
1365
+ kind: "untraced",
1366
+ color: void 0,
1367
+ remainder: true
1368
+ }] : []];
1369
+ const share = (value) => whole === null ? null : value / whole;
1370
+ return /* @__PURE__ */ jsxs("div", {
1371
+ className: "ov-meter",
1372
+ "data-segmented": "true",
1373
+ children: [
1374
+ /* @__PURE__ */ jsx("span", {
1375
+ className: `ov-meter-track${whole === null ? " ov-hatched" : ""}`,
1376
+ "data-hatched": whole === null ? "true" : void 0,
1377
+ "aria-hidden": "true",
1378
+ children: whole !== null && parts.filter((part) => part.value !== null && part.value > 0).map((part) => /* @__PURE__ */ jsx("span", {
1379
+ "data-segment": part.label,
1380
+ "data-hatched": part.kind === "untraced" ? "true" : void 0,
1381
+ className: part.kind === "untraced" ? "ov-hatched" : void 0,
1382
+ style: {
1383
+ width: `${part.value / whole * 100}%`,
1384
+ background: part.kind === "untraced" ? void 0 : part.color
1385
+ }
1386
+ }, part.label))
1387
+ }),
1388
+ /* @__PURE__ */ jsx("ul", {
1389
+ className: "ov-meter-key",
1390
+ children: parts.map((part) => /* @__PURE__ */ jsxs("li", {
1391
+ "data-segment": part.label,
1392
+ children: [
1393
+ /* @__PURE__ */ jsx("i", {
1394
+ className: part.kind === "untraced" || part.value === null ? "ov-hatched" : void 0,
1395
+ style: part.kind === "untraced" || part.value === null ? void 0 : { background: part.color },
1396
+ "aria-hidden": "true"
1397
+ }),
1398
+ /* @__PURE__ */ jsx("span", { children: part.label }),
1399
+ /* @__PURE__ */ jsx("b", { children: part.value === null ? "not recorded" : `${format(part.value)}${whole === null ? "" : ` · ${formatShare(share(part.value))}`}` })
1400
+ ]
1401
+ }, part.label))
1402
+ }),
1403
+ /* @__PURE__ */ jsx("span", {
1404
+ className: "ov-meter-text",
1405
+ children: text ?? (whole === null ? "total not recorded" : `${format(known)} of ${format(whole)} accounted · ${formatShare(known / whole)}`)
1406
+ })
1407
+ ]
1408
+ });
1409
+ }
1197
1410
  /** A small line of one series for a row in a list (one hue; the row's label carries identity). */
1198
1411
  function Spark({ points, max = 1, color = LIVE_SERIES[0] }) {
1199
1412
  if (points.length < 2) return /* @__PURE__ */ jsx("span", {
@@ -1219,97 +1432,254 @@ function Spark({ points, max = 1, color = LIVE_SERIES[0] }) {
1219
1432
  });
1220
1433
  }
1221
1434
  /** 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 }) {
1435
+ * state; hovering a span names it, its state and its span. Times are epoch ms; `axis` labels the time axis.
1436
+ * `lanes` give a row several spans (the same shape as the static `timeline`); a span with `end: null` is open and runs
1437
+ * to `now`. `marks` draw recorded events across every lane, and states in `hatch` are drawn hatched (no data). */
1438
+ function Gantt({ rows, lanes: laneInput, colors, axis, span, marks = [], now, hatch = ["missing", "untraced"] }) {
1224
1439
  const [ref, width, em] = useWidth();
1225
1440
  const [tip, setTip] = useState(null);
1226
- if (!rows.length) return /* @__PURE__ */ jsx("p", {
1441
+ const lanes = laneInput ?? (rows ?? []).map((row, i) => ({
1442
+ id: `${row.label}-${i}`,
1443
+ label: row.label,
1444
+ spans: [{
1445
+ start: row.start,
1446
+ end: row.end,
1447
+ state: row.state,
1448
+ note: row.note
1449
+ }]
1450
+ }));
1451
+ const gap = eventsGap(marks);
1452
+ if (gap) throw new RangeError(gap);
1453
+ if (now !== void 0 && !Number.isFinite(now)) throw new RangeError("now must be a finite time.");
1454
+ if (laneInput) for (const lane of lanes) for (const s of lane.spans) {
1455
+ if (!Number.isFinite(s.start) || s.end !== null && (!Number.isFinite(s.end) || s.end < s.start)) throw new RangeError(`${lane.label}: span times must be finite and end at or after their start.`);
1456
+ if (s.end === null && (now === void 0 || now < s.start)) throw new RangeError(`${lane.label}: an open span needs a now at or after its start.`);
1457
+ }
1458
+ const spans = lanes.flatMap((lane) => lane.spans);
1459
+ if (!spans.length) return /* @__PURE__ */ jsx("p", {
1227
1460
  className: "ov-empty",
1228
1461
  children: "No agent has started."
1229
1462
  });
1463
+ const ends = (s) => s.end ?? now;
1230
1464
  const rowH = Math.round(1.25 * em);
1231
1465
  const labelW = Math.min(Math.round(13 * em), Math.round(width * .32));
1232
- const top = 4;
1466
+ const top = marks.length ? 4 + Math.round(Math.min(3, marks.length) * em + .3 * em) : 4;
1233
1467
  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);
1468
+ const height = top + lanes.length * rowH + bottom;
1469
+ const times = [
1470
+ ...spans.flatMap((s) => [s.start, ends(s)]),
1471
+ ...marks.map((m) => m.at),
1472
+ ...now === void 0 ? [] : [now]
1473
+ ];
1474
+ const t0 = Math.min(...times);
1475
+ const t1 = Math.max(...times, t0 + 6e4);
1237
1476
  const plotW = Math.max(0, width - labelW - 10);
1238
1477
  const x = (t) => labelW + (t - t0) / (t1 - t0) * plotW;
1239
- const fallback = colors.unknown ?? "#8e8e8e";
1478
+ const fallback = colors.unknown ?? "var(--chart-live-faint, var(--chart-muted))";
1479
+ const hatched = new Set(hatch);
1480
+ const plotBottom = top + lanes.length * rowH;
1240
1481
  return /* @__PURE__ */ jsxs("div", {
1241
1482
  ref,
1242
1483
  className: "ov-plot",
1243
1484
  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
- }] : []]
1485
+ children: [
1486
+ width > 0 && /* @__PURE__ */ jsxs("svg", {
1487
+ width,
1488
+ height,
1489
+ role: "img",
1490
+ "aria-label": `${lanes.length} ${laneInput ? "lanes" : "agents"} over time`,
1491
+ children: [
1492
+ [
1493
+ 0,
1494
+ .25,
1495
+ .5,
1496
+ .75,
1497
+ 1
1498
+ ].map((f) => /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("line", {
1499
+ x1: labelW + f * plotW,
1500
+ x2: labelW + f * plotW,
1501
+ y1: top,
1502
+ y2: plotBottom,
1503
+ className: "ov-gridline"
1504
+ }), /* @__PURE__ */ jsx("text", {
1505
+ x: labelW + f * plotW,
1506
+ y: height - em * .35,
1507
+ className: "ov-axis",
1508
+ textAnchor: f === 0 ? "start" : f === 1 ? "end" : "middle",
1509
+ children: axis(t0 + f * (t1 - t0))
1510
+ })] }, f)),
1511
+ lanes.map((lane, i) => {
1512
+ const y = top + i * rowH;
1513
+ return /* @__PURE__ */ jsxs("g", {
1514
+ "data-lane": lane.id,
1515
+ children: [
1516
+ /* @__PURE__ */ jsx("rect", {
1517
+ x: 0,
1518
+ y,
1519
+ width,
1520
+ height: rowH,
1521
+ fill: "transparent",
1522
+ onMouseEnter: () => setTip({
1523
+ x: Math.min(x(ends(lane.spans.at(-1) ?? { end: t1 })) + 8, width - em * 16),
1524
+ y: Math.max(0, y - em),
1525
+ title: lane.label,
1526
+ lines: lane.spans.flatMap((s) => [{
1527
+ label: s.end === null ? `${s.state} (open)` : s.state,
1528
+ value: span(s.start, ends(s)),
1529
+ color: hatched.has(s.state) ? void 0 : colors[s.state] ?? fallback
1530
+ }, ...s.note ? [{
1531
+ label: s.note,
1532
+ value: ""
1533
+ }] : []])
1534
+ })
1535
+ }),
1536
+ /* @__PURE__ */ jsx("text", {
1537
+ x: labelW - em * .5,
1538
+ y: y + rowH / 2,
1539
+ dominantBaseline: "middle",
1540
+ textAnchor: "end",
1541
+ className: "ov-axis ov-gantt-label",
1542
+ children: lane.label.length > 26 ? `${lane.label.slice(0, 25)}…` : lane.label
1543
+ }),
1544
+ lane.spans.map((s, j) => {
1545
+ const color = colors[s.state] ?? fallback;
1546
+ const bx = x(s.start), bw = Math.max(2, x(ends(s)) - bx), by = y + rowH * .18, bh = rowH * .64;
1547
+ const open = s.end === null;
1548
+ return /* @__PURE__ */ jsx("g", {
1549
+ "data-state": s.state,
1550
+ "data-open": open ? "true" : void 0,
1551
+ "data-hatched": hatched.has(s.state) ? "true" : void 0,
1552
+ onMouseEnter: () => setTip({
1553
+ x: Math.min(bx + bw + 8, width - em * 16),
1554
+ y: Math.max(0, y - em),
1555
+ title: lane.label,
1556
+ lines: [{
1557
+ label: open ? `${s.state} (open)` : s.state,
1558
+ value: span(s.start, ends(s)),
1559
+ color: hatched.has(s.state) ? void 0 : color
1560
+ }, ...s.note ? [{
1561
+ label: s.note,
1562
+ value: ""
1563
+ }] : []]
1564
+ }),
1565
+ children: hatched.has(s.state) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("rect", {
1566
+ x: bx,
1567
+ y: by,
1568
+ width: bw,
1569
+ height: bh,
1570
+ className: "ov-hatch-bg"
1571
+ }), /* @__PURE__ */ jsx("path", {
1572
+ d: hatchPath(bx, by, bw, bh, 4),
1573
+ className: "ov-hatch"
1574
+ })] }) : /* @__PURE__ */ jsx("rect", {
1575
+ x: bx,
1576
+ y: by,
1577
+ width: bw,
1578
+ height: bh,
1579
+ rx: 2,
1580
+ fill: color,
1581
+ className: open ? "ov-gantt-open" : void 0
1582
+ })
1583
+ }, j);
1584
+ })
1585
+ ]
1586
+ }, lane.id);
1283
1587
  }),
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 })]
1588
+ now !== void 0 && /* @__PURE__ */ jsx("line", {
1589
+ x1: x(now),
1590
+ x2: x(now),
1591
+ y1: top,
1592
+ y2: plotBottom,
1593
+ className: "ov-now",
1594
+ "data-now": "true"
1595
+ }),
1596
+ [...marks].sort((a, b) => a.at - b.at).map((mark, i) => /* @__PURE__ */ jsx(Mark, {
1597
+ mark,
1598
+ x: x(mark.at),
1599
+ top: 4,
1600
+ bottom: plotBottom,
1601
+ row: i % 3,
1602
+ em,
1603
+ flip: x(mark.at) > labelW + plotW * .62
1604
+ }, `mark-${i}`))
1605
+ ]
1606
+ }),
1607
+ /* @__PURE__ */ jsx(Tooltip, { tip }),
1608
+ /* @__PURE__ */ jsx(EventList, {
1609
+ marks,
1610
+ bands: [],
1611
+ format: axis
1612
+ })
1613
+ ]
1614
+ });
1615
+ }
1616
+ //#endregion
1617
+ //#region src/react/figure-view.tsx
1618
+ /** Embeds a static Figure or Refusal in React: the wide or phone render chosen by the container's width, the data
1619
+ * table in a disclosure. The SVG and table strings come from this library's figure functions, which escape input. */
1620
+ function FigureView({ figure, presentation = "chart" }) {
1621
+ const [ref, width] = useWidth();
1622
+ if (isRefusal(figure)) return /* @__PURE__ */ jsx("p", {
1623
+ className: "ov-refusal",
1624
+ role: "note",
1625
+ "data-refused": figure.id,
1626
+ children: figure.refused
1627
+ });
1628
+ const layout = width > 0 && width < 720 ? figure.svg.narrow ? "narrow" : "table" : "wide";
1629
+ const plot = layout === "wide" ? figure.svg.wide : layout === "narrow" ? figure.svg.narrow : figure.table;
1630
+ const method = [
1631
+ figure.note.method,
1632
+ figure.note.n,
1633
+ ...figure.note.exclusions
1634
+ ].join(" ");
1635
+ return /* @__PURE__ */ jsxs("figure", {
1636
+ ref,
1637
+ className: "tgc-figure tgc-figure-chart tgc-figure-view",
1638
+ "data-figure": figure.id,
1639
+ "data-layout": layout,
1640
+ "aria-label": figure.finding,
1641
+ children: [
1642
+ presentation === "report" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("h3", {
1643
+ className: "tgc-finding",
1644
+ children: figure.finding
1645
+ }), /* @__PURE__ */ jsx("p", {
1646
+ className: "tgc-lede",
1647
+ children: figure.lede
1648
+ })] }),
1649
+ /* @__PURE__ */ jsx("div", {
1650
+ className: `tgc-view-plot tgc-view-${layout}`,
1651
+ role: figure.svg.interactive ? "group" : "img",
1652
+ "aria-label": `${figure.finding} ${figure.lede}`,
1653
+ style: layout === "narrow" ? { maxWidth: 358 } : void 0,
1654
+ dangerouslySetInnerHTML: { __html: plot }
1655
+ }),
1656
+ /* @__PURE__ */ jsxs("details", {
1657
+ className: "tgc-data",
1658
+ children: [
1659
+ /* @__PURE__ */ jsx("summary", { children: "Data and method" }),
1660
+ /* @__PURE__ */ jsx("p", {
1661
+ className: "tgc-lede",
1662
+ children: figure.lede
1663
+ }),
1664
+ /* @__PURE__ */ jsx("p", {
1665
+ className: "tgc-read",
1666
+ children: figure.read
1667
+ }),
1668
+ /* @__PURE__ */ jsx("p", {
1669
+ className: "tgc-read",
1670
+ children: method
1671
+ }),
1672
+ /* @__PURE__ */ jsx("div", {
1673
+ className: "tgc-scroll",
1674
+ role: "region",
1675
+ "aria-label": `${figure.finding} data`,
1676
+ tabIndex: 0,
1677
+ dangerouslySetInnerHTML: { __html: figure.table }
1678
+ })
1679
+ ]
1680
+ })
1681
+ ]
1312
1682
  });
1313
1683
  }
1314
1684
  //#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 };
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 };