From aaffa73d593c46eebed27b9061b6d893c38b08bc Mon Sep 17 00:00:00 2001
From: Alexander Karan <47707063+AlexanderKaran@users.noreply.github.com>
Date: Sat, 25 Jul 2026 11:33:10 +0800
Subject: [PATCH 1/2] Made Line Charts Better
---
.../src/components/VersionLineChart.astro | 255 ++++++++++++++++--
packages/docs/src/styles/starlight.css | 6 +
2 files changed, 236 insertions(+), 25 deletions(-)
diff --git a/packages/docs/src/components/VersionLineChart.astro b/packages/docs/src/components/VersionLineChart.astro
index 741bbb8c..6ab22aed 100644
--- a/packages/docs/src/components/VersionLineChart.astro
+++ b/packages/docs/src/components/VersionLineChart.astro
@@ -1,4 +1,5 @@
---
+import { formatChartValue } from '../lib/chart'
import type { ChartDatum, ChartValueFormat } from '../lib/types'
type ValueTransform = 'bytesToKb' | 'bytesToMb' | 'msToSeconds'
@@ -15,8 +16,8 @@ interface Props {
yAxisLabel: string
}
-const CHART_HEIGHT_PX = 400
-const MOBILE_CHART_HEIGHT_PX = 300
+const CHART_HEIGHT_PX = 340
+const MOBILE_CHART_HEIGHT_PX = 260
const {
title,
@@ -91,19 +92,77 @@ const chartPayload = JSON.stringify({
yAxisLabel,
})
const hasChartData = chartData.length > 0
+const latestDatum = chartData.at(-1)
+const previousDatum = chartData.at(-2)
+const latestChange =
+ latestDatum != null && previousDatum != null
+ ? latestDatum.value - previousDatum.value
+ : null
+const latestChangeLabel =
+ latestChange == null
+ ? null
+ : latestChange === 0
+ ? 'No change'
+ : `${latestChange > 0 ? '+' : 'โ'}${formatChartValue(
+ Math.abs(latestChange),
+ valueFormat,
+ )}`
---
{
hasChartData ? (
-
-
{title}
- {description ?
{description}
: null}
+
+
+
+
{title}
+ {description ?
{description}
: null}
+
+ {latestDatum ? (
+
+
+ Latest ยท v{latestDatum.name}
+
+ {formatChartValue(latestDatum.value, valueFormat)}
+ {latestChangeLabel ? (
+
+ {latestChangeLabel} from previous
+
+ ) : null}
+
+ ) : null}
+
-
-
+
) : null
}
@@ -115,24 +174,92 @@ const hasChartData = chartData.length > 0
>
.version-line-chart {
width: 100%;
- margin-bottom: 2em;
+ margin-block: 1.5rem 2.5rem;
+ margin-inline: 0;
}
.version-line-chart-title {
- font-size: 16px;
+ margin: 0;
+ font-size: 1rem;
+ }
+
+ .version-line-chart-heading {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-bottom: 0.75rem;
+ }
+
+ .version-line-chart-heading p {
+ margin-block: 0.35rem 0;
+ }
+
+ .version-line-chart-latest {
+ display: grid;
+ flex: 0 0 auto;
+ grid-template-columns: auto auto;
+ align-items: baseline;
+ column-gap: 0.75rem;
+ text-align: right;
+ }
+
+ .version-line-chart-latest-label {
+ color: var(--ft-muted);
+ font-size: 0.72rem;
+ font-weight: 650;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ }
+
+ .version-line-chart-latest strong {
+ color: var(--ft-text);
+ font-size: 1.1rem;
+ font-variant-numeric: tabular-nums;
+ letter-spacing: -0.02em;
+ }
+
+ .version-line-chart-change {
+ grid-column: 1 / -1;
+ color: var(--ft-muted);
+ font-size: 0.75rem;
+ font-variant-numeric: tabular-nums;
}
.version-line-chart-wrapper {
position: relative;
+ box-sizing: border-box;
width: 100%;
max-width: 1000px;
height: var(--chartHeight);
margin-inline: auto;
+ padding: 1rem 1rem 0.5rem 0.5rem;
+ border: 1px solid var(--ft-border);
+ border-radius: 0.75rem;
+ background: var(--ft-bg);
+ box-shadow: 0 1px 0 rgb(0 0 0 / 4%);
}
@media (max-width: 768px) {
+ .version-line-chart-heading {
+ display: block;
+ }
+
+ .version-line-chart-latest {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+ margin-top: 0.65rem;
+ column-gap: 0.5rem;
+ }
+
+ .version-line-chart-change {
+ flex-basis: 100%;
+ }
+
.version-line-chart-wrapper {
height: var(--mobileChartHeight);
+ padding: 0.75rem 0.5rem 0.25rem 0.25rem;
}
}
@@ -141,6 +268,7 @@ const hasChartData = chartData.length > 0
import {
CategoryScale,
Chart,
+ Filler,
Legend,
LineController,
LineElement,
@@ -157,6 +285,7 @@ const hasChartData = chartData.length > 0
Chart.register(
CategoryScale,
+ Filler,
Legend,
LineController,
LineElement,
@@ -166,11 +295,13 @@ const hasChartData = chartData.length > 0
)
const chartCache = new WeakMap()
- const LINE_COLOR = '#8f7bd1'
- const POINT_COLOR = '#6f5ab8'
+ const LINE_COLOR = '#697586'
+ const POINT_COLOR = '#4b5565'
const TEXT_COLOR_FALLBACK = '#1f2933'
const MUTED_COLOR_FALLBACK = '#697586'
const BORDER_COLOR_FALLBACK = '#d9dee7'
+ const LINE_FILL_FALLBACK = 'rgba(105, 117, 134, 0.12)'
+ const LINE_FILL_TRANSPARENT_FALLBACK = 'rgba(105, 117, 134, 0)'
function formatAxisTick(
value: string | number,
@@ -225,8 +356,25 @@ const hasChartData = chartData.length > 0
const mutedColor = getCssVar(root, '--ft-muted', MUTED_COLOR_FALLBACK)
const borderColor = getCssVar(root, '--ft-border', BORDER_COLOR_FALLBACK)
const gridColor = getCssVar(root, '--ft-chart-grid', borderColor)
- const lineColor = getCssVar(root, '--ft-chart-bar', LINE_COLOR)
- const pointColor = getCssVar(root, '--ft-chart-bar-hover', POINT_COLOR)
+ const lineColor = getCssVar(root, '--ft-chart-line', LINE_COLOR)
+ const pointColor = getCssVar(root, '--ft-chart-line-hover', POINT_COLOR)
+ const lineFill = getCssVar(root, '--ft-chart-line-fill', LINE_FILL_FALLBACK)
+ const lineFillTransparent = getCssVar(
+ root,
+ '--ft-chart-line-fill-transparent',
+ LINE_FILL_TRANSPARENT_FALLBACK,
+ )
+ const canvasColor = getCssVar(root, '--ft-bg', '#ffffff')
+ const lastPointIndex = data.length - 1
+ const pointRadii = data.map((_, index) =>
+ index === lastPointIndex ? 6 : 4,
+ )
+ const pointBackgroundColors = data.map((_, index) =>
+ index === lastPointIndex ? pointColor : canvasColor,
+ )
+ const prefersReducedMotion = window.matchMedia(
+ '(prefers-reduced-motion: reduce)',
+ ).matches
chartCache.get(canvas)?.destroy()
@@ -239,45 +387,101 @@ const hasChartData = chartData.length > 0
label: payload.yAxisLabel ?? chartTitle,
data: values,
borderColor: lineColor,
- backgroundColor: lineColor,
- borderWidth: 2,
- pointBackgroundColor: pointColor,
- pointBorderColor: pointColor,
- pointHoverRadius: 6,
- pointRadius: 4,
+ backgroundColor: (context) => {
+ const { chart } = context
+ const { chartArea, ctx } = chart
+ if (!chartArea) return lineFill
+
+ const gradient = ctx.createLinearGradient(
+ 0,
+ chartArea.top,
+ 0,
+ chartArea.bottom,
+ )
+ gradient.addColorStop(0, lineFill)
+ gradient.addColorStop(0.72, lineFillTransparent)
+ return gradient
+ },
+ borderCapStyle: 'round',
+ borderJoinStyle: 'round',
+ borderWidth: 3,
+ fill: true,
+ pointBackgroundColor: pointBackgroundColors,
+ pointBorderColor: lineColor,
+ pointBorderWidth: 3,
+ pointHitRadius: 12,
+ pointHoverBackgroundColor: pointColor,
+ pointHoverBorderColor: canvasColor,
+ pointHoverBorderWidth: 3,
+ pointHoverRadius: 7,
+ pointRadius: pointRadii,
+ pointStyle: 'circle',
showLine: true,
- tension: 0.25,
+ tension: 0.32,
},
],
},
options: {
- animation: { duration: 500 },
+ animation: { duration: prefersReducedMotion ? 0 : 500 },
+ interaction: {
+ intersect: false,
+ mode: 'index',
+ },
+ layout: {
+ padding: {
+ left: 4,
+ right: 8,
+ top: 8,
+ },
+ },
maintainAspectRatio: false,
responsive: true,
plugins: {
legend: { display: false },
tooltip: {
+ backgroundColor: textColor,
+ bodyColor: canvasColor,
+ borderColor: lineColor,
+ borderWidth: 1,
callbacks: {
label: (context) =>
- formatChartValue(Number(context.raw ?? 0), payload.valueFormat),
+ `${payload.yAxisLabel}: ${formatChartValue(
+ Number(context.raw ?? 0),
+ payload.valueFormat,
+ )}`,
+ title: (items) => `Version ${items[0]?.label ?? ''}`,
},
+ caretPadding: 8,
+ cornerRadius: 8,
+ displayColors: false,
+ padding: 10,
+ titleColor: canvasColor,
},
},
scales: {
x: {
+ border: { display: false },
grid: { display: false },
ticks: {
- color: textColor,
+ autoSkip: true,
+ autoSkipPadding: 16,
+ color: mutedColor,
font: { size: 12, weight: 500 },
+ maxTicksLimit: 8,
maxRotation: 0,
minRotation: 0,
+ padding: 8,
},
},
y: {
beginAtZero: true,
suggestedMax,
- border: { color: gridColor },
- grid: { color: gridColor },
+ border: { display: false },
+ grid: {
+ color: gridColor,
+ drawTicks: false,
+ lineWidth: 1,
+ },
title: {
color: mutedColor,
display: Boolean(payload.yAxisLabel),
@@ -287,6 +491,7 @@ const hasChartData = chartData.length > 0
ticks: {
color: mutedColor,
font: { size: 12 },
+ padding: 10,
precision: payload.valueFormat === 'count' ? 0 : undefined,
callback: (value) => formatAxisTick(value, payload.valueFormat),
},
diff --git a/packages/docs/src/styles/starlight.css b/packages/docs/src/styles/starlight.css
index 848c4e01..ba277492 100644
--- a/packages/docs/src/styles/starlight.css
+++ b/packages/docs/src/styles/starlight.css
@@ -23,8 +23,14 @@
--ft-chart-bar-hover: var(--sl-color-gray-2);
--ft-chart-value-on-bar: #ffffff;
--ft-chart-grid: var(--sl-color-gray-5);
+ --ft-chart-line: var(--sl-color-gray-3);
+ --ft-chart-line-hover: var(--sl-color-gray-2);
+ --ft-chart-line-fill: rgb(148 163 184 / 13%);
+ --ft-chart-line-fill-transparent: rgb(148 163 184 / 0%);
}
:root[data-theme='light'] {
+ --ft-chart-line-fill: rgb(105 117 134 / 11%);
+ --ft-chart-line-fill-transparent: rgb(105 117 134 / 0%);
}
/* Make main content column wider and sidebars narrower. */
From 2c85a5f1286623734097422e15c6838143f6d0bd Mon Sep 17 00:00:00 2001
From: Alexander Karan <47707063+AlexanderKaran@users.noreply.github.com>
Date: Sat, 25 Jul 2026 13:01:58 +0800
Subject: [PATCH 2/2] Removed turnery
---
.../docs/src/components/VersionLineChart.astro | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/packages/docs/src/components/VersionLineChart.astro b/packages/docs/src/components/VersionLineChart.astro
index 6ab22aed..9ce3cb51 100644
--- a/packages/docs/src/components/VersionLineChart.astro
+++ b/packages/docs/src/components/VersionLineChart.astro
@@ -64,6 +64,14 @@ function transformValue(value: number) {
return value
}
+function formatLatestChange(value: number | null) {
+ if (value == null) return null
+ if (value === 0) return 'No change'
+
+ const sign = value > 0 ? '+' : 'โ'
+ return `${sign}${formatChartValue(Math.abs(value), valueFormat)}`
+}
+
function getVersionChartData(): ChartDatum[] {
return versions.flatMap((stats) => {
const rawValue =
@@ -98,15 +106,7 @@ const latestChange =
latestDatum != null && previousDatum != null
? latestDatum.value - previousDatum.value
: null
-const latestChangeLabel =
- latestChange == null
- ? null
- : latestChange === 0
- ? 'No change'
- : `${latestChange > 0 ? '+' : 'โ'}${formatChartValue(
- Math.abs(latestChange),
- valueFormat,
- )}`
+const latestChangeLabel = formatLatestChange(latestChange)
---
{