forked from jordanlambrecht/tracker-tracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFleetCompositionChart.tsx
More file actions
129 lines (114 loc) · 4.01 KB
/
Copy pathFleetCompositionChart.tsx
File metadata and controls
129 lines (114 loc) · 4.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// src/components/charts/FleetCompositionChart.tsx
"use client"
import type { EChartsOption } from "echarts"
import { formatCount } from "@/lib/formatters"
import type { FleetChartProps, TrackerSnapshotSeries } from "@/types/charts"
import { ChartECharts } from "./lib/ChartECharts"
import { ChartEmptyState } from "./lib/ChartEmptyState"
import {
buildAxisPointer,
buildGlowAreaStyle,
buildTimeXAxis,
insideZoom,
} from "./lib/chart-helpers"
import { carryForwardTimeSeries, collectUnifiedTimestamps } from "./lib/chart-transforms"
import {
CHART_THEME,
chartAxisLabel,
chartGrid,
chartLegend,
chartTooltip,
chartTooltipHeader,
chartTooltipRow,
formatChartTimestamp,
} from "./lib/theme"
interface FleetCompositionChartProps extends FleetChartProps {}
function buildFleetOption(trackerData: TrackerSnapshotSeries[]): EChartsOption {
// Collect unified ms timestamps for the time axis
const allTimestamps = collectUnifiedTimestamps(trackerData)
const series: EChartsOption["series"] = trackerData.map((tracker) => {
// Carry forward last known seedingCount to prevent stacked area collapse at gap timestamps
const data = carryForwardTimeSeries(allTimestamps, tracker.snapshots, (s) => s.seedingCount)
return {
name: tracker.name,
type: "line",
sampling: "lttb",
stack: "fleet",
data,
smooth: true,
symbol: "none",
lineStyle: {
color: tracker.color,
width: 1.5,
},
itemStyle: { color: tracker.color },
areaStyle: buildGlowAreaStyle(tracker.color, 0.6, 0.05),
}
})
return {
backgroundColor: "transparent",
grid: chartGrid({ right: 16, left: 64 }),
legend: chartLegend(),
tooltip: chartTooltip("axis", {
axisPointer: buildAxisPointer(CHART_THEME.borderMid, 0.8, 1),
formatter: (params: unknown) => {
const items = params as Array<{
seriesName: string
value: [number, number] | null
color: string
}>
if (!items?.length) return ""
const firstValue = items[0]?.value
if (!firstValue) return ""
const tsMs = firstValue[0]
const time = formatChartTimestamp(tsMs)
const validItems = items.filter((i) => i.value !== null && i.value !== undefined)
const total = validItems.reduce((sum, i) => sum + (i.value as [number, number])[1], 0)
const sorted = [...validItems].sort(
(a, b) => (b.value as [number, number])[1] - (a.value as [number, number])[1]
)
const header = chartTooltipHeader(time)
const totalRow = `<div style="color:${CHART_THEME.textPrimary};font-weight:600;margin-bottom:4px;">Total: ${formatCount(total)} seeding</div>`
const rows = sorted
.map((item) => {
const val = (item.value as [number, number])[1]
return chartTooltipRow(item.color, item.seriesName, formatCount(val))
})
.join("<br/>")
return header + totalRow + rows
},
}),
xAxis: buildTimeXAxis(),
yAxis: {
type: "value",
name: "Seeding",
nameTextStyle: {
color: CHART_THEME.textTertiary,
fontFamily: CHART_THEME.fontMono,
fontSize: CHART_THEME.fontSizeCompact,
},
axisLine: { show: false },
axisTick: { show: false },
axisLabel: chartAxisLabel({
formatter: (val: number) => formatCount(val),
}),
splitLine: {
lineStyle: {
color: CHART_THEME.gridLine,
width: 1,
},
},
},
dataZoom: insideZoom(allTimestamps.length),
series,
}
}
function FleetCompositionChart({ trackerData, height = 360 }: FleetCompositionChartProps) {
const hasData = trackerData.some((t) => t.snapshots.some((s) => s.seedingCount !== null))
if (!hasData) {
return <ChartEmptyState height={height} message="No fleet data available" />
}
return <ChartECharts option={buildFleetOption(trackerData)} style={{ height, width: "100%" }} />
}
export type { FleetCompositionChartProps }
export { FleetCompositionChart }