Optimizer chart: day boundaries with weekday at 00:00 (#32894)

This commit is contained in:
andig 2026-08-16 19:35:44 +02:00 • committed by GitHub
parent d22b67fd18
commit ba8596be96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 54 additions and 5 deletions

View file

@ -146,7 +146,7 @@ export default defineComponent({
...tooltipStyle(colors.text || ""),
formatter: this.tooltipFormatter,
},
xAxis: slotXAxis(this.times),
xAxis: slotXAxis(this.times, this.weekdayShort),
yAxis: forecastYAxis({
min: undefined,
position: "right",

View file

@ -0,0 +1,35 @@
import { describe, expect, test } from "vite-plus/test";
import { isMidnight, slotXAxis } from "./chart";
// 15min slots starting at the given local hour
function times(startHour: number, count: number): number[] {
const start = new Date(2025, 0, 1, startHour).getTime();
return Array.from({ length: count }, (_, i) => start + i * 15 * 60 * 1000);
}
describe("slotXAxis", () => {
const weekdayShort = () => "Thu";
// 20:00 to 08:00, midnight at index 16
const slots = times(20, 48);
const axis = slotXAxis(slots, weekdayShort);
const label = (i: number) => axis.axisLabel.formatter(String(slots[i]));
test("labels full hours only", () => {
expect(label(0)).toBe("20"); // 20:00
expect(label(1)).toBe(""); // 20:15
expect(label(4)).toBe(""); // 21:00, not a step hour
expect(label(32)).toBe("4"); // 04:00
});
test("shows the weekday at midnight", () => {
expect(label(16)).toBe("0\nThu");
});
test("draws a split line at day boundaries only", () => {
expect(isMidnight(slots[16])).toBe(true);
expect(axis.splitLine.interval(16)).toBe(true);
expect(axis.splitLine.interval(15)).toBe(false);
// no line at the axis start, even if the first slot is midnight
expect(slotXAxis(times(0, 4), weekdayShort).splitLine.interval(0)).toBe(false);
});
});

View file

@ -1,4 +1,5 @@
import { xAxisLabelStyle } from "../Forecast/echarts";
import colors from "@/colors";
import type { BatteryDetail } from "@/types/evcc";
// loadpoint part of a vehicle entry title: "Carport (blue e-Golf)" → "Carport"
@ -19,22 +20,35 @@ export function slotTimes(timestamp: string, dt: number[]): number[] {
return res;
}
// category x axis over slot times, labels at full hours every 4h (6h on mobile)
export function slotXAxis(times: number[]) {
export function isMidnight(time?: number): boolean {
if (time === undefined) return false;
const d = new Date(time);
return d.getHours() === 0 && d.getMinutes() === 0;
}
// category x axis over slot times, labels at full hours every 4h (6h on mobile),
// day boundaries as split line with the weekday shown at 00:00
export function slotXAxis(times: number[], weekdayShort: (d: Date) => string) {
const step = window.innerWidth < 576 ? 6 : 4;
return {
type: "category",
data: times.map(String),
axisLine: { show: false },
axisTick: { show: false },
splitLine: { show: false },
splitLine: {
show: true,
// split lines sit at the left edge of their slot
interval: (index: number) => index > 0 && isMidnight(times[index]),
lineStyle: { color: colors.muted || "", type: "solid" },
},
axisLabel: {
...xAxisLabelStyle(),
interval: 0,
formatter: (value: string) => {
const d = new Date(Number(value));
if (d.getMinutes() !== 0 || d.getHours() % step !== 0) return "";
return String(d.getHours());
const label = String(d.getHours());
return d.getHours() === 0 ? `${label}\n${weekdayShort(d)}` : label;
},
},
};