Explorar el Código

fix(stats): improve mobile charts

Adam hace 3 meses
padre
commit
fb6275e04a
Se han modificado 2 ficheros con 99 adiciones y 22 borrados
  1. 57 13
      packages/stats/app/src/routes/index.css
  2. 42 9
      packages/stats/app/src/routes/index.tsx

+ 57 - 13
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 {
+[data-page="stats"] [data-component="metric-bar"] b {
+  position: absolute;
+  inset: 0 auto 0 0;
   display: block;
+  width: var(--metric-bar-fill);
   height: 5px;
-}
-
-[data-page="stats"] [data-component="metric-bar"] b {
-  flex-basis: 0;
   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;

+ 42 - 9
packages/stats/app/src/routes/index.tsx

@@ -864,11 +864,17 @@ function MarketShare(props: {
             <button
               type="button"
               data-active={props.inspecting && props.activeIndex === index() ? "true" : undefined}
+              data-mobile-hidden={isMarketMobileLabelHidden(index(), props.data.length) ? "true" : undefined}
               onClick={() => props.onActiveIndexChange(index())}
               onPointerEnter={() => props.onActiveIndexChange(index())}
             >
-              <span>{formatTrillions(day.total)}</span>
-              <span>{day.date}</span>
+              <span data-slot="market-axis-label">
+                <span data-slot="market-total">{formatTrillions(day.total)}</span>
+                <span data-slot="market-date">
+                  <span data-slot="market-date-full">{day.date}</span>
+                  <span data-slot="market-date-mobile">{formatMarketMobileDate(day.date)}</span>
+                </span>
+              </span>
             </button>
           )}
         </For>
@@ -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 (
-    <i data-component="metric-bar" data-active={props.active ? "true" : undefined}>
-      <b style={{ "flex-grow": Math.max(props.value / Math.max(props.max, 1), 0.05) }} />
+    <i
+      data-component="metric-bar"
+      data-active={props.active ? "true" : undefined}
+      style={{ "--metric-bar-fill": `${fill() * 100}%` } as JSX.CSSProperties}
+    >
+      <b />
       <em />
     </i>
   )
@@ -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 (