mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-28 13:33:35 +08:00
feat(stats): improve market share chart (#47115)
This commit is contained in:
@@ -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] {
|
||||
|
||||
@@ -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 (
|
||||
<section
|
||||
@@ -947,7 +947,7 @@ function MarketShareSection(props: { data: MarketDay[] }) {
|
||||
description={i18n.t("home.marketShareDescription")}
|
||||
/>
|
||||
<Show
|
||||
when={activeDay()}
|
||||
when={today()}
|
||||
fallback={<EmptyState title={i18n.t("home.noMarketTitle")} description={i18n.t("home.noMarketDescription")} />}
|
||||
>
|
||||
{(day) => (
|
||||
@@ -963,19 +963,14 @@ function MarketShareSection(props: { data: MarketDay[] }) {
|
||||
setActiveIndex(index)
|
||||
setInspecting(true)
|
||||
}}
|
||||
onActiveAuthorChange={(author) => {
|
||||
setActiveAuthor(author)
|
||||
setInspecting(true)
|
||||
}}
|
||||
onActiveAuthorChange={setActiveAuthor}
|
||||
onInspectingChange={setInspecting}
|
||||
/>
|
||||
<MarketShareList
|
||||
data={day().authors}
|
||||
data={rankedMarketAuthors(day())}
|
||||
authorOrder={authorOrder()}
|
||||
activeAuthor={activeAuthor()}
|
||||
onActiveAuthorChange={(author) => {
|
||||
setActiveAuthor(author)
|
||||
setInspecting(true)
|
||||
}}
|
||||
onActiveAuthorChange={setActiveAuthor}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -983,11 +978,7 @@ function MarketShareSection(props: { data: MarketDay[] }) {
|
||||
<div data-slot="market-footer">
|
||||
<p>
|
||||
<span>[*]</span>
|
||||
<strong>
|
||||
{inspecting()
|
||||
? formatMarketDate(activeDay(), i18n.t("home.noData"))
|
||||
: formatMarketRange(props.data, i18n.t("home.noData"))}
|
||||
</strong>
|
||||
<strong>{formatMarketDate(today(), i18n.t("home.noData"))}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -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)
|
||||
}}
|
||||
>
|
||||
<div data-slot="market-labels">
|
||||
<For each={props.data}>
|
||||
@@ -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())}
|
||||
>
|
||||
<span data-slot="market-axis-label">
|
||||
<span data-slot="market-total">{formatTrillions(day.total)}</span>
|
||||
@@ -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())}
|
||||
>
|
||||
<For each={stackedMarketAuthors(day, props.authorOrder)}>
|
||||
{(item) => (
|
||||
@@ -1090,6 +1096,45 @@ function MarketShare(props: {
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<Show when={props.inspecting ? props.data[props.activeIndex] : undefined} keyed>
|
||||
{(day) => (
|
||||
<div
|
||||
id="market-share-tooltip"
|
||||
data-component="chart-tooltip"
|
||||
data-placement={props.activeIndex > 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
|
||||
}
|
||||
>
|
||||
<strong>{day.date}</strong>
|
||||
<span>
|
||||
{formatTrillions(day.total)} {i18n.t("home.total")}
|
||||
</span>
|
||||
<div data-slot="tooltip-divider" />
|
||||
<For each={rankedMarketAuthors(day)}>
|
||||
{(item, index) => (
|
||||
<p
|
||||
data-active={props.activeAuthor === item.author ? "true" : undefined}
|
||||
data-muted={
|
||||
props.activeAuthor !== undefined && props.activeAuthor !== item.author ? "true" : undefined
|
||||
}
|
||||
>
|
||||
<span data-slot="tooltip-label">
|
||||
<i style={{ background: getRankColor(item.author, index(), props.authorOrder, marketColors) }} />
|
||||
<span data-slot="tooltip-name">{item.author}</span>
|
||||
</span>
|
||||
<em>{formatTrillions(item.tokens)}</em>
|
||||
<b>{item.share.toFixed(1)}%</b>
|
||||
</p>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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<string, number>) {
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user