STA-002
Draw a line chart in SVG with no JavaScript
Server-rendered SVG with native tooltips. No chart library, no CDN, no build step — which is what makes it portable to a site you hand to someone else.
Chart libraries are a dependency you have to keep alive. They pull from a CDN that may be blocked, they break on major versions, and they mean a page that shows nothing when a script fails to load.
An SVG polyline is arithmetic. Scale each value into the plot area, join the points, draw four gridlines and three axis labels. It renders before any script has run, works with JavaScript disabled, prints correctly, and adds nothing to the page weight.
The one thing worth knowing is that SVG has native tooltips: a title element inside a shape shows on hover with no code at all.
function chart_line(array $series, int $w = 720, int $h = 220, array $colors = ['#2E6FD6', '#D9822B']): string
{
$padL = 44; $padR = 12; $padT = 12; $padB = 26;
$innerW = $w - $padL - $padR;
$innerH = $h - $padT - $padB;
$max = 1;
foreach ($series as $vals) {
foreach ($vals as $v) { $max = max($max, (int) $v); }
}
$labels = array_keys(reset($series) ?: []);
$n = max(1, count($labels));
$svg = '<svg viewBox="0 0 ' . $w . ' ' . $h . '" role="img" xmlns="http://www.w3.org/2000/svg">';
for ($g = 0; $g <= 4; $g++) {
$y = $padT + ($innerH * $g / 4);
$svg .= '<line x1="' . $padL . '" y1="' . round($y, 1) . '" x2="' . ($w - $padR) . '" y2="' . round($y, 1) . '" stroke="#C9D2DD"/>';
$svg .= '<text x="' . ($padL - 6) . '" y="' . round($y + 4, 1) . '" text-anchor="end" font-size="10" fill="#6B7B8F">'
. (int) round($max - ($max * $g / 4)) . '</text>';
}
$i = 0;
foreach ($series as $name => $vals) {
$pts = []; $x = 0;
foreach (array_values($vals) as $idx => $v) {
$x = $padL + ($n > 1 ? $innerW * $idx / ($n - 1) : $innerW / 2);
$y = $padT + $innerH - ($innerH * ((int) $v) / $max);
$pts[] = round($x, 1) . ',' . round($y, 1);
}
$svg .= '<polyline fill="none" stroke="' . $colors[$i % count($colors)] . '" stroke-width="2" points="' . implode(' ', $pts) . '">'
. '<title>' . htmlspecialchars((string) $name) . '</title></polyline>';
$i++;
}
return $svg . '</svg>';
}
Using it
Feed it a zero-filled date series so quiet days are plotted as zero rather than skipped.
Set width to 100% and height to auto in CSS and the viewBox makes it responsive with no extra work.
Label only the first, middle and last points on the x axis. Thirty rotated dates is noise.
What bites people
Guard against an all-zero series. Dividing by a maximum of zero produces NaN coordinates and an SVG that renders as nothing, with no error anywhere.
Escape any label that came from user data. An SVG title accepts markup like anything else on the page.