fix(panel): charts get real axes, humanized ticks, and dark-theme tooltips (#611)

Every chart hand-rolled its own k-only formatter (22596k-style ticks, left-
clipped y labels), used recharts' default white tooltip (invisible header on
the dark theme), and two charts pinned a numeric XAxis interval that collapses
short series to a single tick. The agent Token Activity chart had no axes at
all and blanked its tooltip date on purpose.

One shared formatTokens/formatBucket (lib/format.ts) and one shared themed
tooltip style (components/charts/chart-tooltip.tsx) now feed all 10 charts;
axis widths/margins sized to the labels; preserveStartEnd tick intervals.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-20 20:29:45 +02:00
committed by GitHub
co-authored by Renn F
parent 3c5ee46347
commit 7248e5b722
15 changed files with 200 additions and 105 deletions
@@ -15,6 +15,8 @@ import { Skeleton } from "@/components/ui/skeleton";
import { SegmentedControl } from "@/components/ui/segmented-control";
import { HelpTip } from "@/components/ui/help-tip";
import { useIsMobile } from "@/hooks/use-is-mobile";
import { formatTokens } from "@/lib/format";
import { chartTooltipStyle } from "@/components/charts/chart-tooltip";
import type { AgentUsageRow } from "@/types";
interface AgentUsageChartProps {
@@ -22,11 +24,6 @@ interface AgentUsageChartProps {
isLoading: boolean;
}
function fmtK(n: number): string {
if (n >= 1_000) return (n / 1_000).toFixed(0) + "k";
return String(n);
}
const VIEW_OPTIONS = [
{ value: "chart", label: "Chart" },
{ value: "table", label: "Table" },
@@ -101,7 +98,7 @@ export function AgentUsageChart({ data, isLoading }: AgentUsageChartProps) {
<ResponsiveContainer width="100%" height={208}>
<BarChart
data={chartData}
margin={{ top: 4, right: 8, left: 0, bottom: 24 }}
margin={{ top: 4, right: 8, left: 0, bottom: isMobile ? 40 : 32 }}
>
<CartesianGrid strokeDasharray="3 3" className="opacity-20" />
<XAxis
@@ -114,18 +111,18 @@ export function AgentUsageChart({ data, isLoading }: AgentUsageChartProps) {
tickLine={false}
/>
<YAxis
tickFormatter={fmtK}
tickFormatter={formatTokens}
tick={{ fontSize: 10 }}
axisLine={false}
tickLine={false}
width={36}
width={46}
/>
<Tooltip
{...chartTooltipStyle}
formatter={(value) => [
fmtK(typeof value === "number" ? value : 0),
formatTokens(typeof value === "number" ? value : 0),
"Tokens",
]}
contentStyle={{ fontSize: 12 }}
/>
<Bar
dataKey="Tokens"
@@ -25,6 +25,7 @@ import {
useRework,
useTeamScorecard,
} from "@/hooks/use-observability";
import { chartTooltipStyle } from "@/components/charts/chart-tooltip";
import type { Scorecard } from "@/types";
const CELLS = ["backend", "frontend", "ux_ui"] as const;
@@ -91,8 +92,8 @@ function CycleTimeCard() {
tickFormatter={(v) => v + "h"}
/>
<Tooltip
{...chartTooltipStyle}
formatter={(value) => [value + "h", "Avg"]}
contentStyle={{ fontSize: 12 }}
/>
<Bar
dataKey="Hours"
@@ -12,6 +12,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { HelpTip } from "@/components/ui/help-tip";
import { useIsMobile } from "@/hooks/use-is-mobile";
import { chartTooltipStyle } from "@/components/charts/chart-tooltip";
import type { ModelUsageSlice } from "@/types";
// Design-system chart tokens — resolves to theme-aware palette
@@ -71,12 +72,12 @@ export function ModelUsageDonut({ data, isLoading }: ModelUsageDonutProps) {
))}
</Pie>
<Tooltip
{...chartTooltipStyle}
formatter={(value, name) => [
(typeof value === "number" ? value : 0).toLocaleString() +
" tokens",
name,
]}
contentStyle={{ fontSize: 12 }}
/>
<Legend wrapperStyle={{ fontSize: isMobile ? 9 : 11 }} />
</PieChart>
+19 -15
View File
@@ -21,6 +21,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { HelpTip } from "@/components/ui/help-tip";
import { ChevronUp, ChevronDown } from "lucide-react";
import { formatTokens } from "@/lib/format";
import type { UsageSession } from "@/types";
const PAGE_SIZE = 10;
@@ -46,8 +47,16 @@ interface Column {
}
const COLUMNS: Column[] = [
{ key: "agent_slug", label: "Agent", tip: "The agent slug that ran this session — click to sort" },
{ key: "model", label: "Model", tip: "Claude/Grok model used for this session — click to sort" },
{
key: "agent_slug",
label: "Agent",
tip: "The agent slug that ran this session — click to sort",
},
{
key: "model",
label: "Model",
tip: "Claude/Grok model used for this session — click to sort",
},
{
key: "started_at",
label: "Started",
@@ -87,11 +96,6 @@ function formatTime(ts: string): string {
});
}
function fmtK(n: number): string {
if (n >= 1_000) return (n / 1_000).toFixed(1) + "k";
return String(n);
}
interface SessionsTableProps {
data: UsageSession[] | undefined;
isLoading: boolean;
@@ -198,16 +202,16 @@ export function SessionsTable({ data, isLoading }: SessionsTableProps) {
{formatTime(s.started_at)}
</TableCell>
<TableCell className="text-xs">
{fmtK(s.total_tokens)}
{formatTokens(s.total_tokens)}
</TableCell>
<TableCell className="text-xs">
{fmtK(s.tokens_input)}
{formatTokens(s.tokens_input)}
</TableCell>
<TableCell className="text-xs">
{fmtK(s.tokens_output)}
{formatTokens(s.tokens_output)}
</TableCell>
<TableCell className="text-xs">
{fmtK(s.tokens_cache)}
{formatTokens(s.tokens_cache)}
</TableCell>
<TableCell className="text-xs">
${s.cost.toFixed(4)}
@@ -241,16 +245,16 @@ export function SessionsTable({ data, isLoading }: SessionsTableProps) {
{formatTime(s.started_at)}
</ResponsiveTableCardRow>
<ResponsiveTableCardRow label="Total">
{fmtK(s.total_tokens)}
{formatTokens(s.total_tokens)}
</ResponsiveTableCardRow>
<ResponsiveTableCardRow label="Input">
{fmtK(s.tokens_input)}
{formatTokens(s.tokens_input)}
</ResponsiveTableCardRow>
<ResponsiveTableCardRow label="Output">
{fmtK(s.tokens_output)}
{formatTokens(s.tokens_output)}
</ResponsiveTableCardRow>
<ResponsiveTableCardRow label="Cache">
{fmtK(s.tokens_cache)}
{formatTokens(s.tokens_cache)}
</ResponsiveTableCardRow>
<ResponsiveTableCardRow label="Cost">
${s.cost.toFixed(4)}
@@ -11,6 +11,7 @@ import {
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { HelpTip } from "@/components/ui/help-tip";
import { chartTooltipStyle } from "@/components/charts/chart-tooltip";
interface TaskStatusSlice {
name: string;
@@ -73,11 +74,11 @@ export function TaskStatusChart({ slices, isLoading }: TaskStatusChartProps) {
))}
</Pie>
<Tooltip
{...chartTooltipStyle}
formatter={(value, name) => [
`${typeof value === "number" ? value : 0} tasks`,
name,
]}
contentStyle={{ fontSize: 12 }}
/>
<Legend wrapperStyle={{ fontSize: 11 }} />
</PieChart>
@@ -86,4 +87,4 @@ export function TaskStatusChart({ slices, isLoading }: TaskStatusChartProps) {
</CardContent>
</Card>
);
}
}
@@ -15,6 +15,8 @@ import { Skeleton } from "@/components/ui/skeleton";
import { SegmentedControl } from "@/components/ui/segmented-control";
import { HelpTip } from "@/components/ui/help-tip";
import { useIsMobile } from "@/hooks/use-is-mobile";
import { formatTokens } from "@/lib/format";
import { chartTooltipStyle } from "@/components/charts/chart-tooltip";
import type { TeamUsageRow } from "@/types";
interface TeamUsageChartProps {
@@ -22,11 +24,6 @@ interface TeamUsageChartProps {
isLoading: boolean;
}
function fmtK(n: number): string {
if (n >= 1_000) return (n / 1_000).toFixed(0) + "k";
return String(n);
}
const VIEW_OPTIONS = [
{ value: "chart", label: "Chart" },
{ value: "table", label: "Table" },
@@ -104,7 +101,7 @@ export function TeamUsageChart({ data, isLoading }: TeamUsageChartProps) {
top: 4,
right: 8,
left: 0,
bottom: isMobile ? 24 : 8,
bottom: isMobile ? 40 : 8,
}}
>
<CartesianGrid strokeDasharray="3 3" className="opacity-20" />
@@ -118,18 +115,18 @@ export function TeamUsageChart({ data, isLoading }: TeamUsageChartProps) {
tickLine={false}
/>
<YAxis
tickFormatter={fmtK}
tickFormatter={formatTokens}
tick={{ fontSize: 10 }}
axisLine={false}
tickLine={false}
width={36}
width={46}
/>
<Tooltip
{...chartTooltipStyle}
formatter={(value) => [
fmtK(typeof value === "number" ? value : 0),
formatTokens(typeof value === "number" ? value : 0),
"Tokens",
]}
contentStyle={{ fontSize: 12 }}
/>
<Bar
dataKey="Tokens"
@@ -14,6 +14,8 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { HelpTip } from "@/components/ui/help-tip";
import { useIsMobile } from "@/hooks/use-is-mobile";
import { formatTokens, formatBucket } from "@/lib/format";
import { chartTooltipStyle } from "@/components/charts/chart-tooltip";
import type { UsageTimePoint } from "@/types";
interface UsageTimeSeriesChartProps {
@@ -21,23 +23,6 @@ interface UsageTimeSeriesChartProps {
isLoading: boolean;
}
function formatBucket(bucket: string): string {
const d = new Date(bucket);
// If the bucket has a non-zero time component it is an hourly bucket → show HH:00.
// Otherwise it is a daily bucket → show MM/DD.
const isHourly =
d.getMinutes() === 0 && (d.getHours() !== 0 || bucket.includes("T"));
if (isHourly && d.getSeconds() === 0 && !bucket.endsWith("T00:00:00.000Z")) {
return d.getHours().toString().padStart(2, "0") + ":00";
}
return d.getMonth() + 1 + "/" + d.getDate();
}
function fmtK(n: number): string {
if (n >= 1_000) return (n / 1_000).toFixed(0) + "k";
return String(n);
}
export function UsageTimeSeriesChart({
data,
isLoading,
@@ -99,23 +84,23 @@ export function UsageTimeSeriesChart({
<XAxis
dataKey="hour"
tick={{ fontSize: isMobile ? 9 : 10 }}
interval={isMobile ? 5 : 3}
interval="preserveStartEnd"
axisLine={false}
tickLine={false}
/>
<YAxis
tickFormatter={fmtK}
tickFormatter={formatTokens}
tick={{ fontSize: 10 }}
axisLine={false}
tickLine={false}
width={36}
width={46}
/>
<Tooltip
{...chartTooltipStyle}
formatter={(value, name) => [
fmtK(typeof value === "number" ? value : 0),
formatTokens(typeof value === "number" ? value : 0),
name,
]}
contentStyle={{ fontSize: 12 }}
/>
<Legend wrapperStyle={{ fontSize: isMobile ? 10 : 12 }} />
<Area