@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.
- package/README.md +69 -24
- package/dist/events-Cw6abvgR.js +491 -0
- package/dist/figure.css +45 -9
- package/dist/index.d.ts +104 -135
- package/dist/index.js +735 -371
- package/dist/react/index.d.ts +50 -15
- package/dist/react/index.js +522 -152
- package/dist/react/live.css +29 -0
- package/dist/timeline-BAjzF-wL.d.ts +183 -0
- package/package.json +25 -13
- package/LICENSE +0 -21
package/dist/react/index.js
CHANGED
|
@@ -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
|
|
1082
|
-
|
|
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: [
|
|
1102
|
-
width,
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
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(
|
|
1151
|
+
r: Math.max(3, em / 5),
|
|
1159
1152
|
fill: s.color,
|
|
1160
1153
|
stroke: "var(--chart-live-surface, #fff)",
|
|
1161
|
-
strokeWidth:
|
|
1162
|
-
}, s.name) :
|
|
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
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
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
|
|
1181
|
-
function
|
|
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
|
|
1223
|
-
|
|
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
|
-
|
|
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 +
|
|
1235
|
-
const
|
|
1236
|
-
|
|
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 ?? "
|
|
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: [
|
|
1245
|
-
width,
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
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
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
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 };
|