mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-31 05:15:32 +00:00
Improve top models chart mobile axis
This commit is contained in:
parent
93ba2dd24a
commit
b623d86f10
2 changed files with 84 additions and 13 deletions
|
|
@ -630,6 +630,7 @@
|
|||
}
|
||||
|
||||
[data-page="stats"] [data-component="top-models-chart"] {
|
||||
--top-models-bar-gap: 12px;
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: 34px minmax(0, 1fr);
|
||||
|
|
@ -641,10 +642,13 @@
|
|||
[data-page="stats"] [data-slot="top-models-axis"],
|
||||
[data-page="stats"] [data-slot="top-models-bars"] {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="top-models-axis"] {
|
||||
gap: var(--top-models-bar-gap);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="top-models-axis"] > div {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
|
|
@ -664,43 +668,54 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="axis-date-mobile"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[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);
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="top-models-bar"] {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
padding-inline: calc(var(--top-models-bar-gap) / 2);
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="top-models-bar"]::before {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
inset: 0 calc(var(--top-models-bar-gap) / 2);
|
||||
content: "";
|
||||
background: var(--stats-dot);
|
||||
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E");
|
||||
mask-position: center top;
|
||||
mask-repeat: repeat;
|
||||
mask-size: 6px 6px;
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E");
|
||||
-webkit-mask-position: center top;
|
||||
-webkit-mask-repeat: repeat;
|
||||
-webkit-mask-size: 6px 6px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="top-models-stack"] {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
right: calc(var(--top-models-bar-gap) / 2);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
left: calc(var(--top-models-bar-gap) / 2);
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
height: var(--top-models-bar-height);
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: var(--stats-bg);
|
||||
box-shadow: 0 -4px 0 var(--stats-bg);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="top-models-stack"] i {
|
||||
|
|
@ -1118,6 +1133,10 @@
|
|||
line-height: 12px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] p[data-muted="true"] {
|
||||
opacity: 0.46;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] [data-slot="tooltip-divider"] + p {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
|
@ -2026,11 +2045,21 @@
|
|||
position: absolute;
|
||||
left: 50%;
|
||||
width: max-content;
|
||||
max-width: 96px;
|
||||
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="axis-date-mobile"] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -403,6 +403,7 @@ function FilterPills<T extends string>(props: {
|
|||
|
||||
function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
|
||||
const [activeIndex, setActiveIndex] = createSignal<number>()
|
||||
const [activeSegment, setActiveSegment] = createSignal<number>()
|
||||
const maxTotal = createMemo(() => getTopModelsMaxTotal(props.data))
|
||||
const activePoint = createMemo(() => props.data[activeIndex() ?? -1])
|
||||
|
||||
|
|
@ -411,10 +412,16 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
|
|||
<div data-slot="top-models-axis" aria-hidden="true">
|
||||
<For each={props.data}>
|
||||
{(day, index) => (
|
||||
<div data-active={activeIndex() === index() ? "true" : undefined}>
|
||||
<div
|
||||
data-active={activeIndex() === index() ? "true" : undefined}
|
||||
data-mobile-hidden={isTopModelsMobileAxisHidden(index(), props.data.length) ? "true" : undefined}
|
||||
>
|
||||
<span data-slot="axis-label">
|
||||
<span>{formatTokens(usageTotal(day))}</span>
|
||||
<span>{day.date}</span>
|
||||
<span data-slot="axis-total">{formatTokens(usageTotal(day))}</span>
|
||||
<span data-slot="axis-date">
|
||||
<span data-slot="axis-date-full">{day.date}</span>
|
||||
<span data-slot="axis-date-mobile">{formatTopModelsMobileDate(day.date, props.range)}</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -431,30 +438,49 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
|
|||
data-active={activeIndex() === dayIndex() ? "true" : undefined}
|
||||
data-muted={activeIndex() !== undefined && activeIndex() !== dayIndex() ? "true" : undefined}
|
||||
style={{ "--top-models-bar-height": `${getTopModelsBarHeight(usageTotal(day), maxTotal())}%` }}
|
||||
onPointerEnter={() => setActiveIndex(dayIndex())}
|
||||
onPointerEnter={() => {
|
||||
setActiveIndex(dayIndex())
|
||||
setActiveSegment(undefined)
|
||||
}}
|
||||
onPointerLeave={(event) => {
|
||||
if (event.pointerType === "touch") return
|
||||
setActiveIndex(undefined)
|
||||
setActiveSegment(undefined)
|
||||
}}
|
||||
onClick={() => setActiveIndex(dayIndex())}
|
||||
onFocus={() => setActiveIndex(dayIndex())}
|
||||
onBlur={() => setActiveIndex(undefined)}
|
||||
onFocus={() => {
|
||||
setActiveIndex(dayIndex())
|
||||
setActiveSegment(undefined)
|
||||
}}
|
||||
onBlur={() => {
|
||||
setActiveIndex(undefined)
|
||||
setActiveSegment(undefined)
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== "Enter" && event.key !== " ") return
|
||||
event.preventDefault()
|
||||
setActiveIndex(dayIndex())
|
||||
setActiveSegment(undefined)
|
||||
}}
|
||||
>
|
||||
<div data-slot="top-models-stack" style={{ "grid-template-rows": getTopModelsSegmentRows(day) }}>
|
||||
<For each={visibleTopModelsSegments(day)}>
|
||||
{(item) => (
|
||||
<i
|
||||
data-series={item.index}
|
||||
data-active={activeSegment() === item.index ? "true" : undefined}
|
||||
style={{
|
||||
background: getTopModelsSegmentColor(
|
||||
item.index,
|
||||
activeIndex() !== undefined && activeIndex() !== dayIndex(),
|
||||
activeSegment(),
|
||||
),
|
||||
}}
|
||||
onPointerEnter={(event) => {
|
||||
event.stopPropagation()
|
||||
setActiveIndex(dayIndex())
|
||||
setActiveSegment(item.index)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
|
|
@ -467,7 +493,12 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
|
|||
<div data-slot="tooltip-divider" />
|
||||
<For each={visibleTopModelsSegments(point())}>
|
||||
{(item) => (
|
||||
<p>
|
||||
<p
|
||||
data-active={activeSegment() === item.index ? "true" : undefined}
|
||||
data-muted={
|
||||
activeSegment() !== undefined && activeSegment() !== item.index ? "true" : undefined
|
||||
}
|
||||
>
|
||||
<span data-slot="tooltip-label">
|
||||
<i style={{ background: usageColors[item.index] }} /> {item.segment.model}
|
||||
</span>
|
||||
|
|
@ -510,11 +541,22 @@ function visibleTopModelsSegments(point: UsagePoint) {
|
|||
return point.segments.map((segment, index) => ({ segment, index })).filter((item) => item.segment.value > 0)
|
||||
}
|
||||
|
||||
function getTopModelsSegmentColor(index: number, muted: boolean) {
|
||||
function getTopModelsSegmentColor(index: number, muted: boolean, activeSegment: number | undefined) {
|
||||
if (activeSegment !== undefined)
|
||||
return activeSegment === index ? (usageColors[index] ?? "var(--stats-text)") : "var(--stats-layer-2)"
|
||||
if (muted) return "var(--stats-layer-2)"
|
||||
return usageColors[index] ?? "var(--stats-text)"
|
||||
}
|
||||
|
||||
function isTopModelsMobileAxisHidden(index: number, count: number) {
|
||||
return count > 7 && index % 2 === 1
|
||||
}
|
||||
|
||||
function formatTopModelsMobileDate(label: string, range: UsageRange) {
|
||||
if (range === "1M" || range === "2M") return label.split(" - ")[0] ?? label
|
||||
return label
|
||||
}
|
||||
|
||||
function usageTotal(point: UsagePoint) {
|
||||
return point.segments.reduce((sum, item) => sum + item.value, 0)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue