diff --git a/packages/stats/app/src/routes/index.css b/packages/stats/app/src/routes/index.css index 2b8acb39029..fc88132d251 100644 --- a/packages/stats/app/src/routes/index.css +++ b/packages/stats/app/src/routes/index.css @@ -2195,6 +2195,7 @@ body { [data-page="stats"] [data-component="market-share"] { --market-gap: 12px; + position: relative; display: grid; grid-template-rows: auto minmax(0, 1fr); gap: 12px; @@ -2360,6 +2361,106 @@ body { font-weight: 500; } +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] { + top: 52px; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 0; + width: 228px; + min-width: 228px; + padding: 0; + border: 0; + background: #fffffff2; + box-shadow: + 0 0 0 0.5px #00000024, + 0 8px 16px #0000000f, + 0 4px 8px #00000014; + color: var(--stats-text); +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"][data-placement="right"] { + right: auto; + left: calc(var(--market-tooltip-left) + 8px); +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"][data-placement="left"] { + right: calc(var(--market-tooltip-right) + 8px); + left: auto; +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] strong, +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] > span { + display: block; + font-size: 11px; + line-height: 12px; + white-space: nowrap; +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] strong { + padding: 8px 8px 0; + font-weight: 500; +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] > span { + padding: 4px 8px 8px; + color: var(--stats-muted); +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] [data-slot="tooltip-divider"] { + height: 0.5px; + margin: 0; +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] p { + grid-template-columns: minmax(0, 1fr) auto auto; + gap: 8px; + height: 16px; + margin: 4px 0 0; + padding: 0 8px; + font-size: 11px; + font-weight: 500; + line-height: 12px; +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] p[data-muted="true"] { + opacity: 0.46; +} + +[data-page="stats"] + [data-component="market-share"] + > [data-component="chart-tooltip"] + [data-slot="tooltip-divider"] + + p { + margin-top: 8px; +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] p:last-child { + margin-bottom: 8px; +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] [data-slot="tooltip-label"] { + grid-template-columns: 16px minmax(0, 1fr); + gap: 4px; +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] i { + width: 6px; + height: 6px; + justify-self: center; +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] em, +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] b { + font-style: normal; + font-weight: 500; + white-space: nowrap; +} + +[data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] em { + color: var(--stats-muted); +} + [data-page="stats"] [data-slot="market-footer"] { display: flex; align-items: center; @@ -7208,6 +7309,11 @@ body { [data-page="stats"][data-theme="dark"] :is([data-section="top-models"], [data-section="unique-users"]) [data-component="chart-tooltip"], +:root[data-stats-theme="dark"] + [data-page="stats"]:not([data-theme="light"]) + [data-component="market-share"] + > [data-component="chart-tooltip"], +[data-page="stats"][data-theme="dark"] [data-component="market-share"] > [data-component="chart-tooltip"], :root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) :is([data-section="top-models"], [data-section="unique-users"]) @@ -8560,6 +8666,25 @@ body { transform: none; } + [data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"] { + position: fixed; + top: auto; + right: 12px; + bottom: 12px; + left: 12px; + z-index: 40; + width: auto; + min-width: 0; + max-height: min(320px, 48vh); + overflow: auto; + transform: none; + } + + [data-page="stats"] [data-component="market-share"] > [data-component="chart-tooltip"][data-placement] { + right: 12px; + left: 12px; + } + [data-page="stats"] :is([data-section="top-models"], [data-section="unique-users"]) [data-component="chart-tooltip"][data-placement] { diff --git a/packages/stats/app/src/routes/index.tsx b/packages/stats/app/src/routes/index.tsx index 5a08aa011ae..a24f803e1e3 100644 --- a/packages/stats/app/src/routes/index.tsx +++ b/packages/stats/app/src/routes/index.tsx @@ -928,7 +928,7 @@ function MarketShareSection(props: { data: MarketDay[] }) { const [inspecting, setInspecting] = createSignal(false) const authorOrder = createMemo(() => getMarketAuthorOrder(props.data)) const selectedIndex = createMemo(() => Math.min(activeIndex(), Math.max(props.data.length - 1, 0))) - const activeDay = createMemo(() => props.data[selectedIndex()]) + const today = createMemo(() => props.data[props.data.length - 1]) return (
} > {(day) => ( @@ -963,19 +963,14 @@ function MarketShareSection(props: { data: MarketDay[] }) { setActiveIndex(index) setInspecting(true) }} - onActiveAuthorChange={(author) => { - setActiveAuthor(author) - setInspecting(true) - }} + onActiveAuthorChange={setActiveAuthor} + onInspectingChange={setInspecting} /> { - setActiveAuthor(author) - setInspecting(true) - }} + onActiveAuthorChange={setActiveAuthor} /> )} @@ -983,11 +978,7 @@ function MarketShareSection(props: { data: MarketDay[] }) {

[*] - - {inspecting() - ? formatMarketDate(activeDay(), i18n.t("home.noData")) - : formatMarketRange(props.data, i18n.t("home.noData"))} - + {formatMarketDate(today(), i18n.t("home.noData"))}

@@ -1002,10 +993,15 @@ function MarketShare(props: { activeAuthor: string | undefined inspecting: boolean onActiveIndexChange: (index: number) => void - onActiveAuthorChange: (author: string) => void + onActiveAuthorChange: (author: string | undefined) => void + onInspectingChange: (inspecting: boolean) => void }) { const i18n = useI18n() let chartRef: HTMLDivElement | undefined + const inspectDay = (index: number) => { + props.onActiveIndexChange(index) + props.onActiveAuthorChange(undefined) + } createEffect(() => scrollDenseChartToEnd(chartRef, props.range, props.data.length)) @@ -1018,6 +1014,10 @@ function MarketShare(props: { role="img" aria-label={i18n.t("home.marketChart")} style={{ "--market-count": props.data.length } as JSX.CSSProperties} + onPointerLeave={(event) => { + if (event.pointerType === "touch") return + props.onInspectingChange(false) + }} >
@@ -1028,8 +1028,11 @@ function MarketShare(props: { data-active={props.inspecting && props.activeIndex === index() ? "true" : undefined} data-label-hidden={isColumnLabelHidden(index(), props.data.length) ? "true" : undefined} data-mobile-hidden={isMarketMobileLabelHidden(index(), props.data.length) ? "true" : undefined} - onClick={() => props.onActiveIndexChange(index())} - onPointerEnter={() => props.onActiveIndexChange(index())} + aria-describedby={props.inspecting && props.activeIndex === index() ? "market-share-tooltip" : undefined} + onBlur={() => props.onInspectingChange(false)} + onClick={() => inspectDay(index())} + onFocus={() => inspectDay(index())} + onPointerEnter={() => inspectDay(index())} > {formatTrillions(day.total)} @@ -1049,8 +1052,11 @@ function MarketShare(props: { type="button" aria-label={`${day.date} ${formatTrillions(day.total)}`} data-active={props.inspecting && props.activeIndex === index() ? "true" : undefined} - onClick={() => props.onActiveIndexChange(index())} - onPointerEnter={() => props.onActiveIndexChange(index())} + aria-describedby={props.inspecting && props.activeIndex === index() ? "market-share-tooltip" : undefined} + onBlur={() => props.onInspectingChange(false)} + onClick={() => inspectDay(index())} + onFocus={() => inspectDay(index())} + onPointerEnter={() => inspectDay(index())} > {(item) => ( @@ -1090,6 +1096,45 @@ function MarketShare(props: { )}
+ + {(day) => ( +
props.data.length * 0.62 ? "left" : "right"} + role="tooltip" + style={ + { + "--market-tooltip-left": `${((props.activeIndex + 0.5) / props.data.length) * 100}%`, + "--market-tooltip-right": `${100 - ((props.activeIndex + 0.5) / props.data.length) * 100}%`, + } as JSX.CSSProperties + } + > + {day.date} + + {formatTrillions(day.total)} {i18n.t("home.total")} + +
+ + {(item, index) => ( +

+ + + {item.author} + + {formatTrillions(item.tokens)} + {item.share.toFixed(1)}% +

+ )} +
+
+ )} +
) } @@ -1248,6 +1293,10 @@ function getMarketSegmentColor(author: string, color: string, activeAuthor: stri return "var(--stats-bar-idle)" } +function rankedMarketAuthors(day: MarketDay) { + return day.authors.toSorted((a, b) => b.tokens - a.tokens || a.author.localeCompare(b.author)) +} + function stackedMarketAuthors(day: MarketDay, order: Map) { return day.authors .map((author, index) => ({ author, index })) @@ -1302,16 +1351,6 @@ function formatMarketDate(day: MarketDay | undefined, fallback: string) { return formatMarketDateLabel(day.date) } -function formatMarketRange(data: MarketDay[], fallback: string) { - const first = data[0]?.date - const last = data[data.length - 1]?.date - if (!first || !last) return fallback - 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()