@tangle-network/charts 0.3.1 → 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 +71 -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 +123 -1
- package/dist/react/index.js +931 -4
- package/dist/react/live.css +56 -0
- package/dist/timeline-BAjzF-wL.d.ts +183 -0
- package/package.json +28 -14
- package/LICENSE +0 -21
package/dist/react/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Fragment,
|
|
1
|
+
import { d as hatchPath, i as eventsGap, m as markerPath, o as isRefusal } from "../events-Cw6abvgR.js";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment as Fragment$1, useEffect, useId, useRef, useState } from "react";
|
|
3
4
|
//#region src/react/sparkline.tsx
|
|
4
5
|
const DEFAULT_SPARKLINE_WIDTH = 96;
|
|
5
6
|
const DEFAULT_SPARKLINE_HEIGHT = 24;
|
|
@@ -515,7 +516,7 @@ function StackedBarChart(props) {
|
|
|
515
516
|
children: "Value"
|
|
516
517
|
})
|
|
517
518
|
] }) }),
|
|
518
|
-
/* @__PURE__ */ jsx("tbody", { children: buckets.map((bucket) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("tr", { children: [
|
|
519
|
+
/* @__PURE__ */ jsx("tbody", { children: buckets.map((bucket) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("tr", { children: [
|
|
519
520
|
/* @__PURE__ */ jsx("th", {
|
|
520
521
|
scope: "row",
|
|
521
522
|
children: bucket.label
|
|
@@ -755,4 +756,930 @@ function WaterfallHeader({ label = "Span", attribution, durationLabel = "Duratio
|
|
|
755
756
|
});
|
|
756
757
|
}
|
|
757
758
|
//#endregion
|
|
758
|
-
|
|
759
|
+
//#region src/react/live.tsx
|
|
760
|
+
/** Reusable, em-scaled charts for live operational series. Values, labels and status colors belong to callers. */
|
|
761
|
+
const LIVE_SERIES = [
|
|
762
|
+
"#8b7cf6",
|
|
763
|
+
"#0ea5a0",
|
|
764
|
+
"#c98500",
|
|
765
|
+
"#e2508f",
|
|
766
|
+
"#4a90e2"
|
|
767
|
+
];
|
|
768
|
+
const formatCompact = (value) => value === null || value === void 0 || !Number.isFinite(value) ? "—" : new Intl.NumberFormat("en-US", {
|
|
769
|
+
notation: "compact",
|
|
770
|
+
maximumFractionDigits: 1
|
|
771
|
+
}).format(value);
|
|
772
|
+
const formatShare = (value) => value === null || !Number.isFinite(value) ? "—" : `${Math.round(value * 100)}%`;
|
|
773
|
+
/** The rendered width and font size of an element, so an SVG chart draws at its real size and its geometry follows
|
|
774
|
+
* the page's type. */
|
|
775
|
+
function useWidth() {
|
|
776
|
+
const [element, ref] = useState(null);
|
|
777
|
+
const [size, setSize] = useState({
|
|
778
|
+
width: 0,
|
|
779
|
+
em: 16
|
|
780
|
+
});
|
|
781
|
+
useEffect(() => {
|
|
782
|
+
if (!element) return;
|
|
783
|
+
const observer = new ResizeObserver(([entry]) => setSize({
|
|
784
|
+
width: Math.floor(entry.contentRect.width),
|
|
785
|
+
em: parseFloat(getComputedStyle(element).fontSize) || 16
|
|
786
|
+
}));
|
|
787
|
+
observer.observe(element);
|
|
788
|
+
return () => observer.disconnect();
|
|
789
|
+
}, [element]);
|
|
790
|
+
return [
|
|
791
|
+
ref,
|
|
792
|
+
size.width,
|
|
793
|
+
size.em
|
|
794
|
+
];
|
|
795
|
+
}
|
|
796
|
+
/** A nice ceiling for an axis and its three gridline values. */
|
|
797
|
+
function ticksFor(max) {
|
|
798
|
+
if (!(max > 0)) return {
|
|
799
|
+
top: 1,
|
|
800
|
+
ticks: [
|
|
801
|
+
0,
|
|
802
|
+
.5,
|
|
803
|
+
1
|
|
804
|
+
]
|
|
805
|
+
};
|
|
806
|
+
const power = 10 ** Math.floor(Math.log10(max));
|
|
807
|
+
const step = [
|
|
808
|
+
1,
|
|
809
|
+
2,
|
|
810
|
+
2.5,
|
|
811
|
+
5,
|
|
812
|
+
10
|
|
813
|
+
].map((m) => m * power).find((s) => s * 4 >= max) ?? power * 10;
|
|
814
|
+
const top = Math.ceil(max / step) * step;
|
|
815
|
+
return {
|
|
816
|
+
top,
|
|
817
|
+
ticks: [
|
|
818
|
+
0,
|
|
819
|
+
top / 2,
|
|
820
|
+
top
|
|
821
|
+
]
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function Tooltip({ tip }) {
|
|
825
|
+
if (!tip) return null;
|
|
826
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
827
|
+
className: "ov-tip",
|
|
828
|
+
role: "tooltip",
|
|
829
|
+
style: {
|
|
830
|
+
left: tip.x,
|
|
831
|
+
top: tip.y
|
|
832
|
+
},
|
|
833
|
+
children: [/* @__PURE__ */ jsx("strong", { children: tip.title }), tip.lines.map((line) => /* @__PURE__ */ jsxs("span", {
|
|
834
|
+
className: "ov-tip-line",
|
|
835
|
+
children: [
|
|
836
|
+
line.color && /* @__PURE__ */ jsx("i", { style: { background: line.color } }),
|
|
837
|
+
/* @__PURE__ */ jsx("span", { children: line.label }),
|
|
838
|
+
/* @__PURE__ */ jsx("b", { children: line.value })
|
|
839
|
+
]
|
|
840
|
+
}, line.label))]
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
/** Bars per day (or per any label: `tick` shortens it on the axis), stacked by series, on one axis; hovering a bar lists
|
|
844
|
+
* every series' value for it, and `onOpen` makes a bar a link. */
|
|
845
|
+
function DayBars({ days, series, format = formatCompact, rows = 13, missing = "No runs started", tick = (day) => day.slice(5), onOpen }) {
|
|
846
|
+
const [ref, width, em] = useWidth();
|
|
847
|
+
const height = Math.round(rows * em);
|
|
848
|
+
const [tip, setTip] = useState(null);
|
|
849
|
+
const totals = days.map((_, i) => series.reduce((sum, s) => sum + (s.values[i] ?? 0), 0));
|
|
850
|
+
const { top, ticks } = ticksFor(Math.max(...totals));
|
|
851
|
+
const left = Math.round(3.4 * em);
|
|
852
|
+
const bottom = Math.round(1.8 * em);
|
|
853
|
+
const plotW = Math.max(0, width - left - 8);
|
|
854
|
+
const plotH = height - bottom - 8;
|
|
855
|
+
const slot = days.length ? plotW / days.length : 0;
|
|
856
|
+
const bar = Math.max(3, slot * .72);
|
|
857
|
+
const labelEvery = Math.max(1, Math.ceil(days.length / Math.max(1, Math.floor(plotW / (4.5 * em)))));
|
|
858
|
+
const labelGap = Math.ceil(4.5 * em / Math.max(slot, 1));
|
|
859
|
+
const y = (v) => 8 + plotH - v / top * plotH;
|
|
860
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
861
|
+
ref,
|
|
862
|
+
className: "ov-plot",
|
|
863
|
+
onMouseLeave: () => setTip(null),
|
|
864
|
+
children: [width > 0 && /* @__PURE__ */ jsxs("svg", {
|
|
865
|
+
width,
|
|
866
|
+
height,
|
|
867
|
+
role: "img",
|
|
868
|
+
"aria-label": `${series.map((s) => s.name).join(", ")} per day`,
|
|
869
|
+
children: [ticks.map((t) => /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("line", {
|
|
870
|
+
x1: left,
|
|
871
|
+
x2: width - 8,
|
|
872
|
+
y1: y(t),
|
|
873
|
+
y2: y(t),
|
|
874
|
+
className: "ov-gridline"
|
|
875
|
+
}), /* @__PURE__ */ jsx("text", {
|
|
876
|
+
x: left - em * .4,
|
|
877
|
+
y: y(t),
|
|
878
|
+
dominantBaseline: "middle",
|
|
879
|
+
className: "ov-axis",
|
|
880
|
+
textAnchor: "end",
|
|
881
|
+
children: format(t)
|
|
882
|
+
})] }, t)), days.map((day, i) => {
|
|
883
|
+
let base = 0;
|
|
884
|
+
const x = left + i * slot + (slot - bar) / 2;
|
|
885
|
+
const showTip = () => setTip({
|
|
886
|
+
x: Math.min(left + i * slot + slot + 8, width - em * 14),
|
|
887
|
+
y: 8,
|
|
888
|
+
title: `${day} · ${series.every((s) => s.values[i] === null) ? missing : format(totals[i] ?? 0)}`,
|
|
889
|
+
lines: series.map((s) => ({
|
|
890
|
+
label: s.name,
|
|
891
|
+
value: s.values[i] === null ? missing : format(s.values[i] ?? 0),
|
|
892
|
+
color: s.color
|
|
893
|
+
}))
|
|
894
|
+
});
|
|
895
|
+
return /* @__PURE__ */ jsxs("g", {
|
|
896
|
+
role: onOpen ? "button" : void 0,
|
|
897
|
+
tabIndex: onOpen ? 0 : void 0,
|
|
898
|
+
"aria-label": onOpen ? `Open ${day}` : void 0,
|
|
899
|
+
onFocus: onOpen ? showTip : void 0,
|
|
900
|
+
onBlur: onOpen ? () => setTip(null) : void 0,
|
|
901
|
+
onKeyDown: onOpen ? (event) => {
|
|
902
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
903
|
+
event.preventDefault();
|
|
904
|
+
onOpen(i);
|
|
905
|
+
}
|
|
906
|
+
} : void 0,
|
|
907
|
+
children: [
|
|
908
|
+
series.map((s) => {
|
|
909
|
+
const v = s.values[i] ?? 0;
|
|
910
|
+
if (!v) return null;
|
|
911
|
+
const y0 = y(base);
|
|
912
|
+
base += v;
|
|
913
|
+
const y1 = y(base);
|
|
914
|
+
return /* @__PURE__ */ jsx("rect", {
|
|
915
|
+
x,
|
|
916
|
+
y: y1,
|
|
917
|
+
width: bar,
|
|
918
|
+
height: Math.max(2, y0 - y1 - 2),
|
|
919
|
+
rx: 2,
|
|
920
|
+
fill: s.color
|
|
921
|
+
}, s.name);
|
|
922
|
+
}),
|
|
923
|
+
(i === days.length - 1 || i % labelEvery === 0 && i + labelGap <= days.length - 1) && /* @__PURE__ */ jsx("text", {
|
|
924
|
+
x: x + bar / 2,
|
|
925
|
+
y: height - em * .35,
|
|
926
|
+
className: "ov-axis",
|
|
927
|
+
textAnchor: "middle",
|
|
928
|
+
children: tick(day)
|
|
929
|
+
}),
|
|
930
|
+
/* @__PURE__ */ jsx("rect", {
|
|
931
|
+
x: left + i * slot,
|
|
932
|
+
y: 8,
|
|
933
|
+
width: slot,
|
|
934
|
+
height: plotH,
|
|
935
|
+
fill: "transparent",
|
|
936
|
+
style: onOpen ? { cursor: "pointer" } : void 0,
|
|
937
|
+
onClick: onOpen ? () => onOpen(i) : void 0,
|
|
938
|
+
onMouseEnter: showTip
|
|
939
|
+
})
|
|
940
|
+
]
|
|
941
|
+
}, day);
|
|
942
|
+
})]
|
|
943
|
+
}), /* @__PURE__ */ jsx(Tooltip, { tip })]
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
/** Ranked horizontal bars with the value and an optional note on each row; past `limit` rows the rest are counted. */
|
|
947
|
+
function HBars({ rows, format = formatCompact, color = LIVE_SERIES[0], max, limit = 8 }) {
|
|
948
|
+
const top = max ?? Math.max(1, ...rows.map((row) => row.value));
|
|
949
|
+
if (!rows.length) return /* @__PURE__ */ jsx("p", {
|
|
950
|
+
className: "ov-empty",
|
|
951
|
+
children: "Nothing recorded."
|
|
952
|
+
});
|
|
953
|
+
const rest = rows.slice(limit);
|
|
954
|
+
return /* @__PURE__ */ jsxs("ol", {
|
|
955
|
+
className: "ov-hbars",
|
|
956
|
+
children: [rows.slice(0, limit).map((row) => {
|
|
957
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
958
|
+
/* @__PURE__ */ jsx("span", {
|
|
959
|
+
className: "ov-hbar-label",
|
|
960
|
+
title: row.label,
|
|
961
|
+
children: row.label
|
|
962
|
+
}),
|
|
963
|
+
/* @__PURE__ */ jsx("span", {
|
|
964
|
+
className: "ov-hbar-track",
|
|
965
|
+
children: /* @__PURE__ */ jsx("span", { style: {
|
|
966
|
+
width: `${Math.max(1, row.value / top * 100)}%`,
|
|
967
|
+
background: row.color ?? color
|
|
968
|
+
} })
|
|
969
|
+
}),
|
|
970
|
+
/* @__PURE__ */ jsx("span", {
|
|
971
|
+
className: "ov-hbar-value",
|
|
972
|
+
children: format(row.value)
|
|
973
|
+
}),
|
|
974
|
+
row.note && /* @__PURE__ */ jsx("span", {
|
|
975
|
+
className: "ov-hbar-note",
|
|
976
|
+
children: row.note
|
|
977
|
+
})
|
|
978
|
+
] });
|
|
979
|
+
return /* @__PURE__ */ jsx("li", { children: row.href ? /* @__PURE__ */ jsx("a", {
|
|
980
|
+
href: row.href,
|
|
981
|
+
children: content
|
|
982
|
+
}) : /* @__PURE__ */ jsx("div", { children: content }) }, row.label);
|
|
983
|
+
}), rest.length > 0 && /* @__PURE__ */ jsxs("li", {
|
|
984
|
+
className: "ov-hbars-more",
|
|
985
|
+
children: [
|
|
986
|
+
"+",
|
|
987
|
+
rest.length,
|
|
988
|
+
" more · ",
|
|
989
|
+
format(rest.reduce((a, row) => a + row.value, 0))
|
|
990
|
+
]
|
|
991
|
+
})]
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
/** A histogram of counts per bin; each bar's tooltip names its range and count. */
|
|
995
|
+
function Histogram({ bins, format = formatCompact, color = LIVE_SERIES[0], unit, rows = 12 }) {
|
|
996
|
+
const [ref, width, em] = useWidth();
|
|
997
|
+
const height = Math.round(rows * em);
|
|
998
|
+
const [tip, setTip] = useState(null);
|
|
999
|
+
const first = bins.findIndex((b) => b.n > 0);
|
|
1000
|
+
const last = bins.length - 1 - [...bins].reverse().findIndex((b) => b.n > 0);
|
|
1001
|
+
const shown = first < 0 ? [] : bins.slice(first, last + 1);
|
|
1002
|
+
const { top, ticks } = ticksFor(Math.max(...shown.map((b) => b.n), 1));
|
|
1003
|
+
const left = Math.round(3 * em);
|
|
1004
|
+
const bottom = Math.round(1.8 * em);
|
|
1005
|
+
const plotW = Math.max(0, width - left - 8);
|
|
1006
|
+
const plotH = height - bottom - 8;
|
|
1007
|
+
const slot = shown.length ? plotW / shown.length : 0;
|
|
1008
|
+
const y = (v) => 8 + plotH - v / top * plotH;
|
|
1009
|
+
if (!shown.length) return /* @__PURE__ */ jsx("p", {
|
|
1010
|
+
className: "ov-empty",
|
|
1011
|
+
children: "Nothing recorded."
|
|
1012
|
+
});
|
|
1013
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1014
|
+
ref,
|
|
1015
|
+
className: "ov-plot",
|
|
1016
|
+
onMouseLeave: () => setTip(null),
|
|
1017
|
+
children: [width > 0 && /* @__PURE__ */ jsxs("svg", {
|
|
1018
|
+
width,
|
|
1019
|
+
height,
|
|
1020
|
+
role: "img",
|
|
1021
|
+
"aria-label": `Distribution of ${unit}`,
|
|
1022
|
+
children: [ticks.map((t) => /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("line", {
|
|
1023
|
+
x1: left,
|
|
1024
|
+
x2: width - 8,
|
|
1025
|
+
y1: y(t),
|
|
1026
|
+
y2: y(t),
|
|
1027
|
+
className: "ov-gridline"
|
|
1028
|
+
}), /* @__PURE__ */ jsx("text", {
|
|
1029
|
+
x: left - em * .4,
|
|
1030
|
+
y: y(t),
|
|
1031
|
+
dominantBaseline: "middle",
|
|
1032
|
+
className: "ov-axis",
|
|
1033
|
+
textAnchor: "end",
|
|
1034
|
+
children: formatCompact(t)
|
|
1035
|
+
})] }, t)), shown.map((bin, i) => /* @__PURE__ */ jsxs("g", { children: [
|
|
1036
|
+
/* @__PURE__ */ jsx("rect", {
|
|
1037
|
+
x: left + i * slot + 2,
|
|
1038
|
+
y: y(bin.n),
|
|
1039
|
+
width: Math.max(3, slot - 4),
|
|
1040
|
+
height: Math.max(bin.n ? 2 : 0, 8 + plotH - y(bin.n)),
|
|
1041
|
+
rx: 2,
|
|
1042
|
+
fill: color
|
|
1043
|
+
}),
|
|
1044
|
+
(i % Math.max(1, Math.ceil(shown.length / 6)) === 0 || i === shown.length - 1) && /* @__PURE__ */ jsx("text", {
|
|
1045
|
+
x: left + i * slot + slot / 2,
|
|
1046
|
+
y: height - em * .35,
|
|
1047
|
+
className: "ov-axis",
|
|
1048
|
+
textAnchor: "middle",
|
|
1049
|
+
children: format(bin.lo)
|
|
1050
|
+
}),
|
|
1051
|
+
/* @__PURE__ */ jsx("rect", {
|
|
1052
|
+
x: left + i * slot,
|
|
1053
|
+
y: 8,
|
|
1054
|
+
width: slot,
|
|
1055
|
+
height: plotH,
|
|
1056
|
+
fill: "transparent",
|
|
1057
|
+
onMouseEnter: () => setTip({
|
|
1058
|
+
x: Math.min(left + i * slot + slot + 6, width - em * 14),
|
|
1059
|
+
y: 8,
|
|
1060
|
+
title: `${format(bin.lo)} to ${format(bin.hi)} ${unit}`,
|
|
1061
|
+
lines: [{
|
|
1062
|
+
label: "count",
|
|
1063
|
+
value: String(bin.n)
|
|
1064
|
+
}]
|
|
1065
|
+
})
|
|
1066
|
+
})
|
|
1067
|
+
] }, i))]
|
|
1068
|
+
}), /* @__PURE__ */ jsx(Tooltip, { tip })]
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
/** Lines over time on one axis (at most four series); the crosshair readout lists each series at the nearest time. A
|
|
1072
|
+
* `dots` series draws its points as marks (checkpoints) instead of a line; `axis` labels the time axis (default MM-DD). */
|
|
1073
|
+
function Lines({ series, format = formatCompact, rows = 13, axis, marks = [], bands = [] }) {
|
|
1074
|
+
const [ref, width, em] = useWidth();
|
|
1075
|
+
const height = Math.round(rows * em);
|
|
1076
|
+
const [hover, setHover] = useState(null);
|
|
1077
|
+
const gap = eventsGap(marks, bands);
|
|
1078
|
+
if (gap) throw new RangeError(gap);
|
|
1079
|
+
const all = series.flatMap((s) => s.points);
|
|
1080
|
+
if (!all.length) return /* @__PURE__ */ jsx("p", {
|
|
1081
|
+
className: "ov-empty",
|
|
1082
|
+
children: "No history yet."
|
|
1083
|
+
});
|
|
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);
|
|
1091
|
+
const { top, ticks } = ticksFor(Math.max(...all.map((p) => p[1])));
|
|
1092
|
+
const left = Math.round(3.4 * em);
|
|
1093
|
+
const bottom = Math.round(1.8 * em);
|
|
1094
|
+
const plotW = Math.max(0, width - left - 10);
|
|
1095
|
+
const plotH = height - bottom - 8;
|
|
1096
|
+
const x = (t) => left + (t1 > t0 ? (t - t0) / (t1 - t0) * plotW : 0);
|
|
1097
|
+
const y = (v) => 8 + plotH - v / top * plotH;
|
|
1098
|
+
const nearest = (points, t) => points.reduce((best, p) => Math.abs(p[0] - t) < Math.abs(best[0] - t) ? p : best, points[0]);
|
|
1099
|
+
const day = (t) => new Date(t).toISOString().slice(5, 16).replace("T", " ");
|
|
1100
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1101
|
+
ref,
|
|
1102
|
+
className: "ov-plot",
|
|
1103
|
+
onMouseMove: (event) => {
|
|
1104
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
1105
|
+
const px = event.clientX - box.left;
|
|
1106
|
+
setHover(px >= left ? t0 + (px - left) / Math.max(1, plotW) * (t1 - t0) : null);
|
|
1107
|
+
},
|
|
1108
|
+
onMouseLeave: () => setHover(null),
|
|
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", {
|
|
1149
|
+
cx: x(p[0]),
|
|
1150
|
+
cy: y(p[1]),
|
|
1151
|
+
r: Math.max(3, em / 5),
|
|
1152
|
+
fill: s.color,
|
|
1153
|
+
stroke: "var(--chart-live-surface, #fff)",
|
|
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
|
+
};
|
|
1214
|
+
})
|
|
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))
|
|
1242
|
+
})
|
|
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
|
+
]
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
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
|
+
});
|
|
1337
|
+
const share = used !== null && used !== void 0 && total ? Math.min(1, used / total) : null;
|
|
1338
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1339
|
+
className: "ov-meter",
|
|
1340
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1341
|
+
className: "ov-meter-track",
|
|
1342
|
+
children: /* @__PURE__ */ jsx("span", { style: {
|
|
1343
|
+
width: `${(share ?? 0) * 100}%`,
|
|
1344
|
+
background: tone ?? (share !== null && share > .9 ? "var(--chart-live-danger, #d85050)" : share !== null && share > .7 ? "var(--chart-live-warning, #c98500)" : LIVE_SERIES[0])
|
|
1345
|
+
} })
|
|
1346
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1347
|
+
className: "ov-meter-text",
|
|
1348
|
+
children: text ?? `${format(used ?? NaN)} of ${format(total ?? NaN)} · ${formatShare(share)}`
|
|
1349
|
+
})]
|
|
1350
|
+
});
|
|
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
|
+
}
|
|
1410
|
+
/** A small line of one series for a row in a list (one hue; the row's label carries identity). */
|
|
1411
|
+
function Spark({ points, max = 1, color = LIVE_SERIES[0] }) {
|
|
1412
|
+
if (points.length < 2) return /* @__PURE__ */ jsx("span", {
|
|
1413
|
+
className: "ov-spark-empty",
|
|
1414
|
+
children: "collecting"
|
|
1415
|
+
});
|
|
1416
|
+
const t0 = points[0][0];
|
|
1417
|
+
const t1 = points.at(-1)[0];
|
|
1418
|
+
const w = 160;
|
|
1419
|
+
const h = 32;
|
|
1420
|
+
const domain = Math.max(1, max);
|
|
1421
|
+
const d = points.map((p) => `${(p[0] - t0) / Math.max(1, t1 - t0) * w},${30 - Math.min(domain, p[1]) / domain * 28}`).join(" ");
|
|
1422
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
1423
|
+
viewBox: `0 0 ${w} ${h}`,
|
|
1424
|
+
className: "ov-spark",
|
|
1425
|
+
"aria-hidden": "true",
|
|
1426
|
+
children: /* @__PURE__ */ jsx("polyline", {
|
|
1427
|
+
fill: "none",
|
|
1428
|
+
stroke: color,
|
|
1429
|
+
strokeWidth: 1.6,
|
|
1430
|
+
points: d
|
|
1431
|
+
})
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
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
|
|
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"] }) {
|
|
1439
|
+
const [ref, width, em] = useWidth();
|
|
1440
|
+
const [tip, setTip] = useState(null);
|
|
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", {
|
|
1460
|
+
className: "ov-empty",
|
|
1461
|
+
children: "No agent has started."
|
|
1462
|
+
});
|
|
1463
|
+
const ends = (s) => s.end ?? now;
|
|
1464
|
+
const rowH = Math.round(1.25 * em);
|
|
1465
|
+
const labelW = Math.min(Math.round(13 * em), Math.round(width * .32));
|
|
1466
|
+
const top = marks.length ? 4 + Math.round(Math.min(3, marks.length) * em + .3 * em) : 4;
|
|
1467
|
+
const bottom = Math.round(1.8 * em);
|
|
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);
|
|
1476
|
+
const plotW = Math.max(0, width - labelW - 10);
|
|
1477
|
+
const x = (t) => labelW + (t - t0) / (t1 - t0) * plotW;
|
|
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;
|
|
1481
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1482
|
+
ref,
|
|
1483
|
+
className: "ov-plot",
|
|
1484
|
+
onMouseLeave: () => setTip(null),
|
|
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);
|
|
1587
|
+
}),
|
|
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
|
+
]
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
//#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 };
|