@sckyzo/cosmos-charts 0.2.0 → 0.3.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/CHANGELOG.md +91 -0
- package/README.md +107 -22
- package/THIRD_PARTY_NOTICES.md +81 -0
- package/dist/BarChart.cjs +137 -0
- package/dist/BarChart.cjs.map +1 -0
- package/dist/BarChart.js +135 -0
- package/dist/BarChart.js.map +1 -0
- package/dist/BoxPlotChart.cjs +214 -0
- package/dist/BoxPlotChart.cjs.map +1 -0
- package/dist/BoxPlotChart.js +212 -0
- package/dist/BoxPlotChart.js.map +1 -0
- package/dist/BumpChart.cjs +328 -0
- package/dist/BumpChart.cjs.map +1 -0
- package/dist/BumpChart.js +326 -0
- package/dist/BumpChart.js.map +1 -0
- package/dist/CalendarChart.cjs +216 -0
- package/dist/CalendarChart.cjs.map +1 -0
- package/dist/CalendarChart.js +214 -0
- package/dist/CalendarChart.js.map +1 -0
- package/dist/DonutChart.cjs +122 -0
- package/dist/DonutChart.cjs.map +1 -0
- package/dist/DonutChart.js +120 -0
- package/dist/DonutChart.js.map +1 -0
- package/dist/FunnelChart.cjs +422 -0
- package/dist/FunnelChart.cjs.map +1 -0
- package/dist/FunnelChart.js +420 -0
- package/dist/FunnelChart.js.map +1 -0
- package/dist/GradientRadial.cjs +51 -0
- package/dist/GradientRadial.cjs.map +1 -0
- package/dist/GradientRadial.js +49 -0
- package/dist/GradientRadial.js.map +1 -0
- package/dist/HeatmapChart.cjs +146 -0
- package/dist/HeatmapChart.cjs.map +1 -0
- package/dist/HeatmapChart.js +144 -0
- package/dist/HeatmapChart.js.map +1 -0
- package/dist/LineAreaChart.cjs +98 -0
- package/dist/LineAreaChart.cjs.map +1 -0
- package/dist/LineAreaChart.js +96 -0
- package/dist/LineAreaChart.js.map +1 -0
- package/dist/MixedChart.cjs +195 -0
- package/dist/MixedChart.cjs.map +1 -0
- package/dist/MixedChart.js +193 -0
- package/dist/MixedChart.js.map +1 -0
- package/dist/RadarChart.cjs +109 -0
- package/dist/RadarChart.cjs.map +1 -0
- package/dist/RadarChart.js +107 -0
- package/dist/RadarChart.js.map +1 -0
- package/dist/RadialBarChart.cjs +99 -0
- package/dist/RadialBarChart.cjs.map +1 -0
- package/dist/RadialBarChart.js +97 -0
- package/dist/RadialBarChart.js.map +1 -0
- package/dist/RangeBarChart.cjs +218 -0
- package/dist/RangeBarChart.cjs.map +1 -0
- package/dist/RangeBarChart.js +216 -0
- package/dist/RangeBarChart.js.map +1 -0
- package/dist/RealtimeChart.cjs +173 -0
- package/dist/RealtimeChart.cjs.map +1 -0
- package/dist/RealtimeChart.js +171 -0
- package/dist/RealtimeChart.js.map +1 -0
- package/dist/ScatterChart.cjs +134 -0
- package/dist/ScatterChart.cjs.map +1 -0
- package/dist/ScatterChart.js +131 -0
- package/dist/ScatterChart.js.map +1 -0
- package/dist/SemiCircleGauge.cjs +142 -0
- package/dist/SemiCircleGauge.cjs.map +1 -0
- package/dist/SemiCircleGauge.js +140 -0
- package/dist/SemiCircleGauge.js.map +1 -0
- package/dist/Sparkline.cjs +117 -0
- package/dist/Sparkline.cjs.map +1 -0
- package/dist/Sparkline.js +115 -0
- package/dist/Sparkline.js.map +1 -0
- package/dist/StrokedGauge.cjs +94 -0
- package/dist/StrokedGauge.cjs.map +1 -0
- package/dist/StrokedGauge.js +92 -0
- package/dist/StrokedGauge.js.map +1 -0
- package/dist/TreemapChart.cjs +263 -0
- package/dist/TreemapChart.cjs.map +1 -0
- package/dist/TreemapChart.js +261 -0
- package/dist/TreemapChart.js.map +1 -0
- package/dist/WaterfallChart.cjs +235 -0
- package/dist/WaterfallChart.cjs.map +1 -0
- package/dist/WaterfallChart.js +233 -0
- package/dist/WaterfallChart.js.map +1 -0
- package/dist/WorldMap.cjs +2088 -0
- package/dist/WorldMap.cjs.map +1 -0
- package/dist/WorldMap.js +603 -0
- package/dist/WorldMap.js.map +1 -0
- package/dist/chunk-3N43QT36.js +60 -0
- package/dist/chunk-3N43QT36.js.map +1 -0
- package/dist/chunk-5C2RLJYZ.js +11 -0
- package/dist/chunk-5C2RLJYZ.js.map +1 -0
- package/dist/chunk-6ALKZLN2.js +127 -0
- package/dist/chunk-6ALKZLN2.js.map +1 -0
- package/dist/chunk-6BKLL7LZ.cjs +30 -0
- package/dist/chunk-6BKLL7LZ.cjs.map +1 -0
- package/dist/chunk-6NRRABPF.cjs +64 -0
- package/dist/chunk-6NRRABPF.cjs.map +1 -0
- package/dist/chunk-AAYHG7YK.cjs +30 -0
- package/dist/chunk-AAYHG7YK.cjs.map +1 -0
- package/dist/chunk-AOSO5QHL.js +19 -0
- package/dist/chunk-AOSO5QHL.js.map +1 -0
- package/dist/chunk-BFEDLXVW.js +12 -0
- package/dist/chunk-BFEDLXVW.js.map +1 -0
- package/dist/chunk-DBZQI4HM.cjs +14 -0
- package/dist/chunk-DBZQI4HM.cjs.map +1 -0
- package/dist/chunk-F2PMFYJ5.cjs +40 -0
- package/dist/chunk-F2PMFYJ5.cjs.map +1 -0
- package/dist/chunk-FUZW3H5X.js +38 -0
- package/dist/chunk-FUZW3H5X.js.map +1 -0
- package/dist/chunk-GDZLYYL2.js +27 -0
- package/dist/chunk-GDZLYYL2.js.map +1 -0
- package/dist/chunk-GVTMZYOM.cjs +30 -0
- package/dist/chunk-GVTMZYOM.cjs.map +1 -0
- package/dist/chunk-GZRMVE4X.cjs +131 -0
- package/dist/chunk-GZRMVE4X.cjs.map +1 -0
- package/dist/chunk-HMH6DI7U.js +27 -0
- package/dist/chunk-HMH6DI7U.js.map +1 -0
- package/dist/chunk-ICL5K2CW.js +280 -0
- package/dist/chunk-ICL5K2CW.js.map +1 -0
- package/dist/chunk-IW6EWB26.cjs +21 -0
- package/dist/chunk-IW6EWB26.cjs.map +1 -0
- package/dist/chunk-K3R7JGOW.cjs +18 -0
- package/dist/chunk-K3R7JGOW.cjs.map +1 -0
- package/dist/chunk-LT3UC65O.cjs +68 -0
- package/dist/chunk-LT3UC65O.cjs.map +1 -0
- package/dist/chunk-MN4CDVOS.cjs +63 -0
- package/dist/chunk-MN4CDVOS.cjs.map +1 -0
- package/dist/chunk-OCD6JIIX.js +23 -0
- package/dist/chunk-OCD6JIIX.js.map +1 -0
- package/dist/chunk-RILUG7JB.js +44 -0
- package/dist/chunk-RILUG7JB.js.map +1 -0
- package/dist/chunk-RLHGPHC7.cjs +51 -0
- package/dist/chunk-RLHGPHC7.cjs.map +1 -0
- package/dist/chunk-SGG7XKOY.js +58 -0
- package/dist/chunk-SGG7XKOY.js.map +1 -0
- package/dist/chunk-T3K6Q34U.cjs +82 -0
- package/dist/chunk-T3K6Q34U.cjs.map +1 -0
- package/dist/chunk-TPH5T3DA.cjs +282 -0
- package/dist/chunk-TPH5T3DA.cjs.map +1 -0
- package/dist/chunk-TYQWZR3S.cjs +13 -0
- package/dist/chunk-TYQWZR3S.cjs.map +1 -0
- package/dist/chunk-UATB2IRY.js +51 -0
- package/dist/chunk-UATB2IRY.js.map +1 -0
- package/dist/chunk-UJIV5PVI.js +13 -0
- package/dist/chunk-UJIV5PVI.js.map +1 -0
- package/dist/chunk-URNXLVSK.js +44 -0
- package/dist/chunk-URNXLVSK.js.map +1 -0
- package/dist/chunk-VIOSQN4Z.js +79 -0
- package/dist/chunk-VIOSQN4Z.js.map +1 -0
- package/dist/chunk-X3YZP5WF.cjs +46 -0
- package/dist/chunk-X3YZP5WF.cjs.map +1 -0
- package/dist/chunk-YWYF5CCJ.js +61 -0
- package/dist/chunk-YWYF5CCJ.js.map +1 -0
- package/dist/chunk-Z4LZFMDH.cjs +60 -0
- package/dist/chunk-Z4LZFMDH.cjs.map +1 -0
- package/dist/index.cjs +154 -1166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +470 -80
- package/dist/index.d.ts +470 -80
- package/dist/index.js +23 -1144
- package/dist/index.js.map +1 -1
- package/dist/theme.cjs +88 -0
- package/dist/theme.cjs.map +1 -0
- package/dist/theme.js +84 -0
- package/dist/theme.js.map +1 -0
- package/package.json +23 -12
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCategoryKeys } from './chunk-FUZW3H5X.js';
|
|
3
|
+
import { referenceMarks } from './chunk-SGG7XKOY.js';
|
|
4
|
+
import { useHiddenSeries, legendProps, ChartLegend } from './chunk-6ALKZLN2.js';
|
|
5
|
+
import { ANIMATION } from './chunk-5C2RLJYZ.js';
|
|
6
|
+
import { useAxis, xAxisTitle, yAxisTitle } from './chunk-RILUG7JB.js';
|
|
7
|
+
import { TooltipCard } from './chunk-VIOSQN4Z.js';
|
|
8
|
+
import { hasNoData, seriesKey } from './chunk-UJIV5PVI.js';
|
|
9
|
+
import { ChartEmpty, frameProps } from './chunk-3N43QT36.js';
|
|
10
|
+
import { GRID, BORDER, seriesColor } from './chunk-UATB2IRY.js';
|
|
11
|
+
import { BarChart, CartesianGrid, XAxis, YAxis, Legend, Tooltip, Bar } from 'recharts';
|
|
12
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
13
|
+
|
|
14
|
+
var UTC_DATE_TIME = new Intl.DateTimeFormat("en-GB", {
|
|
15
|
+
day: "2-digit",
|
|
16
|
+
month: "short",
|
|
17
|
+
hour: "2-digit",
|
|
18
|
+
minute: "2-digit",
|
|
19
|
+
hourCycle: "h23",
|
|
20
|
+
timeZone: "UTC"
|
|
21
|
+
});
|
|
22
|
+
var UTC_DAY = new Intl.DateTimeFormat("en-GB", {
|
|
23
|
+
day: "2-digit",
|
|
24
|
+
month: "short",
|
|
25
|
+
timeZone: "UTC"
|
|
26
|
+
});
|
|
27
|
+
var UTC_TIME = new Intl.DateTimeFormat("en-GB", {
|
|
28
|
+
hour: "2-digit",
|
|
29
|
+
minute: "2-digit",
|
|
30
|
+
hourCycle: "h23",
|
|
31
|
+
timeZone: "UTC"
|
|
32
|
+
});
|
|
33
|
+
var defaultFormatDate = (timestamp) => UTC_DATE_TIME.format(timestamp);
|
|
34
|
+
var HOUR = 36e5;
|
|
35
|
+
var DAY = 24 * HOUR;
|
|
36
|
+
var STEPS = [HOUR, 2 * HOUR, 3 * HOUR, 6 * HOUR, 12 * HOUR, DAY, 2 * DAY, 7 * DAY, 30 * DAY];
|
|
37
|
+
var timeTicks = (min, max) => {
|
|
38
|
+
const step = STEPS.find((s) => (max - min) / s <= 8) ?? STEPS[STEPS.length - 1];
|
|
39
|
+
const first = Math.floor(min / step) * step;
|
|
40
|
+
const last = Math.max(Math.ceil(max / step) * step, first + step);
|
|
41
|
+
return Array.from({ length: Math.round((last - first) / step) + 1 }, (_, i) => first + i * step);
|
|
42
|
+
};
|
|
43
|
+
var tickLabel = (timestamp) => timestamp % DAY === 0 ? UTC_DAY.format(timestamp) : UTC_TIME.format(timestamp);
|
|
44
|
+
var GROUP_KEY = "all";
|
|
45
|
+
var rangesShape = (key, colors, visible) => (props) => {
|
|
46
|
+
const { x, y, width, height } = props;
|
|
47
|
+
const row = props.payload;
|
|
48
|
+
const bounds = row[key];
|
|
49
|
+
if (!bounds) return null;
|
|
50
|
+
const [low, high] = bounds;
|
|
51
|
+
const span = high - low || 1;
|
|
52
|
+
const lane = Math.abs(height);
|
|
53
|
+
const top = Math.min(y, y + height);
|
|
54
|
+
return /* @__PURE__ */ jsx("g", { children: row.ranges.filter((r) => visible(r.series) && (key === GROUP_KEY || key === seriesKey(r.series))).map((r, i) => /* @__PURE__ */ jsx(
|
|
55
|
+
"rect",
|
|
56
|
+
{
|
|
57
|
+
x: x + (r.start - low) / span * width,
|
|
58
|
+
y: top,
|
|
59
|
+
width: Math.max((r.end - r.start) / span * width, 2),
|
|
60
|
+
height: lane,
|
|
61
|
+
rx: 3,
|
|
62
|
+
fill: colors(r.series)
|
|
63
|
+
},
|
|
64
|
+
i
|
|
65
|
+
)) });
|
|
66
|
+
};
|
|
67
|
+
var SeriesLegend = ({
|
|
68
|
+
entries,
|
|
69
|
+
hidden,
|
|
70
|
+
onToggle,
|
|
71
|
+
column
|
|
72
|
+
}) => /* @__PURE__ */ jsx(ChartLegend, { payload: entries, hidden, onToggle, column });
|
|
73
|
+
var RangeBarChart = ({
|
|
74
|
+
series,
|
|
75
|
+
groupRows = false,
|
|
76
|
+
colors,
|
|
77
|
+
formatDate = defaultFormatDate,
|
|
78
|
+
xTitle,
|
|
79
|
+
yTitle,
|
|
80
|
+
referenceLines,
|
|
81
|
+
referenceBands,
|
|
82
|
+
showLegend,
|
|
83
|
+
legendPosition = "top",
|
|
84
|
+
emptyMessage = "No data",
|
|
85
|
+
ariaLabel,
|
|
86
|
+
children,
|
|
87
|
+
height = 320,
|
|
88
|
+
width
|
|
89
|
+
}) => {
|
|
90
|
+
const hidden = useHiddenSeries();
|
|
91
|
+
const axis = useAxis();
|
|
92
|
+
const categoryKeys = useCategoryKeys("vertical");
|
|
93
|
+
const name = ariaLabel ?? `Range chart: ${series.map((s) => s.name).join(", ")}`;
|
|
94
|
+
if (hasNoData(series)) {
|
|
95
|
+
return /* @__PURE__ */ jsx(ChartEmpty, { message: emptyMessage, ariaLabel: name, height, width });
|
|
96
|
+
}
|
|
97
|
+
const categories = [...new Set(series.flatMap((s) => s.data.map((p) => p.x)))];
|
|
98
|
+
const all = series.flatMap((s) => s.data.flatMap((p) => p.y));
|
|
99
|
+
const ticks = timeTicks(Math.min(...all), Math.max(...all));
|
|
100
|
+
const color = (index) => seriesColor(index, colors);
|
|
101
|
+
const visible = (index) => !hidden.hidden.has(seriesKey(index));
|
|
102
|
+
const rows = categories.map((category) => {
|
|
103
|
+
const ranges = series.flatMap(
|
|
104
|
+
(s, index) => s.data.filter((p) => p.x === category).map((p) => ({ series: index, start: p.y[0], end: p.y[1] }))
|
|
105
|
+
);
|
|
106
|
+
const bounds = (of) => of.length > 0 ? [Math.min(...of.map((r) => r.start)), Math.max(...of.map((r) => r.end))] : void 0;
|
|
107
|
+
return {
|
|
108
|
+
category,
|
|
109
|
+
ranges,
|
|
110
|
+
[GROUP_KEY]: bounds(ranges),
|
|
111
|
+
...Object.fromEntries(
|
|
112
|
+
series.map((_, index) => [
|
|
113
|
+
seriesKey(index),
|
|
114
|
+
bounds(ranges.filter((r) => r.series === index))
|
|
115
|
+
])
|
|
116
|
+
)
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
const tooltip = ({ active, payload, label }) => {
|
|
120
|
+
const row = payload?.[0]?.payload;
|
|
121
|
+
if (!active || !row) return null;
|
|
122
|
+
return /* @__PURE__ */ jsx(
|
|
123
|
+
TooltipCard,
|
|
124
|
+
{
|
|
125
|
+
title: label,
|
|
126
|
+
rows: row.ranges.filter((r) => visible(r.series)).map((r) => ({
|
|
127
|
+
name: series[r.series].name,
|
|
128
|
+
value: `${formatDate(r.start)} \u2013 ${formatDate(r.end)}`,
|
|
129
|
+
color: color(r.series)
|
|
130
|
+
}))
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
return /* @__PURE__ */ jsxs(
|
|
135
|
+
BarChart,
|
|
136
|
+
{
|
|
137
|
+
ref: categoryKeys,
|
|
138
|
+
...frameProps({ height, width, ariaLabel: name }),
|
|
139
|
+
data: rows,
|
|
140
|
+
layout: "vertical",
|
|
141
|
+
margin: { top: 4, right: 16, bottom: 0, left: 0 },
|
|
142
|
+
barGap: 2,
|
|
143
|
+
barCategoryGap: "20%",
|
|
144
|
+
children: [
|
|
145
|
+
/* @__PURE__ */ jsx(CartesianGrid, { stroke: GRID, strokeDasharray: "4 4" }),
|
|
146
|
+
/* @__PURE__ */ jsx(
|
|
147
|
+
XAxis,
|
|
148
|
+
{
|
|
149
|
+
type: "number",
|
|
150
|
+
domain: [ticks[0], ticks[ticks.length - 1]],
|
|
151
|
+
ticks,
|
|
152
|
+
tickFormatter: tickLabel,
|
|
153
|
+
...axis,
|
|
154
|
+
...xAxisTitle(xTitle)
|
|
155
|
+
}
|
|
156
|
+
),
|
|
157
|
+
/* @__PURE__ */ jsx(YAxis, { type: "category", dataKey: "category", width: "auto", ...axis, ...yAxisTitle(yTitle) }),
|
|
158
|
+
(showLegend ?? series.length > 1) && /* @__PURE__ */ jsx(
|
|
159
|
+
Legend,
|
|
160
|
+
{
|
|
161
|
+
...legendProps(legendPosition, hidden),
|
|
162
|
+
content: /* @__PURE__ */ jsx(
|
|
163
|
+
SeriesLegend,
|
|
164
|
+
{
|
|
165
|
+
entries: series.map((s, index) => ({
|
|
166
|
+
value: s.name,
|
|
167
|
+
type: "circle",
|
|
168
|
+
color: color(index),
|
|
169
|
+
dataKey: seriesKey(index)
|
|
170
|
+
})),
|
|
171
|
+
hidden: hidden.hidden,
|
|
172
|
+
onToggle: hidden.toggle,
|
|
173
|
+
column: legendPosition === "left" || legendPosition === "right"
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
),
|
|
178
|
+
/* @__PURE__ */ jsx(
|
|
179
|
+
Tooltip,
|
|
180
|
+
{
|
|
181
|
+
content: tooltip,
|
|
182
|
+
cursor: { fill: BORDER, fillOpacity: 0.35, radius: 8 },
|
|
183
|
+
animationDuration: 150
|
|
184
|
+
}
|
|
185
|
+
),
|
|
186
|
+
groupRows ? /* @__PURE__ */ jsx(
|
|
187
|
+
Bar,
|
|
188
|
+
{
|
|
189
|
+
name: "Ranges",
|
|
190
|
+
dataKey: GROUP_KEY,
|
|
191
|
+
maxBarSize: 32,
|
|
192
|
+
shape: rangesShape(GROUP_KEY, color, visible),
|
|
193
|
+
...ANIMATION
|
|
194
|
+
}
|
|
195
|
+
) : series.map((s, index) => /* @__PURE__ */ jsx(
|
|
196
|
+
Bar,
|
|
197
|
+
{
|
|
198
|
+
name: s.name,
|
|
199
|
+
dataKey: seriesKey(index),
|
|
200
|
+
maxBarSize: 28,
|
|
201
|
+
shape: rangesShape(seriesKey(index), color, visible),
|
|
202
|
+
hide: !visible(index),
|
|
203
|
+
...ANIMATION
|
|
204
|
+
},
|
|
205
|
+
seriesKey(index)
|
|
206
|
+
)),
|
|
207
|
+
referenceMarks({ referenceLines, referenceBands, axis: "x" }),
|
|
208
|
+
children
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export { RangeBarChart };
|
|
215
|
+
//# sourceMappingURL=RangeBarChart.js.map
|
|
216
|
+
//# sourceMappingURL=RangeBarChart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/RangeBarChart.tsx"],"names":[],"mappings":";;;;;;;;;;;;;AAuDA,IAAM,aAAA,GAAgB,IAAI,IAAA,CAAK,cAAA,CAAe,OAAA,EAAS;AAAA,EACrD,GAAA,EAAK,SAAA;AAAA,EACL,KAAA,EAAO,OAAA;AAAA,EACP,IAAA,EAAM,SAAA;AAAA,EACN,MAAA,EAAQ,SAAA;AAAA,EACR,SAAA,EAAW,KAAA;AAAA,EACX,QAAA,EAAU;AACZ,CAAC,CAAA;AACD,IAAM,OAAA,GAAU,IAAI,IAAA,CAAK,cAAA,CAAe,OAAA,EAAS;AAAA,EAC/C,GAAA,EAAK,SAAA;AAAA,EACL,KAAA,EAAO,OAAA;AAAA,EACP,QAAA,EAAU;AACZ,CAAC,CAAA;AACD,IAAM,QAAA,GAAW,IAAI,IAAA,CAAK,cAAA,CAAe,OAAA,EAAS;AAAA,EAChD,IAAA,EAAM,SAAA;AAAA,EACN,MAAA,EAAQ,SAAA;AAAA,EACR,SAAA,EAAW,KAAA;AAAA,EACX,QAAA,EAAU;AACZ,CAAC,CAAA;AAED,IAAM,iBAAA,GAAoB,CAAC,SAAA,KAAsB,aAAA,CAAc,OAAO,SAAS,CAAA;AAE/E,IAAM,IAAA,GAAO,IAAA;AACb,IAAM,MAAM,EAAA,GAAK,IAAA;AACjB,IAAM,QAAQ,CAAC,IAAA,EAAM,CAAA,GAAI,IAAA,EAAM,IAAI,IAAA,EAAM,CAAA,GAAI,IAAA,EAAM,EAAA,GAAK,MAAM,GAAA,EAAK,CAAA,GAAI,KAAK,CAAA,GAAI,GAAA,EAAK,KAAK,GAAG,CAAA;AAG7F,IAAM,SAAA,GAAY,CAAC,GAAA,EAAa,GAAA,KAAgB;AAC9C,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAA,CAAO,GAAA,GAAM,GAAA,IAAO,CAAA,IAAK,CAAC,CAAA,IAAK,KAAA,CAAM,KAAA,CAAM,SAAS,CAAC,CAAA;AAC9E,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,IAAI,CAAA,GAAI,IAAA;AACvC,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,IAAA,CAAK,MAAM,IAAI,CAAA,GAAI,IAAA,EAAM,KAAA,GAAQ,IAAI,CAAA;AAChE,EAAA,OAAO,MAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,IAAA,CAAK,OAAO,IAAA,GAAO,KAAA,IAAS,IAAI,CAAA,GAAI,GAAE,EAAG,CAAC,GAAG,CAAA,KAAM,KAAA,GAAQ,IAAI,IAAI,CAAA;AACjG,CAAA;AAGA,IAAM,SAAA,GAAY,CAAC,SAAA,KACjB,SAAA,GAAY,GAAA,KAAQ,CAAA,GAAI,OAAA,CAAQ,MAAA,CAAO,SAAS,CAAA,GAAI,QAAA,CAAS,MAAA,CAAO,SAAS,CAAA;AAK/E,IAAM,SAAA,GAAY,KAAA;AAKlB,IAAM,cACJ,CAAC,GAAA,EAAa,MAAA,EAAoC,OAAA,KAClD,CAAC,KAAA,KAAyB;AACxB,EAAA,MAAM,EAAE,CAAA,EAAG,CAAA,EAAG,KAAA,EAAO,QAAO,GAAI,KAAA;AAChC,EAAA,MAAM,MAAM,KAAA,CAAM,OAAA;AAClB,EAAA,MAAM,MAAA,GAAS,IAAI,GAAG,CAAA;AACtB,EAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AACpB,EAAA,MAAM,CAAC,GAAA,EAAK,IAAI,CAAA,GAAI,MAAA;AACpB,EAAA,MAAM,IAAA,GAAO,OAAO,GAAA,IAAO,CAAA;AAC3B,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA;AAC5B,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAI,MAAM,CAAA;AAClC,EAAA,uBACE,GAAA,CAAC,OACE,QAAA,EAAA,GAAA,CAAI,MAAA,CACF,OAAO,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAA,CAAE,MAAM,CAAA,KAAM,QAAQ,SAAA,IAAa,GAAA,KAAQ,UAAU,CAAA,CAAE,MAAM,EAAE,CAAA,CACrF,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,qBACP,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MAEC,CAAA,EAAG,CAAA,GAAA,CAAM,CAAA,CAAE,KAAA,GAAQ,OAAO,IAAA,GAAQ,KAAA;AAAA,MAClC,CAAA,EAAG,GAAA;AAAA,MACH,KAAA,EAAO,KAAK,GAAA,CAAA,CAAM,CAAA,CAAE,MAAM,CAAA,CAAE,KAAA,IAAS,IAAA,GAAQ,KAAA,EAAO,CAAC,CAAA;AAAA,MACrD,MAAA,EAAQ,IAAA;AAAA,MACR,EAAA,EAAI,CAAA;AAAA,MACJ,IAAA,EAAM,MAAA,CAAO,CAAA,CAAE,MAAM;AAAA,KAAA;AAAA,IANhB;AAAA,GAQR,CAAA,EACL,CAAA;AAEJ,CAAA;AAIF,IAAM,eAAe,CAAC;AAAA,EACpB,OAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,yBACG,WAAA,EAAA,EAAY,OAAA,EAAS,OAAA,EAAS,MAAA,EAAgB,UAAoB,MAAA,EAAgB,CAAA;AAG9E,IAAM,gBAAgB,CAAC;AAAA,EAC5B,MAAA;AAAA,EACA,SAAA,GAAY,KAAA;AAAA,EACZ,MAAA;AAAA,EACA,UAAA,GAAa,iBAAA;AAAA,EACb,MAAA;AAAA,EACA,MAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,UAAA;AAAA,EACA,cAAA,GAAiB,KAAA;AAAA,EACjB,YAAA,GAAe,SAAA;AAAA,EACf,SAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA,GAAS,GAAA;AAAA,EACT;AACF,CAAA,KAA0B;AACxB,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,MAAM,OAAO,OAAA,EAAQ;AAErB,EAAA,MAAM,YAAA,GAAe,gBAAgB,UAAU,CAAA;AAC/C,EAAA,MAAM,IAAA,GAAO,SAAA,IAAa,CAAA,aAAA,EAAgB,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA;AAC9E,EAAA,IAAI,SAAA,CAAU,MAAM,CAAA,EAAG;AACrB,IAAA,2BAAQ,UAAA,EAAA,EAAW,OAAA,EAAS,cAAc,SAAA,EAAW,IAAA,EAAM,QAAgB,KAAA,EAAc,CAAA;AAAA,EAC3F;AAEA,EAAA,MAAM,aAAa,CAAC,GAAG,IAAI,GAAA,CAAI,MAAA,CAAO,QAAQ,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA,KAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;AAC7E,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,CAAK,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAA,CAAE,CAAC,CAAC,CAAA;AAC5D,EAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,IAAA,CAAK,GAAA,CAAI,GAAG,GAAG,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,GAAG,GAAG,CAAC,CAAA;AAC1D,EAAA,MAAM,KAAA,GAAQ,CAAC,KAAA,KAAkB,WAAA,CAAY,OAAO,MAAM,CAAA;AAC1D,EAAA,MAAM,OAAA,GAAU,CAAC,KAAA,KAAkB,CAAC,OAAO,MAAA,CAAO,GAAA,CAAI,SAAA,CAAU,KAAK,CAAC,CAAA;AAEtE,EAAA,MAAM,IAAA,GAAc,UAAA,CAAW,GAAA,CAAI,CAAC,QAAA,KAAa;AAC/C,IAAA,MAAM,SAAS,MAAA,CAAO,OAAA;AAAA,MAAQ,CAAC,CAAA,EAAG,KAAA,KAChC,CAAA,CAAE,IAAA,CACC,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,CAAA,KAAM,QAAQ,CAAA,CAC9B,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,KAAA,EAAO,CAAA,CAAE,CAAA,CAAE,CAAC,CAAA,EAAG,GAAA,EAAK,CAAA,CAAE,CAAA,CAAE,CAAC,CAAA,EAAE,CAAE;AAAA,KAC/D;AACA,IAAA,MAAM,MAAA,GAAS,CAAC,EAAA,KACd,EAAA,CAAG,MAAA,GAAS,CAAA,GACP,CAAC,IAAA,CAAK,GAAA,CAAI,GAAG,EAAA,CAAG,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,KAAK,CAAC,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,GAAG,EAAA,CAAG,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,GAAG,CAAC,CAAC,CAAA,GACxE,MAAA;AACN,IAAA,OAAO;AAAA,MACL,QAAA;AAAA,MACA,MAAA;AAAA,MACA,CAAC,SAAS,GAAG,MAAA,CAAO,MAAM,CAAA;AAAA,MAC1B,GAAG,MAAA,CAAO,WAAA;AAAA,QACR,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,EAAG,KAAA,KAAU;AAAA,UACvB,UAAU,KAAK,CAAA;AAAA,UACf,MAAA,CAAO,OAAO,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,KAAK,CAAC;AAAA,SAChD;AAAA;AACH,KACF;AAAA,EACF,CAAC,CAAA;AAED,EAAA,MAAM,UAAU,CAAC,EAAE,MAAA,EAAQ,OAAA,EAAS,OAAM,KAAoC;AAC5E,IAAA,MAAM,GAAA,GAAM,OAAA,GAAU,CAAC,CAAA,EAAG,OAAA;AAC1B,IAAA,IAAI,CAAC,MAAA,IAAU,CAAC,GAAA,EAAK,OAAO,IAAA;AAC5B,IAAA,uBACE,GAAA;AAAA,MAAC,WAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAO,KAAA;AAAA,QACP,IAAA,EAAM,GAAA,CAAI,MAAA,CACP,MAAA,CAAO,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAA,CAAE,MAAM,CAAC,CAAA,CAC/B,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,UACX,IAAA,EAAM,MAAA,CAAO,CAAA,CAAE,MAAM,CAAA,CAAE,IAAA;AAAA,UACvB,KAAA,EAAO,CAAA,EAAG,UAAA,CAAW,CAAA,CAAE,KAAK,CAAC,CAAA,QAAA,EAAM,UAAA,CAAW,CAAA,CAAE,GAAG,CAAC,CAAA,CAAA;AAAA,UACpD,KAAA,EAAO,KAAA,CAAM,CAAA,CAAE,MAAM;AAAA,SACvB,CAAE;AAAA;AAAA,KACN;AAAA,EAEJ,CAAA;AAEA,EAAA,uBACE,IAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,YAAA;AAAA,MACJ,GAAG,UAAA,CAAW,EAAE,QAAQ,KAAA,EAAO,SAAA,EAAW,MAAM,CAAA;AAAA,MACjD,IAAA,EAAM,IAAA;AAAA,MACN,MAAA,EAAO,UAAA;AAAA,MACP,MAAA,EAAQ,EAAE,GAAA,EAAK,CAAA,EAAG,OAAO,EAAA,EAAI,MAAA,EAAQ,CAAA,EAAG,IAAA,EAAM,CAAA,EAAE;AAAA,MAChD,MAAA,EAAQ,CAAA;AAAA,MACR,cAAA,EAAe,KAAA;AAAA,MAGf,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,aAAA,EAAA,EAAc,MAAA,EAAQ,IAAA,EAAM,eAAA,EAAgB,KAAA,EAAM,CAAA;AAAA,wBACnD,GAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,MAAA,EAAQ,CAAC,KAAA,CAAM,CAAC,GAAG,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAC,CAAA;AAAA,YAC1C,KAAA;AAAA,YACA,aAAA,EAAe,SAAA;AAAA,YACd,GAAG,IAAA;AAAA,YACH,GAAG,WAAW,MAAM;AAAA;AAAA,SACvB;AAAA,wBACA,GAAA,CAAC,KAAA,EAAA,EAAM,IAAA,EAAK,UAAA,EAAW,OAAA,EAAQ,UAAA,EAAW,KAAA,EAAM,MAAA,EAAQ,GAAG,IAAA,EAAO,GAAG,UAAA,CAAW,MAAM,CAAA,EAAG,CAAA;AAAA,QAAA,CACvF,UAAA,IAAc,MAAA,CAAO,MAAA,GAAS,CAAA,qBAC9B,GAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACE,GAAG,WAAA,CAAY,cAAA,EAAgB,MAAM,CAAA;AAAA,YACtC,OAAA,kBACE,GAAA;AAAA,cAAC,YAAA;AAAA,cAAA;AAAA,gBACC,OAAA,EAAS,MAAA,CAAO,GAAA,CAAI,CAAC,GAAG,KAAA,MAAW;AAAA,kBACjC,OAAO,CAAA,CAAE,IAAA;AAAA,kBACT,IAAA,EAAM,QAAA;AAAA,kBACN,KAAA,EAAO,MAAM,KAAK,CAAA;AAAA,kBAClB,OAAA,EAAS,UAAU,KAAK;AAAA,iBAC1B,CAAE,CAAA;AAAA,gBACF,QAAQ,MAAA,CAAO,MAAA;AAAA,gBACf,UAAU,MAAA,CAAO,MAAA;AAAA,gBACjB,MAAA,EAAQ,cAAA,KAAmB,MAAA,IAAU,cAAA,KAAmB;AAAA;AAAA;AAC1D;AAAA,SAEJ;AAAA,wBAEF,GAAA;AAAA,UAAC,OAAA;AAAA,UAAA;AAAA,YACC,OAAA,EAAS,OAAA;AAAA,YACT,QAAQ,EAAE,IAAA,EAAM,QAAQ,WAAA,EAAa,IAAA,EAAM,QAAQ,CAAA,EAAE;AAAA,YACrD,iBAAA,EAAmB;AAAA;AAAA,SACrB;AAAA,QACC,SAAA,mBACC,GAAA;AAAA,UAAC,GAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,OAAA,EAAS,SAAA;AAAA,YACT,UAAA,EAAY,EAAA;AAAA,YACZ,KAAA,EAAO,WAAA,CAAY,SAAA,EAAW,KAAA,EAAO,OAAO,CAAA;AAAA,YAC3C,GAAG;AAAA;AAAA,SACN,GAEA,MAAA,CAAO,GAAA,CAAI,CAAC,GAAG,KAAA,qBACb,GAAA;AAAA,UAAC,GAAA;AAAA,UAAA;AAAA,YAEC,MAAM,CAAA,CAAE,IAAA;AAAA,YACR,OAAA,EAAS,UAAU,KAAK,CAAA;AAAA,YACxB,UAAA,EAAY,EAAA;AAAA,YACZ,OAAO,WAAA,CAAY,SAAA,CAAU,KAAK,CAAA,EAAG,OAAO,OAAO,CAAA;AAAA,YACnD,IAAA,EAAM,CAAC,OAAA,CAAQ,KAAK,CAAA;AAAA,YACnB,GAAG;AAAA,WAAA;AAAA,UANC,UAAU,KAAK;AAAA,SAQvB,CAAA;AAAA,QAEF,eAAe,EAAE,cAAA,EAAgB,cAAA,EAAgB,IAAA,EAAM,KAAK,CAAA;AAAA,QAC5D;AAAA;AAAA;AAAA,GACH;AAEJ","file":"RangeBarChart.js","sourcesContent":["import type { ComponentProps } from 'react';\nimport {\n Bar,\n BarChart,\n CartesianGrid,\n Legend,\n Tooltip,\n XAxis,\n YAxis,\n type BarShapeProps,\n type LegendPayload,\n type TooltipContentProps,\n} from 'recharts';\nimport { ANIMATION } from './core/animation';\nimport { useAxis, xAxisTitle, yAxisTitle } from './core/axes';\nimport { useCategoryKeys } from './core/categoryKeys';\nimport { TooltipCard } from './core/ChartTooltip';\nimport { ChartLegend, legendProps, useHiddenSeries } from './core/ChartLegend';\nimport { BORDER, GRID, seriesColor } from './core/colors';\nimport { hasNoData, seriesKey } from './core/data';\nimport { ChartEmpty, frameProps } from './core/frame';\nimport { referenceMarks } from './core/reference';\nimport type {\n ChartAxisTitleProps,\n ChartEmptyProps,\n ChartLegendProps,\n ChartProps,\n ChartReferenceProps,\n} from './types';\n\nexport type RangeBarPoint = {\n /** Row label: the service, rack or host the range belongs to. */\n x: string;\n /** Start and end, as timestamps in milliseconds. */\n y: [number, number];\n};\n\nexport type RangeBarChartProps = ChartProps &\n ChartEmptyProps &\n ChartLegendProps &\n ChartAxisTitleProps &\n ChartReferenceProps & {\n /** One series per kind of range (maintenance, deploy, incident…), drawn on a time axis. */\n series: { name: string; data: RangeBarPoint[] }[];\n /** Draw every series of a row on one lane, instead of one lane per series. */\n groupRows?: boolean;\n /** Override the default Cosmos palette. */\n colors?: string[];\n /** Format the start and end times in the tooltip. Default: `14 Oct, 02:00`, in UTC like the axis. */\n formatDate?: (timestamp: number) => string;\n /** Show the legend (auto-true when 2+ series). */\n showLegend?: boolean;\n };\n\n// The axis labels are UTC, so the default tooltip is too.\nconst UTC_DATE_TIME = new Intl.DateTimeFormat('en-GB', {\n day: '2-digit',\n month: 'short',\n hour: '2-digit',\n minute: '2-digit',\n hourCycle: 'h23',\n timeZone: 'UTC',\n});\nconst UTC_DAY = new Intl.DateTimeFormat('en-GB', {\n day: '2-digit',\n month: 'short',\n timeZone: 'UTC',\n});\nconst UTC_TIME = new Intl.DateTimeFormat('en-GB', {\n hour: '2-digit',\n minute: '2-digit',\n hourCycle: 'h23',\n timeZone: 'UTC',\n});\n\nconst defaultFormatDate = (timestamp: number) => UTC_DATE_TIME.format(timestamp);\n\nconst HOUR = 3_600_000;\nconst DAY = 24 * HOUR;\nconst STEPS = [HOUR, 2 * HOUR, 3 * HOUR, 6 * HOUR, 12 * HOUR, DAY, 2 * DAY, 7 * DAY, 30 * DAY];\n\n/** Round UTC ticks that cover `[min, max]`: the first step that leaves at most eight of them. */\nconst timeTicks = (min: number, max: number) => {\n const step = STEPS.find((s) => (max - min) / s <= 8) ?? STEPS[STEPS.length - 1];\n const first = Math.floor(min / step) * step;\n const last = Math.max(Math.ceil(max / step) * step, first + step);\n return Array.from({ length: Math.round((last - first) / step) + 1 }, (_, i) => first + i * step);\n};\n\n/** A tick label: the day at midnight, the time otherwise. */\nconst tickLabel = (timestamp: number) =>\n timestamp % DAY === 0 ? UTC_DAY.format(timestamp) : UTC_TIME.format(timestamp);\n\ntype Range = { series: number; start: number; end: number };\ntype Row = { category: string; ranges: Range[] } & Record<string, unknown>;\n\nconst GROUP_KEY = 'all';\n\n// A bar's value is the span from the first start to the last end of what it\n// draws in a row, so Recharts sizes and places it; the ranges inside are drawn\n// by the shape, at the same scale.\nconst rangesShape =\n (key: string, colors: (series: number) => string, visible: (series: number) => boolean) =>\n (props: BarShapeProps) => {\n const { x, y, width, height } = props;\n const row = props.payload as Row;\n const bounds = row[key] as [number, number] | undefined;\n if (!bounds) return null;\n const [low, high] = bounds;\n const span = high - low || 1;\n const lane = Math.abs(height);\n const top = Math.min(y, y + height);\n return (\n <g>\n {row.ranges\n .filter((r) => visible(r.series) && (key === GROUP_KEY || key === seriesKey(r.series)))\n .map((r, i) => (\n <rect\n key={i}\n x={x + ((r.start - low) / span) * width}\n y={top}\n width={Math.max(((r.end - r.start) / span) * width, 2)}\n height={lane}\n rx={3}\n fill={colors(r.series)}\n />\n ))}\n </g>\n );\n };\n\n// One legend entry per series, whichever way the bars are drawn: Recharts' own\n// entries (one per bar) are left aside.\nconst SeriesLegend = ({\n entries,\n hidden,\n onToggle,\n column,\n}: { entries: LegendPayload[] } & Omit<ComponentProps<typeof ChartLegend>, 'payload'>) => (\n <ChartLegend payload={entries} hidden={hidden} onToggle={onToggle} column={column} />\n);\n\nexport const RangeBarChart = ({\n series,\n groupRows = false,\n colors,\n formatDate = defaultFormatDate,\n xTitle,\n yTitle,\n referenceLines,\n referenceBands,\n showLegend,\n legendPosition = 'top',\n emptyMessage = 'No data',\n ariaLabel,\n children,\n height = 320,\n width,\n}: RangeBarChartProps) => {\n const hidden = useHiddenSeries();\n const axis = useAxis();\n // Recharts names the layout after the axis the categories sit on.\n const categoryKeys = useCategoryKeys('vertical');\n const name = ariaLabel ?? `Range chart: ${series.map((s) => s.name).join(', ')}`;\n if (hasNoData(series)) {\n return <ChartEmpty message={emptyMessage} ariaLabel={name} height={height} width={width} />;\n }\n\n const categories = [...new Set(series.flatMap((s) => s.data.map((p) => p.x)))];\n const all = series.flatMap((s) => s.data.flatMap((p) => p.y));\n const ticks = timeTicks(Math.min(...all), Math.max(...all));\n const color = (index: number) => seriesColor(index, colors);\n const visible = (index: number) => !hidden.hidden.has(seriesKey(index));\n\n const rows: Row[] = categories.map((category) => {\n const ranges = series.flatMap((s, index) =>\n s.data\n .filter((p) => p.x === category)\n .map((p) => ({ series: index, start: p.y[0], end: p.y[1] }))\n );\n const bounds = (of: Range[]) =>\n of.length > 0\n ? ([Math.min(...of.map((r) => r.start)), Math.max(...of.map((r) => r.end))] as const)\n : undefined;\n return {\n category,\n ranges,\n [GROUP_KEY]: bounds(ranges),\n ...Object.fromEntries(\n series.map((_, index) => [\n seriesKey(index),\n bounds(ranges.filter((r) => r.series === index)),\n ])\n ),\n };\n });\n\n const tooltip = ({ active, payload, label }: Partial<TooltipContentProps>) => {\n const row = payload?.[0]?.payload as Row | undefined;\n if (!active || !row) return null;\n return (\n <TooltipCard\n title={label}\n rows={row.ranges\n .filter((r) => visible(r.series))\n .map((r) => ({\n name: series[r.series].name,\n value: `${formatDate(r.start)} – ${formatDate(r.end)}`,\n color: color(r.series),\n }))}\n />\n );\n };\n\n return (\n <BarChart\n ref={categoryKeys}\n {...frameProps({ height, width, ariaLabel: name })}\n data={rows}\n layout=\"vertical\"\n margin={{ top: 4, right: 16, bottom: 0, left: 0 }}\n barGap={2}\n barCategoryGap=\"20%\"\n >\n {/* Time runs left to right: vertical grid lines, plus a line between rows. */}\n <CartesianGrid stroke={GRID} strokeDasharray=\"4 4\" />\n <XAxis\n type=\"number\"\n domain={[ticks[0], ticks[ticks.length - 1]]}\n ticks={ticks}\n tickFormatter={tickLabel}\n {...axis}\n {...xAxisTitle(xTitle)}\n />\n <YAxis type=\"category\" dataKey=\"category\" width=\"auto\" {...axis} {...yAxisTitle(yTitle)} />\n {(showLegend ?? series.length > 1) && (\n <Legend\n {...legendProps(legendPosition, hidden)}\n content={\n <SeriesLegend\n entries={series.map((s, index) => ({\n value: s.name,\n type: 'circle',\n color: color(index),\n dataKey: seriesKey(index),\n }))}\n hidden={hidden.hidden}\n onToggle={hidden.toggle}\n column={legendPosition === 'left' || legendPosition === 'right'}\n />\n }\n />\n )}\n <Tooltip\n content={tooltip}\n cursor={{ fill: BORDER, fillOpacity: 0.35, radius: 8 }}\n animationDuration={150}\n />\n {groupRows ? (\n <Bar\n name=\"Ranges\"\n dataKey={GROUP_KEY}\n maxBarSize={32}\n shape={rangesShape(GROUP_KEY, color, visible)}\n {...ANIMATION}\n />\n ) : (\n series.map((s, index) => (\n <Bar\n key={seriesKey(index)}\n name={s.name}\n dataKey={seriesKey(index)}\n maxBarSize={28}\n shape={rangesShape(seriesKey(index), color, visible)}\n hide={!visible(index)}\n {...ANIMATION}\n />\n ))\n )}\n {referenceMarks({ referenceLines, referenceBands, axis: 'x' })}\n {children}\n </BarChart>\n );\n};\n"]}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var chunkRLHGPHC7_cjs = require('./chunk-RLHGPHC7.cjs');
|
|
5
|
+
var chunkT3K6Q34U_cjs = require('./chunk-T3K6Q34U.cjs');
|
|
6
|
+
var chunk6NRRABPF_cjs = require('./chunk-6NRRABPF.cjs');
|
|
7
|
+
var chunkLT3UC65O_cjs = require('./chunk-LT3UC65O.cjs');
|
|
8
|
+
var react = require('react');
|
|
9
|
+
var recharts = require('recharts');
|
|
10
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
11
|
+
|
|
12
|
+
var CLOCK = new Intl.DateTimeFormat("en-GB", {
|
|
13
|
+
hour: "2-digit",
|
|
14
|
+
minute: "2-digit",
|
|
15
|
+
second: "2-digit",
|
|
16
|
+
hourCycle: "h23"
|
|
17
|
+
});
|
|
18
|
+
var clock = (timestamp) => CLOCK.format(timestamp);
|
|
19
|
+
var Threshold = ({ y, label, color }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
20
|
+
recharts.ReferenceLine,
|
|
21
|
+
{
|
|
22
|
+
y,
|
|
23
|
+
stroke: color,
|
|
24
|
+
strokeWidth: 1.5,
|
|
25
|
+
strokeDasharray: "6 4",
|
|
26
|
+
label: label === "" ? void 0 : {
|
|
27
|
+
value: label,
|
|
28
|
+
position: "insideTopRight",
|
|
29
|
+
offset: 4,
|
|
30
|
+
// Text on the card, whatever the status color: it reads on every theme.
|
|
31
|
+
fill: chunkLT3UC65O_cjs.TEXT,
|
|
32
|
+
style: { fill: chunkLT3UC65O_cjs.TEXT, fontSize: chunkRLHGPHC7_cjs.TEXT_SIZE, fontWeight: 600 }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
var createDefaultGenerator = () => {
|
|
37
|
+
let last = 50;
|
|
38
|
+
return () => {
|
|
39
|
+
last += (Math.random() - 0.5) * 8;
|
|
40
|
+
last = Math.max(20, Math.min(95, last));
|
|
41
|
+
return Math.round(last);
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
var RealtimeChart = ({
|
|
45
|
+
generator,
|
|
46
|
+
intervalMs = 2e3,
|
|
47
|
+
windowSize = 30,
|
|
48
|
+
min = 0,
|
|
49
|
+
max = 100,
|
|
50
|
+
warnThreshold = 70,
|
|
51
|
+
critThreshold = 85,
|
|
52
|
+
warnLabel = "WARN",
|
|
53
|
+
critLabel = "CRIT",
|
|
54
|
+
showTimeAxis = false,
|
|
55
|
+
xTitle,
|
|
56
|
+
yTitle,
|
|
57
|
+
formatValue,
|
|
58
|
+
ariaLabel,
|
|
59
|
+
children,
|
|
60
|
+
height = 240,
|
|
61
|
+
width
|
|
62
|
+
}) => {
|
|
63
|
+
const axis = chunkRLHGPHC7_cjs.useAxis();
|
|
64
|
+
const [defaultGenerator] = react.useState(createDefaultGenerator);
|
|
65
|
+
const generate = generator ?? defaultGenerator;
|
|
66
|
+
const [mountedAt] = react.useState(Date.now);
|
|
67
|
+
const [samples, setSamples] = react.useState(
|
|
68
|
+
() => Array.from({ length: windowSize }, (_, index) => ({
|
|
69
|
+
time: mountedAt - (windowSize - 1 - index) * intervalMs,
|
|
70
|
+
value: generate()
|
|
71
|
+
}))
|
|
72
|
+
);
|
|
73
|
+
const [sizedFor, setSizedFor] = react.useState(windowSize);
|
|
74
|
+
if (sizedFor !== windowSize) {
|
|
75
|
+
setSizedFor(windowSize);
|
|
76
|
+
const missing = windowSize - samples.length;
|
|
77
|
+
const oldest = samples[0]?.time ?? mountedAt;
|
|
78
|
+
setSamples(
|
|
79
|
+
missing <= 0 ? samples.slice(samples.length - windowSize) : [
|
|
80
|
+
...Array.from({ length: missing }, (_, index) => ({
|
|
81
|
+
time: oldest - (missing - index) * intervalMs,
|
|
82
|
+
value: generate()
|
|
83
|
+
})),
|
|
84
|
+
...samples
|
|
85
|
+
]
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
react.useEffect(() => {
|
|
89
|
+
const id = setInterval(() => {
|
|
90
|
+
setSamples((current) => [...current.slice(1), { time: Date.now(), value: generate() }]);
|
|
91
|
+
}, intervalMs);
|
|
92
|
+
return () => clearInterval(id);
|
|
93
|
+
}, [generate, intervalMs]);
|
|
94
|
+
const format = formatValue ?? String;
|
|
95
|
+
const tooltip = ({ active, payload }) => {
|
|
96
|
+
const sample = payload?.[0]?.payload;
|
|
97
|
+
if (!active || !sample) return null;
|
|
98
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
99
|
+
chunkT3K6Q34U_cjs.TooltipCard,
|
|
100
|
+
{
|
|
101
|
+
title: showTimeAxis ? clock(sample.time) : void 0,
|
|
102
|
+
rows: [{ name: "value", value: format(sample.value), color: chunkLT3UC65O_cjs.SERIES[0] }]
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
107
|
+
recharts.LineChart,
|
|
108
|
+
{
|
|
109
|
+
...chunk6NRRABPF_cjs.frameProps({
|
|
110
|
+
height,
|
|
111
|
+
width,
|
|
112
|
+
ariaLabel: ariaLabel ?? "Live values, newest on the right"
|
|
113
|
+
}),
|
|
114
|
+
data: samples,
|
|
115
|
+
margin: { top: 4, right: 12, bottom: 0, left: 0 },
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { ...chunkRLHGPHC7_cjs.GRID_PROPS }),
|
|
118
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
119
|
+
recharts.XAxis,
|
|
120
|
+
{
|
|
121
|
+
dataKey: "time",
|
|
122
|
+
type: "number",
|
|
123
|
+
domain: ["dataMin", "dataMax"],
|
|
124
|
+
hide: !showTimeAxis,
|
|
125
|
+
tickFormatter: clock,
|
|
126
|
+
...axis,
|
|
127
|
+
...showTimeAxis ? chunkRLHGPHC7_cjs.xAxisTitle(xTitle) : {}
|
|
128
|
+
}
|
|
129
|
+
),
|
|
130
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
131
|
+
recharts.YAxis,
|
|
132
|
+
{
|
|
133
|
+
domain: [min, max],
|
|
134
|
+
allowDataOverflow: true,
|
|
135
|
+
interval: 0,
|
|
136
|
+
width: "auto",
|
|
137
|
+
tickFormatter: formatValue,
|
|
138
|
+
...axis,
|
|
139
|
+
...chunkRLHGPHC7_cjs.yAxisTitle(yTitle)
|
|
140
|
+
}
|
|
141
|
+
),
|
|
142
|
+
/* @__PURE__ */ jsxRuntime.jsx(Threshold, { y: warnThreshold, label: warnLabel, color: chunkLT3UC65O_cjs.STATUS_WARN }),
|
|
143
|
+
/* @__PURE__ */ jsxRuntime.jsx(Threshold, { y: critThreshold, label: critLabel, color: chunkLT3UC65O_cjs.STATUS_CRIT }),
|
|
144
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
145
|
+
recharts.Tooltip,
|
|
146
|
+
{
|
|
147
|
+
content: tooltip,
|
|
148
|
+
cursor: { stroke: chunkLT3UC65O_cjs.BORDER, strokeDasharray: "4 4" },
|
|
149
|
+
animationDuration: 0
|
|
150
|
+
}
|
|
151
|
+
),
|
|
152
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
153
|
+
recharts.Line,
|
|
154
|
+
{
|
|
155
|
+
name: "value",
|
|
156
|
+
dataKey: "value",
|
|
157
|
+
type: "monotone",
|
|
158
|
+
stroke: chunkLT3UC65O_cjs.SERIES[0],
|
|
159
|
+
strokeWidth: 2,
|
|
160
|
+
dot: false,
|
|
161
|
+
activeDot: { r: 5, fill: chunkLT3UC65O_cjs.PANEL, stroke: chunkLT3UC65O_cjs.SERIES[0], strokeWidth: 2.5 },
|
|
162
|
+
isAnimationActive: false
|
|
163
|
+
}
|
|
164
|
+
),
|
|
165
|
+
children
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
exports.RealtimeChart = RealtimeChart;
|
|
172
|
+
//# sourceMappingURL=RealtimeChart.cjs.map
|
|
173
|
+
//# sourceMappingURL=RealtimeChart.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/RealtimeChart.tsx"],"names":["jsx","ReferenceLine","TEXT","TEXT_SIZE","useAxis","useState","useEffect","TooltipCard","SERIES","jsxs","LineChart","frameProps","CartesianGrid","GRID_PROPS","XAxis","xAxisTitle","YAxis","yAxisTitle","STATUS_WARN","STATUS_CRIT","Tooltip","BORDER","Line","PANEL"],"mappings":"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","file":"RealtimeChart.cjs","sourcesContent":["import { useEffect, useState } from 'react';\nimport {\n CartesianGrid,\n Line,\n LineChart,\n ReferenceLine,\n Tooltip,\n XAxis,\n YAxis,\n type TooltipContentProps,\n} from 'recharts';\nimport { GRID_PROPS, TEXT_SIZE, useAxis, xAxisTitle, yAxisTitle } from './core/axes';\nimport { TooltipCard } from './core/ChartTooltip';\nimport { BORDER, PANEL, SERIES, STATUS_CRIT, STATUS_WARN, TEXT } from './core/colors';\nimport { frameProps } from './core/frame';\nimport type { ChartAxisTitleProps, ChartProps } from './types';\n\nexport type RealtimeChartProps = ChartProps &\n ChartAxisTitleProps & {\n /** Sample generator — defaults to a noisy walk between 30 and 80. */\n generator?: () => number;\n /** Interval in ms — defaults to 2000. */\n intervalMs?: number;\n windowSize?: number;\n /** Bottom of the y axis. Default 0. */\n min?: number;\n /** Top of the y axis, above `min`. Default 100. */\n max?: number;\n warnThreshold?: number;\n critThreshold?: number;\n /** Text of the warning threshold's label. Default `WARN`; an empty string draws the line alone. */\n warnLabel?: string;\n /** Text of the critical threshold's label. Default `CRIT`; an empty string draws the line alone. */\n critLabel?: string;\n /**\n * Label the x axis with the time of each sample (`14:03:25`, in local time), and the\n * tooltip with it, and `xTitle` has an axis to name. Default off: no x axis.\n */\n showTimeAxis?: boolean;\n /** Format the y-axis labels and the tooltip values. */\n formatValue?: (value: number) => string;\n };\n\ntype Sample = { time: number; value: number };\n\n// The time axis reads as a clock.\nconst CLOCK = new Intl.DateTimeFormat('en-GB', {\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hourCycle: 'h23',\n});\nconst clock = (timestamp: number) => CLOCK.format(timestamp);\n\n/** A threshold: a line in its status color, and a label on it unless `label` is empty. */\nconst Threshold = ({ y, label, color }: { y: number; label: string; color: string }) => (\n <ReferenceLine\n y={y}\n stroke={color}\n strokeWidth={1.5}\n strokeDasharray=\"6 4\"\n label={\n label === ''\n ? undefined\n : {\n value: label,\n position: 'insideTopRight',\n offset: 4,\n // Text on the card, whatever the status color: it reads on every theme.\n fill: TEXT,\n style: { fill: TEXT, fontSize: TEXT_SIZE, fontWeight: 600 },\n }\n }\n />\n);\n\n// One walk per chart: a module-level one made every instance continue the last.\nconst createDefaultGenerator = () => {\n let last = 50;\n return () => {\n last += (Math.random() - 0.5) * 8;\n last = Math.max(20, Math.min(95, last));\n return Math.round(last);\n };\n};\n\nexport const RealtimeChart = ({\n generator,\n intervalMs = 2000,\n windowSize = 30,\n min = 0,\n max = 100,\n warnThreshold = 70,\n critThreshold = 85,\n warnLabel = 'WARN',\n critLabel = 'CRIT',\n showTimeAxis = false,\n xTitle,\n yTitle,\n formatValue,\n ariaLabel,\n children,\n height = 240,\n width,\n}: RealtimeChartProps) => {\n const axis = useAxis();\n const [defaultGenerator] = useState(createDefaultGenerator);\n const generate = generator ?? defaultGenerator;\n // Each sample with the time it was taken, for the time axis. A window that\n // starts full is dated as if it had been filled at the sampling pace.\n const [mountedAt] = useState(Date.now);\n const [samples, setSamples] = useState<Sample[]>(() =>\n Array.from({ length: windowSize }, (_, index) => ({\n time: mountedAt - (windowSize - 1 - index) * intervalMs,\n value: generate(),\n }))\n );\n\n // A new windowSize trims the oldest samples or prepends new ones. Adjusted\n // while rendering, which React redoes at once without painting the old window.\n const [sizedFor, setSizedFor] = useState(windowSize);\n if (sizedFor !== windowSize) {\n setSizedFor(windowSize);\n const missing = windowSize - samples.length;\n const oldest = samples[0]?.time ?? mountedAt;\n setSamples(\n missing <= 0\n ? samples.slice(samples.length - windowSize)\n : [\n ...Array.from({ length: missing }, (_, index) => ({\n time: oldest - (missing - index) * intervalMs,\n value: generate(),\n })),\n ...samples,\n ]\n );\n }\n\n useEffect(() => {\n const id = setInterval(() => {\n setSamples((current) => [...current.slice(1), { time: Date.now(), value: generate() }]);\n }, intervalMs);\n return () => clearInterval(id);\n }, [generate, intervalMs]);\n\n const format = formatValue ?? String;\n\n // The card of the sample under the pointer, with its time when the axis shows it.\n const tooltip = ({ active, payload }: Partial<TooltipContentProps>) => {\n const sample = payload?.[0]?.payload as Sample | undefined;\n if (!active || !sample) return null;\n return (\n <TooltipCard\n title={showTimeAxis ? clock(sample.time) : undefined}\n rows={[{ name: 'value', value: format(sample.value), color: SERIES[0] }]}\n />\n );\n };\n\n return (\n <LineChart\n {...frameProps({\n height,\n width,\n ariaLabel: ariaLabel ?? 'Live values, newest on the right',\n })}\n data={samples}\n margin={{ top: 4, right: 12, bottom: 0, left: 0 }}\n >\n <CartesianGrid {...GRID_PROPS} />\n {/* The samples are not on a scale of their own unless the axis is the clock. */}\n <XAxis\n dataKey=\"time\"\n type=\"number\"\n domain={['dataMin', 'dataMax']}\n hide={!showTimeAxis}\n tickFormatter={clock}\n {...axis}\n {...(showTimeAxis ? xAxisTitle(xTitle) : {})}\n />\n <YAxis\n domain={[min, max]}\n allowDataOverflow\n // The bottom tick is `min`: keep it.\n interval={0}\n width=\"auto\"\n tickFormatter={formatValue}\n {...axis}\n {...yAxisTitle(yTitle)}\n />\n <Threshold y={warnThreshold} label={warnLabel} color={STATUS_WARN} />\n <Threshold y={critThreshold} label={critLabel} color={STATUS_CRIT} />\n <Tooltip\n content={tooltip}\n cursor={{ stroke: BORDER, strokeDasharray: '4 4' }}\n animationDuration={0}\n />\n <Line\n name=\"value\"\n dataKey=\"value\"\n type=\"monotone\"\n stroke={SERIES[0]}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 5, fill: PANEL, stroke: SERIES[0], strokeWidth: 2.5 }}\n // A new sample moves the window: no animation, so the line redraws steadily.\n isAnimationActive={false}\n />\n {children}\n </LineChart>\n );\n};\n"]}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useAxis, GRID_PROPS, xAxisTitle, yAxisTitle, TEXT_SIZE } from './chunk-RILUG7JB.js';
|
|
3
|
+
import { TooltipCard } from './chunk-VIOSQN4Z.js';
|
|
4
|
+
import { frameProps } from './chunk-3N43QT36.js';
|
|
5
|
+
import { STATUS_WARN, STATUS_CRIT, BORDER, SERIES, PANEL, TEXT } from './chunk-UATB2IRY.js';
|
|
6
|
+
import { useState, useEffect } from 'react';
|
|
7
|
+
import { LineChart, CartesianGrid, XAxis, YAxis, Tooltip, Line, ReferenceLine } from 'recharts';
|
|
8
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
9
|
+
|
|
10
|
+
var CLOCK = new Intl.DateTimeFormat("en-GB", {
|
|
11
|
+
hour: "2-digit",
|
|
12
|
+
minute: "2-digit",
|
|
13
|
+
second: "2-digit",
|
|
14
|
+
hourCycle: "h23"
|
|
15
|
+
});
|
|
16
|
+
var clock = (timestamp) => CLOCK.format(timestamp);
|
|
17
|
+
var Threshold = ({ y, label, color }) => /* @__PURE__ */ jsx(
|
|
18
|
+
ReferenceLine,
|
|
19
|
+
{
|
|
20
|
+
y,
|
|
21
|
+
stroke: color,
|
|
22
|
+
strokeWidth: 1.5,
|
|
23
|
+
strokeDasharray: "6 4",
|
|
24
|
+
label: label === "" ? void 0 : {
|
|
25
|
+
value: label,
|
|
26
|
+
position: "insideTopRight",
|
|
27
|
+
offset: 4,
|
|
28
|
+
// Text on the card, whatever the status color: it reads on every theme.
|
|
29
|
+
fill: TEXT,
|
|
30
|
+
style: { fill: TEXT, fontSize: TEXT_SIZE, fontWeight: 600 }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
var createDefaultGenerator = () => {
|
|
35
|
+
let last = 50;
|
|
36
|
+
return () => {
|
|
37
|
+
last += (Math.random() - 0.5) * 8;
|
|
38
|
+
last = Math.max(20, Math.min(95, last));
|
|
39
|
+
return Math.round(last);
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
var RealtimeChart = ({
|
|
43
|
+
generator,
|
|
44
|
+
intervalMs = 2e3,
|
|
45
|
+
windowSize = 30,
|
|
46
|
+
min = 0,
|
|
47
|
+
max = 100,
|
|
48
|
+
warnThreshold = 70,
|
|
49
|
+
critThreshold = 85,
|
|
50
|
+
warnLabel = "WARN",
|
|
51
|
+
critLabel = "CRIT",
|
|
52
|
+
showTimeAxis = false,
|
|
53
|
+
xTitle,
|
|
54
|
+
yTitle,
|
|
55
|
+
formatValue,
|
|
56
|
+
ariaLabel,
|
|
57
|
+
children,
|
|
58
|
+
height = 240,
|
|
59
|
+
width
|
|
60
|
+
}) => {
|
|
61
|
+
const axis = useAxis();
|
|
62
|
+
const [defaultGenerator] = useState(createDefaultGenerator);
|
|
63
|
+
const generate = generator ?? defaultGenerator;
|
|
64
|
+
const [mountedAt] = useState(Date.now);
|
|
65
|
+
const [samples, setSamples] = useState(
|
|
66
|
+
() => Array.from({ length: windowSize }, (_, index) => ({
|
|
67
|
+
time: mountedAt - (windowSize - 1 - index) * intervalMs,
|
|
68
|
+
value: generate()
|
|
69
|
+
}))
|
|
70
|
+
);
|
|
71
|
+
const [sizedFor, setSizedFor] = useState(windowSize);
|
|
72
|
+
if (sizedFor !== windowSize) {
|
|
73
|
+
setSizedFor(windowSize);
|
|
74
|
+
const missing = windowSize - samples.length;
|
|
75
|
+
const oldest = samples[0]?.time ?? mountedAt;
|
|
76
|
+
setSamples(
|
|
77
|
+
missing <= 0 ? samples.slice(samples.length - windowSize) : [
|
|
78
|
+
...Array.from({ length: missing }, (_, index) => ({
|
|
79
|
+
time: oldest - (missing - index) * intervalMs,
|
|
80
|
+
value: generate()
|
|
81
|
+
})),
|
|
82
|
+
...samples
|
|
83
|
+
]
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const id = setInterval(() => {
|
|
88
|
+
setSamples((current) => [...current.slice(1), { time: Date.now(), value: generate() }]);
|
|
89
|
+
}, intervalMs);
|
|
90
|
+
return () => clearInterval(id);
|
|
91
|
+
}, [generate, intervalMs]);
|
|
92
|
+
const format = formatValue ?? String;
|
|
93
|
+
const tooltip = ({ active, payload }) => {
|
|
94
|
+
const sample = payload?.[0]?.payload;
|
|
95
|
+
if (!active || !sample) return null;
|
|
96
|
+
return /* @__PURE__ */ jsx(
|
|
97
|
+
TooltipCard,
|
|
98
|
+
{
|
|
99
|
+
title: showTimeAxis ? clock(sample.time) : void 0,
|
|
100
|
+
rows: [{ name: "value", value: format(sample.value), color: SERIES[0] }]
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
return /* @__PURE__ */ jsxs(
|
|
105
|
+
LineChart,
|
|
106
|
+
{
|
|
107
|
+
...frameProps({
|
|
108
|
+
height,
|
|
109
|
+
width,
|
|
110
|
+
ariaLabel: ariaLabel ?? "Live values, newest on the right"
|
|
111
|
+
}),
|
|
112
|
+
data: samples,
|
|
113
|
+
margin: { top: 4, right: 12, bottom: 0, left: 0 },
|
|
114
|
+
children: [
|
|
115
|
+
/* @__PURE__ */ jsx(CartesianGrid, { ...GRID_PROPS }),
|
|
116
|
+
/* @__PURE__ */ jsx(
|
|
117
|
+
XAxis,
|
|
118
|
+
{
|
|
119
|
+
dataKey: "time",
|
|
120
|
+
type: "number",
|
|
121
|
+
domain: ["dataMin", "dataMax"],
|
|
122
|
+
hide: !showTimeAxis,
|
|
123
|
+
tickFormatter: clock,
|
|
124
|
+
...axis,
|
|
125
|
+
...showTimeAxis ? xAxisTitle(xTitle) : {}
|
|
126
|
+
}
|
|
127
|
+
),
|
|
128
|
+
/* @__PURE__ */ jsx(
|
|
129
|
+
YAxis,
|
|
130
|
+
{
|
|
131
|
+
domain: [min, max],
|
|
132
|
+
allowDataOverflow: true,
|
|
133
|
+
interval: 0,
|
|
134
|
+
width: "auto",
|
|
135
|
+
tickFormatter: formatValue,
|
|
136
|
+
...axis,
|
|
137
|
+
...yAxisTitle(yTitle)
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
/* @__PURE__ */ jsx(Threshold, { y: warnThreshold, label: warnLabel, color: STATUS_WARN }),
|
|
141
|
+
/* @__PURE__ */ jsx(Threshold, { y: critThreshold, label: critLabel, color: STATUS_CRIT }),
|
|
142
|
+
/* @__PURE__ */ jsx(
|
|
143
|
+
Tooltip,
|
|
144
|
+
{
|
|
145
|
+
content: tooltip,
|
|
146
|
+
cursor: { stroke: BORDER, strokeDasharray: "4 4" },
|
|
147
|
+
animationDuration: 0
|
|
148
|
+
}
|
|
149
|
+
),
|
|
150
|
+
/* @__PURE__ */ jsx(
|
|
151
|
+
Line,
|
|
152
|
+
{
|
|
153
|
+
name: "value",
|
|
154
|
+
dataKey: "value",
|
|
155
|
+
type: "monotone",
|
|
156
|
+
stroke: SERIES[0],
|
|
157
|
+
strokeWidth: 2,
|
|
158
|
+
dot: false,
|
|
159
|
+
activeDot: { r: 5, fill: PANEL, stroke: SERIES[0], strokeWidth: 2.5 },
|
|
160
|
+
isAnimationActive: false
|
|
161
|
+
}
|
|
162
|
+
),
|
|
163
|
+
children
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export { RealtimeChart };
|
|
170
|
+
//# sourceMappingURL=RealtimeChart.js.map
|
|
171
|
+
//# sourceMappingURL=RealtimeChart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/RealtimeChart.tsx"],"names":[],"mappings":"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","file":"RealtimeChart.js","sourcesContent":["import { useEffect, useState } from 'react';\nimport {\n CartesianGrid,\n Line,\n LineChart,\n ReferenceLine,\n Tooltip,\n XAxis,\n YAxis,\n type TooltipContentProps,\n} from 'recharts';\nimport { GRID_PROPS, TEXT_SIZE, useAxis, xAxisTitle, yAxisTitle } from './core/axes';\nimport { TooltipCard } from './core/ChartTooltip';\nimport { BORDER, PANEL, SERIES, STATUS_CRIT, STATUS_WARN, TEXT } from './core/colors';\nimport { frameProps } from './core/frame';\nimport type { ChartAxisTitleProps, ChartProps } from './types';\n\nexport type RealtimeChartProps = ChartProps &\n ChartAxisTitleProps & {\n /** Sample generator — defaults to a noisy walk between 30 and 80. */\n generator?: () => number;\n /** Interval in ms — defaults to 2000. */\n intervalMs?: number;\n windowSize?: number;\n /** Bottom of the y axis. Default 0. */\n min?: number;\n /** Top of the y axis, above `min`. Default 100. */\n max?: number;\n warnThreshold?: number;\n critThreshold?: number;\n /** Text of the warning threshold's label. Default `WARN`; an empty string draws the line alone. */\n warnLabel?: string;\n /** Text of the critical threshold's label. Default `CRIT`; an empty string draws the line alone. */\n critLabel?: string;\n /**\n * Label the x axis with the time of each sample (`14:03:25`, in local time), and the\n * tooltip with it, and `xTitle` has an axis to name. Default off: no x axis.\n */\n showTimeAxis?: boolean;\n /** Format the y-axis labels and the tooltip values. */\n formatValue?: (value: number) => string;\n };\n\ntype Sample = { time: number; value: number };\n\n// The time axis reads as a clock.\nconst CLOCK = new Intl.DateTimeFormat('en-GB', {\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hourCycle: 'h23',\n});\nconst clock = (timestamp: number) => CLOCK.format(timestamp);\n\n/** A threshold: a line in its status color, and a label on it unless `label` is empty. */\nconst Threshold = ({ y, label, color }: { y: number; label: string; color: string }) => (\n <ReferenceLine\n y={y}\n stroke={color}\n strokeWidth={1.5}\n strokeDasharray=\"6 4\"\n label={\n label === ''\n ? undefined\n : {\n value: label,\n position: 'insideTopRight',\n offset: 4,\n // Text on the card, whatever the status color: it reads on every theme.\n fill: TEXT,\n style: { fill: TEXT, fontSize: TEXT_SIZE, fontWeight: 600 },\n }\n }\n />\n);\n\n// One walk per chart: a module-level one made every instance continue the last.\nconst createDefaultGenerator = () => {\n let last = 50;\n return () => {\n last += (Math.random() - 0.5) * 8;\n last = Math.max(20, Math.min(95, last));\n return Math.round(last);\n };\n};\n\nexport const RealtimeChart = ({\n generator,\n intervalMs = 2000,\n windowSize = 30,\n min = 0,\n max = 100,\n warnThreshold = 70,\n critThreshold = 85,\n warnLabel = 'WARN',\n critLabel = 'CRIT',\n showTimeAxis = false,\n xTitle,\n yTitle,\n formatValue,\n ariaLabel,\n children,\n height = 240,\n width,\n}: RealtimeChartProps) => {\n const axis = useAxis();\n const [defaultGenerator] = useState(createDefaultGenerator);\n const generate = generator ?? defaultGenerator;\n // Each sample with the time it was taken, for the time axis. A window that\n // starts full is dated as if it had been filled at the sampling pace.\n const [mountedAt] = useState(Date.now);\n const [samples, setSamples] = useState<Sample[]>(() =>\n Array.from({ length: windowSize }, (_, index) => ({\n time: mountedAt - (windowSize - 1 - index) * intervalMs,\n value: generate(),\n }))\n );\n\n // A new windowSize trims the oldest samples or prepends new ones. Adjusted\n // while rendering, which React redoes at once without painting the old window.\n const [sizedFor, setSizedFor] = useState(windowSize);\n if (sizedFor !== windowSize) {\n setSizedFor(windowSize);\n const missing = windowSize - samples.length;\n const oldest = samples[0]?.time ?? mountedAt;\n setSamples(\n missing <= 0\n ? samples.slice(samples.length - windowSize)\n : [\n ...Array.from({ length: missing }, (_, index) => ({\n time: oldest - (missing - index) * intervalMs,\n value: generate(),\n })),\n ...samples,\n ]\n );\n }\n\n useEffect(() => {\n const id = setInterval(() => {\n setSamples((current) => [...current.slice(1), { time: Date.now(), value: generate() }]);\n }, intervalMs);\n return () => clearInterval(id);\n }, [generate, intervalMs]);\n\n const format = formatValue ?? String;\n\n // The card of the sample under the pointer, with its time when the axis shows it.\n const tooltip = ({ active, payload }: Partial<TooltipContentProps>) => {\n const sample = payload?.[0]?.payload as Sample | undefined;\n if (!active || !sample) return null;\n return (\n <TooltipCard\n title={showTimeAxis ? clock(sample.time) : undefined}\n rows={[{ name: 'value', value: format(sample.value), color: SERIES[0] }]}\n />\n );\n };\n\n return (\n <LineChart\n {...frameProps({\n height,\n width,\n ariaLabel: ariaLabel ?? 'Live values, newest on the right',\n })}\n data={samples}\n margin={{ top: 4, right: 12, bottom: 0, left: 0 }}\n >\n <CartesianGrid {...GRID_PROPS} />\n {/* The samples are not on a scale of their own unless the axis is the clock. */}\n <XAxis\n dataKey=\"time\"\n type=\"number\"\n domain={['dataMin', 'dataMax']}\n hide={!showTimeAxis}\n tickFormatter={clock}\n {...axis}\n {...(showTimeAxis ? xAxisTitle(xTitle) : {})}\n />\n <YAxis\n domain={[min, max]}\n allowDataOverflow\n // The bottom tick is `min`: keep it.\n interval={0}\n width=\"auto\"\n tickFormatter={formatValue}\n {...axis}\n {...yAxisTitle(yTitle)}\n />\n <Threshold y={warnThreshold} label={warnLabel} color={STATUS_WARN} />\n <Threshold y={critThreshold} label={critLabel} color={STATUS_CRIT} />\n <Tooltip\n content={tooltip}\n cursor={{ stroke: BORDER, strokeDasharray: '4 4' }}\n animationDuration={0}\n />\n <Line\n name=\"value\"\n dataKey=\"value\"\n type=\"monotone\"\n stroke={SERIES[0]}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 5, fill: PANEL, stroke: SERIES[0], strokeWidth: 2.5 }}\n // A new sample moves the window: no animation, so the line redraws steadily.\n isAnimationActive={false}\n />\n {children}\n </LineChart>\n );\n};\n"]}
|