fix(stats): rotate dense chart labels
This commit is contained in:
@@ -1065,10 +1065,66 @@
|
|||||||
flex-direction: column;
|
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"] {
|
[data-page="stats"] [data-slot="axis-date-mobile"] {
|
||||||
display: none;
|
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"] {
|
[data-page="stats"] [data-slot="top-models-bars"] {
|
||||||
width: calc(100% + var(--top-models-bar-gap));
|
width: calc(100% + var(--top-models-bar-gap));
|
||||||
margin-inline: calc(var(--top-models-bar-gap) / -2);
|
margin-inline: calc(var(--top-models-bar-gap) / -2);
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ function formatUpdatedAtLabel(value: { date: string; time: string }) {
|
|||||||
|
|
||||||
function TopModelsSection(props: { data: StatsHomeData["usage"] }) {
|
function TopModelsSection(props: { data: StatsHomeData["usage"] }) {
|
||||||
const [product, setProduct] = createSignal<UsageProduct>("All Users")
|
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 [sheet, setSheet] = createSignal<"product" | "range">()
|
||||||
const data = createMemo(() => props.data[product()][range()])
|
const data = createMemo(() => props.data[product()][range()])
|
||||||
|
|
||||||
@@ -585,6 +585,7 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
|
|||||||
<div
|
<div
|
||||||
data-component="top-models-chart"
|
data-component="top-models-chart"
|
||||||
data-range={props.range}
|
data-range={props.range}
|
||||||
|
data-dense-labels={isDenseColumnRange(props.range) ? "true" : undefined}
|
||||||
role="img"
|
role="img"
|
||||||
aria-label="Stacked top model usage chart"
|
aria-label="Stacked top model usage chart"
|
||||||
>
|
>
|
||||||
@@ -593,6 +594,7 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
|
|||||||
{(day, index) => (
|
{(day, index) => (
|
||||||
<div
|
<div
|
||||||
data-active={activeIndex() === index() ? "true" : undefined}
|
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}
|
data-mobile-hidden={isTopModelsMobileAxisHidden(index(), props.data.length) ? "true" : undefined}
|
||||||
>
|
>
|
||||||
<span data-slot="axis-label">
|
<span data-slot="axis-label">
|
||||||
@@ -755,6 +757,16 @@ function isTopModelsMobileAxisHidden(index: number, count: number) {
|
|||||||
return count > 7 && index % 2 === 1
|
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) {
|
function formatTopModelsMobileDate(label: string, range: UsageRange) {
|
||||||
if (range === "1M" || range === "2M") return label.split(" - ")[0] ?? label
|
if (range === "1M" || range === "2M") return label.split(" - ")[0] ?? label
|
||||||
return label
|
return label
|
||||||
@@ -771,7 +783,7 @@ function formatTokens(value: number) {
|
|||||||
|
|
||||||
function LeaderboardSection(props: { data: StatsHomeData["leaderboard"] }) {
|
function LeaderboardSection(props: { data: StatsHomeData["leaderboard"] }) {
|
||||||
const [product, setProduct] = createSignal<UsageProduct>("All Users")
|
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()])
|
const data = createMemo(() => props.data[product()][range()])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -870,7 +882,7 @@ function formatChange(value: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function MarketShareSection(props: { data: StatsHomeData["market"] }) {
|
function MarketShareSection(props: { data: StatsHomeData["market"] }) {
|
||||||
const [range, setRange] = createSignal<UsageRange>("1W")
|
const [range, setRange] = createSignal<UsageRange>("2M")
|
||||||
const [activeIndex, setActiveIndex] = createSignal(2)
|
const [activeIndex, setActiveIndex] = createSignal(2)
|
||||||
const [activeAuthor, setActiveAuthor] = createSignal<string>()
|
const [activeAuthor, setActiveAuthor] = createSignal<string>()
|
||||||
const [inspecting, setInspecting] = createSignal(false)
|
const [inspecting, setInspecting] = createSignal(false)
|
||||||
@@ -898,6 +910,7 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) {
|
|||||||
<>
|
<>
|
||||||
<MarketShare
|
<MarketShare
|
||||||
data={data()}
|
data={data()}
|
||||||
|
range={range()}
|
||||||
activeIndex={selectedIndex()}
|
activeIndex={selectedIndex()}
|
||||||
activeAuthor={activeAuthor()}
|
activeAuthor={activeAuthor()}
|
||||||
inspecting={inspecting()}
|
inspecting={inspecting()}
|
||||||
@@ -944,6 +957,7 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) {
|
|||||||
|
|
||||||
function MarketShare(props: {
|
function MarketShare(props: {
|
||||||
data: MarketDay[]
|
data: MarketDay[]
|
||||||
|
range: UsageRange
|
||||||
activeIndex: number
|
activeIndex: number
|
||||||
activeAuthor: string | undefined
|
activeAuthor: string | undefined
|
||||||
inspecting: boolean
|
inspecting: boolean
|
||||||
@@ -953,6 +967,8 @@ function MarketShare(props: {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-component="market-share"
|
data-component="market-share"
|
||||||
|
data-range={props.range}
|
||||||
|
data-dense-labels={isDenseColumnRange(props.range) ? "true" : undefined}
|
||||||
role="img"
|
role="img"
|
||||||
aria-label="Market share by model author"
|
aria-label="Market share by model author"
|
||||||
style={{ "--market-count": props.data.length } as JSX.CSSProperties}
|
style={{ "--market-count": props.data.length } as JSX.CSSProperties}
|
||||||
@@ -963,6 +979,7 @@ function MarketShare(props: {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-active={props.inspecting && props.activeIndex === index() ? "true" : undefined}
|
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}
|
data-mobile-hidden={isMarketMobileLabelHidden(index(), props.data.length) ? "true" : undefined}
|
||||||
onClick={() => props.onActiveIndexChange(index())}
|
onClick={() => props.onActiveIndexChange(index())}
|
||||||
onPointerEnter={() => props.onActiveIndexChange(index())}
|
onPointerEnter={() => props.onActiveIndexChange(index())}
|
||||||
|
|||||||
@@ -330,13 +330,9 @@ function createBuckets(window: DateWindow, range: UsageRange): Bucket[] {
|
|||||||
const count =
|
const count =
|
||||||
range === "1D"
|
range === "1D"
|
||||||
? 1
|
? 1
|
||||||
: range === "2W"
|
: range === "1W" || range === "2W" || range === "1M" || range === "2M" || range === "3M"
|
||||||
? 14
|
? Math.ceil(span / DAY_MS)
|
||||||
: range === "1M"
|
: Math.max(1, Math.min(7, Math.ceil(span / DAY_MS)))
|
||||||
? 4
|
|
||||||
: range === "2M"
|
|
||||||
? 8
|
|
||||||
: Math.max(1, Math.min(7, Math.ceil(span / DAY_MS)))
|
|
||||||
const size = span / count
|
const size = span / count
|
||||||
return Array.from({ length: count }, (_, index) => {
|
return Array.from({ length: count }, (_, index) => {
|
||||||
const start = window.start + index * size
|
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]))
|
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)
|
const date = new Date(start)
|
||||||
if (range === "YTD") return months[date.getUTCMonth()]
|
if (range === "YTD") return months[date.getUTCMonth()]
|
||||||
if (range === "ALL")
|
if (range === "ALL")
|
||||||
return date.getUTCFullYear() === new Date().getUTCFullYear()
|
return date.getUTCFullYear() === new Date().getUTCFullYear()
|
||||||
? months[date.getUTCMonth()]
|
? months[date.getUTCMonth()]
|
||||||
: String(date.getUTCFullYear())
|
: String(date.getUTCFullYear())
|
||||||
if (range === "1M" || range === "2M") return `${formatDay(start)} - ${formatDay(end - DAY_MS)}`
|
|
||||||
return formatDay(start)
|
return formatDay(start)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user