diff --git a/components/charts/BatteryLifeChart.tsx b/components/charts/BatteryLifeChart.tsx new file mode 100644 index 0000000..488d58e --- /dev/null +++ b/components/charts/BatteryLifeChart.tsx @@ -0,0 +1,101 @@ +"use client" + +import { EChartWrapper, getDeviceColor } from "./EChartWrapper" + +interface BatteryLifePoint { + id: string + hardwareSlug: string + tdpWatts: number + estimatedBatteryMin: number + estimatedBatteryHours: number + wattHours: number | null + tdpMax: number | null + estimatedAtMaxTdpMin: number | null +} + +interface BatteryLifeChartProps { + data: BatteryLifePoint[] + deviceNames?: Record +} + +export function BatteryLifeChart({ data, deviceNames }: BatteryLifeChartProps) { + if (!data || data.length === 0) { + return
No battery data available
+ } + + // Group by device for separate series + const deviceGroups = new Map() + for (const point of data) { + const existing = deviceGroups.get(point.hardwareSlug) || [] + existing.push(point) + deviceGroups.set(point.hardwareSlug, existing) + } + + // Build trend lines: for each device, compute wattHours / tdp = hours for a range of TDPs + const series: any[] = [] + let seriesIdx = 0 + + for (const [slug, points] of deviceGroups.entries()) { + const color = getDeviceColor(seriesIdx) + const name = deviceNames?.[slug] || slug + + // Scatter points: TDP vs battery hours + series.push({ + name, + type: "scatter" as const, + data: points.map((p) => [p.tdpWatts, p.estimatedBatteryHours]), + itemStyle: { color }, + symbolSize: 10, + }) + + // Trend line: compute theoretical curve using average wattHours for this device + const avgWh = points.reduce((sum, p) => sum + (p.wattHours ?? 0), 0) / points.length + if (avgWh > 0) { + const tdpRange = [2, 5, 8, 10, 12, 15, 18, 20, 25, 30].filter( + (tdp) => tdp <= (points[0].tdpMax ?? 30), + ) + series.push({ + name: `${name} (est.)`, + type: "line" as const, + data: tdpRange.map((tdp) => [tdp, Math.round((avgWh / tdp) * 10) / 10]), + lineStyle: { color, type: "dashed" as const, width: 1 }, + symbol: "none", + silent: true, + }) + } + + seriesIdx++ + } + + const option = { + tooltip: { + trigger: "item" as const, + formatter: (params: any) => { + if (params.seriesName.includes("(est.)")) return "" + return `${params.seriesName}
TDP: ${params.value[0]}W
Battery: ~${params.value[1]}h` + }, + }, + legend: { + textStyle: { color: "#999" }, + top: 0, + }, + grid: { left: 60, right: 20, top: 40, bottom: 40 }, + xAxis: { + type: "value" as const, + name: "TDP (W)", + nameTextStyle: { color: "#999" }, + splitLine: { lineStyle: { color: "#333" } }, + axisLabel: { color: "#999" }, + }, + yAxis: { + type: "value" as const, + name: "Battery (h)", + nameTextStyle: { color: "#999" }, + splitLine: { lineStyle: { color: "#333" } }, + axisLabel: { color: "#999" }, + }, + series, + } + + return +}