refactor(sparkline): move min/max readout to a corner badge

On-chart extrema labels were colliding with the Y-axis ticks at the
top, the X-axis timestamps at the bottom, and the chart line itself
when min/max sat near a chart edge. Replace the floating labels with
a single rounded pill in the chart's top-right corner that lists
"▲ max  ▼ min", outside the drawing area. Dots still mark the points
on the line. Also nudge Y tick text 4px left, push X timestamps down
with tickMargin=14, and widen YAxis to 56px so values like "234 KB/s"
don't crowd the chart.
This commit is contained in:
MHSanaei 2026-05-27 18:18:08 +02:00
parent e23599cb18
commit be5425cbed
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
2 changed files with 147 additions and 111 deletions

View file

@ -2,3 +2,33 @@
display: block;
width: 100%;
}
.sparkline-container {
position: relative;
width: 100%;
}
.sparkline-extrema {
position: absolute;
top: 2px;
right: 8px;
display: inline-flex;
align-items: center;
gap: 12px;
padding: 2px 8px;
background: color-mix(in srgb, var(--ant-color-bg-elevated) 88%, transparent);
border: 1px solid var(--ant-color-border-secondary);
border-radius: 999px;
font-size: 11px;
font-weight: 600;
line-height: 16px;
pointer-events: none;
z-index: 1;
}
.sparkline-extrema .extrema-item {
display: inline-flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}

View file

@ -137,7 +137,7 @@ export default function Sparkline({
if (points[i].value > points[maxIdx].value) maxIdx = i;
}
if (minIdx === maxIdx) return null;
return { min: points[minIdx], max: points[maxIdx] };
return { min: points[minIdx], max: points[maxIdx], minIdx, maxIdx };
}, [points, extrema?.show]);
const fmtExtrema = extrema?.formatter ?? yFormatter;
@ -145,8 +145,27 @@ export default function Sparkline({
const maxColor = extrema?.maxColor ?? DEFAULT_MAX_COLOR;
return (
<div className="sparkline-container">
{extremaPoints && (
<div className="sparkline-extrema" aria-hidden="true">
<span className="extrema-item" style={{ color: maxColor }}>
{fmtExtrema(extremaPoints.max.value)}
</span>
<span className="extrema-item" style={{ color: minColor }}>
{fmtExtrema(extremaPoints.min.value)}
</span>
</div>
)}
<ResponsiveContainer width="100%" height={height} className="sparkline-svg">
<AreaChart data={points} margin={{ top: 6, right: 6, bottom: showAxes ? 14 : 4, left: showAxes ? 4 : 4 }}>
<AreaChart
data={points}
margin={{
top: showAxes ? 14 : 6,
right: showAxes ? 12 : 6,
bottom: showAxes ? 26 : 4,
left: 4,
}}
>
<defs>
<linearGradient id={gradId} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={stroke} stopOpacity={fillOpacity} />
@ -162,18 +181,20 @@ export default function Sparkline({
tick={{ fontSize: 10, fill: 'var(--ant-color-text-tertiary)' }}
axisLine={false}
tickLine={false}
tickMargin={14}
interval={0}
ticks={xTickIndexes?.map((i) => points[i]?.label).filter(Boolean) as string[] | undefined}
/>
<YAxis
domain={yDomain}
hide={!showAxes}
tick={{ fontSize: 10, fill: 'var(--ant-color-text-tertiary)' }}
tick={{ fontSize: 10, fill: 'var(--ant-color-text-tertiary)', dx: -4 }}
axisLine={false}
tickLine={false}
tickMargin={8}
tickFormatter={yFormatter}
ticks={yTicks}
width={48}
width={56}
/>
{showTooltip && (
<Tooltip
@ -219,14 +240,6 @@ export default function Sparkline({
fill={maxColor}
stroke="var(--ant-color-bg-elevated)"
strokeWidth={2}
label={{
value: `${fmtExtrema(extremaPoints.max.value)}`,
position: 'top',
fontSize: 10.5,
fill: maxColor,
fontWeight: 600,
offset: 8,
}}
ifOverflow="extendDomain"
/>
<ReferenceDot
@ -236,14 +249,6 @@ export default function Sparkline({
fill={minColor}
stroke="var(--ant-color-bg-elevated)"
strokeWidth={2}
label={{
value: `${fmtExtrema(extremaPoints.min.value)}`,
position: 'bottom',
fontSize: 10.5,
fill: minColor,
fontWeight: 600,
offset: 8,
}}
ifOverflow="extendDomain"
/>
</>
@ -260,5 +265,6 @@ export default function Sparkline({
/>
</AreaChart>
</ResponsiveContainer>
</div>
);
}