All Taste components
Animated charts
Drawing line chart
A weekly trend line that draws itself left to right over last week’s, then marks today with a pulsing point.
line draw · area fill · comparison line · end point · tooltip · legend
Active users
24,380+8.1%
This weekLast week
3,920 today
/* Add once to globals.css (draw comes from the Taste preset):
@keyframes area-in { from { opacity: 0; transform: scaleY(.55); } }
@keyframes breathe { from { transform: scale(1); opacity: .6; } to { transform: scale(1.35); opacity: .16; } }
*/
const thisWeek = [38, 44, 41, 52, 49, 58, 55, 66, 61, 72, 69, 82];
const lastWeek = [34, 36, 39, 40, 44, 43, 47, 50, 49, 53, 56, 58];
const W = 320, H = 150;
const toPoints = (vals: number[]) => vals.map((n, i) => [(i * W) / (vals.length - 1), 144 - (n - 30) * 2.1] as const);
const round = (n: number) => Math.round(n * 10) / 10;
/** Smooth path through the points (Catmull-Rom as cubic Bézier). */
function smooth(vals: number[]) {
const p = toPoints(vals);
let d = "M" + round(p[0][0]) + "," + round(p[0][1]);
for (let i = 0; i < p.length - 1; i++) {
const a = p[Math.max(i - 1, 0)], b = p[i], c = p[i + 1], e = p[Math.min(i + 2, p.length - 1)];
d += " C" + round(b[0] + (c[0] - a[0]) / 6) + "," + round(b[1] + (c[1] - a[1]) / 6) +
" " + round(c[0] - (e[0] - b[0]) / 6) + "," + round(c[1] - (e[1] - b[1]) / 6) +
" " + round(c[0]) + "," + round(c[1]);
}
return d;
}
export function DrawingLineChart() {
const line = smooth(thisWeek);
const end = toPoints(thisWeek)[thisWeek.length - 1];
return (
<section aria-label="Active users chart" className="mx-auto grid max-w-2xl gap-4 rounded-lg border border-border bg-bg p-[18px] font-sans">
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
<p className="text-xs font-medium text-fg-subtle">Active users</p>
<div className="mt-1 flex items-center gap-2.5">
<span className="text-[28px] font-medium tabular-nums tracking-tight text-fg">24,380</span>
<span className="rounded-full bg-success-soft px-2 py-0.5 text-xs font-medium text-success-fg">+8.1%</span>
</div>
</div>
<div className="flex gap-3.5 text-xs text-fg-muted">
<span className="flex items-center gap-1.5"><i className="h-0.5 w-3.5 rounded bg-primary" />This week</span>
<span className="flex items-center gap-1.5"><i className="h-0.5 w-3.5 rounded bg-border-strong" />Last week</span>
</div>
</div>
<div className="relative">
<svg viewBox={"0 0 " + W + " " + H} className="block h-auto w-full overflow-visible" aria-hidden>
<defs>
<linearGradient id="line-fill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stopColor="var(--tst-primary)" stopOpacity=".24" />
<stop offset="1" stopColor="var(--tst-primary)" stopOpacity="0" />
</linearGradient>
</defs>
{[20, 62, 104, 146].map((y) => <line key={y} x1="0" x2={W} y1={y} y2={y} className="stroke-border [stroke-dasharray:3_4]" />)}
<path d={smooth(lastWeek)} pathLength={1} className="fill-none stroke-border-strong [stroke-dasharray:1] [stroke-linecap:round] [stroke-width:1.5] animate-draw motion-reduce:animate-none" />
<path d={line + " L" + W + "," + H + " L0," + H + " Z"} fill="url(#line-fill)" className="origin-bottom [transform-box:fill-box] animate-[area-in_1s_cubic-bezier(.2,.7,.1,1)_.45s_both] motion-reduce:animate-none" />
<path d={line} pathLength={1} className="fill-none stroke-primary [stroke-dasharray:1] [stroke-linecap:round] [stroke-width:2.25] animate-draw [animation-delay:120ms] motion-reduce:animate-none" />
<circle cx={end[0]} cy={end[1]} r="9" className="origin-center fill-primary [transform-box:fill-box] animate-[breathe_1.6s_ease-in-out_1.4s_infinite_alternate] motion-reduce:animate-none" opacity=".3" />
<circle cx={end[0]} cy={end[1]} r="4.5" className="origin-center fill-primary stroke-bg [stroke-width:2] [transform-box:fill-box] animate-pop [animation-delay:1050ms] motion-reduce:animate-none" />
</svg>
<div className="absolute right-0 -translate-y-[calc(100%+14px)]" style={{ top: (end[1] / H) * 100 + "%" }}>
<div className="origin-bottom-right whitespace-nowrap rounded bg-fg px-2.5 py-1.5 text-xs text-bg shadow-lg animate-pop [animation-delay:1150ms] motion-reduce:animate-none">
<b>3,920</b> <span className="opacity-70">today</span>
</div>
</div>
</div>
<div aria-hidden className="-mt-2 grid grid-cols-7 text-center text-[10.5px] text-fg-subtle">
{["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map((d) => <span key={d}>{d}</span>)}
</div>
</section>
);
}
The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.