A hand-built SVG column chart against a target reference line; bars rise and the target draws in on first view, breaches tint red.
npx shadcn@latest add @paragon/emissions-vs-target"use client";
import * as React from "react";
import { useInView, useReducedMotion } from "motion/react";
import { cn } from "@/lib/utils";
export interface EmissionsVsTargetProps extends React.ComponentProps<"div"> {
/** One value per period. */
data: number[];
/** X labels, one per data point. */
labels: string[];
/** Target ceiling drawn as a horizontal reference line. */
target: number;
height?: number;
/** Unit suffix for the target readout. */
unit?: string;
formatValue?: (value: number) => string;
/** Accessible description. */
label?: string;
/** Renders the final state immediately, no draw-in. */
static?: boolean;
}
/**
* A hand-built SVG column chart against a target reference line. Columns rise
* from the baseline on first view via a grouped scaleY transform (non-scaling
* stroke unaffected), and a bar tints destructive when it breaches the target.
* The target line and its dashed stroke draw once. Responsive via
* ResizeObserver; reduced motion (or `static`) renders the final chart
* immediately.
*/
export function EmissionsVsTarget({
data,
labels,
target,
height = 200,
unit = "t",
formatValue = (v) => v.toLocaleString("en-US"),
label,
static: isStatic = false,
className,
...props
}: EmissionsVsTargetProps) {
const ref = React.useRef<HTMLDivElement>(null);
const [width, setWidth] = React.useState(0);
const reducedMotion = useReducedMotion();
const inView = useInView(ref, { once: true, margin: "0px 0px -32px 0px" });
const animate = !isStatic && !reducedMotion;
const drawn = !animate || inView;
React.useLayoutEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new ResizeObserver(([entry]) =>
setWidth(entry.contentRect.width),
);
observer.observe(el);
return () => observer.disconnect();
}, []);
const pad = { top: 12, right: 12, bottom: 22, left: 40 };
const innerW = Math.max(width - pad.left - pad.right, 0);
const plotBottom = height - pad.bottom;
const innerH = plotBottom - pad.top;
const dataMax = Math.max(target, ...data);
const yMax = Math.ceil((dataMax * 1.15) / 10) * 10 || 1;
const n = data.length;
const slot = n > 0 ? innerW / n : 0;
const barW = slot * 0.56;
const yFor = (v: number) => plotBottom - (v / yMax) * innerH;
const targetY = yFor(target);
return (
<div
ref={ref}
data-slot="emissions-vs-target"
className={cn("w-full max-w-md", className)}
{...props}
>
{width > 0 && (
<svg
role="img"
aria-label={
label ??
`Emissions by period against a target of ${formatValue(target)} ${unit}`
}
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
className="block"
>
{[0, 0.5, 1].map((f) => {
const v = yMax * f;
return (
<g key={f}>
<line
x1={pad.left}
x2={width - pad.right}
y1={yFor(v)}
y2={yFor(v)}
stroke="currentColor"
strokeOpacity={f === 0 ? 0.15 : 0.07}
/>
<text
x={pad.left - 8}
y={yFor(v)}
textAnchor="end"
dominantBaseline="central"
fontSize={10}
className="fill-muted-foreground tabular-nums"
>
{formatValue(v)}
</text>
</g>
);
})}
{/* Bars rise together */}
<g
style={{
transform: drawn ? "scaleY(1)" : "scaleY(0)",
transformOrigin: `0px ${plotBottom}px`,
transition: animate
? "transform 650ms var(--ease-out)"
: undefined,
}}
>
{data.map((v, i) => {
const x = pad.left + i * slot + (slot - barW) / 2;
const over = v > target;
return (
<rect
key={i}
x={x}
y={yFor(v)}
width={barW}
height={Math.max(0, plotBottom - yFor(v))}
rx={2}
fill={over ? "var(--destructive)" : "var(--primary)"}
fillOpacity={over ? 0.9 : 0.85}
/>
);
})}
</g>
{/* Target reference line — draws in */}
<TargetLine
x1={pad.left}
x2={width - pad.right}
y={targetY}
animate={animate}
drawn={drawn}
/>
<text
x={width - pad.right}
y={targetY - 5}
textAnchor="end"
fontSize={10}
className="fill-warning tabular-nums"
style={{
opacity: drawn ? 1 : 0,
transition: animate
? "opacity 250ms var(--ease-out) 400ms"
: undefined,
}}
>
Target {formatValue(target)} {unit}
</text>
{labels.map((text, i) => (
<text
key={i}
x={pad.left + i * slot + slot / 2}
y={height - 6}
textAnchor="middle"
fontSize={10}
className="fill-muted-foreground"
>
{text}
</text>
))}
</svg>
)}
</div>
);
}
function TargetLine({
x1,
x2,
y,
animate,
drawn,
}: {
x1: number;
x2: number;
y: number;
animate: boolean;
drawn: boolean;
}) {
const length = Math.abs(x2 - x1);
return (
<line
x1={x1}
x2={x2}
y1={y}
y2={y}
stroke="var(--warning)"
strokeWidth={1.5}
strokeDasharray={animate ? `${length}` : "5 4"}
strokeDashoffset={animate ? (drawn ? 0 : length) : 0}
style={
animate
? { transition: "stroke-dashoffset 500ms var(--ease-out) 250ms" }
: undefined
}
/>
);
}