chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { useId } from 'react';
import { cn } from '#/lib/utils.js';
/**
* A smooth open path through `points` as a Catmull-Rom spline expressed in cubic
* Béziers, so the sparkline flows in rounded hills rather than jagged peaks.
* Each control point is pulled a sixth of the way along the segment between its
* neighbours; the y of every control point is clamped to the drawing box so a
* spline overshoot can't dip the fill below the baseline or spill past the top.
* @param {[number, number][]} points
* @param {number} height
* @returns {string}
*/
function splinePath(points, height) {
const clampY = (y) => Math.max(0, Math.min(height, y));
let d = `M${points[0][0].toFixed(2)} ${points[0][1].toFixed(2)}`;
for (let i = 0; i < points.length - 1; i++) {
const [x0, y0] = points[i - 1] || points[i];
const [x1, y1] = points[i];
const [x2, y2] = points[i + 1];
const [x3, y3] = points[i + 2] || points[i + 1];
const c1x = x1 + (x2 - x0) / 6;
const c1y = clampY(y1 + (y2 - y0) / 6);
const c2x = x2 - (x3 - x1) / 6;
const c2y = clampY(y2 - (y3 - y1) / 6);
d += ` C${c1x.toFixed(2)} ${c1y.toFixed(2)} ${c2x.toFixed(2)} ${c2y.toFixed(2)} ${x2.toFixed(2)} ${y2.toFixed(2)}`;
}
return d;
}
/**
* A tiny area chart drawn edge-to-edge, meant to sit behind a row as an ambient
* backdrop rather than a chart you read precisely. `values` are bucket counts,
* oldest to newest; the shape is normalized to its own peak, smoothed into a
* spline, and stretched to fill via a non-uniform viewBox. The fill fades out
* toward the top so text above it stays legible, and the whole thing renders in
* the ring indigo at low opacity. Nothing is drawn for an empty or all-zero
* series.
* @param {{values: number[]|null|undefined, className?: string}} props
*/
export function Sparkline({ values, className }) {
const gradientId = useId();
if (!values || values.length < 2) return null;
const max = Math.max(...values);
if (max <= 0) return null;
const width = 100;
const height = 32;
const step = width / (values.length - 1);
const points = /** @type {[number, number][]} */ (
values.map((value, i) => [i * step, height - (value / max) * height])
);
const line = splinePath(points, height);
const area = `${line} L${width} ${height} L0 ${height} Z`;
return (
<svg
viewBox={`0 0 ${width} ${height}`}
preserveAspectRatio="none"
className={cn('h-full w-full text-ring', className)}
aria-hidden="true"
>
<defs>
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="currentColor" stopOpacity="0" />
<stop offset="100%" stopColor="currentColor" stopOpacity="0.14" />
</linearGradient>
</defs>
<path d={area} fill={`url(#${gradientId})`} />
<path
d={line}
fill="none"
stroke="currentColor"
strokeOpacity="0.25"
strokeWidth="1"
strokeLinejoin="round"
strokeLinecap="round"
vectorEffect="non-scaling-stroke"
/>
</svg>
);
}