From fb6275e04a6456f300d9033a2824feb142e283c0 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Fri, 29 May 2026 07:27:30 -0500 Subject: [PATCH] fix(stats): improve mobile charts --- packages/stats/app/src/routes/index.css | 72 ++++++++++++++++++++----- packages/stats/app/src/routes/index.tsx | 51 ++++++++++++++---- 2 files changed, 100 insertions(+), 23 deletions(-) diff --git a/packages/stats/app/src/routes/index.css b/packages/stats/app/src/routes/index.css index 17fbda83ab..5aea25fbb5 100644 --- a/packages/stats/app/src/routes/index.css +++ b/packages/stats/app/src/routes/index.css @@ -1719,6 +1719,7 @@ [data-page="stats"] [data-slot="leader-copy"] strong { color: var(--stats-text); font-weight: 600; + line-height: 1.3; } [data-page="stats"] [data-slot="leader-copy"] span { @@ -1799,6 +1800,14 @@ font-weight: 600; } +[data-page="stats"] [data-slot="market-axis-label"] { + display: grid; +} + +[data-page="stats"] [data-slot="market-date-mobile"] { + display: none; +} + [data-page="stats"] [data-slot="market-bars"] { flex: 1; min-height: 0; @@ -1977,23 +1986,21 @@ } [data-page="stats"] [data-component="metric-bar"] { - display: flex; - align-items: center; - gap: 3px; + position: relative; + display: block; flex: 1; min-width: 0; height: 5px; + background: var(--stats-layer-2); font-style: normal; } -[data-page="stats"] [data-component="metric-bar"] b, -[data-page="stats"] [data-component="metric-bar"] em { - display: block; - height: 5px; -} - [data-page="stats"] [data-component="metric-bar"] b { - flex-basis: 0; + position: absolute; + inset: 0 auto 0 0; + display: block; + width: var(--metric-bar-fill); + height: 5px; background: var(--stats-text); } @@ -2002,12 +2009,10 @@ } [data-page="stats"] [data-component="metric-bar"] em { - flex: 1; - min-width: 8px; - background: var(--stats-layer-2); + display: none; } -[data-page="stats"] [data-component="metric-bar"][data-active="true"] em { +[data-page="stats"] [data-component="metric-bar"][data-active="true"] { background: var(--stats-line-strong); } @@ -2311,6 +2316,13 @@ grid-template-rows: none; } + [data-page="stats"] [data-component="token-tooltip"] { + position: static; + order: -1; + width: 100%; + margin-bottom: 8px; + } + [data-page="stats"] [data-slot="market-footer"] { align-items: flex-start; flex-direction: column; @@ -2376,6 +2388,11 @@ height: 400px; } + [data-page="stats"] [data-component="market-share"] { + grid-template-rows: 40px minmax(0, 1fr); + height: 400px; + } + [data-page="stats"] [data-slot="top-models-axis"] > div { position: relative; display: flex; @@ -2385,6 +2402,14 @@ font-weight: 600; } + [data-page="stats"] [data-slot="market-labels"] button { + position: relative; + align-items: center; + justify-content: center; + height: 40px; + line-height: 1.2; + } + [data-page="stats"] [data-slot="axis-label"] { position: absolute; left: 50%; @@ -2394,16 +2419,35 @@ transform-origin: left center; } + [data-page="stats"] [data-slot="market-axis-label"] { + position: absolute; + left: 50%; + width: max-content; + max-width: 72px; + transform: rotate(-90deg) translateX(-50%); + transform-origin: left center; + } + [data-page="stats"] [data-slot="top-models-axis"] > div[data-mobile-hidden="true"] [data-slot="axis-label"], [data-page="stats"] [data-slot="axis-total"], [data-page="stats"] [data-slot="axis-date-full"] { display: none; } + [data-page="stats"] [data-slot="market-labels"] button[data-mobile-hidden="true"] [data-slot="market-axis-label"], + [data-page="stats"] [data-slot="market-total"], + [data-page="stats"] [data-slot="market-date-full"] { + display: none; + } + [data-page="stats"] [data-slot="axis-date-mobile"] { display: block; } + [data-page="stats"] [data-slot="market-date-mobile"] { + display: block; + } + [data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] { position: fixed; top: auto; diff --git a/packages/stats/app/src/routes/index.tsx b/packages/stats/app/src/routes/index.tsx index 5ee4f5f2ec..b2ad4a82a6 100644 --- a/packages/stats/app/src/routes/index.tsx +++ b/packages/stats/app/src/routes/index.tsx @@ -864,11 +864,17 @@ function MarketShare(props: { )} @@ -964,21 +970,43 @@ function getMarketSegmentColor(author: string, color: string, activeAuthor: stri return "var(--stats-bar-idle)" } +function isMarketMobileLabelHidden(index: number, count: number) { + return count > 7 && index % 2 === 1 +} + +function formatMarketMobileDate(label: string) { + return marketDateParts(label).start +} + function formatTrillions(value: number) { return `${value.toFixed(value >= 10 ? 0 : 1)}T` } function formatMarketDate(day: MarketDay | undefined) { if (!day) return "No data" - return `${day.date} ${new Date().getFullYear()}` + return formatMarketDateLabel(day.date) } function formatMarketRange(data: MarketDay[]) { const first = data[0]?.date const last = data[data.length - 1]?.date if (!first || !last) return "No data" + const start = marketDateParts(first).start + const end = marketDateParts(last).end + if (start === end) return formatMarketDateLabel(start) + return `${start} ${new Date().getFullYear()} → ${end} ${new Date().getFullYear()}` +} + +function formatMarketDateLabel(label: string) { + const parts = marketDateParts(label) const year = new Date().getFullYear() - return `${first} ${year} → ${last} ${year}` + if (parts.start === parts.end) return `${parts.start} ${year}` + return `${parts.start} ${year} → ${parts.end} ${year}` +} + +function marketDateParts(label: string) { + const [start, end] = label.split(" - ") + return { start: start ?? label, end: end ?? start ?? label } } function TokenCostSection(props: { data: StatsHomeData["tokenCost"] }) { @@ -1019,7 +1047,7 @@ function TokenCostChart(props: { activeIndex: number onActiveIndexChange: (index: number) => void }) { - const max = createMemo(() => Math.max(1, ...props.data.map((item) => item.total))) + const max = createMemo(() => Math.max(0, ...props.data.map((item) => item.total)) || 1) const active = createMemo(() => props.data[props.activeIndex] ?? props.data[0]) return ( @@ -1066,9 +1094,14 @@ function formatDollars(value: number) { } function MetricBar(props: { value: number; max: number; active: boolean }) { + const fill = createMemo(() => Math.min(1, Math.max(props.value / props.max, props.value > 0 ? 0.03 : 0))) return ( - - + + ) @@ -1115,8 +1148,8 @@ function SessionCostChart(props: { activeIndex: number onActiveIndexChange: (index: number) => void }) { - const maxCost = createMemo(() => Math.max(1, ...props.data.map((item) => item.cost))) - const maxTokens = createMemo(() => Math.max(1, ...props.data.map((item) => item.tokens))) + const maxCost = createMemo(() => Math.max(0, ...props.data.map((item) => item.cost)) || 1) + const maxTokens = createMemo(() => Math.max(0, ...props.data.map((item) => item.tokens)) || 1) const active = createMemo(() => props.data[props.activeIndex] ?? props.data[0]) return (