Sfoglia il codice sorgente

fix(stats): stabilize stacked chart bands

Adam 2 mesi fa
parent
commit
9854c5d910
1 ha cambiato i file con 68 aggiunte e 15 eliminazioni
  1. 68 15
      packages/stats/app/src/routes/index.tsx

+ 68 - 15
packages/stats/app/src/routes/index.tsx

@@ -598,6 +598,7 @@ function TopModelsChart(props: {
 }) {
   const [activeIndex, setActiveIndex] = createSignal<number>()
   const maxTotal = createMemo(() => getTopModelsMaxTotal(props.data))
+  const segmentOrder = createMemo(() => getTopModelsSegmentOrder(props.data))
   const activePoint = createMemo(() => props.data[activeIndex() ?? -1])
 
   return (
@@ -671,8 +672,11 @@ function TopModelsChart(props: {
                 props.onActiveModelChange(undefined)
               }}
             >
-              <div data-slot="top-models-stack" style={{ "grid-template-rows": getTopModelsSegmentRows(day) }}>
-                <For each={stackedTopModelsSegments(day)}>
+              <div
+                data-slot="top-models-stack"
+                style={{ "grid-template-rows": getTopModelsSegmentRows(day, segmentOrder()) }}
+              >
+                <For each={stackedTopModelsSegments(day, segmentOrder())}>
                   {(item) => (
                     <i
                       data-series={item.index}
@@ -682,6 +686,7 @@ function TopModelsChart(props: {
                         background: getTopModelsSegmentColor(
                           item.segment.model,
                           item.index,
+                          segmentOrder(),
                           activeIndex() !== undefined && activeIndex() !== dayIndex(),
                           props.activeModel,
                         ),
@@ -725,7 +730,10 @@ function TopModelsChart(props: {
                           }
                         >
                           <span data-slot="tooltip-label">
-                            <i style={{ background: usageColors[item.index] }} /> {item.segment.model}
+                            <i
+                              style={{ background: getRankColor(item.segment.model, item.index, segmentOrder(), usageColors) }}
+                            />{" "}
+                            {item.segment.model}
                           </span>
                           <b>{formatTokens(item.segment.value)}</b>
                         </p>
@@ -754,10 +762,10 @@ function getTopModelsMaxTotal(data: UsagePoint[]) {
   return max
 }
 
-function getTopModelsSegmentRows(point: UsagePoint) {
+function getTopModelsSegmentRows(point: UsagePoint, order: Map<string, number>) {
   const total = usageTotal(point)
   if (total <= 0) return ""
-  return stackedTopModelsSegments(point)
+  return stackedTopModelsSegments(point, order)
     .map((item) => `${(item.segment.value / total) * 100}%`)
     .join(" ")
 }
@@ -766,17 +774,29 @@ function visibleTopModelsSegments(point: UsagePoint) {
   return point.segments.map((segment, index) => ({ segment, index })).filter((item) => item.segment.value > 0)
 }
 
-function stackedTopModelsSegments(point: UsagePoint) {
+function stackedTopModelsSegments(point: UsagePoint, order: Map<string, number>) {
   return visibleTopModelsSegments(point)
     .slice()
-    .sort((a, b) => a.segment.value - b.segment.value || a.index - b.index)
+    .sort((a, b) => (order.get(b.segment.model) ?? b.index) - (order.get(a.segment.model) ?? a.index))
+}
+
+function getTopModelsSegmentOrder(data: UsagePoint[]) {
+  return getRankOrder(
+    data.flatMap((point) => point.segments.map((segment, index) => ({ key: segment.model, value: segment.value, index }))),
+  )
 }
 
-function getTopModelsSegmentColor(model: string, index: number, muted: boolean, activeModel: string | undefined) {
+function getTopModelsSegmentColor(
+  model: string,
+  index: number,
+  order: Map<string, number>,
+  muted: boolean,
+  activeModel: string | undefined,
+) {
   if (activeModel !== undefined)
-    return activeModel === model ? (usageColors[index] ?? "var(--stats-text)") : "var(--stats-layer-2)"
+    return activeModel === model ? getRankColor(model, index, order, usageColors) : "var(--stats-layer-2)"
   if (muted) return "var(--stats-layer-2)"
-  return usageColors[index] ?? "var(--stats-text)"
+  return getRankColor(model, index, order, usageColors)
 }
 
 function isTopModelsMobileAxisHidden(index: number, count: number) {
@@ -895,6 +915,7 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) {
   const [activeAuthor, setActiveAuthor] = createSignal<string>()
   const [inspecting, setInspecting] = createSignal(false)
   const data = createMemo(() => props.data[range()])
+  const authorOrder = createMemo(() => getMarketAuthorOrder(data()))
   const selectedIndex = createMemo(() => Math.min(activeIndex(), Math.max(data().length - 1, 0)))
   const activeDay = createMemo(() => data()[selectedIndex()])
 
@@ -919,6 +940,7 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) {
             <MarketShare
               data={data()}
               range={range()}
+              authorOrder={authorOrder()}
               activeIndex={selectedIndex()}
               activeAuthor={activeAuthor()}
               inspecting={inspecting()}
@@ -933,6 +955,7 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) {
             />
             <MarketShareList
               data={day().authors}
+              authorOrder={authorOrder()}
               activeAuthor={activeAuthor()}
               onActiveAuthorChange={(author) => {
                 setActiveAuthor(author)
@@ -966,6 +989,7 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) {
 function MarketShare(props: {
   data: MarketDay[]
   range: UsageRange
+  authorOrder: Map<string, number>
   activeIndex: number
   activeAuthor: string | undefined
   inspecting: boolean
@@ -1013,9 +1037,10 @@ function MarketShare(props: {
               onClick={() => props.onActiveIndexChange(index())}
               onPointerEnter={() => props.onActiveIndexChange(index())}
             >
-              <For each={stackedMarketAuthors(day)}>
+              <For each={stackedMarketAuthors(day, props.authorOrder)}>
                 {(item) => (
                   <span
+                    data-author={item.author.author}
                     data-active={props.activeAuthor === item.author.author ? "true" : undefined}
                     data-muted={
                       props.activeAuthor !== undefined && props.activeAuthor !== item.author.author ? "true" : undefined
@@ -1023,7 +1048,7 @@ function MarketShare(props: {
                     style={{
                       "background-color": getMarketSegmentColor(
                         item.author.author,
-                        marketColors[item.index] ?? "var(--stats-text)",
+                        getRankColor(item.author.author, item.index, props.authorOrder, marketColors),
                         props.activeAuthor,
                       ),
                       "flex-grow": item.author.share,
@@ -1056,6 +1081,7 @@ function MarketShare(props: {
 
 function MarketShareList(props: {
   data: MarketDay["authors"]
+  authorOrder: Map<string, number>
   activeAuthor: string | undefined
   onActiveAuthorChange: (author: string) => void
 }) {
@@ -1077,7 +1103,7 @@ function MarketShareList(props: {
             }}
           >
             <span>{String(index() + 1).padStart(2, "0")}</span>
-            <i style={{ background: marketColors[index()] }} />
+            <i style={{ background: getRankColor(item.author, index(), props.authorOrder, marketColors) }} />
             <strong>{item.author}</strong>
             <em>{formatTrillions(item.tokens)}</em>
             <b>{item.share.toFixed(1)}%</b>
@@ -1094,11 +1120,38 @@ function getMarketSegmentColor(author: string, color: string, activeAuthor: stri
   return "var(--stats-bar-idle)"
 }
 
-function stackedMarketAuthors(day: MarketDay) {
+function stackedMarketAuthors(day: MarketDay, order: Map<string, number>) {
   return day.authors
     .map((author, index) => ({ author, index }))
     .slice()
-    .sort((a, b) => a.author.share - b.author.share || a.index - b.index)
+    .sort((a, b) => (order.get(b.author.author) ?? b.index) - (order.get(a.author.author) ?? a.index))
+}
+
+function getMarketAuthorOrder(data: MarketDay[]) {
+  return getRankOrder(
+    data.flatMap((day) => day.authors.map((author, index) => ({ key: author.author, value: author.tokens, index }))),
+  )
+}
+
+function getRankOrder(items: { key: string; value: number; index: number }[]) {
+  return new Map<string, number>(
+    Object.values(
+      items.reduce<Record<string, { key: string; value: number; index: number }>>((result, item) => {
+        result[item.key] = {
+          key: item.key,
+          value: (result[item.key]?.value ?? 0) + item.value,
+          index: Math.min(result[item.key]?.index ?? item.index, item.index),
+        }
+        return result
+      }, {}),
+    )
+      .toSorted((a, b) => b.value - a.value || a.index - b.index || a.key.localeCompare(b.key))
+      .map((item, index) => [item.key, index] as const),
+  )
+}
+
+function getRankColor(key: string, fallbackIndex: number, order: Map<string, number>, colors: readonly string[]) {
+  return colors[order.get(key) ?? fallbackIndex] ?? "var(--stats-text)"
 }
 
 function isMarketMobileLabelHidden(index: number, count: number) {