@epilot/spark-ui 1.0.0-alpha.3 → 1.0.0-alpha.4
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/dist/components/attachment.d.cts +1 -1
- package/dist/components/attachment.d.ts +1 -1
- package/dist/components/bar-chart.cjs +48 -47
- package/dist/components/bar-chart.cjs.map +1 -1
- package/dist/components/bar-chart.d.cts +7 -2
- package/dist/components/bar-chart.d.ts +7 -2
- package/dist/components/bar-chart.js +48 -47
- package/dist/components/bar-chart.js.map +1 -1
- package/dist/components/bubble.d.cts +1 -1
- package/dist/components/bubble.d.ts +1 -1
- package/dist/components/button.cjs +1 -1
- package/dist/components/button.cjs.map +1 -1
- package/dist/components/button.d.cts +8 -0
- package/dist/components/button.d.ts +8 -0
- package/dist/components/button.js +1 -1
- package/dist/components/button.js.map +1 -1
- package/dist/components/callout.d.cts +1 -1
- package/dist/components/callout.d.ts +1 -1
- package/dist/components/energy-bar-chart.cjs +2 -1
- package/dist/components/energy-bar-chart.cjs.map +1 -1
- package/dist/components/energy-bar-chart.js +2 -1
- package/dist/components/energy-bar-chart.js.map +1 -1
- package/dist/components/energy-history-chart.cjs +14 -6
- package/dist/components/energy-history-chart.cjs.map +1 -1
- package/dist/components/energy-history-chart.d.cts +11 -3
- package/dist/components/energy-history-chart.d.ts +11 -3
- package/dist/components/energy-history-chart.js +14 -6
- package/dist/components/energy-history-chart.js.map +1 -1
- package/dist/components/field.cjs +1 -1
- package/dist/components/field.js +1 -1
- package/dist/components/input-group.cjs +1 -1
- package/dist/components/input-group.js +1 -1
- package/dist/components/line-chart.cjs +55 -15
- package/dist/components/line-chart.cjs.map +1 -1
- package/dist/components/line-chart.d.cts +7 -0
- package/dist/components/line-chart.d.ts +7 -0
- package/dist/components/line-chart.js +55 -15
- package/dist/components/line-chart.js.map +1 -1
- package/dist/components/power-history-chart.cjs +14 -37
- package/dist/components/power-history-chart.cjs.map +1 -1
- package/dist/components/power-history-chart.d.cts +10 -1
- package/dist/components/power-history-chart.d.ts +10 -1
- package/dist/components/power-history-chart.js +15 -38
- package/dist/components/power-history-chart.js.map +1 -1
- package/dist/components/sankey-chart.cjs +8 -41
- package/dist/components/sankey-chart.cjs.map +1 -1
- package/dist/components/sankey-chart.d.cts +8 -0
- package/dist/components/sankey-chart.d.ts +8 -0
- package/dist/components/sankey-chart.js +8 -41
- package/dist/components/sankey-chart.js.map +1 -1
- package/dist/components/segmented-control.cjs +44 -0
- package/dist/components/segmented-control.cjs.map +1 -0
- package/dist/components/segmented-control.d.cts +33 -0
- package/dist/components/segmented-control.d.ts +33 -0
- package/dist/components/segmented-control.js +40 -0
- package/dist/components/segmented-control.js.map +1 -0
- package/dist/components/separator.cjs +1 -1
- package/dist/components/separator.cjs.map +1 -1
- package/dist/components/separator.js +1 -1
- package/dist/components/separator.js.map +1 -1
- package/dist/components/solar-forecast-chart.cjs +4 -3
- package/dist/components/solar-forecast-chart.cjs.map +1 -1
- package/dist/components/solar-forecast-chart.d.cts +4 -1
- package/dist/components/solar-forecast-chart.d.ts +4 -1
- package/dist/components/solar-forecast-chart.js +4 -3
- package/dist/components/solar-forecast-chart.js.map +1 -1
- package/dist/components/theme.cjs +1 -1
- package/dist/components/theme.js +1 -1
- package/dist/components/tooltip.cjs +1 -1
- package/dist/components/tooltip.cjs.map +1 -1
- package/dist/components/tooltip.js +1 -1
- package/dist/components/tooltip.js.map +1 -1
- package/dist/styles/index.css +37 -0
- package/dist/styles/spark-default.css +45 -40
- package/dist/styles/theme.css +78 -0
- package/dist/{theme-B3WtiByA.js → theme-C19BbKfO.js} +2 -2
- package/dist/theme-C19BbKfO.js.map +1 -0
- package/dist/{theme-C9_OSe0S.cjs → theme-CfFWrMEF.cjs} +2 -2
- package/dist/theme-CfFWrMEF.cjs.map +1 -0
- package/dist/utils/build-sankey-nodes.cjs +16 -3
- package/dist/utils/build-sankey-nodes.cjs.map +1 -1
- package/dist/utils/build-sankey-nodes.js +16 -3
- package/dist/utils/build-sankey-nodes.js.map +1 -1
- package/dist/utils/chart-scale.cjs +43 -0
- package/dist/utils/chart-scale.cjs.map +1 -0
- package/dist/utils/chart-scale.d.cts +22 -0
- package/dist/utils/chart-scale.d.ts +22 -0
- package/dist/utils/chart-scale.js +41 -0
- package/dist/utils/chart-scale.js.map +1 -0
- package/package.json +1 -1
- package/dist/theme-B3WtiByA.js.map +0 -1
- package/dist/theme-C9_OSe0S.cjs.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../utils/cn.js";
|
|
3
3
|
import { localeNumberFormat } from "../utils/format.js";
|
|
4
|
+
import { buildYAxisScale, formatYAxisTick } from "../utils/chart-scale.js";
|
|
4
5
|
import { ChartTooltipCard, clampTooltipPosition, useMeasuredSize } from "./chart.js";
|
|
5
6
|
import { useCallback, useMemo } from "react";
|
|
6
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -88,7 +89,7 @@ const LEGEND_COLORS = [
|
|
|
88
89
|
"var(--orange-9)",
|
|
89
90
|
"var(--red-9)"
|
|
90
91
|
];
|
|
91
|
-
const LineChart = ({ series, width, height, margin = DEFAULT_MARGIN, curve: defaultCurve = "monotone", showGrid = true, showTooltip: enableTooltip = true, className, xTickCount = 5, yTickCount = 4, formatDate = defaultFormatDate, formatTooltipDate, formatValue = defaultFormatValue, yDomain, referenceLines, topContent }) => {
|
|
92
|
+
const LineChart = ({ series, width, height, margin = DEFAULT_MARGIN, curve: defaultCurve = "monotone", showGrid = true, showTooltip: enableTooltip = true, className, xTickCount = 5, yTickCount = 4, formatDate = defaultFormatDate, formatTooltipDate, formatValue = defaultFormatValue, yAxisUnit, yDomain, referenceLines, topContent }) => {
|
|
92
93
|
const { showTooltip, hideTooltip, tooltipData, tooltipLeft, tooltipTop, tooltipOpen } = useTooltip();
|
|
93
94
|
const innerWidth = Math.max(0, width - margin.left - margin.right);
|
|
94
95
|
const innerHeight = Math.max(0, height - margin.top - margin.bottom);
|
|
@@ -97,17 +98,28 @@ const LineChart = ({ series, width, height, margin = DEFAULT_MARGIN, curve: defa
|
|
|
97
98
|
range: [0, innerWidth],
|
|
98
99
|
domain: extent(allData, (d) => d.date)
|
|
99
100
|
}), [allData, innerWidth]);
|
|
101
|
+
const yAxisScale = useMemo(() => {
|
|
102
|
+
if (yAxisUnit === void 0) return null;
|
|
103
|
+
let min = 0;
|
|
104
|
+
let max = 0;
|
|
105
|
+
for (const d of allData) {
|
|
106
|
+
if (d.value < min) min = d.value;
|
|
107
|
+
if (d.value > max) max = d.value;
|
|
108
|
+
}
|
|
109
|
+
return buildYAxisScale(min, max);
|
|
110
|
+
}, [allData, yAxisUnit]);
|
|
100
111
|
const yScale = useMemo(() => scaleLinear({
|
|
101
112
|
range: [innerHeight, 0],
|
|
102
|
-
domain: yDomain ?? computeYDomain(allData),
|
|
103
|
-
nice: !yDomain
|
|
113
|
+
domain: yDomain ?? (yAxisScale ? [yAxisScale.min, yAxisScale.max] : computeYDomain(allData)),
|
|
114
|
+
nice: !yDomain && !yAxisScale
|
|
104
115
|
}), [
|
|
105
116
|
allData,
|
|
106
117
|
innerHeight,
|
|
107
|
-
yDomain
|
|
118
|
+
yDomain,
|
|
119
|
+
yAxisScale
|
|
108
120
|
]);
|
|
109
121
|
const xTicks = xScale.ticks(xTickCount);
|
|
110
|
-
const yTicks = yScale.ticks(yTickCount);
|
|
122
|
+
const yTicks = yAxisScale ? yAxisScale.ticks : yScale.ticks(yTickCount);
|
|
111
123
|
const handleTooltip = useCallback((event) => {
|
|
112
124
|
if (!enableTooltip) return;
|
|
113
125
|
const point = localPoint(event);
|
|
@@ -208,22 +220,50 @@ const LineChart = ({ series, width, height, margin = DEFAULT_MARGIN, curve: defa
|
|
|
208
220
|
}, ref.key);
|
|
209
221
|
}),
|
|
210
222
|
xTicks.map((tick) => /* @__PURE__ */ jsx("text", {
|
|
211
|
-
className: "fill-slate-a11 text-xs",
|
|
212
223
|
dy: "1em",
|
|
224
|
+
fill: "var(--gray-a11)",
|
|
225
|
+
fontSize: 10,
|
|
213
226
|
textAnchor: "middle",
|
|
214
227
|
x: xScale(tick),
|
|
215
228
|
y: innerHeight + 8,
|
|
216
229
|
children: formatDate(tick)
|
|
217
230
|
}, `x-${tick.valueOf()}`)),
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
231
|
+
yAxisUnit !== void 0 && /* @__PURE__ */ jsx("line", {
|
|
232
|
+
stroke: "var(--gray-a6)",
|
|
233
|
+
strokeWidth: 1,
|
|
234
|
+
x1: 0,
|
|
235
|
+
x2: 0,
|
|
236
|
+
y1: 0,
|
|
237
|
+
y2: innerHeight
|
|
238
|
+
}),
|
|
239
|
+
yTicks.map((tick) => {
|
|
240
|
+
const y = yScale(tick);
|
|
241
|
+
return /* @__PURE__ */ jsxs("g", { children: [yAxisUnit !== void 0 && /* @__PURE__ */ jsx("line", {
|
|
242
|
+
stroke: "var(--gray-a8)",
|
|
243
|
+
strokeWidth: 1,
|
|
244
|
+
x1: -4,
|
|
245
|
+
x2: 0,
|
|
246
|
+
y1: y,
|
|
247
|
+
y2: y
|
|
248
|
+
}), /* @__PURE__ */ jsx("text", {
|
|
249
|
+
dy: "0.32em",
|
|
250
|
+
fill: "var(--gray-a11)",
|
|
251
|
+
fontSize: 10,
|
|
252
|
+
textAnchor: "end",
|
|
253
|
+
x: yAxisUnit !== void 0 ? -7 : -4,
|
|
254
|
+
y,
|
|
255
|
+
children: yAxisUnit !== void 0 ? formatYAxisTick(tick) : formatValue(tick)
|
|
256
|
+
})] }, `y-${tick}`);
|
|
257
|
+
}),
|
|
258
|
+
yAxisUnit !== void 0 && /* @__PURE__ */ jsx("text", {
|
|
259
|
+
fill: "var(--gray-a11)",
|
|
260
|
+
fontSize: 10,
|
|
261
|
+
fontWeight: 600,
|
|
262
|
+
textAnchor: "start",
|
|
263
|
+
x: -margin.left,
|
|
264
|
+
y: -6,
|
|
265
|
+
children: yAxisUnit
|
|
266
|
+
}),
|
|
227
267
|
series.map((s, i) => /* @__PURE__ */ jsx(LinePath, {
|
|
228
268
|
className: cn(LINE_COLORS[i % LINE_COLORS.length], s.className),
|
|
229
269
|
curve: resolveCurve(s.curve ?? defaultCurve),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"line-chart.js","names":[],"sources":["../../src/components/line-chart.tsx"],"sourcesContent":["import {\n curveLinear,\n curveMonotoneX,\n curveStep,\n curveStepAfter,\n curveStepBefore,\n} from '@visx/curve'\nimport { localPoint } from '@visx/event'\nimport { Group } from '@visx/group'\nimport { scaleLinear, scaleTime } from '@visx/scale'\nimport { LinePath } from '@visx/shape'\nimport { useTooltip } from '@visx/tooltip'\nimport { bisector, extent } from '@visx/vendor/d3-array'\nimport * as React from 'react'\nimport { useCallback, useMemo } from 'react'\nimport { cn } from '../utils/cn'\nimport { localeNumberFormat } from '../utils/format'\nimport {\n ChartTooltipCard,\n type ChartTooltipRow,\n clampTooltipPosition,\n useMeasuredSize,\n} from './chart'\n\nexport interface LineChartDatum {\n date: Date\n value: number\n}\n\nexport type LineChartCurve =\n | 'monotone'\n | 'step'\n | 'step-after'\n | 'step-before'\n | 'linear'\n\nexport interface LineChartSeries {\n id: string\n data: LineChartDatum[]\n /** Label shown in the hover tooltip. Defaults to `id`. */\n label?: React.ReactNode\n className?: string\n /** Curve interpolation type. Default: inherits from LineChartProps.curve */\n curve?: LineChartCurve\n /** Render as a dashed line */\n dashed?: boolean\n /** Custom dash pattern (e.g. \"8,4\" or \"4,2,1,2\"). Only applies when dashed is true. */\n dashPattern?: string\n}\n\nexport interface LineChartProps {\n series: LineChartSeries[]\n width: number\n height: number\n margin?: { top: number; right: number; bottom: number; left: number }\n /** Default curve for all series. Default: 'monotone' */\n curve?: LineChartCurve\n showGrid?: boolean\n showTooltip?: boolean\n className?: string\n xTickCount?: number\n yTickCount?: number\n /** Fixes the y-axis to an explicit `[min, max]`. Defaults to the data extent. */\n yDomain?: [number, number]\n /** Formats the x-axis tick labels. */\n formatDate?: (date: Date) => string\n /** Formats the tooltip title. Defaults to `formatDate`. */\n formatTooltipDate?: (date: Date) => string\n formatValue?: (value: number) => string\n /**\n * Vertical reference markers positioned on the time axis (e.g. sunrise /\n * sunset). Each renders a dashed line across the plot with an optional label.\n */\n referenceLines?: Array<{ key: string; date: Date; label?: React.ReactNode }>\n /**\n * HTML content pinned to the top of the plot at a given time (e.g. an hourly\n * weather icon). Positioned by the same time scale as the series, so it lines\n * up with the curve below it.\n */\n topContent?: Array<{ key: string; date: Date; content: React.ReactNode }>\n}\n\nconst DEFAULT_MARGIN = { top: 16, right: 16, bottom: 40, left: 48 }\n\nconst CURVE_MAP = {\n monotone: curveMonotoneX,\n step: curveStep,\n 'step-after': curveStepAfter,\n 'step-before': curveStepBefore,\n linear: curveLinear,\n} as const\n\nconst resolveCurve = (type: LineChartCurve) => CURVE_MAP[type]\n\nconst dateBisector = bisector<LineChartDatum, Date>((d) => d.date)\nconst bisectDate = (data: LineChartDatum[], x: Date, lo?: number) =>\n dateBisector.left(data, x, lo)\n\n/** Whether `date` falls within a series' own x-range (data is sorted ascending). */\nfunction withinDomain(data: LineChartDatum[], date: Date): boolean {\n const first = data[0]\n const last = data[data.length - 1]\n if (!first || !last) return false\n const t = date.valueOf()\n return t >= first.date.valueOf() && t <= last.date.valueOf()\n}\n\n/**\n * y-domain padded 10% beyond the data extent. The baseline is pinned to 0 so\n * all-positive series render from the axis up; a series that dips below 0\n * (signed grid / battery power) extends the domain downward to keep the zero\n * crossing visible.\n */\nfunction computeYDomain(data: LineChartDatum[]): [number, number] {\n let min = 0\n let max = 0\n for (const d of data) {\n if (d.value < min) min = d.value\n if (d.value > max) max = d.value\n }\n if (min === max) return [min, min + 1]\n return [min * 1.1, max * 1.1]\n}\n\nfunction nearestDatum(\n data: LineChartDatum[],\n date: Date,\n): LineChartDatum | undefined {\n if (data.length === 0) return undefined\n const index = bisectDate(data, date, 1)\n const d0 = data[index - 1]\n const d1 = data[index]\n if (!d0) return d1\n if (!d1) return d0\n return date.valueOf() - d0.date.valueOf() > d1.date.valueOf() - date.valueOf()\n ? d1\n : d0\n}\n\nconst defaultFormatDate = (date: Date) =>\n date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })\n\nconst defaultValueFormat = localeNumberFormat()\nconst defaultFormatValue = (value: number) => defaultValueFormat.format(value)\n\nconst LINE_COLORS = [\n 'stroke-spark-9',\n 'stroke-green-9',\n 'stroke-blue-9',\n 'stroke-orange-9',\n 'stroke-red-9',\n]\n\nconst DOT_COLORS = [\n 'fill-spark-9',\n 'fill-green-9',\n 'fill-blue-9',\n 'fill-orange-9',\n 'fill-red-9',\n]\n\n// CSS-var colours matching LINE_COLORS, used for the tooltip legend dots.\nconst LEGEND_COLORS = [\n 'var(--spark-9)',\n 'var(--green-9)',\n 'var(--blue-9)',\n 'var(--orange-9)',\n 'var(--red-9)',\n]\n\nconst LineChart: React.FC<LineChartProps> = ({\n series,\n width,\n height,\n margin = DEFAULT_MARGIN,\n curve: defaultCurve = 'monotone',\n showGrid = true,\n showTooltip: enableTooltip = true,\n className,\n xTickCount = 5,\n yTickCount = 4,\n formatDate = defaultFormatDate,\n formatTooltipDate,\n formatValue = defaultFormatValue,\n yDomain,\n referenceLines,\n topContent,\n}) => {\n const {\n showTooltip,\n hideTooltip,\n tooltipData,\n tooltipLeft,\n tooltipTop,\n tooltipOpen,\n } = useTooltip<{ date: Date }>()\n\n const innerWidth = Math.max(0, width - margin.left - margin.right)\n const innerHeight = Math.max(0, height - margin.top - margin.bottom)\n\n const allData = useMemo(() => series.flatMap((s) => s.data), [series])\n\n const xScale = useMemo(\n () =>\n scaleTime({\n range: [0, innerWidth],\n domain: extent(allData, (d) => d.date) as [Date, Date],\n }),\n [allData, innerWidth],\n )\n\n const yScale = useMemo(\n () =>\n scaleLinear({\n range: [innerHeight, 0],\n domain: yDomain ?? computeYDomain(allData),\n nice: !yDomain,\n }),\n [allData, innerHeight, yDomain],\n )\n\n const xTicks = xScale.ticks(xTickCount)\n const yTicks = yScale.ticks(yTickCount)\n\n const handleTooltip = useCallback(\n (\n event:\n | React.TouchEvent<SVGRectElement>\n | React.MouseEvent<SVGRectElement>,\n ) => {\n if (!enableTooltip) return\n\n const point = localPoint(event)\n if (!point) return\n\n const x0 = xScale.invert(point.x - margin.left)\n\n // Snap to the data point closest to the cursor across all series, so the\n // cursor line + tooltip line up with real values.\n let anchorDate: Date | null = null\n let bestDistance = Infinity\n for (const s of series) {\n const d = nearestDatum(s.data, x0)\n if (!d) continue\n const distance = Math.abs(d.date.valueOf() - x0.valueOf())\n if (distance < bestDistance) {\n bestDistance = distance\n anchorDate = d.date\n }\n }\n\n if (!anchorDate) return\n\n showTooltip({\n tooltipData: { date: anchorDate },\n tooltipLeft: xScale(anchorDate) + margin.left,\n tooltipTop: margin.top,\n })\n },\n [enableTooltip, margin, series, showTooltip, xScale],\n )\n\n const tooltipRows: ChartTooltipRow[] = tooltipData\n ? series.flatMap((s, i) => {\n const d = nearestDatum(s.data, tooltipData.date)\n if (!d || !withinDomain(s.data, tooltipData.date)) return []\n return [\n {\n key: s.id,\n label: s.label ?? s.id,\n color: LEGEND_COLORS[i % LEGEND_COLORS.length] as string,\n value: formatValue(d.value),\n },\n ]\n })\n : []\n\n const [tooltipRef, tooltipSize] = useMeasuredSize<HTMLDivElement>()\n const tooltipPos = clampTooltipPosition({\n anchorX: tooltipLeft ?? 0,\n anchorY: tooltipTop ?? margin.top,\n width,\n height,\n tipWidth: tooltipSize.width,\n tipHeight: tooltipSize.height,\n placement: 'beside',\n })\n\n if (width < 10 || height < 10) return null\n\n return (\n <div className={cn('relative', className)}>\n <svg className=\"block\" height={height} width={width}>\n <Group left={margin.left} top={margin.top}>\n {/* Grid lines */}\n {showGrid &&\n yTicks.map((tick) => (\n <line\n className=\"stroke-slate-a4\"\n key={`grid-y-${tick}`}\n strokeDasharray=\"4,4\"\n x1={0}\n x2={innerWidth}\n y1={yScale(tick)}\n y2={yScale(tick)}\n />\n ))}\n\n {/* X axis */}\n <line\n className=\"stroke-slate-a6\"\n x1={0}\n x2={innerWidth}\n y1={innerHeight}\n y2={innerHeight}\n />\n\n {/* Reference markers (e.g. sunrise / sunset) */}\n {referenceLines?.map((ref) => {\n const x = xScale(ref.date)\n if (!Number.isFinite(x)) return null\n // Sit the label beside the line, on whichever side keeps it in\n // frame: right of a left-half marker, left of a right-half one.\n const anchorEnd = x > innerWidth / 2\n return (\n <g key={ref.key} pointerEvents=\"none\">\n <line\n className=\"stroke-orange-8\"\n strokeDasharray=\"3,3\"\n x1={x}\n x2={x}\n y1={0}\n y2={innerHeight}\n />\n {ref.label != null && (\n <text\n className=\"fill-orange-11 text-[10px]\"\n dy=\"0.9em\"\n textAnchor={anchorEnd ? 'end' : 'start'}\n x={x + (anchorEnd ? -4 : 4)}\n y={0}\n >\n {ref.label}\n </text>\n )}\n </g>\n )\n })}\n\n {/* X axis labels */}\n {xTicks.map((tick) => (\n <text\n className=\"fill-slate-a11 text-xs\"\n dy=\"1em\"\n key={`x-${tick.valueOf()}`}\n textAnchor=\"middle\"\n x={xScale(tick)}\n y={innerHeight + 8}\n >\n {formatDate(tick)}\n </text>\n ))}\n\n {/* Y axis labels */}\n {yTicks.map((tick) => (\n <text\n className=\"fill-slate-a11 text-xs\"\n dx=\"-0.5em\"\n dy=\"0.32em\"\n key={`y-${tick}`}\n textAnchor=\"end\"\n x={-4}\n y={yScale(tick)}\n >\n {formatValue(tick)}\n </text>\n ))}\n\n {/* Lines */}\n {series.map((s, i) => (\n <LinePath\n className={cn(LINE_COLORS[i % LINE_COLORS.length], s.className)}\n curve={resolveCurve(s.curve ?? defaultCurve)}\n data={s.data}\n fill=\"none\"\n key={s.id}\n strokeDasharray={s.dashed ? (s.dashPattern ?? '6,4') : undefined}\n strokeWidth={2}\n x={(d) => xScale(d.date)}\n y={(d) => yScale(d.value)}\n />\n ))}\n\n {/* Tooltip hover target */}\n {enableTooltip && (\n <rect\n fill=\"transparent\"\n height={innerHeight}\n onMouseLeave={hideTooltip}\n onMouseMove={handleTooltip}\n onTouchEnd={hideTooltip}\n onTouchMove={handleTooltip}\n width={innerWidth}\n x={0}\n y={0}\n />\n )}\n\n {/* Cursor line + a dot per series */}\n {tooltipOpen && tooltipData && (\n <>\n <line\n className=\"stroke-slate-a6\"\n pointerEvents=\"none\"\n strokeDasharray=\"4,4\"\n x1={xScale(tooltipData.date)}\n x2={xScale(tooltipData.date)}\n y1={0}\n y2={innerHeight}\n />\n {series.map((s, i) => {\n const d = nearestDatum(s.data, tooltipData.date)\n if (!d || !withinDomain(s.data, tooltipData.date)) return null\n return (\n <circle\n key={s.id}\n className={cn(DOT_COLORS[i % DOT_COLORS.length])}\n cx={xScale(d.date)}\n cy={yScale(d.value)}\n pointerEvents=\"none\"\n r={4}\n stroke=\"white\"\n strokeWidth={2}\n />\n )\n })}\n </>\n )}\n </Group>\n </svg>\n\n {topContent && topContent.length > 0 && (\n <div className=\"pointer-events-none absolute inset-0\">\n {topContent.map((item) => {\n const x = xScale(item.date)\n if (!Number.isFinite(x)) return null\n return (\n <div\n key={item.key}\n className=\"absolute -translate-x-1/2\"\n style={{ left: margin.left + x, top: 0 }}\n >\n {item.content}\n </div>\n )\n })}\n </div>\n )}\n\n <div\n ref={tooltipRef}\n className=\"pointer-events-none absolute z-10\"\n style={{\n left: tooltipPos.left,\n top: tooltipPos.top,\n opacity: tooltipOpen && tooltipData ? 1 : 0,\n transition:\n 'left 140ms ease-out, top 140ms ease-out, opacity 140ms ease-out',\n }}\n >\n {tooltipData && (\n <ChartTooltipCard\n label={(formatTooltipDate ?? formatDate)(tooltipData.date)}\n rows={tooltipRows}\n />\n )}\n </div>\n </div>\n )\n}\n\nexport { LineChart }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"line-chart.js","names":[],"sources":["../../src/components/line-chart.tsx"],"sourcesContent":["import {\n curveLinear,\n curveMonotoneX,\n curveStep,\n curveStepAfter,\n curveStepBefore,\n} from '@visx/curve'\nimport { localPoint } from '@visx/event'\nimport { Group } from '@visx/group'\nimport { scaleLinear, scaleTime } from '@visx/scale'\nimport { LinePath } from '@visx/shape'\nimport { useTooltip } from '@visx/tooltip'\nimport { bisector, extent } from '@visx/vendor/d3-array'\nimport * as React from 'react'\nimport { useCallback, useMemo } from 'react'\nimport { buildYAxisScale, formatYAxisTick } from '../utils/chart-scale'\nimport { cn } from '../utils/cn'\nimport { localeNumberFormat } from '../utils/format'\nimport {\n ChartTooltipCard,\n type ChartTooltipRow,\n clampTooltipPosition,\n useMeasuredSize,\n} from './chart'\n\nexport interface LineChartDatum {\n date: Date\n value: number\n}\n\nexport type LineChartCurve =\n | 'monotone'\n | 'step'\n | 'step-after'\n | 'step-before'\n | 'linear'\n\nexport interface LineChartSeries {\n id: string\n data: LineChartDatum[]\n /** Label shown in the hover tooltip. Defaults to `id`. */\n label?: React.ReactNode\n className?: string\n /** Curve interpolation type. Default: inherits from LineChartProps.curve */\n curve?: LineChartCurve\n /** Render as a dashed line */\n dashed?: boolean\n /** Custom dash pattern (e.g. \"8,4\" or \"4,2,1,2\"). Only applies when dashed is true. */\n dashPattern?: string\n}\n\nexport interface LineChartProps {\n series: LineChartSeries[]\n width: number\n height: number\n margin?: { top: number; right: number; bottom: number; left: number }\n /** Default curve for all series. Default: 'monotone' */\n curve?: LineChartCurve\n showGrid?: boolean\n showTooltip?: boolean\n className?: string\n xTickCount?: number\n yTickCount?: number\n /** Fixes the y-axis to an explicit `[min, max]`. Defaults to the data extent. */\n yDomain?: [number, number]\n /** Formats the x-axis tick labels. */\n formatDate?: (date: Date) => string\n /** Formats the tooltip title. Defaults to `formatDate`. */\n formatTooltipDate?: (date: Date) => string\n formatValue?: (value: number) => string\n /**\n * Unit shown once at the top of the y-axis (e.g. \"kW\"). When set, y-axis tick\n * labels render as plain numbers (matching BarChart / PowerHistoryChart)\n * instead of repeating `formatValue` on every tick. `formatValue` still feeds\n * the tooltip. Omit to keep the per-tick `formatValue` labels.\n */\n yAxisUnit?: string\n /**\n * Vertical reference markers positioned on the time axis (e.g. sunrise /\n * sunset). Each renders a dashed line across the plot with an optional label.\n */\n referenceLines?: Array<{ key: string; date: Date; label?: React.ReactNode }>\n /**\n * HTML content pinned to the top of the plot at a given time (e.g. an hourly\n * weather icon). Positioned by the same time scale as the series, so it lines\n * up with the curve below it.\n */\n topContent?: Array<{ key: string; date: Date; content: React.ReactNode }>\n}\n\nconst DEFAULT_MARGIN = { top: 16, right: 16, bottom: 40, left: 48 }\n\nconst CURVE_MAP = {\n monotone: curveMonotoneX,\n step: curveStep,\n 'step-after': curveStepAfter,\n 'step-before': curveStepBefore,\n linear: curveLinear,\n} as const\n\nconst resolveCurve = (type: LineChartCurve) => CURVE_MAP[type]\n\nconst dateBisector = bisector<LineChartDatum, Date>((d) => d.date)\nconst bisectDate = (data: LineChartDatum[], x: Date, lo?: number) =>\n dateBisector.left(data, x, lo)\n\n/** Whether `date` falls within a series' own x-range (data is sorted ascending). */\nfunction withinDomain(data: LineChartDatum[], date: Date): boolean {\n const first = data[0]\n const last = data[data.length - 1]\n if (!first || !last) return false\n const t = date.valueOf()\n return t >= first.date.valueOf() && t <= last.date.valueOf()\n}\n\n/**\n * y-domain padded 10% beyond the data extent. The baseline is pinned to 0 so\n * all-positive series render from the axis up; a series that dips below 0\n * (signed grid / battery power) extends the domain downward to keep the zero\n * crossing visible.\n */\nfunction computeYDomain(data: LineChartDatum[]): [number, number] {\n let min = 0\n let max = 0\n for (const d of data) {\n if (d.value < min) min = d.value\n if (d.value > max) max = d.value\n }\n if (min === max) return [min, min + 1]\n return [min * 1.1, max * 1.1]\n}\n\nfunction nearestDatum(\n data: LineChartDatum[],\n date: Date,\n): LineChartDatum | undefined {\n if (data.length === 0) return undefined\n const index = bisectDate(data, date, 1)\n const d0 = data[index - 1]\n const d1 = data[index]\n if (!d0) return d1\n if (!d1) return d0\n return date.valueOf() - d0.date.valueOf() > d1.date.valueOf() - date.valueOf()\n ? d1\n : d0\n}\n\nconst defaultFormatDate = (date: Date) =>\n date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })\n\nconst defaultValueFormat = localeNumberFormat()\nconst defaultFormatValue = (value: number) => defaultValueFormat.format(value)\n\nconst LINE_COLORS = [\n 'stroke-spark-9',\n 'stroke-green-9',\n 'stroke-blue-9',\n 'stroke-orange-9',\n 'stroke-red-9',\n]\n\nconst DOT_COLORS = [\n 'fill-spark-9',\n 'fill-green-9',\n 'fill-blue-9',\n 'fill-orange-9',\n 'fill-red-9',\n]\n\n// CSS-var colours matching LINE_COLORS, used for the tooltip legend dots.\nconst LEGEND_COLORS = [\n 'var(--spark-9)',\n 'var(--green-9)',\n 'var(--blue-9)',\n 'var(--orange-9)',\n 'var(--red-9)',\n]\n\nconst LineChart: React.FC<LineChartProps> = ({\n series,\n width,\n height,\n margin = DEFAULT_MARGIN,\n curve: defaultCurve = 'monotone',\n showGrid = true,\n showTooltip: enableTooltip = true,\n className,\n xTickCount = 5,\n yTickCount = 4,\n formatDate = defaultFormatDate,\n formatTooltipDate,\n formatValue = defaultFormatValue,\n yAxisUnit,\n yDomain,\n referenceLines,\n topContent,\n}) => {\n const {\n showTooltip,\n hideTooltip,\n tooltipData,\n tooltipLeft,\n tooltipTop,\n tooltipOpen,\n } = useTooltip<{ date: Date }>()\n\n const innerWidth = Math.max(0, width - margin.left - margin.right)\n const innerHeight = Math.max(0, height - margin.top - margin.bottom)\n\n const allData = useMemo(() => series.flatMap((s) => s.data), [series])\n\n const xScale = useMemo(\n () =>\n scaleTime({\n range: [0, innerWidth],\n domain: extent(allData, (d) => d.date) as [Date, Date],\n }),\n [allData, innerWidth],\n )\n\n // With a unit, the y-axis is generated by the shared scale util (nice ticks\n // + rounded bounds) so it matches BarChart / PowerHistoryChart exactly.\n const yAxisScale = useMemo(() => {\n if (yAxisUnit === undefined) return null\n let min = 0\n let max = 0\n for (const d of allData) {\n if (d.value < min) min = d.value\n if (d.value > max) max = d.value\n }\n return buildYAxisScale(min, max)\n }, [allData, yAxisUnit])\n\n const yScale = useMemo(\n () =>\n scaleLinear({\n range: [innerHeight, 0],\n domain:\n yDomain ??\n (yAxisScale\n ? [yAxisScale.min, yAxisScale.max]\n : computeYDomain(allData)),\n nice: !yDomain && !yAxisScale,\n }),\n [allData, innerHeight, yDomain, yAxisScale],\n )\n\n const xTicks = xScale.ticks(xTickCount)\n const yTicks = yAxisScale ? yAxisScale.ticks : yScale.ticks(yTickCount)\n\n const handleTooltip = useCallback(\n (\n event:\n | React.TouchEvent<SVGRectElement>\n | React.MouseEvent<SVGRectElement>,\n ) => {\n if (!enableTooltip) return\n\n const point = localPoint(event)\n if (!point) return\n\n const x0 = xScale.invert(point.x - margin.left)\n\n // Snap to the data point closest to the cursor across all series, so the\n // cursor line + tooltip line up with real values.\n let anchorDate: Date | null = null\n let bestDistance = Infinity\n for (const s of series) {\n const d = nearestDatum(s.data, x0)\n if (!d) continue\n const distance = Math.abs(d.date.valueOf() - x0.valueOf())\n if (distance < bestDistance) {\n bestDistance = distance\n anchorDate = d.date\n }\n }\n\n if (!anchorDate) return\n\n showTooltip({\n tooltipData: { date: anchorDate },\n tooltipLeft: xScale(anchorDate) + margin.left,\n tooltipTop: margin.top,\n })\n },\n [enableTooltip, margin, series, showTooltip, xScale],\n )\n\n const tooltipRows: ChartTooltipRow[] = tooltipData\n ? series.flatMap((s, i) => {\n const d = nearestDatum(s.data, tooltipData.date)\n if (!d || !withinDomain(s.data, tooltipData.date)) return []\n return [\n {\n key: s.id,\n label: s.label ?? s.id,\n color: LEGEND_COLORS[i % LEGEND_COLORS.length] as string,\n value: formatValue(d.value),\n },\n ]\n })\n : []\n\n const [tooltipRef, tooltipSize] = useMeasuredSize<HTMLDivElement>()\n const tooltipPos = clampTooltipPosition({\n anchorX: tooltipLeft ?? 0,\n anchorY: tooltipTop ?? margin.top,\n width,\n height,\n tipWidth: tooltipSize.width,\n tipHeight: tooltipSize.height,\n placement: 'beside',\n })\n\n if (width < 10 || height < 10) return null\n\n return (\n <div className={cn('relative', className)}>\n <svg className=\"block\" height={height} width={width}>\n <Group left={margin.left} top={margin.top}>\n {/* Grid lines */}\n {showGrid &&\n yTicks.map((tick) => (\n <line\n className=\"stroke-slate-a4\"\n key={`grid-y-${tick}`}\n strokeDasharray=\"4,4\"\n x1={0}\n x2={innerWidth}\n y1={yScale(tick)}\n y2={yScale(tick)}\n />\n ))}\n\n {/* X axis */}\n <line\n className=\"stroke-slate-a6\"\n x1={0}\n x2={innerWidth}\n y1={innerHeight}\n y2={innerHeight}\n />\n\n {/* Reference markers (e.g. sunrise / sunset) */}\n {referenceLines?.map((ref) => {\n const x = xScale(ref.date)\n if (!Number.isFinite(x)) return null\n // Sit the label beside the line, on whichever side keeps it in\n // frame: right of a left-half marker, left of a right-half one.\n const anchorEnd = x > innerWidth / 2\n return (\n <g key={ref.key} pointerEvents=\"none\">\n <line\n className=\"stroke-orange-8\"\n strokeDasharray=\"3,3\"\n x1={x}\n x2={x}\n y1={0}\n y2={innerHeight}\n />\n {ref.label != null && (\n <text\n className=\"fill-orange-11 text-[10px]\"\n dy=\"0.9em\"\n textAnchor={anchorEnd ? 'end' : 'start'}\n x={x + (anchorEnd ? -4 : 4)}\n y={0}\n >\n {ref.label}\n </text>\n )}\n </g>\n )\n })}\n\n {/* X axis labels */}\n {xTicks.map((tick) => (\n <text\n dy=\"1em\"\n fill=\"var(--gray-a11)\"\n fontSize={10}\n key={`x-${tick.valueOf()}`}\n textAnchor=\"middle\"\n x={xScale(tick)}\n y={innerHeight + 8}\n >\n {formatDate(tick)}\n </text>\n ))}\n\n {/* Y axis. With `yAxisUnit`, matches BarChart: a vertical axis line,\n short tick marks, plain numeric ticks, and the unit as a bold\n top-left header (top tick hidden). Without it, ticks repeat\n `formatValue` and no axis line/marks are drawn. */}\n {yAxisUnit !== undefined && (\n <line\n stroke=\"var(--gray-a6)\"\n strokeWidth={1}\n x1={0}\n x2={0}\n y1={0}\n y2={innerHeight}\n />\n )}\n {yTicks.map((tick) => {\n const y = yScale(tick)\n return (\n <g key={`y-${tick}`}>\n {yAxisUnit !== undefined && (\n <line\n stroke=\"var(--gray-a8)\"\n strokeWidth={1}\n x1={-4}\n x2={0}\n y1={y}\n y2={y}\n />\n )}\n <text\n dy=\"0.32em\"\n fill=\"var(--gray-a11)\"\n fontSize={10}\n textAnchor=\"end\"\n x={yAxisUnit !== undefined ? -7 : -4}\n y={y}\n >\n {yAxisUnit !== undefined\n ? formatYAxisTick(tick)\n : formatValue(tick)}\n </text>\n </g>\n )\n })}\n {yAxisUnit !== undefined && (\n <text\n fill=\"var(--gray-a11)\"\n fontSize={10}\n fontWeight={600}\n textAnchor=\"start\"\n x={-margin.left}\n y={-6}\n >\n {yAxisUnit}\n </text>\n )}\n\n {/* Lines */}\n {series.map((s, i) => (\n <LinePath\n className={cn(LINE_COLORS[i % LINE_COLORS.length], s.className)}\n curve={resolveCurve(s.curve ?? defaultCurve)}\n data={s.data}\n fill=\"none\"\n key={s.id}\n strokeDasharray={s.dashed ? (s.dashPattern ?? '6,4') : undefined}\n strokeWidth={2}\n x={(d) => xScale(d.date)}\n y={(d) => yScale(d.value)}\n />\n ))}\n\n {/* Tooltip hover target */}\n {enableTooltip && (\n <rect\n fill=\"transparent\"\n height={innerHeight}\n onMouseLeave={hideTooltip}\n onMouseMove={handleTooltip}\n onTouchEnd={hideTooltip}\n onTouchMove={handleTooltip}\n width={innerWidth}\n x={0}\n y={0}\n />\n )}\n\n {/* Cursor line + a dot per series */}\n {tooltipOpen && tooltipData && (\n <>\n <line\n className=\"stroke-slate-a6\"\n pointerEvents=\"none\"\n strokeDasharray=\"4,4\"\n x1={xScale(tooltipData.date)}\n x2={xScale(tooltipData.date)}\n y1={0}\n y2={innerHeight}\n />\n {series.map((s, i) => {\n const d = nearestDatum(s.data, tooltipData.date)\n if (!d || !withinDomain(s.data, tooltipData.date)) return null\n return (\n <circle\n key={s.id}\n className={cn(DOT_COLORS[i % DOT_COLORS.length])}\n cx={xScale(d.date)}\n cy={yScale(d.value)}\n pointerEvents=\"none\"\n r={4}\n stroke=\"white\"\n strokeWidth={2}\n />\n )\n })}\n </>\n )}\n </Group>\n </svg>\n\n {topContent && topContent.length > 0 && (\n <div className=\"pointer-events-none absolute inset-0\">\n {topContent.map((item) => {\n const x = xScale(item.date)\n if (!Number.isFinite(x)) return null\n return (\n <div\n key={item.key}\n className=\"absolute -translate-x-1/2\"\n style={{ left: margin.left + x, top: 0 }}\n >\n {item.content}\n </div>\n )\n })}\n </div>\n )}\n\n <div\n ref={tooltipRef}\n className=\"pointer-events-none absolute z-10\"\n style={{\n left: tooltipPos.left,\n top: tooltipPos.top,\n opacity: tooltipOpen && tooltipData ? 1 : 0,\n transition:\n 'left 140ms ease-out, top 140ms ease-out, opacity 140ms ease-out',\n }}\n >\n {tooltipData && (\n <ChartTooltipCard\n label={(formatTooltipDate ?? formatDate)(tooltipData.date)}\n rows={tooltipRows}\n />\n )}\n </div>\n </div>\n )\n}\n\nexport { LineChart }\n"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const require_chunk = require("../chunk-aJGBMT86.cjs");
|
|
4
4
|
require("../utils/chars.cjs");
|
|
5
5
|
const require_utils_format = require("../utils/format.cjs");
|
|
6
|
+
const require_utils_chart_scale = require("../utils/chart-scale.cjs");
|
|
6
7
|
const require_components_chart = require("./chart.cjs");
|
|
7
8
|
const require_components_series_legend = require("./series-legend.cjs");
|
|
8
9
|
const require_components_typography = require("./typography.cjs");
|
|
@@ -41,32 +42,6 @@ const Y_AXIS_GUTTER = 34;
|
|
|
41
42
|
const X_TICK_COUNT = 6;
|
|
42
43
|
const DEFAULT_HEIGHT = 260;
|
|
43
44
|
const tBisector = (0, _visx_vendor_d3_array.bisector)((d) => d.t);
|
|
44
|
-
function niceStep(rough) {
|
|
45
|
-
const normalized = rough === 0 ? 1 : rough;
|
|
46
|
-
const magnitude = Math.pow(10, Math.floor(Math.log10(normalized)));
|
|
47
|
-
return ([
|
|
48
|
-
1,
|
|
49
|
-
2,
|
|
50
|
-
2.5,
|
|
51
|
-
5,
|
|
52
|
-
10
|
|
53
|
-
].find((value) => value * magnitude >= normalized) ?? 10) * magnitude;
|
|
54
|
-
}
|
|
55
|
-
function buildAreaYTicks(min, max) {
|
|
56
|
-
const step = niceStep((max - min || 1) / 4);
|
|
57
|
-
const lo = Math.floor(min / step) * step;
|
|
58
|
-
const hi = Math.ceil(max / step) * step;
|
|
59
|
-
const ticks = [];
|
|
60
|
-
for (let value = lo; value <= hi + step * .001; value += step) ticks.push(Number(value.toFixed(6)));
|
|
61
|
-
return {
|
|
62
|
-
ticks,
|
|
63
|
-
lo,
|
|
64
|
-
hi
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
function formatYTick(value) {
|
|
68
|
-
return Math.abs(value) >= 10 ? require_utils_format.integerFormat.format(value) : require_utils_format.oneDecimalFormat.format(value);
|
|
69
|
-
}
|
|
70
45
|
/**
|
|
71
46
|
* Diverging "power sources" area chart over a continuous time axis: solar
|
|
72
47
|
* (orange), grid (blue) and battery (green) areas around a zero baseline, with
|
|
@@ -74,10 +49,12 @@ function formatYTick(value) {
|
|
|
74
49
|
* charging) dip below zero. For instantaneous power (day view). A legend at the
|
|
75
50
|
* top toggles each series on/off; the y-axis rescales to the visible ones. visx.
|
|
76
51
|
*/
|
|
77
|
-
function PowerHistoryChart({ data, unit, formatX, formatValue, labels, highlightAt, title, className, height = DEFAULT_HEIGHT }) {
|
|
52
|
+
function PowerHistoryChart({ data, unit, formatX, formatTooltipX, formatValue, labels, omitSeries, highlightAt, title, className, height = DEFAULT_HEIGHT }) {
|
|
53
|
+
const fmtTooltipX = formatTooltipX ?? formatX;
|
|
78
54
|
const containerRef = (0, react.useRef)(null);
|
|
79
55
|
const [width, setWidth] = (0, react.useState)(0);
|
|
80
56
|
const [hiddenSeries, setHiddenSeries] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
57
|
+
const omitted = (0, react.useMemo)(() => new Set(omitSeries), [omitSeries]);
|
|
81
58
|
const toggleSeries = (0, react.useCallback)((id) => {
|
|
82
59
|
setHiddenSeries((prev) => {
|
|
83
60
|
const next = new Set(prev);
|
|
@@ -86,7 +63,7 @@ function PowerHistoryChart({ data, unit, formatX, formatValue, labels, highlight
|
|
|
86
63
|
return next;
|
|
87
64
|
});
|
|
88
65
|
}, []);
|
|
89
|
-
const isHidden = (0, react.useCallback)((key) => hiddenSeries.has(key), [hiddenSeries]);
|
|
66
|
+
const isHidden = (0, react.useCallback)((key) => omitted.has(key) || hiddenSeries.has(key), [hiddenSeries, omitted]);
|
|
90
67
|
const { showTooltip, hideTooltip, tooltipData, tooltipLeft, tooltipTop, tooltipOpen } = (0, _visx_tooltip.useTooltip)();
|
|
91
68
|
(0, react.useEffect)(() => {
|
|
92
69
|
const element = containerRef.current;
|
|
@@ -106,13 +83,13 @@ function PowerHistoryChart({ data, unit, formatX, formatValue, labels, highlight
|
|
|
106
83
|
let min = 0;
|
|
107
84
|
let max = 0;
|
|
108
85
|
for (const d of data) {
|
|
109
|
-
if (!
|
|
110
|
-
if (!
|
|
111
|
-
if (!
|
|
86
|
+
if (!isHidden("solar")) max = Math.max(max, d.solar);
|
|
87
|
+
if (!isHidden("consumption")) max = Math.max(max, d.consumption);
|
|
88
|
+
if (!isHidden("grid")) {
|
|
112
89
|
max = Math.max(max, d.grid);
|
|
113
90
|
min = Math.min(min, d.grid);
|
|
114
91
|
}
|
|
115
|
-
if (!
|
|
92
|
+
if (!isHidden("battery")) {
|
|
116
93
|
max = Math.max(max, d.battery);
|
|
117
94
|
min = Math.min(min, d.battery);
|
|
118
95
|
}
|
|
@@ -121,8 +98,8 @@ function PowerHistoryChart({ data, unit, formatX, formatValue, labels, highlight
|
|
|
121
98
|
yMin: min,
|
|
122
99
|
yMax: max
|
|
123
100
|
};
|
|
124
|
-
}, [data,
|
|
125
|
-
const { ticks: yTicks, lo, hi } = (0, react.useMemo)(() =>
|
|
101
|
+
}, [data, isHidden]);
|
|
102
|
+
const { ticks: yTicks, min: lo, max: hi } = (0, react.useMemo)(() => require_utils_chart_scale.buildYAxisScale(yMin, yMax), [yMin, yMax]);
|
|
126
103
|
const plotRight = Math.max(Y_AXIS_GUTTER, width - PADDING_RIGHT);
|
|
127
104
|
const xScale = (0, react.useMemo)(() => (0, _visx_scale.scaleLinear)({
|
|
128
105
|
domain: [tMin, tMax === tMin ? tMin + 1 : tMax],
|
|
@@ -163,7 +140,7 @@ function PowerHistoryChart({ data, unit, formatX, formatValue, labels, highlight
|
|
|
163
140
|
showTooltip,
|
|
164
141
|
xScale
|
|
165
142
|
]);
|
|
166
|
-
const legendItems = SERIES_KEYS.map((key) => ({
|
|
143
|
+
const legendItems = SERIES_KEYS.filter((key) => !omitted.has(key)).map((key) => ({
|
|
167
144
|
id: key,
|
|
168
145
|
label: labels[key],
|
|
169
146
|
color: SERIES_COLORS[key]
|
|
@@ -237,7 +214,7 @@ function PowerHistoryChart({ data, unit, formatX, formatValue, labels, highlight
|
|
|
237
214
|
textAnchor: "end",
|
|
238
215
|
fill: "var(--gray-a11)",
|
|
239
216
|
fontSize: 10,
|
|
240
|
-
children:
|
|
217
|
+
children: require_utils_chart_scale.formatYAxisTick(tick)
|
|
241
218
|
})] }, `y-tick-${tick}`);
|
|
242
219
|
}),
|
|
243
220
|
AREA_KEYS.filter((key) => !isHidden(key)).map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_shape.Area, {
|
|
@@ -344,7 +321,7 @@ function PowerHistoryChart({ data, unit, formatX, formatValue, labels, highlight
|
|
|
344
321
|
transition: "left 140ms ease-out, top 140ms ease-out, opacity 140ms ease-out"
|
|
345
322
|
},
|
|
346
323
|
children: tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_chart.ChartTooltipCard, {
|
|
347
|
-
label:
|
|
324
|
+
label: fmtTooltipX(tooltipData.t),
|
|
348
325
|
rows: tooltipRows
|
|
349
326
|
})
|
|
350
327
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"power-history-chart.cjs","names":["integerFormat","oneDecimalFormat","valueFormat","useMeasuredSize","clampTooltipPosition","SectionTitle","SeriesLegend","Area","curveMonotoneX","LinePath","ChartTooltipCard"],"sources":["../../src/components/power-history-chart.tsx"],"sourcesContent":["import { curveMonotoneX } from '@visx/curve'\nimport { localPoint } from '@visx/event'\nimport { scaleLinear } from '@visx/scale'\nimport { Area, LinePath } from '@visx/shape'\nimport { useTooltip } from '@visx/tooltip'\nimport { bisector } from '@visx/vendor/d3-array'\nimport * as React from 'react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { NARROW_NBSP } from '../utils/chars'\nimport { cn } from '../utils/cn'\nimport {\n integerFormat,\n oneDecimalFormat,\n twoDecimalFormat as valueFormat,\n} from '../utils/format'\nimport {\n ChartTooltipCard,\n type ChartTooltipRow,\n clampTooltipPosition,\n useMeasuredSize,\n} from './chart'\nimport { SeriesLegend, type SeriesLegendItem } from './series-legend'\nimport { SectionTitle } from './typography'\n\nexport interface PowerHistoryPoint {\n /** Epoch milliseconds. */\n t: number\n solar: number\n /** Signed: positive = import, negative = feed-in. */\n grid: number\n /** Signed: positive = discharge, negative = charge. */\n battery: number\n consumption: number\n}\n\nexport interface PowerHistoryChartProps {\n data: PowerHistoryPoint[]\n /** Axis/tooltip unit, e.g. \"kW\". */\n unit: string\n formatX: (t: number) => string\n formatValue?: (value: number) => string\n labels: {\n solar: string\n grid: string\n battery: string\n consumption: string\n }\n /** Epoch ms to mark with the current-time dashed line (omit to hide). */\n highlightAt?: number\n title?: string\n className?: string\n height?: number\n}\n\nconst AREA_KEYS = ['solar', 'grid', 'battery'] as const\n\n// All toggleable series, in legend order. The areas (solar/grid/battery) plus\n// the consumption overlay line.\nconst SERIES_KEYS = ['solar', 'grid', 'battery', 'consumption'] as const\ntype SeriesKey = (typeof SERIES_KEYS)[number]\n\nconst SERIES_COLORS = {\n solar: 'var(--orange-a9)',\n grid: 'var(--blue-9)',\n battery: 'var(--green-a9)',\n consumption: 'var(--gray-12)',\n} as const\n\nconst PADDING_TOP = 20\nconst PADDING_BOTTOM = 28\nconst PADDING_RIGHT = 8\nconst Y_AXIS_GUTTER = 34\nconst X_TICK_COUNT = 6\nconst DEFAULT_HEIGHT = 260\n\nconst tBisector = bisector<PowerHistoryPoint, number>((d) => d.t)\n\nfunction niceStep(rough: number) {\n const normalized = rough === 0 ? 1 : rough\n const magnitude = Math.pow(10, Math.floor(Math.log10(normalized)))\n const steps = [1, 2, 2.5, 5, 10]\n return (\n (steps.find((value) => value * magnitude >= normalized) ?? 10) * magnitude\n )\n}\n\n// Nice y ticks that always include zero, supporting negative values (feed-in /\n// battery charging dip below the baseline).\nfunction buildAreaYTicks(min: number, max: number) {\n const step = niceStep((max - min || 1) / 4)\n const lo = Math.floor(min / step) * step\n const hi = Math.ceil(max / step) * step\n const ticks: number[] = []\n for (let value = lo; value <= hi + step * 0.001; value += step) {\n ticks.push(Number(value.toFixed(6)))\n }\n return { ticks, lo, hi }\n}\n\nfunction formatYTick(value: number) {\n return Math.abs(value) >= 10\n ? integerFormat.format(value)\n : oneDecimalFormat.format(value)\n}\n\n/**\n * Diverging \"power sources\" area chart over a continuous time axis: solar\n * (orange), grid (blue) and battery (green) areas around a zero baseline, with\n * consumption overlaid as a line. Negative values (grid feed-in, battery\n * charging) dip below zero. For instantaneous power (day view). A legend at the\n * top toggles each series on/off; the y-axis rescales to the visible ones. visx.\n */\nexport function PowerHistoryChart({\n data,\n unit,\n formatX,\n formatValue,\n labels,\n highlightAt,\n title,\n className,\n height = DEFAULT_HEIGHT,\n}: PowerHistoryChartProps) {\n const containerRef = useRef<HTMLDivElement>(null)\n const [width, setWidth] = useState(0)\n const [hiddenSeries, setHiddenSeries] = useState<ReadonlySet<string>>(\n () => new Set(),\n )\n\n const toggleSeries = useCallback((id: string) => {\n setHiddenSeries((prev) => {\n const next = new Set(prev)\n if (next.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n }\n return next\n })\n }, [])\n\n const isHidden = useCallback(\n (key: SeriesKey) => hiddenSeries.has(key),\n [hiddenSeries],\n )\n\n const {\n showTooltip,\n hideTooltip,\n tooltipData,\n tooltipLeft,\n tooltipTop,\n tooltipOpen,\n } = useTooltip<PowerHistoryPoint>()\n\n useEffect(() => {\n const element = containerRef.current\n if (!element) return\n\n const resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n setWidth(entry.contentRect.width)\n })\n\n resizeObserver.observe(element)\n return () => resizeObserver.disconnect()\n }, [])\n\n const fmtValue = (value: number) =>\n formatValue\n ? formatValue(value)\n : `${valueFormat.format(value)}${NARROW_NBSP}${unit}`\n\n const tMin = data.length ? (data[0]?.t ?? 0) : 0\n const tMax = data.length ? (data[data.length - 1]?.t ?? 0) : 0\n\n const { yMin, yMax } = useMemo(() => {\n let min = 0\n let max = 0\n for (const d of data) {\n if (!hiddenSeries.has('solar')) max = Math.max(max, d.solar)\n if (!hiddenSeries.has('consumption')) max = Math.max(max, d.consumption)\n if (!hiddenSeries.has('grid')) {\n max = Math.max(max, d.grid)\n min = Math.min(min, d.grid)\n }\n if (!hiddenSeries.has('battery')) {\n max = Math.max(max, d.battery)\n min = Math.min(min, d.battery)\n }\n }\n return { yMin: min, yMax: max }\n }, [data, hiddenSeries])\n\n const {\n ticks: yTicks,\n lo,\n hi,\n } = useMemo(() => buildAreaYTicks(yMin, yMax), [yMin, yMax])\n\n const plotRight = Math.max(Y_AXIS_GUTTER, width - PADDING_RIGHT)\n\n const xScale = useMemo(\n () =>\n scaleLinear<number>({\n domain: [tMin, tMax === tMin ? tMin + 1 : tMax],\n range: [Y_AXIS_GUTTER, plotRight],\n }),\n [tMin, tMax, plotRight],\n )\n\n const yScale = useMemo(\n () =>\n scaleLinear<number>({\n domain: [lo, hi === lo ? lo + 1 : hi],\n range: [height - PADDING_BOTTOM, PADDING_TOP],\n }),\n [lo, hi, height],\n )\n\n const xTicks = useMemo(() => {\n if (tMax <= tMin) return [tMin]\n return Array.from(\n { length: X_TICK_COUNT },\n (_, i) => tMin + (i / (X_TICK_COUNT - 1)) * (tMax - tMin),\n )\n }, [tMin, tMax])\n\n const handleTooltip = useCallback(\n (\n event:\n | React.TouchEvent<SVGRectElement>\n | React.MouseEvent<SVGRectElement>,\n ) => {\n const point = localPoint(event)\n if (!point || data.length === 0) return\n\n const xMs = xScale.invert(point.x)\n const index = tBisector.left(data, xMs, 1)\n const d0 = data[index - 1]\n const d1 = data[index]\n if (!d0) return\n\n const d = d1 && xMs - d0.t > d1.t - xMs ? d1 : d0\n showTooltip({\n tooltipData: d,\n tooltipLeft: xScale(d.t),\n tooltipTop: PADDING_TOP,\n })\n },\n [data, showTooltip, xScale],\n )\n\n const legendItems: SeriesLegendItem[] = SERIES_KEYS.map((key) => ({\n id: key,\n label: labels[key],\n color: SERIES_COLORS[key],\n }))\n\n const tooltipRows: ChartTooltipRow[] = tooltipData\n ? SERIES_KEYS.filter((key) => !isHidden(key)).map((key) => ({\n key,\n label: labels[key],\n color: SERIES_COLORS[key],\n value: fmtValue(tooltipData[key]),\n }))\n : []\n\n const showHighlight =\n highlightAt !== undefined && highlightAt >= tMin && highlightAt <= tMax\n\n const [tooltipRef, tooltipSize] = useMeasuredSize<HTMLDivElement>()\n const tooltipPos = clampTooltipPosition({\n anchorX: tooltipLeft ?? 0,\n anchorY: tooltipTop ?? PADDING_TOP,\n width,\n height,\n tipWidth: tooltipSize.width,\n tipHeight: tooltipSize.height,\n placement: 'beside',\n })\n\n return (\n <div className={cn('flex w-full flex-col gap-2', className)}>\n {title && <SectionTitle>{title}</SectionTitle>}\n <SeriesLegend\n items={legendItems}\n hiddenIds={hiddenSeries}\n onToggle={toggleSeries}\n />\n <span className=\"px-1 text-xs text-gray-light\">{unit}</span>\n <div ref={containerRef} className=\"relative w-full\" style={{ height }}>\n {width > 0 && (\n <svg width={width} height={height}>\n <defs>\n {AREA_KEYS.map((key) => (\n <linearGradient\n key={key}\n id={`power-history-fill-${key}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop\n offset=\"0%\"\n stopColor={SERIES_COLORS[key]}\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor={SERIES_COLORS[key]}\n stopOpacity={0.04}\n />\n </linearGradient>\n ))}\n </defs>\n\n {yTicks.map((tick) => {\n const y = yScale(tick)\n return (\n <g key={`y-tick-${tick}`}>\n <line\n x1={Y_AXIS_GUTTER}\n x2={plotRight}\n y1={y}\n y2={y}\n stroke=\"var(--gray-a4)\"\n strokeWidth={1}\n />\n <text\n x={Y_AXIS_GUTTER - 7}\n y={y + 3}\n textAnchor=\"end\"\n fill=\"var(--gray-a11)\"\n fontSize={10}\n >\n {formatYTick(tick)}\n </text>\n </g>\n )\n })}\n\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <Area<PowerHistoryPoint>\n key={`area-${key}`}\n data={data}\n x={(d) => xScale(d.t)}\n y0={yScale(0)}\n y1={(d) => yScale(d[key])}\n curve={curveMonotoneX}\n fill={`url(#power-history-fill-${key})`}\n stroke=\"transparent\"\n />\n ))}\n\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <LinePath<PowerHistoryPoint>\n key={`line-${key}`}\n data={data}\n x={(d) => xScale(d.t)}\n y={(d) => yScale(d[key])}\n curve={curveMonotoneX}\n stroke={SERIES_COLORS[key]}\n strokeWidth={1.5}\n fill=\"none\"\n />\n ))}\n\n {!isHidden('consumption') && (\n <LinePath<PowerHistoryPoint>\n data={data}\n x={(d) => xScale(d.t)}\n y={(d) => yScale(d.consumption)}\n curve={curveMonotoneX}\n stroke={SERIES_COLORS.consumption}\n strokeWidth={1.5}\n fill=\"none\"\n />\n )}\n\n <line\n x1={Y_AXIS_GUTTER}\n x2={plotRight}\n y1={yScale(0)}\n y2={yScale(0)}\n stroke=\"var(--slate-a6)\"\n strokeWidth={1}\n />\n\n {showHighlight && (\n <line\n x1={xScale(highlightAt)}\n x2={xScale(highlightAt)}\n y1={PADDING_TOP}\n y2={height - PADDING_BOTTOM}\n stroke=\"var(--spark-a9)\"\n strokeWidth={1.5}\n strokeDasharray=\"5 4\"\n pointerEvents=\"none\"\n />\n )}\n\n {xTicks.map((tick) => (\n <text\n key={`x-tick-${tick}`}\n x={xScale(tick)}\n y={height - PADDING_BOTTOM + 16}\n textAnchor=\"middle\"\n fill=\"var(--gray-a11)\"\n fontSize={10}\n >\n {formatX(tick)}\n </text>\n ))}\n\n {tooltipOpen && tooltipData && (\n <>\n <line\n x1={xScale(tooltipData.t)}\n x2={xScale(tooltipData.t)}\n y1={PADDING_TOP}\n y2={height - PADDING_BOTTOM}\n stroke=\"var(--slate-a6)\"\n strokeDasharray=\"4 4\"\n pointerEvents=\"none\"\n />\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <circle\n key={`dot-${key}`}\n cx={xScale(tooltipData.t)}\n cy={yScale(tooltipData[key])}\n r={3.5}\n fill={SERIES_COLORS[key]}\n stroke=\"white\"\n strokeWidth={1.5}\n pointerEvents=\"none\"\n />\n ))}\n {!isHidden('consumption') && (\n <circle\n cx={xScale(tooltipData.t)}\n cy={yScale(tooltipData.consumption)}\n r={3.5}\n fill={SERIES_COLORS.consumption}\n stroke=\"white\"\n strokeWidth={1.5}\n pointerEvents=\"none\"\n />\n )}\n </>\n )}\n\n <rect\n x={Y_AXIS_GUTTER}\n y={PADDING_TOP}\n width={Math.max(0, plotRight - Y_AXIS_GUTTER)}\n height={Math.max(0, height - PADDING_BOTTOM - PADDING_TOP)}\n fill=\"transparent\"\n onMouseMove={handleTooltip}\n onMouseLeave={hideTooltip}\n onTouchMove={handleTooltip}\n onTouchEnd={hideTooltip}\n />\n </svg>\n )}\n\n <div\n ref={tooltipRef}\n className=\"pointer-events-none absolute z-10\"\n style={{\n left: tooltipPos.left,\n top: tooltipPos.top,\n opacity: tooltipOpen && tooltipData ? 1 : 0,\n transition:\n 'left 140ms ease-out, top 140ms ease-out, opacity 140ms ease-out',\n }}\n >\n {tooltipData && (\n <ChartTooltipCard\n label={formatX(tooltipData.t)}\n rows={tooltipRows}\n />\n )}\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"power-history-chart.cjs","names":["valueFormat","buildYAxisScale","useMeasuredSize","clampTooltipPosition","SectionTitle","SeriesLegend","formatYAxisTick","Area","curveMonotoneX","LinePath","ChartTooltipCard"],"sources":["../../src/components/power-history-chart.tsx"],"sourcesContent":["import { curveMonotoneX } from '@visx/curve'\nimport { localPoint } from '@visx/event'\nimport { scaleLinear } from '@visx/scale'\nimport { Area, LinePath } from '@visx/shape'\nimport { useTooltip } from '@visx/tooltip'\nimport { bisector } from '@visx/vendor/d3-array'\nimport * as React from 'react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { NARROW_NBSP } from '../utils/chars'\nimport { buildYAxisScale, formatYAxisTick } from '../utils/chart-scale'\nimport { cn } from '../utils/cn'\nimport { twoDecimalFormat as valueFormat } from '../utils/format'\nimport {\n ChartTooltipCard,\n type ChartTooltipRow,\n clampTooltipPosition,\n useMeasuredSize,\n} from './chart'\nimport { SeriesLegend, type SeriesLegendItem } from './series-legend'\nimport { SectionTitle } from './typography'\n\nexport interface PowerHistoryPoint {\n /** Epoch milliseconds. */\n t: number\n solar: number\n /** Signed: positive = import, negative = feed-in. */\n grid: number\n /** Signed: positive = discharge, negative = charge. */\n battery: number\n consumption: number\n}\n\nexport interface PowerHistoryChartProps {\n data: PowerHistoryPoint[]\n /** Axis/tooltip unit, e.g. \"kW\". */\n unit: string\n formatX: (t: number) => string\n /** Tooltip time formatter. Defaults to `formatX`; use to keep the tooltip's\n * full clock time when the axis ticks are compact (hour-only). */\n formatTooltipX?: (t: number) => string\n formatValue?: (value: number) => string\n labels: {\n solar: string\n grid: string\n battery: string\n consumption: string\n }\n /** Series to drop entirely (no legend entry, area, or tooltip row). */\n omitSeries?: readonly PowerHistorySeriesKey[]\n /** Epoch ms to mark with the current-time dashed line (omit to hide). */\n highlightAt?: number\n title?: string\n className?: string\n height?: number\n}\n\nconst AREA_KEYS = ['solar', 'grid', 'battery'] as const\n\n// All toggleable series, in legend order. The areas (solar/grid/battery) plus\n// the consumption overlay line.\nconst SERIES_KEYS = ['solar', 'grid', 'battery', 'consumption'] as const\nexport type PowerHistorySeriesKey = (typeof SERIES_KEYS)[number]\ntype SeriesKey = PowerHistorySeriesKey\n\nconst SERIES_COLORS = {\n solar: 'var(--orange-a9)',\n grid: 'var(--blue-9)',\n battery: 'var(--green-a9)',\n consumption: 'var(--gray-12)',\n} as const\n\nconst PADDING_TOP = 20\nconst PADDING_BOTTOM = 28\nconst PADDING_RIGHT = 8\nconst Y_AXIS_GUTTER = 34\nconst X_TICK_COUNT = 6\nconst DEFAULT_HEIGHT = 260\n\nconst tBisector = bisector<PowerHistoryPoint, number>((d) => d.t)\n\n/**\n * Diverging \"power sources\" area chart over a continuous time axis: solar\n * (orange), grid (blue) and battery (green) areas around a zero baseline, with\n * consumption overlaid as a line. Negative values (grid feed-in, battery\n * charging) dip below zero. For instantaneous power (day view). A legend at the\n * top toggles each series on/off; the y-axis rescales to the visible ones. visx.\n */\nexport function PowerHistoryChart({\n data,\n unit,\n formatX,\n formatTooltipX,\n formatValue,\n labels,\n omitSeries,\n highlightAt,\n title,\n className,\n height = DEFAULT_HEIGHT,\n}: PowerHistoryChartProps) {\n const fmtTooltipX = formatTooltipX ?? formatX\n const containerRef = useRef<HTMLDivElement>(null)\n const [width, setWidth] = useState(0)\n const [hiddenSeries, setHiddenSeries] = useState<ReadonlySet<string>>(\n () => new Set(),\n )\n const omitted = useMemo(() => new Set<SeriesKey>(omitSeries), [omitSeries])\n\n const toggleSeries = useCallback((id: string) => {\n setHiddenSeries((prev) => {\n const next = new Set(prev)\n if (next.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n }\n return next\n })\n }, [])\n\n const isHidden = useCallback(\n (key: SeriesKey) => omitted.has(key) || hiddenSeries.has(key),\n [hiddenSeries, omitted],\n )\n\n const {\n showTooltip,\n hideTooltip,\n tooltipData,\n tooltipLeft,\n tooltipTop,\n tooltipOpen,\n } = useTooltip<PowerHistoryPoint>()\n\n useEffect(() => {\n const element = containerRef.current\n if (!element) return\n\n const resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n setWidth(entry.contentRect.width)\n })\n\n resizeObserver.observe(element)\n return () => resizeObserver.disconnect()\n }, [])\n\n const fmtValue = (value: number) =>\n formatValue\n ? formatValue(value)\n : `${valueFormat.format(value)}${NARROW_NBSP}${unit}`\n\n const tMin = data.length ? (data[0]?.t ?? 0) : 0\n const tMax = data.length ? (data[data.length - 1]?.t ?? 0) : 0\n\n const { yMin, yMax } = useMemo(() => {\n let min = 0\n let max = 0\n for (const d of data) {\n if (!isHidden('solar')) max = Math.max(max, d.solar)\n if (!isHidden('consumption')) max = Math.max(max, d.consumption)\n if (!isHidden('grid')) {\n max = Math.max(max, d.grid)\n min = Math.min(min, d.grid)\n }\n if (!isHidden('battery')) {\n max = Math.max(max, d.battery)\n min = Math.min(min, d.battery)\n }\n }\n return { yMin: min, yMax: max }\n }, [data, isHidden])\n\n const {\n ticks: yTicks,\n min: lo,\n max: hi,\n } = useMemo(() => buildYAxisScale(yMin, yMax), [yMin, yMax])\n\n const plotRight = Math.max(Y_AXIS_GUTTER, width - PADDING_RIGHT)\n\n const xScale = useMemo(\n () =>\n scaleLinear<number>({\n domain: [tMin, tMax === tMin ? tMin + 1 : tMax],\n range: [Y_AXIS_GUTTER, plotRight],\n }),\n [tMin, tMax, plotRight],\n )\n\n const yScale = useMemo(\n () =>\n scaleLinear<number>({\n domain: [lo, hi === lo ? lo + 1 : hi],\n range: [height - PADDING_BOTTOM, PADDING_TOP],\n }),\n [lo, hi, height],\n )\n\n const xTicks = useMemo(() => {\n if (tMax <= tMin) return [tMin]\n return Array.from(\n { length: X_TICK_COUNT },\n (_, i) => tMin + (i / (X_TICK_COUNT - 1)) * (tMax - tMin),\n )\n }, [tMin, tMax])\n\n const handleTooltip = useCallback(\n (\n event:\n | React.TouchEvent<SVGRectElement>\n | React.MouseEvent<SVGRectElement>,\n ) => {\n const point = localPoint(event)\n if (!point || data.length === 0) return\n\n const xMs = xScale.invert(point.x)\n const index = tBisector.left(data, xMs, 1)\n const d0 = data[index - 1]\n const d1 = data[index]\n if (!d0) return\n\n const d = d1 && xMs - d0.t > d1.t - xMs ? d1 : d0\n showTooltip({\n tooltipData: d,\n tooltipLeft: xScale(d.t),\n tooltipTop: PADDING_TOP,\n })\n },\n [data, showTooltip, xScale],\n )\n\n const legendItems: SeriesLegendItem[] = SERIES_KEYS.filter(\n (key) => !omitted.has(key),\n ).map((key) => ({\n id: key,\n label: labels[key],\n color: SERIES_COLORS[key],\n }))\n\n const tooltipRows: ChartTooltipRow[] = tooltipData\n ? SERIES_KEYS.filter((key) => !isHidden(key)).map((key) => ({\n key,\n label: labels[key],\n color: SERIES_COLORS[key],\n value: fmtValue(tooltipData[key]),\n }))\n : []\n\n const showHighlight =\n highlightAt !== undefined && highlightAt >= tMin && highlightAt <= tMax\n\n const [tooltipRef, tooltipSize] = useMeasuredSize<HTMLDivElement>()\n const tooltipPos = clampTooltipPosition({\n anchorX: tooltipLeft ?? 0,\n anchorY: tooltipTop ?? PADDING_TOP,\n width,\n height,\n tipWidth: tooltipSize.width,\n tipHeight: tooltipSize.height,\n placement: 'beside',\n })\n\n return (\n <div className={cn('flex w-full flex-col gap-2', className)}>\n {title && <SectionTitle>{title}</SectionTitle>}\n <SeriesLegend\n items={legendItems}\n hiddenIds={hiddenSeries}\n onToggle={toggleSeries}\n />\n <span className=\"px-1 text-xs text-gray-light\">{unit}</span>\n <div ref={containerRef} className=\"relative w-full\" style={{ height }}>\n {width > 0 && (\n <svg width={width} height={height}>\n <defs>\n {AREA_KEYS.map((key) => (\n <linearGradient\n key={key}\n id={`power-history-fill-${key}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop\n offset=\"0%\"\n stopColor={SERIES_COLORS[key]}\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor={SERIES_COLORS[key]}\n stopOpacity={0.04}\n />\n </linearGradient>\n ))}\n </defs>\n\n {yTicks.map((tick) => {\n const y = yScale(tick)\n return (\n <g key={`y-tick-${tick}`}>\n <line\n x1={Y_AXIS_GUTTER}\n x2={plotRight}\n y1={y}\n y2={y}\n stroke=\"var(--gray-a4)\"\n strokeWidth={1}\n />\n <text\n x={Y_AXIS_GUTTER - 7}\n y={y + 3}\n textAnchor=\"end\"\n fill=\"var(--gray-a11)\"\n fontSize={10}\n >\n {formatYAxisTick(tick)}\n </text>\n </g>\n )\n })}\n\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <Area<PowerHistoryPoint>\n key={`area-${key}`}\n data={data}\n x={(d) => xScale(d.t)}\n y0={yScale(0)}\n y1={(d) => yScale(d[key])}\n curve={curveMonotoneX}\n fill={`url(#power-history-fill-${key})`}\n stroke=\"transparent\"\n />\n ))}\n\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <LinePath<PowerHistoryPoint>\n key={`line-${key}`}\n data={data}\n x={(d) => xScale(d.t)}\n y={(d) => yScale(d[key])}\n curve={curveMonotoneX}\n stroke={SERIES_COLORS[key]}\n strokeWidth={1.5}\n fill=\"none\"\n />\n ))}\n\n {!isHidden('consumption') && (\n <LinePath<PowerHistoryPoint>\n data={data}\n x={(d) => xScale(d.t)}\n y={(d) => yScale(d.consumption)}\n curve={curveMonotoneX}\n stroke={SERIES_COLORS.consumption}\n strokeWidth={1.5}\n fill=\"none\"\n />\n )}\n\n <line\n x1={Y_AXIS_GUTTER}\n x2={plotRight}\n y1={yScale(0)}\n y2={yScale(0)}\n stroke=\"var(--slate-a6)\"\n strokeWidth={1}\n />\n\n {showHighlight && (\n <line\n x1={xScale(highlightAt)}\n x2={xScale(highlightAt)}\n y1={PADDING_TOP}\n y2={height - PADDING_BOTTOM}\n stroke=\"var(--spark-a9)\"\n strokeWidth={1.5}\n strokeDasharray=\"5 4\"\n pointerEvents=\"none\"\n />\n )}\n\n {xTicks.map((tick) => (\n <text\n key={`x-tick-${tick}`}\n x={xScale(tick)}\n y={height - PADDING_BOTTOM + 16}\n textAnchor=\"middle\"\n fill=\"var(--gray-a11)\"\n fontSize={10}\n >\n {formatX(tick)}\n </text>\n ))}\n\n {tooltipOpen && tooltipData && (\n <>\n <line\n x1={xScale(tooltipData.t)}\n x2={xScale(tooltipData.t)}\n y1={PADDING_TOP}\n y2={height - PADDING_BOTTOM}\n stroke=\"var(--slate-a6)\"\n strokeDasharray=\"4 4\"\n pointerEvents=\"none\"\n />\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <circle\n key={`dot-${key}`}\n cx={xScale(tooltipData.t)}\n cy={yScale(tooltipData[key])}\n r={3.5}\n fill={SERIES_COLORS[key]}\n stroke=\"white\"\n strokeWidth={1.5}\n pointerEvents=\"none\"\n />\n ))}\n {!isHidden('consumption') && (\n <circle\n cx={xScale(tooltipData.t)}\n cy={yScale(tooltipData.consumption)}\n r={3.5}\n fill={SERIES_COLORS.consumption}\n stroke=\"white\"\n strokeWidth={1.5}\n pointerEvents=\"none\"\n />\n )}\n </>\n )}\n\n <rect\n x={Y_AXIS_GUTTER}\n y={PADDING_TOP}\n width={Math.max(0, plotRight - Y_AXIS_GUTTER)}\n height={Math.max(0, height - PADDING_BOTTOM - PADDING_TOP)}\n fill=\"transparent\"\n onMouseMove={handleTooltip}\n onMouseLeave={hideTooltip}\n onTouchMove={handleTooltip}\n onTouchEnd={hideTooltip}\n />\n </svg>\n )}\n\n <div\n ref={tooltipRef}\n className=\"pointer-events-none absolute z-10\"\n style={{\n left: tooltipPos.left,\n top: tooltipPos.top,\n opacity: tooltipOpen && tooltipData ? 1 : 0,\n transition:\n 'left 140ms ease-out, top 140ms ease-out, opacity 140ms ease-out',\n }}\n >\n {tooltipData && (\n <ChartTooltipCard\n label={fmtTooltipX(tooltipData.t)}\n rows={tooltipRows}\n />\n )}\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -18,6 +18,9 @@ interface PowerHistoryChartProps {
|
|
|
18
18
|
/** Axis/tooltip unit, e.g. "kW". */
|
|
19
19
|
unit: string;
|
|
20
20
|
formatX: (t: number) => string;
|
|
21
|
+
/** Tooltip time formatter. Defaults to `formatX`; use to keep the tooltip's
|
|
22
|
+
* full clock time when the axis ticks are compact (hour-only). */
|
|
23
|
+
formatTooltipX?: (t: number) => string;
|
|
21
24
|
formatValue?: (value: number) => string;
|
|
22
25
|
labels: {
|
|
23
26
|
solar: string;
|
|
@@ -25,12 +28,16 @@ interface PowerHistoryChartProps {
|
|
|
25
28
|
battery: string;
|
|
26
29
|
consumption: string;
|
|
27
30
|
};
|
|
31
|
+
/** Series to drop entirely (no legend entry, area, or tooltip row). */
|
|
32
|
+
omitSeries?: readonly PowerHistorySeriesKey[];
|
|
28
33
|
/** Epoch ms to mark with the current-time dashed line (omit to hide). */
|
|
29
34
|
highlightAt?: number;
|
|
30
35
|
title?: string;
|
|
31
36
|
className?: string;
|
|
32
37
|
height?: number;
|
|
33
38
|
}
|
|
39
|
+
declare const SERIES_KEYS: readonly ["solar", "grid", "battery", "consumption"];
|
|
40
|
+
type PowerHistorySeriesKey = (typeof SERIES_KEYS)[number];
|
|
34
41
|
/**
|
|
35
42
|
* Diverging "power sources" area chart over a continuous time axis: solar
|
|
36
43
|
* (orange), grid (blue) and battery (green) areas around a zero baseline, with
|
|
@@ -42,13 +49,15 @@ declare function PowerHistoryChart({
|
|
|
42
49
|
data,
|
|
43
50
|
unit,
|
|
44
51
|
formatX,
|
|
52
|
+
formatTooltipX,
|
|
45
53
|
formatValue,
|
|
46
54
|
labels,
|
|
55
|
+
omitSeries,
|
|
47
56
|
highlightAt,
|
|
48
57
|
title,
|
|
49
58
|
className,
|
|
50
59
|
height
|
|
51
60
|
}: PowerHistoryChartProps): _$react_jsx_runtime0.JSX.Element;
|
|
52
61
|
//#endregion
|
|
53
|
-
export { PowerHistoryChart, PowerHistoryChartProps, PowerHistoryPoint };
|
|
62
|
+
export { PowerHistoryChart, PowerHistoryChartProps, PowerHistoryPoint, PowerHistorySeriesKey };
|
|
54
63
|
//# sourceMappingURL=power-history-chart.d.cts.map
|
|
@@ -18,6 +18,9 @@ interface PowerHistoryChartProps {
|
|
|
18
18
|
/** Axis/tooltip unit, e.g. "kW". */
|
|
19
19
|
unit: string;
|
|
20
20
|
formatX: (t: number) => string;
|
|
21
|
+
/** Tooltip time formatter. Defaults to `formatX`; use to keep the tooltip's
|
|
22
|
+
* full clock time when the axis ticks are compact (hour-only). */
|
|
23
|
+
formatTooltipX?: (t: number) => string;
|
|
21
24
|
formatValue?: (value: number) => string;
|
|
22
25
|
labels: {
|
|
23
26
|
solar: string;
|
|
@@ -25,12 +28,16 @@ interface PowerHistoryChartProps {
|
|
|
25
28
|
battery: string;
|
|
26
29
|
consumption: string;
|
|
27
30
|
};
|
|
31
|
+
/** Series to drop entirely (no legend entry, area, or tooltip row). */
|
|
32
|
+
omitSeries?: readonly PowerHistorySeriesKey[];
|
|
28
33
|
/** Epoch ms to mark with the current-time dashed line (omit to hide). */
|
|
29
34
|
highlightAt?: number;
|
|
30
35
|
title?: string;
|
|
31
36
|
className?: string;
|
|
32
37
|
height?: number;
|
|
33
38
|
}
|
|
39
|
+
declare const SERIES_KEYS: readonly ["solar", "grid", "battery", "consumption"];
|
|
40
|
+
type PowerHistorySeriesKey = (typeof SERIES_KEYS)[number];
|
|
34
41
|
/**
|
|
35
42
|
* Diverging "power sources" area chart over a continuous time axis: solar
|
|
36
43
|
* (orange), grid (blue) and battery (green) areas around a zero baseline, with
|
|
@@ -42,13 +49,15 @@ declare function PowerHistoryChart({
|
|
|
42
49
|
data,
|
|
43
50
|
unit,
|
|
44
51
|
formatX,
|
|
52
|
+
formatTooltipX,
|
|
45
53
|
formatValue,
|
|
46
54
|
labels,
|
|
55
|
+
omitSeries,
|
|
47
56
|
highlightAt,
|
|
48
57
|
title,
|
|
49
58
|
className,
|
|
50
59
|
height
|
|
51
60
|
}: PowerHistoryChartProps): _$react_jsx_runtime0.JSX.Element;
|
|
52
61
|
//#endregion
|
|
53
|
-
export { PowerHistoryChart, PowerHistoryChartProps, PowerHistoryPoint };
|
|
62
|
+
export { PowerHistoryChart, PowerHistoryChartProps, PowerHistoryPoint, PowerHistorySeriesKey };
|
|
54
63
|
//# sourceMappingURL=power-history-chart.d.ts.map
|