fix(usage): improve responsive token trend axis

This commit is contained in:
lzt404 committed 2026-09-23 02:47:28 +08:00
1 parent 286f4bbe1b
commit 820ad4d2c4
4 files changed
+82 -8

No files matched your search

+23 -6
View File
@@ -1,5 +1,5 @@
import { useConfirmation } from '../components/ConfirmationDialog';
import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent, type PointerEvent as ReactPointerEvent } from 'react';
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type KeyboardEvent, type PointerEvent as ReactPointerEvent } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
import {
@@ -1144,7 +1144,7 @@ function UsageTrend({
const [hoveredRatio, setHoveredRatio] = useState<number | null>(null);
const [hiddenModels, setHiddenModels] = useState<string[]>([]);
const plotRef = useRef<HTMLDivElement>(null);
const [plotWidth, setPlotWidth] = useState(800);
const [plotWidth, setPlotWidth] = useState(0);
const series = useMemo(
() => buildUsageTrendSeries(points, range),
@@ -1164,12 +1164,29 @@ function UsageTrend({
const count = series.points.length;
useEffect(() => {
useLayoutEffect(() => {
const plot = plotRef.current;
if (!plot) return;
const observer = new ResizeObserver(([entry]) => setPlotWidth(entry.contentRect.width));
observer.observe(plot);
return () => observer.disconnect();
let frame = 0;
const updateWidth = (width: number) => {
cancelAnimationFrame(frame);
frame = requestAnimationFrame(() => {
const next = Math.max(0, Math.round(width));
setPlotWidth((current) => current === next ? current : next);
});
};
const measure = () => updateWidth(plot.getBoundingClientRect().width);
measure();
const observer = typeof ResizeObserver === 'undefined'
? null
: new ResizeObserver(([entry]) => updateWidth(entry.contentRect.width));
observer?.observe(plot);
window.addEventListener('resize', measure);
return () => {
cancelAnimationFrame(frame);
observer?.disconnect();
window.removeEventListener('resize', measure);
};
}, [count > 0]);
useEffect(() => {
+30 -1
View File
@@ -265,7 +265,36 @@ export function trendTimeAxisTicks(start: Date, end: Date, width: number, labelW
if (span <= 0) return [start];
// Choose a readable time interval from the selected duration and available space.
const minStep = span * labelWidth / Math.max(labelWidth, width);
const steps = [1 / 60, 5 / 60, 15 / 60, 0.5, 1, 2, 3, 6, 12, 24, 48, 72, 168, 336, 720, 2160, 4380, 8760];
const steps = [
1 / 60,
2 / 60,
5 / 60,
10 / 60,
15 / 60,
20 / 60,
0.5,
0.75,
1,
2,
3,
4,
6,
8,
12,
24,
48,
72,
96,
168,
336,
720,
1440,
2160,
2880,
4380,
8760,
17520,
];
const step = steps.map((hours) => hours * HOUR_MS).find((value) => value >= minStep)
?? niceCeiling(minStep / (8760 * HOUR_MS)) * 8760 * HOUR_MS;
const ticks = [start];
+18 -1
View File
@@ -68,7 +68,24 @@ const base = 'http://127.0.0.1:1421';
return axis.getBoundingClientRect().bottom <= layout.getBoundingClientRect().bottom + 1;
}), 'The complete X axis is reachable by scrolling the overview');
console.log('PASS: 1213px single row stat cards and constrained wrapped overview scrolling passed.');
const narrowAxis = await page.evaluate(() => ({
width: document.querySelector('.usage-trend-plot')?.getBoundingClientRect().width ?? 0,
ticks: document.querySelectorAll('.usage-trend-x-axis span').length,
}));
await page.setViewportSize({ width: 1500, height: 600 });
await page.waitForFunction((previous) => {
const plot = document.querySelector('.usage-trend-plot');
const ticks = document.querySelectorAll('.usage-trend-x-axis span').length;
return !!plot && plot.getBoundingClientRect().width > previous.width && ticks > previous.ticks;
}, narrowAxis);
const wideAxis = await page.evaluate(() => ({
width: document.querySelector('.usage-trend-plot')?.getBoundingClientRect().width ?? 0,
ticks: document.querySelectorAll('.usage-trend-x-axis span').length,
}));
assert.ok(wideAxis.width > narrowAxis.width, 'The trend plot follows the wider window');
assert.ok(wideAxis.ticks > narrowAxis.ticks, 'The X axis adds readable ticks when more width is available');
console.log('PASS: usage overview layout and responsive trend axis passed.');
} finally {
await browser.close();
}
+11
View File
@@ -254,6 +254,17 @@ describe('usage trend helpers', () => {
expect(months.every((date) => date.getDate() === 1)).toBe(true);
});
test('increases time tick density progressively as the chart stretches', () => {
const start = new Date(2026, 8, 14, 0, 0);
const end = new Date(2026, 8, 15, 0, 0);
const widths = [320, 480, 640, 800, 1000, 1400];
const counts = widths.map((width) => trendTimeAxisTicks(start, end, width, 112).length);
expect(counts.every((count, index) => index === 0 || count >= counts[index - 1])).toBe(true);
expect(new Set(counts).size).toBeGreaterThanOrEqual(4);
expect(counts.at(-1)).toBeGreaterThan(counts[0]);
});
test('stacks model tokens and groups overflow models', () => {
const start = new Date(2026, 8, 14, 10, 0, 0);
const end = new Date(2026, 8, 14, 12, 0, 0);