fix(usage): improve responsive token trend axis
This commit is contained in:
1 parent
286f4bbe1b
commit
820ad4d2c4
4 files changed
+82
-8
No files matched your search
@@ -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(() => {
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user