From f55b70b4e33cc43fbb64add9fbdd26abfeea1e21 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 28 May 2026 14:03:30 -0500 Subject: [PATCH] feat(stats): refine top models chart scaling --- packages/stats/app/src/routes/index.css | 401 ++++++++++++++++++++++- packages/stats/app/src/routes/index.tsx | 409 +++++++++++++++--------- packages/stats/core/src/domain/home.ts | 51 ++- 3 files changed, 687 insertions(+), 174 deletions(-) diff --git a/packages/stats/app/src/routes/index.css b/packages/stats/app/src/routes/index.css index 868f40e4c2..7bf7ce3a78 100644 --- a/packages/stats/app/src/routes/index.css +++ b/packages/stats/app/src/routes/index.css @@ -407,6 +407,9 @@ [data-page="stats"] [data-section="chart"], [data-page="stats"] [data-section="newsletter"] { border-bottom: 1px solid var(--stats-line); + box-shadow: + inset 1px 0 var(--stats-line), + inset -1px 0 var(--stats-line); padding: var(--stats-section-padding) var(--stats-page-padding); } @@ -590,6 +593,208 @@ min-height: 0; } +[data-page="stats"] [data-section="top-models"] { + box-sizing: border-box; + margin-top: 1px; + box-shadow: + 0 -1px var(--stats-line), + inset 0 -1px var(--stats-line), + inset 1px 0 var(--stats-line), + inset -1px 0 var(--stats-line); + padding: 80px 40px; + color: var(--stats-text); +} + +[data-page="stats"] [data-slot="top-models-title"] { + max-width: 1200px; + margin-bottom: 40px; + color: var(--stats-muted); + font-size: 28px; + font-weight: 400; + line-height: 1.5; + letter-spacing: 0; +} + +[data-page="stats"] [data-slot="top-models-title"] strong { + color: var(--stats-text); + font-weight: 500; +} + +[data-page="stats"] [data-slot="top-models-title"] span { + color: var(--stats-muted); + font-weight: 400; +} + +[data-page="stats"] [data-slot="top-models-mobile-controls"] { + display: none; +} + +[data-page="stats"] [data-component="top-models-chart"] { + position: relative; + display: grid; + grid-template-rows: 34px minmax(0, 1fr); + gap: 12px; + width: 100%; + height: 560px; +} + +[data-page="stats"] [data-slot="top-models-axis"], +[data-page="stats"] [data-slot="top-models-bars"] { + display: flex; + gap: 12px; + min-width: 0; +} + +[data-page="stats"] [data-slot="top-models-axis"] > div { + flex: 1 1 0; + min-width: 0; + color: var(--stats-faint); + font-size: 11px; + font-weight: 500; + line-height: 1.5; +} + +[data-page="stats"] [data-slot="top-models-axis"] > div[data-active="true"] { + color: var(--stats-text); + font-weight: 500; +} + +[data-page="stats"] [data-slot="axis-label"] { + display: flex; + flex-direction: column; +} + +[data-page="stats"] [data-slot="top-models-bars"] { + min-height: 0; +} + +[data-page="stats"] [data-slot="top-models-bar"] { + position: relative; + flex: 1 1 0; + min-width: 0; + height: 100%; + outline: none; + cursor: pointer; +} + +[data-page="stats"] [data-slot="top-models-bar"]::before { + position: absolute; + inset: 0; + content: ""; + background: var(--stats-dot); + mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E"); + mask-repeat: repeat; + mask-size: 6px 6px; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E"); + -webkit-mask-repeat: repeat; + -webkit-mask-size: 6px 6px; +} + +[data-page="stats"] [data-slot="top-models-stack"] { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + display: grid; + height: var(--top-models-bar-height); + min-height: 0; + overflow: hidden; + background: var(--stats-bg); +} + +[data-page="stats"] [data-slot="top-models-stack"] i { + box-sizing: border-box; + display: block; + min-height: 0; + transition: background 120ms ease; +} + +[data-page="stats"] [data-slot="top-models-stack"] i + i { + border-top: 2px solid var(--stats-bg); +} + +[data-page="stats"] [data-slot="mobile-filter-button"] { + display: inline-flex; + flex: 1 1 0; + align-items: center; + justify-content: center; + gap: 6px; + height: 32px; + min-width: 0; + overflow: hidden; + padding: 0 12px; + color: var(--stats-text); + background: var(--stats-bg); + border: 0; + border-radius: 0; + box-shadow: + 0 0 0 0 #00000024, + 0 0 0 0.5px #00000024, + 0 1px 1.5px 0 #0000001a; + font-size: 13px; + font-weight: 600; + line-height: 1.1; +} + +[data-page="stats"] [data-slot="mobile-filter-button"] svg { + flex: 0 0 auto; + color: var(--stats-muted); +} + +[data-page="stats"] [data-component="mobile-filter-sheet"] { + position: fixed; + inset: 0; + z-index: 30; + display: flex; + align-items: flex-end; + padding: 0 8px 8px; + background: #00000066; + backdrop-filter: blur(3px); +} + +[data-page="stats"] [data-slot="filter-sheet-panel"] { + width: 100%; + background: var(--stats-bg); + box-shadow: 0 8px 24px #00000026; +} + +[data-page="stats"] [data-slot="filter-sheet-panel"] button { + position: relative; + display: flex; + align-items: center; + width: 100%; + min-height: 44px; + padding: 0 42px; + color: var(--stats-faint); + background: transparent; + border: 0; + border-radius: 0; + border-bottom: 1px solid var(--stats-line); + font-size: 13px; + font-weight: 600; + line-height: 1.1; + text-align: left; +} + +[data-page="stats"] [data-slot="filter-sheet-panel"] button:last-child { + border-bottom: 0; +} + +[data-page="stats"] [data-slot="filter-sheet-panel"] button[data-active="true"] { + color: var(--stats-text); + background: transparent; +} + +[data-page="stats"] [data-slot="filter-sheet-panel"] button[data-active="true"]::before { + position: absolute; + left: 20px; + width: 6px; + height: 6px; + content: ""; + background: currentColor; +} + [data-page="stats"] [data-slot="section-header"] { display: flex; justify-content: space-between; @@ -636,6 +841,25 @@ background: var(--stats-text); } +[data-page="stats"] button[data-slot="mobile-filter-button"] { + padding: 0 12px; + color: var(--stats-text); + background: var(--stats-bg); + border-radius: 0; +} + +[data-page="stats"] [data-slot="filter-sheet-panel"] button { + padding: 0 42px; + color: var(--stats-faint); + background: transparent; + border-radius: 0; +} + +[data-page="stats"] [data-slot="filter-sheet-panel"] button[data-active="true"] { + color: var(--stats-text); + background: transparent; +} + [data-page="stats"] [data-component="usage-chart"], [data-page="stats"] [data-component="country-map"] { position: relative; @@ -710,6 +934,10 @@ margin-top: 32px; } +[data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] { + margin-top: 40px; +} + [data-page="stats"] [data-component="usage-filter"] { display: flex; align-items: center; @@ -720,44 +948,51 @@ } [data-page="stats"] [data-component="usage-filter"][data-variant="range"] { - gap: 4px; + gap: 6px; } [data-page="stats"] [data-component="usage-filter"] button { color: var(--stats-faint); background: transparent; - border: 1px solid transparent; + border: 0; border-radius: 0; padding: 0; font-size: 13px; + font-weight: 500; line-height: 17px; } +[data-page="stats"] [data-component="usage-filter"] button:hover { + color: var(--stats-text); +} + [data-page="stats"] [data-component="usage-filter"] button[data-active="true"] { color: var(--stats-text); background: transparent; + font-weight: 500; } [data-page="stats"] [data-component="usage-filter"][data-variant="product"] button[data-active="true"] { display: flex; align-items: center; - gap: 8px; + gap: 6px; } [data-page="stats"] [data-component="usage-filter"][data-variant="product"] button[data-active="true"]::before { content: ""; - width: 8px; - height: 8px; - background: var(--stats-text); + width: 16px; + height: 16px; + background: linear-gradient(var(--stats-text), var(--stats-text)) center / 6px 6px no-repeat; } [data-page="stats"] [data-component="usage-filter"][data-variant="range"] button { - min-width: 34px; - padding: 3px 7px; + height: 24px; + width: 36px; + padding: 0; } [data-page="stats"] [data-component="usage-filter"][data-variant="range"] button[data-active="true"] { - border-color: var(--stats-line-strong); + background: var(--stats-layer-2); } [data-page="stats"] [data-component="chart-tooltip"], @@ -821,6 +1056,91 @@ color: var(--stats-text); } +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] { + top: 110px; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 0; + width: 192px; + min-width: 192px; + padding: 0; + background: #fffffff2; + border: 0; + box-shadow: + 0 0 0 0.5px #00000024, + 0 8px 16px #0000000f, + 0 4px 8px #00000014; + color: var(--stats-text); +} + +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"][data-placement="right"] { + right: auto; + left: calc(100% + 8px); +} + +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"][data-placement="left"] { + right: calc(100% + 8px); + left: auto; +} + +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] strong, +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] > span { + display: block; + font-size: 11px; + line-height: 12px; + white-space: nowrap; +} + +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] strong { + padding: 8px 8px 0; + font-weight: 500; +} + +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] > span { + padding: 4px 8px 8px; + color: var(--stats-muted); +} + +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] [data-slot="tooltip-divider"] { + height: 0.5px; + margin: 0; +} + +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] p { + grid-template-columns: minmax(0, 1fr) auto; + gap: 4px; + height: 16px; + margin: 4px 0 0; + padding: 0 8px; + font-size: 11px; + font-weight: 500; + line-height: 12px; +} + +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] [data-slot="tooltip-divider"] + p { + margin-top: 8px; +} + +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] p:last-child { + margin-bottom: 8px; +} + +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] [data-slot="tooltip-label"] { + grid-template-columns: 16px minmax(0, 1fr); + gap: 4px; +} + +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] i { + width: 6px; + height: 6px; + justify-self: center; +} + +[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] b { + font-weight: 500; +} + [data-page="stats"] [data-component="leaderboard"] { display: block; } @@ -1547,6 +1867,12 @@ } } +@media (max-width: 74rem) { + [data-page="stats"] [data-section="top-models"] { + padding: 64px 32px; + } +} + @media (max-width: 58rem) { [data-page="stats"] { --stats-page-padding: 24px; @@ -1584,6 +1910,15 @@ flex-wrap: wrap; } + [data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] { + align-items: center; + flex-direction: row; + } + + [data-page="stats"] [data-section="top-models"] [data-component="usage-filter"] { + flex-wrap: nowrap; + } + [data-page="stats"] [data-slot="leaderboard-grid"], [data-page="stats"] [data-slot="leaderboard-compact"] { grid-template-columns: 1fr; @@ -1653,6 +1988,54 @@ } } +@media (max-width: 47.999rem) { + [data-page="stats"] [data-section="top-models"] { + padding: 48px 24px; + } + + [data-page="stats"] [data-slot="top-models-title"] { + margin-bottom: 32px; + font-size: 16px; + } + + [data-page="stats"] [data-slot="top-models-mobile-controls"] { + display: flex; + gap: 8px; + margin-bottom: 32px; + } + + [data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] { + display: none; + } + + [data-page="stats"] [data-component="top-models-chart"] { + grid-template-rows: 40px minmax(0, 1fr); + height: 400px; + } + + [data-page="stats"] [data-slot="top-models-axis"] > div { + position: relative; + display: flex; + align-items: center; + justify-content: center; + height: 40px; + font-weight: 600; + } + + [data-page="stats"] [data-slot="axis-label"] { + position: absolute; + left: 50%; + width: max-content; + max-width: 96px; + transform: rotate(-90deg) translateX(-50%); + transform-origin: left center; + } + + [data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] { + display: none; + } +} + @media (max-width: 40rem) { [data-page="stats"] [data-component="footer"], [data-page="stats"] [data-component="legal"] { diff --git a/packages/stats/app/src/routes/index.tsx b/packages/stats/app/src/routes/index.tsx index 31d4decec9..829895c291 100644 --- a/packages/stats/app/src/routes/index.tsx +++ b/packages/stats/app/src/routes/index.tsx @@ -17,13 +17,19 @@ import { } from "@opencode-ai/stats-core/domain/home" import { runtime } from "@opencode-ai/stats-core/runtime" import { createAsync, query } from "@solidjs/router" -import { scaleBand, scaleLinear } from "d3-scale" import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show, type JSX } from "solid-js" import { getRequestEvent } from "solid-js/web" -const products = ["All Users", "Zen", "Go", "Enterprise"] as const -const tokenProducts = ["Zen", "Go", "Enterprise"] as const -const ranges = ["1D", "1W", "1M", "3M", "YTD", "ALL"] as const +const products = ["All Users", "Zen", "Go"] as const +const tokenProducts = ["Zen", "Go"] as const +const ranges = ["1D", "1W", "2W", "1M", "2M"] as const +const rangeLabels: Record = { + "1D": "1 Day", + "1W": "1 Week", + "2W": "2 Weeks", + "1M": "1 Month", + "2M": "2 Months", +} const headerLinks = [ { href: "#top-models", label: "Top Models" }, { href: "#leaderboard", label: "Leaderboard" }, @@ -31,7 +37,19 @@ const headerLinks = [ { href: "#token-cost", label: "Token Cost" }, { href: "#session-cost", label: "Session Cost" }, ] as const -const usageColors = ["#ff5d64", "#ff8a00", "#8bef00", "#12c8b3", "#18c7dc", "#6c7dff", "#9d73f7"] +const usageColors = [ + "#ed6aff", + "#a684ff", + "#7c86ff", + "#51a2ff", + "#00d3f2", + "#00d5be", + "#00bc7d", + "#9ae600", + "#ffb900", + "#ff8904", + "#ff6467", +] const marketColors = ["#ed6aff", "#a684ff", "#7c86ff", "#51a2ff", "#00d3f2", "#00d5be", "#00bc7d", "#9ae600", "#ffb900"] const countryPositions = [ { x: 112, y: 96 }, @@ -79,7 +97,7 @@ export default function StatsHome() { {(stats) => ( <> - + @@ -180,23 +198,154 @@ function formatUpdatedAt(value: string, timeZone: string) { }).format(date) } -function UsageSection(props: { data: StatsHomeData["usage"] }) { +function TopModelsSection(props: { data: StatsHomeData["usage"] }) { const [product, setProduct] = createSignal("All Users") const [range, setRange] = createSignal("1W") + const [sheet, setSheet] = createSignal<"product" | "range">() const data = createMemo(() => props.data[product()][range()]) + createEffect(() => { + if (!sheet()) return + if (typeof document === "undefined") return + const htmlOverflow = document.documentElement.style.overflow + const bodyOverflow = document.body.style.overflow + document.documentElement.style.overflow = "hidden" + document.body.style.overflow = "hidden" + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") setSheet(undefined) + } + document.addEventListener("keydown", onKeyDown) + onCleanup(() => { + document.documentElement.style.overflow = htmlOverflow + document.body.style.overflow = bodyOverflow + document.removeEventListener("keydown", onKeyDown) + }) + }) + return ( - +
+

+ Top models. Usage of models across OpenCode. +

+
+ setSheet(sheet() === "product" ? undefined : "product")} + /> + setSheet(sheet() === "range" ? undefined : "range")} + /> +
usageTotal(item) > 0)} fallback={} > - +
- + + {(kind) => ( + { + setProduct(value) + setSheet(undefined) + }} + onRangeSelect={(value) => { + setRange(value) + setSheet(undefined) + }} + onClose={() => setSheet(undefined)} + /> + )} + +
+ ) +} + +function MobileFilterButton(props: { label: string; value: string; expanded: boolean; onClick: () => void }) { + return ( + + ) +} + +function MobileFilterSheet(props: { + kind: "product" | "range" + product: UsageProduct + range: UsageRange + onProductSelect: (product: UsageProduct) => void + onRangeSelect: (range: UsageRange) => void + onClose: () => void +}) { + return ( +
+
+ + {(item) => ( + + )} + + } + > + + {(item) => ( + + )} + + +
+
+ ) +} + +function ChevronDown() { + return ( + ) } @@ -252,158 +401,118 @@ function FilterPills(props: { ) } -function UsageChart(props: { data: UsagePoint[] }) { +function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) { const [activeIndex, setActiveIndex] = createSignal() - const [activeSegment, setActiveSegment] = createSignal() - const height = 434 - const width = 920 - const headerOffset = 46 - const segmentGap = 2 - const maxTotal = createMemo(() => Math.max(1, Math.max(...props.data.map((item) => usageTotal(item))) * 1.02)) + const maxTotal = createMemo(() => getTopModelsMaxTotal(props.data)) const activePoint = createMemo(() => props.data[activeIndex() ?? -1]) - const y = createMemo(() => scaleLinear([0, maxTotal()], [height, 0])) - const x = createMemo(() => - scaleBand( - props.data.map((_, index) => String(index)), - [0, width], - ).paddingInner(0.08), - ) - const activeBar = createMemo(() => { - const index = activeIndex() - const point = activePoint() - if (index === undefined) return - if (!point) return - return { - point, - x: x()(String(index)) ?? 0, - width: x().bandwidth(), - } - }) return ( -
- - - - - - +
+