Просмотр исходного кода

fix(stats): rotate dense chart labels

Adam 2 месяцев назад
Родитель
Сommit
ce4e0e8a4e

+ 56 - 0
packages/stats/app/src/routes/index.css

@@ -1065,10 +1065,66 @@
   flex-direction: column;
 }
 
+[data-page="stats"] [data-slot="top-models-axis"] > div[data-label-hidden="true"]:not([data-active="true"])
+  [data-slot="axis-label"],
+[data-page="stats"] [data-slot="market-labels"] button[data-label-hidden="true"]:not([data-active="true"])
+  [data-slot="market-axis-label"] {
+  visibility: hidden;
+}
+
 [data-page="stats"] [data-slot="axis-date-mobile"] {
   display: none;
 }
 
+[data-page="stats"] [data-component="top-models-chart"][data-dense-labels="true"] {
+  grid-template-rows: 40px minmax(0, 1fr);
+}
+
+[data-page="stats"] [data-component="market-share"][data-dense-labels="true"] {
+  grid-template-rows: 40px minmax(0, 1fr);
+}
+
+[data-page="stats"] [data-component="top-models-chart"][data-dense-labels="true"]
+  [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-component="market-share"][data-dense-labels="true"] [data-slot="market-labels"] button {
+  position: relative;
+  align-items: center;
+  justify-content: center;
+  height: 40px;
+  line-height: 1.2;
+}
+
+[data-page="stats"] [data-component="top-models-chart"][data-dense-labels="true"] [data-slot="axis-label"],
+[data-page="stats"] [data-component="market-share"][data-dense-labels="true"] [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-component="top-models-chart"][data-dense-labels="true"] [data-slot="axis-total"],
+[data-page="stats"] [data-component="top-models-chart"][data-dense-labels="true"] [data-slot="axis-date-full"],
+[data-page="stats"] [data-component="market-share"][data-dense-labels="true"] [data-slot="market-total"],
+[data-page="stats"] [data-component="market-share"][data-dense-labels="true"] [data-slot="market-date-full"] {
+  display: none;
+}
+
+[data-page="stats"] [data-component="top-models-chart"][data-dense-labels="true"] [data-slot="axis-date-mobile"],
+[data-page="stats"] [data-component="market-share"][data-dense-labels="true"] [data-slot="market-date-mobile"] {
+  display: block;
+}
+
 [data-page="stats"] [data-slot="top-models-bars"] {
   width: calc(100% + var(--top-models-bar-gap));
   margin-inline: calc(var(--top-models-bar-gap) / -2);

+ 20 - 3
packages/stats/app/src/routes/index.tsx

@@ -370,7 +370,7 @@ function formatUpdatedAtLabel(value: { date: string; time: string }) {
 
 function TopModelsSection(props: { data: StatsHomeData["usage"] }) {
   const [product, setProduct] = createSignal<UsageProduct>("All Users")
-  const [range, setRange] = createSignal<UsageRange>("1W")
+  const [range, setRange] = createSignal<UsageRange>("2M")
   const [sheet, setSheet] = createSignal<"product" | "range">()
   const data = createMemo(() => props.data[product()][range()])
 
@@ -585,6 +585,7 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
     <div
       data-component="top-models-chart"
       data-range={props.range}
+      data-dense-labels={isDenseColumnRange(props.range) ? "true" : undefined}
       role="img"
       aria-label="Stacked top model usage chart"
     >
@@ -593,6 +594,7 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
           {(day, index) => (
             <div
               data-active={activeIndex() === index() ? "true" : undefined}
+              data-label-hidden={isColumnLabelHidden(index(), props.data.length) ? "true" : undefined}
               data-mobile-hidden={isTopModelsMobileAxisHidden(index(), props.data.length) ? "true" : undefined}
             >
               <span data-slot="axis-label">
@@ -755,6 +757,16 @@ function isTopModelsMobileAxisHidden(index: number, count: number) {
   return count > 7 && index % 2 === 1
 }
 
+function isColumnLabelHidden(index: number, count: number) {
+  if (count <= 20) return false
+  const interval = Math.ceil(count / 8)
+  return index !== count - 1 && index % interval !== 0
+}
+
+function isDenseColumnRange(range: UsageRange) {
+  return range === "1M" || range === "2M"
+}
+
 function formatTopModelsMobileDate(label: string, range: UsageRange) {
   if (range === "1M" || range === "2M") return label.split(" - ")[0] ?? label
   return label
@@ -771,7 +783,7 @@ function formatTokens(value: number) {
 
 function LeaderboardSection(props: { data: StatsHomeData["leaderboard"] }) {
   const [product, setProduct] = createSignal<UsageProduct>("All Users")
-  const [range, setRange] = createSignal<UsageRange>("1W")
+  const [range, setRange] = createSignal<UsageRange>("2M")
   const data = createMemo(() => props.data[product()][range()])
 
   return (
@@ -870,7 +882,7 @@ function formatChange(value: number) {
 }
 
 function MarketShareSection(props: { data: StatsHomeData["market"] }) {
-  const [range, setRange] = createSignal<UsageRange>("1W")
+  const [range, setRange] = createSignal<UsageRange>("2M")
   const [activeIndex, setActiveIndex] = createSignal(2)
   const [activeAuthor, setActiveAuthor] = createSignal<string>()
   const [inspecting, setInspecting] = createSignal(false)
@@ -898,6 +910,7 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) {
           <>
             <MarketShare
               data={data()}
+              range={range()}
               activeIndex={selectedIndex()}
               activeAuthor={activeAuthor()}
               inspecting={inspecting()}
@@ -944,6 +957,7 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) {
 
 function MarketShare(props: {
   data: MarketDay[]
+  range: UsageRange
   activeIndex: number
   activeAuthor: string | undefined
   inspecting: boolean
@@ -953,6 +967,8 @@ function MarketShare(props: {
   return (
     <div
       data-component="market-share"
+      data-range={props.range}
+      data-dense-labels={isDenseColumnRange(props.range) ? "true" : undefined}
       role="img"
       aria-label="Market share by model author"
       style={{ "--market-count": props.data.length } as JSX.CSSProperties}
@@ -963,6 +979,7 @@ function MarketShare(props: {
             <button
               type="button"
               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())}

+ 4 - 9
packages/stats/core/src/domain/home.ts

@@ -330,13 +330,9 @@ function createBuckets(window: DateWindow, range: UsageRange): Bucket[] {
   const count =
     range === "1D"
       ? 1
-      : range === "2W"
-        ? 14
-        : range === "1M"
-          ? 4
-          : range === "2M"
-            ? 8
-            : Math.max(1, Math.min(7, Math.ceil(span / DAY_MS)))
+      : range === "1W" || range === "2W" || range === "1M" || range === "2M" || range === "3M"
+        ? Math.ceil(span / DAY_MS)
+        : Math.max(1, Math.min(7, Math.ceil(span / DAY_MS)))
   const size = span / count
   return Array.from({ length: count }, (_, index) => {
     const start = window.start + index * size
@@ -443,14 +439,13 @@ function periodKeyTime(value: string) {
   return Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))
 }
 
-function formatBucketLabel(start: number, end: number, range: UsageRange) {
+function formatBucketLabel(start: number, _end: number, range: UsageRange) {
   const date = new Date(start)
   if (range === "YTD") return months[date.getUTCMonth()]
   if (range === "ALL")
     return date.getUTCFullYear() === new Date().getUTCFullYear()
       ? months[date.getUTCMonth()]
       : String(date.getUTCFullYear())
-  if (range === "1M" || range === "2M") return `${formatDay(start)} - ${formatDay(end - DAY_MS)}`
   return formatDay(start)
 }