Data Dashboard

Bar, donut, and line charts drawn with SVG

The dataset and approach

All three charts share a small hardcoded dataset — monthly sales figures for four product lines. Rather than writing SVG by hand, the charts are built entirely in JavaScript using document.createElementNS('http://www.w3.org/2000/svg', tagName). This namespace-aware version of createElement is required because SVG elements live in a different XML namespace than HTML elements — without it the browser creates HTML-flavored nodes that don't render as SVG. This technique is the core of Tutorial 14: Scripting SVG with JavaScript.

The palette stays on-brand: teal (#0f766e), green (#1f8a4c), orange (#e07b00), amber (#f0a500), and red (#d6452c).

Bar chart: the building function

The bar chart maps each month's total sales to a rectangle height. The SVG coordinate system grows downward, so a bar of height h must be positioned at y = chartHeight - h — otherwise all the bars would hang off the bottom edge. A <text> element floated just above each bar shows the value.

const SVG_NS = 'http://www.w3.org/2000/svg'; function svgEl(tag, attrs) { const el = document.createElementNS(SVG_NS, tag); for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v); return el; } function buildBarChart(svgId, data, color) { const svg = document.getElementById(svgId); const W = 300, H = 160, PAD = { top: 16, right: 12, bottom: 32, left: 36 }; const chartW = W - PAD.left - PAD.right; const chartH = H - PAD.top - PAD.bottom; const max = Math.max(...data.map(d => d.value)); const barW = chartW / data.length * 0.6; const barGap = chartW / data.length; data.forEach((d, i) => { const barH = (d.value / max) * chartH; const x = PAD.left + i * barGap + (barGap - barW) / 2; const y = PAD.top + chartH - barH; // Bar rectangle svg.appendChild(svgEl('rect', { x, y, width: barW, height: barH, fill: color })); // Value label above bar const lbl = svgEl('text', { x: x + barW / 2, y: y - 4, 'text-anchor': 'middle', 'font-size': '9', fill: '#333' }); lbl.textContent = d.value; svg.appendChild(lbl); // X-axis label const axLbl = svgEl('text', { x: x + barW / 2, y: H - PAD.bottom + 14, 'text-anchor': 'middle', 'font-size': '8', fill: '#555' }); axLbl.textContent = d.label; svg.appendChild(axLbl); }); // Baseline axis line svg.appendChild(svgEl('line', { x1: PAD.left, y1: PAD.top + chartH, x2: W - PAD.right, y2: PAD.top + chartH, stroke: '#ccc', 'stroke-width': '1' })); }

Donut chart: arcs from path data

A donut chart is a set of arc segments, each covering a fraction of a full circle. The math converts each value to an angle, then uses the SVG A (arc) command to draw from the end of the previous segment to the end of the current one. The arc is stroked on a circle rather than filled, creating the donut hole for free.

The large-arc-flag must be 1 whenever the segment spans more than 180° — otherwise the short-arc interpretation clips the shape. This is the same flag covered in Tutorial 04: Paths.

function polarToXY(cx, cy, r, angleDeg) { const rad = (angleDeg - 90) * Math.PI / 180; return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) }; } function buildDonutChart(svgId, slices, cx, cy, r) { const svg = document.getElementById(svgId); let startAngle = 0; const total = slices.reduce((s, sl) => s + sl.value, 0); slices.forEach(sl => { const sweep = (sl.value / total) * 360; const endAngle = startAngle + sweep; const start = polarToXY(cx, cy, r, startAngle); const end = polarToXY(cx, cy, r, endAngle); const largeArc = sweep > 180 ? 1 : 0; const path = svgEl('path', { d: `M ${start.x} ${start.y} A ${r} ${r} 0 ${largeArc} 1 ${end.x} ${end.y}`, fill: 'none', stroke: sl.color, 'stroke-width': '28', 'stroke-linecap': 'butt' }); svg.appendChild(path); startAngle = endAngle; }); }

Live demo

Monthly Sales — Bar Chart

Monthly Sales Bar Chart Bar chart showing monthly sales figures for January through June. Values: Jan 42, Feb 58, Mar 35, Apr 71, May 63, Jun 49.

Sales by Product — Donut Chart

Sales by Product Donut Chart Donut chart showing sales distribution across four products: Widgets 38%, Gadgets 27%, Tools 21%, Parts 14%.

Revenue Trend — Line Chart

Revenue Trend Line Chart Line chart showing revenue trend from Q1 to Q4. Values: Q1 $82k, Q2 $95k, Q3 $88k, Q4 $112k.

Accessibility in data charts

Each chart <svg> carries role="img" so assistive technology treats it as a single image rather than a collection of shapes. The first child of each SVG is a <title> naming the chart, followed by a <desc> that summarizes the key data in plain prose — a screen-reader user gets the information without having to navigate individual shapes. For richer interactivity, Tutorial 15 covers keyboard focus management and ARIA patterns for interactive charts.

The legend for the donut chart is built as a <dl> outside the SVG, so it is part of the normal reading order and fully accessible without SVG-specific workarounds.

How the namespace matters

The critical line in every chart-building function is:

const el = document.createElementNS('http://www.w3.org/2000/svg', tag);

If you use plain document.createElement('rect') instead, the browser creates an unknown HTML element. It parses fine, but the layout engine does not apply SVG rendering — the element is invisible. The namespace URI http://www.w3.org/2000/svg is how the browser knows to activate the SVG rendering pipeline for that node.