Chapter 39 — Charts, Canvas and SVG
Section 1 — Why we visualise data
A table of numbers can be accurate and still be hard to understand. A list of monthly sales figures, for example, may take a minute to read and still leave you unsure whether sales are rising, falling, or merely wobbling. A chart turns that same information into a shape your eye can grasp in seconds: a line climbing, bars getting taller, or one slice dwarfing the rest. That speed matters because most people do not study a dashboard the way they study a report. They glance at it, look for what changed, and decide what to do next. Visualisation exists to make that glance reliable, not merely decorative.
The key idea is that position, length, and area are easier for humans to compare than digits. Two bars side by side make a difference obvious even when the numbers differ by only a few percent, while two numbers in a column require deliberate subtraction. Colour can help, but it should support the shape rather than replace it, because colour alone is hard to order and easy to misread. A good chart therefore chooses the visual property that matches the question. If the question is how much, use length.
Charts also reveal patterns that tables hide. A sudden spike, a weekly rhythm, a gap where data is missing, or a value that breaks the trend will often jump out of a chart while remaining invisible in a column of figures. That is why charts are useful for exploration as well as presentation. A developer checking a new data feed, for instance, can spot a broken sensor or a duplicated day far faster from a line chart than from scrolling through rows. The chart does not replace the table, though. The table remains the precise record, and the best interfaces offer both: the chart for the pattern, and the table or tooltip for the exact value.
There are limits to what a chart should claim. A chart can make a small difference look enormous if its axis is cropped, and it can hide uncertainty if it draws a smooth line through noisy data. It can also overwhelm if it tries to show everything at once. A useful habit is to state the question before choosing the chart. What should the reader be able to answer after five seconds? Which values matter, and which are context? If you cannot answer those questions, you do not yet have a chart design; you have a pile of data looking for a purpose. Start from the question, choose the simplest shape that answers it, and add detail only when it helps that answer.
Finally, remember that a chart is an interface, not a picture. People may need its values read aloud, printed, copied, or viewed on a small phone screen. A chart that only works as a large, colourful image on a desktop monitor has failed part of its job. Plan from the start for a text alternative, for labels that stay legible at small sizes, and for numbers that can be reached without hovering. When you treat visualisation as communication with real readers, including readers who cannot see the chart at all, your charts become clearer for everyone, not just more accessible in a narrow sense.
Check: Why can a chart be easier to understand than a table of the same numbers?
Section 2 — Choosing the right chart type
Choosing a chart type is choosing what comparison you want the reader to make. A bar chart compares quantities, a line chart shows change over time, a pie chart shows shares of a whole, and a scatter plot shows the relationship between two quantities. Beginners often choose by appearance, picking whatever looks modern or impressive, but the right question is always what the reader needs to compare. If you get that comparison wrong, no amount of colour, animation, or polish will save the chart, because the reader will be comparing the wrong thing, or struggling to compare anything at all.
Bar charts are the most versatile and the most forgiving choice. Use them when you have categories, such as products, regions, or months, and a value for each. Horizontal bars work well when category names are long, because the labels have room to breathe on the left. Vertical bars work well when categories are short and the order matters, such as months in a year. Keep the number of bars manageable: beyond about twelve, a bar chart becomes a fence of similar shapes, and grouping, filtering, or a different view may serve better.
Line charts suit continuous change, especially over time. They show direction, slope, and turning points, which is why they dominate dashboards for sales, traffic, and measurements. Use one line for one series, and be careful with many lines: beyond three or four, lines tangle and colours blur. When you have many series, consider small multiples, which are many small charts with the same scale, one per series, so each trend can be read without competition. Area charts are line charts with the space below filled; they emphasise volume, but stacked areas can mislead, because only the bottom series sits on a flat baseline and the others ride on top of moving ones.
Pie charts deserve caution. They work only for a small number of shares of a single whole, such as three or four categories that add to one hundred percent. With more slices, small differences become impossible to judge, and the chart becomes a colourful puzzle. A donut chart, which is a pie with a hole, is slightly better for labelling, because the centre can hold a total, but it has the same limits. If precise comparison matters, a bar chart of the same shares will almost always be clearer. Do not use a pie chart for values that do not form a whole, such as unrelated totals from different years, because the shape will imply a whole that does not exist.
A practical workflow helps. First, write the question in one sentence. Second, name the comparison: bigger than, changing over time, share of, or related to. Third, pick the simplest chart that makes that comparison directly, and sketch it with real or realistic data before writing code. Fourth, check it with a fresh eye: can someone answer the question in five seconds, and can they get the exact value if they need it? If either answer is no, simplify, relabel, or change the chart type. Choosing well is mostly a matter of resisting the urge to be clever and serving the reader's actual question instead.
Check: A reader needs to compare sales across six regions: which chart type fits best, and why?
Section 3 — Canvas, SVG, and plain HTML charts
Front-end charts are usually drawn in one of three ways: with the canvas element, with SVG, or with plain HTML and CSS. Each approach has a different strength, and choosing between them is one of the first decisions in any chart project. Canvas is a bitmap drawing surface: you issue drawing commands in JavaScript, and the browser paints pixels. SVG is a vector document: you describe shapes as elements in the markup, and the browser draws them. HTML and CSS charts use ordinary elements, such as divs sized to represent values, which is simple and accessible but limited. None of these is best for every job; the right choice depends on how much data you have, how interactive the chart must be, and who needs to use it.
Canvas shines when there is a lot to draw. A scatter plot with ten thousand points, a real-time waveform, or a heatmap with thousands of cells can be painted on canvas far faster than the same picture built from ten thousand individual elements, because canvas has no per-point element to manage. It also suits animation and games, where the whole picture is redrawn many times per second. The trade-off is that canvas forgets what it drew. Once a shape is painted, it is just pixels; there is no element to hover, focus, or restyle.
SVG shines when the chart is a document of shapes that need to be individually interactive, styled, and accessible. Each bar, line, and label is an element, so you can attach a hover style, a focus handler, or a title, and you can inspect it in developer tools just like any other element. SVG scales cleanly to any screen size without blurring, because it is vector, and it can be styled with CSS. The trade-off is cost: thousands of SVG elements can slow a page, and very large datasets may need canvas instead. For most business charts, with dozens or hundreds of points, SVG is comfortable and pleasant to work with, and it is often the best default.
HTML and CSS charts are underrated for simple cases. A set of horizontal bars can be built from divs whose widths are percentages, styled with CSS, labelled with text, and read by screen readers with no extra work. Sparklines and progress-style indicators can be built this way too. The limits appear quickly: you cannot easily draw a smooth line chart, a scatter plot, or a curved area with divs, and positioning becomes fiddly. Still, for a simple bar display in a list or card, HTML and CSS may be the most robust choice, because it degrades gracefully, prints well, and needs no JavaScript at all.
A good rule of thumb is to start with the simplest approach that meets the need. Use HTML and CSS for simple bars and indicators. Use SVG for typical interactive charts with a manageable number of elements. Use canvas for large, fast, or constantly redrawing pictures. You can also mix them, which later sections explore: a canvas layer for dense data with an SVG layer for axes and labels, for example. The decision is not a matter of fashion; it is a trade between speed, interactivity, accessibility, and how much code you are willing to maintain.
Check: When would you choose canvas over SVG for a chart, and what do you give up?
Section 4 — Setting up a canvas elementA canvas starts as an empty element in your HTML, and its most common beginner mistake is confusing its two sizes. A canvas has an internal pixel size, set by its width and height attributes, and a displayed size, set by CSS. If you set only the CSS size, the browser stretches a small internal bitmap to fill it, and your chart looks blurry. If you set only the attributes, the displayed size may not match your layout. The correct habit is to set both deliberately: the attributes define how many pixels the drawing surface holds, and CSS defines how large it appears on the page.
A minimal setup looks like this:
<canvas id="chart" width="600" height="300"></canvas>
const canvas = document.getElementById("chart");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#2563eb";
ctx.fillRect(20, 20, 100, 50);
The getContext method returns a drawing context, an object with methods for drawing shapes, lines, text, and images. The "2d" context is the one used for ordinary charts; a "webgl" context exists for three-dimensional graphics, but it is a different world and not needed for chart work. Keep a reference to the context, because you will call its methods repeatedly. Note that the context is stateful: properties such as fillStyle, strokeStyle, and lineWidth stay set until you change them, which is convenient but also a source of bugs when a setting from one drawing step leaks into the next.
Before drawing, think about the drawing area. A chart rarely uses the full canvas edge to edge; it needs margins for axes, labels, and titles. A common pattern is to define a plot area inside the canvas, for example leaving forty pixels on the left for value labels and thirty at the bottom for category labels, and drawing the data only within that rectangle. Writing those margins as named variables, rather than scattering numbers through your code, makes the chart far easier to adjust later. It also makes the mapping from data to pixels explicit, which is the heart of every chart: a function that turns a value into a horizontal or vertical position.
Fallback content matters too. Anything placed between the opening and closing canvas tags is shown only if the browser does not support canvas, which is rare today, but the more important accessibility step is to provide a text alternative outside the canvas, such as a table of the same data or a summary paragraph. Give the canvas a role and a label where appropriate, and do not rely on the canvas alone to communicate. A chart that is only pixels is a chart that some readers cannot use at all, no matter how crisp it looks.
Finally, test the setup at real sizes. Open the page, resize the window, and check that the canvas does not stretch or squash unexpectedly. If your layout changes the canvas display size with CSS, you may need to resize the internal bitmap to match, and redraw. That resize-and-redraw cycle is a normal part of canvas work, not a failure of your code. Getting the setup right, with clear margins, a stored context, and a text alternative, gives every later section in this chapter a solid foundation to build on.
Check: Why can a canvas chart look blurry even when its CSS size is correct?
Section 5 — Drawing shapes on canvas
Canvas drawing begins with rectangles, and rectangles alone can build a surprising number of charts. The fillRect method draws a filled rectangle, strokeRect draws only its outline, and clearRect erases a rectangle to transparency. Each takes four numbers: the x and y position of the top-left corner, and the width and height. Remember that canvas coordinates start at the top-left corner, with x increasing to the right and y increasing downwards. That last point catches many beginners, because chart values increase upwards while canvas y increases downwards, so converting a value to a y position usually involves subtracting from the plot height rather than adding to it.
Circles and arcs use a different method, because canvas has no fillCircle. You begin a path with beginPath, describe an arc with the arc method, and then fill or stroke it. The arc method takes a centre, a radius, a start angle, and an end angle, measured in radians, with zero pointing to the right and increasing clockwise, because y points down. A full circle therefore runs from zero to two times pi. This may feel awkward at first, but it becomes natural with practice, and it is the basis for pie charts, donut charts, scatter plots with round markers, and gauges.
Lines and polygons are built from paths too. You move to a starting point with moveTo, draw straight segments with lineTo, and then stroke the path to make it visible or fill it to make a solid shape. A triangle, for example, is three lineTo calls followed by closePath, which draws a line back to the start. Paths are the general tool in canvas: bars, lines, areas, and custom markers are all paths or rectangles, and learning to build and reuse them is the core canvas skill. Set strokeStyle for the line colour, lineWidth for its thickness, and lineJoin for how corners are handled, before stroking, because those properties belong to the context state at the moment you stroke.
Colour and style deserve the same care as shape. The fillStyle and strokeStyle properties accept colour strings, gradients, and patterns. Gradients are created with createLinearGradient or createRadialGradient, given colour stops, and then assigned as a fill style. They can make a chart look polished, but use them sparingly in data displays, because a gradient can make equal values look different at different positions. For most charts, flat colours with good contrast are clearer and more honest. Save gradients for backgrounds or decorative elements, and test them in greyscale to check that the chart still reads without colour.
A useful exercise is to draw a small scene by hand: a few rectangles of different heights, a circle, a triangle, and a line, each with a deliberate colour. Then change one property at a time and redraw, watching what changes and what stays the same. That experiment teaches the stateful nature of the context faster than reading about it. If a shape appears in the wrong colour, check what fillStyle was set most recently before it was drawn. If a shape is missing, check whether you forgot to fill or stroke its path. Most canvas bugs are one of those two mistakes, and both are fixed by reading your drawing code in order, as the browser executes it.
Check: Why must you call beginPath before drawing a new canvas shape?
Section 6 — Paths, lines, and curves
Paths are the canvas tool for everything that is not a rectangle. A path is a sequence of moves and lines that you describe, and then make visible by stroking or filling. You start with beginPath, move the pen with moveTo, add segments with lineTo, and finish with stroke or fill. Until you stroke or fill, nothing appears on screen, which surprises beginners who expect each lineTo to draw immediately. Think of a path as a plan, and stroke and fill as the acts that execute it. You can build a long, complex plan, change the stroke colour, and then execute it once, which is efficient and gives you control over the final appearance.
Straight lines are only the beginning. The quadraticCurveTo method draws a curve with one control point, which pulls the line towards itself like a magnet, and bezierCurveTo uses two control points for finer control. These curves are essential for smooth line charts, rounded shapes, and decorative elements. They are also the reason many charts look hand-tuned: a curve that passes near its control points but not through them can smooth a jagged series without inventing data, provided you are honest about what smoothing does.
Stroke styling changes how a path reads. The lineWidth property sets thickness in pixels, lineCap sets whether ends are flat, round, or squared off, and lineJoin sets how corners are joined. Round caps and joins often look friendlier for charts, while square joins suit crisp, technical displays. The setLineDash method lets you draw dashed lines, which are useful for gridlines, targets, and projections, because a dashed line reads as secondary or hypothetical rather than measured. Reset the dash with an empty array when you return to solid data lines, or your next data line will be dashed by accident, a classic state-leak bug.
Filling a path has its own rules. When a path crosses itself or contains holes, the fill rule decides which regions are inside. The default nonzero rule and the evenodd rule differ in such cases, and fill accepts a rule as an optional argument. For most chart work, simple non-crossing paths make the rule irrelevant, but donut shapes and overlapping areas are exceptions worth knowing about. A donut, for example, can be built as a path with an outer circle and an inner circle in opposite directions, or more simply as a thick stroked circle, which later sections use. Choose the simpler construction when it gives the same picture, because simpler code is easier to verify.
A practical habit is to write small path helper functions: one that draws a polyline through an array of points, one that draws a smooth curve through points, and one that closes a shape for filling. Helpers keep your chart code readable, because the chart logic, such as scaling values to pixels, stays separate from the drawing mechanics. They also make bugs local: if every line in the chart is wrong in the same way, the helper is the place to look. Test helpers with tiny inputs, such as two points or three points, before trusting them with real data, and draw their control points as dots during development if a curve misbehaves. Seeing the geometry is usually faster than reasoning about it blind.
Check: What is the difference between describing a path and stroking it?
Section 7 — Canvas text and stylingText on canvas is drawn with fillText and strokeText, and it behaves differently from HTML text in ways that matter for charts. Canvas text is painted as pixels, so it cannot be selected, searched, or resized by the browser's text controls, and it does not wrap automatically. You must give it a string, an x position, and a y position, and the browser paints it there. That simplicity is useful for axis labels and values, but it means you are responsible for measuring, positioning, and keeping text legible.
The font property controls the typeface, size, and weight in a single CSS-like string, such as "14px sans-serif" or "bold 12px system-ui". Set it before drawing text, because it is part of the context state, just like fillStyle. The textAlign property sets horizontal alignment relative to the x position: left, right, centre, or start and end, which respect text direction. The textBaseline property sets vertical alignment: top, middle, bottom, or alphabetic, which is the default and sits text on its baseline. For chart labels, centre alignment with a middle baseline is often easiest for values above bars, while right alignment suits value labels on a vertical axis.
Measuring text is essential for avoiding overlaps. The measureText method returns an object with a width property for the given string in the current font, which lets you centre a label, check whether it fits in a bar, or decide to shorten it. It does not give you a reliable height in all browsers without extra metrics, so allow generous vertical spacing rather than packing labels tightly. A good practice is to draw labels in a second pass, after the data shapes, so that text sits on top and is never hidden behind a bar. Also keep label colours dark enough against the background, and avoid placing text directly on a busy pattern or gradient.
Styling text well is mostly about restraint. Use one or two font sizes in a chart: a slightly larger size for a title or total, and a single size for axis labels and values. Use weight, not colour alone, to emphasise a total or a key value. Avoid rotated text where possible, because it is hard to read and hard to position, but when category labels are long, rotating them or switching to horizontal bars is better than letting them overlap. If you rotate, save the context, translate to the label position, rotate, draw the text, and restore the context, so the rotation does not leak into later drawing.
Finally, remember that canvas text is not a substitute for accessible text. A screen reader will not read your painted axis labels, so provide the same values in a table or a summary. Canvas text is a visual aid for sighted readers, and it should be treated as such. Check your chart by zooming the page, by viewing it on a small screen, and by turning off colour: if the labels still read clearly and the values are available elsewhere, your text handling is doing its job. If labels overlap, blur, or disappear at small sizes, simplify the labels before reaching for a smaller font.
Check: Why must you measure canvas text yourself instead of relying on the browser to wrap it?
Section 8 — Coordinates, scales, and margins
Every chart is a mapping from data values to screen positions, and that mapping is called a scale. A scale takes a value in the data domain, such as sales from zero to one thousand, and returns a position in the pixel range, such as the height of the plot area. Understanding scales explicitly, rather than scattering arithmetic through drawing code, is what separates a chart that is easy to adjust from one that breaks whenever the data changes. Write scale functions, test them with known values, and let all drawing go through them, so a change in data range or canvas size touches one place, not dozens.
A linear scale is the simplest and most common. For a vertical bar chart, you might map zero to the bottom of the plot area and the maximum value to the top. Because canvas y increases downwards, the formula usually subtracts: the y position equals the plot bottom minus the fraction of the maximum multiplied by the plot height. For a horizontal position, categories are often spaced evenly: divide the plot width by the number of categories, and centre each bar in its slot. Keep the plot area as named values, such as plotLeft, plotTop, plotWidth, and plotHeight, derived from the canvas size minus margins.
Choosing the data domain deserves care. For bar charts, the domain should usually start at zero, because bar length encodes value. For line charts, starting at zero is often less critical, because slope and change are the message, but cropping the axis can still exaggerate small changes, so label the axis clearly and consider whether a zero baseline would be more honest. Always include the actual maximum, with a little headroom, so the largest bar or point does not touch the top edge and its label has room. If your data includes negative values, the scale must handle them: the zero line sits inside the plot area, bars grow up or down from it, and the domain spans from the minimum to the maximum.
Other scales exist for other data. A logarithmic scale is useful when values span several orders of magnitude, such as website traffic from tiny pages to huge ones, but it must be labelled clearly, because equal distances no longer mean equal differences. A time scale maps dates to positions, handling uneven gaps between measurements correctly, whereas treating dates as evenly spaced categories would hide those gaps. A categorical scale maps names to slots, with no arithmetic meaning between them. You do not need a library to implement a simple linear or categorical scale, and writing one by hand teaches you what libraries do for you, which makes their behaviour less mysterious later.
Test your scales with edge cases: zero, the maximum, a negative value, and a single data point. A single point should still produce a sensible chart, not a division by zero or a bar of infinite width. A dataset where all values are equal should not collapse to a flat line at the top or bottom without explanation. These edge cases are where hand-written scales most often fail, and they are also where real data most often surprises you. A scale that survives them is a scale you can trust with live data.
Check: In a vertical bar chart, why is the y position usually calculated by subtracting from the plot bottom?
Section 9 — Drawing a bar chart on canvasA bar chart on canvas is a good first complete chart, because it uses rectangles, scales, and labels, and nothing else exotic. Start with data as an array of objects, each with a label and a value, such as months and sales. Compute the maximum value, define margins and a plot area, and write two scale helpers: one that returns the x position and width for a category index, and one that returns the y position and height for a value. With those helpers, drawing is a loop: for each item, fill a rectangle, then draw its label below the plot and its value above the bar.
Bar width and spacing need a decision. Divide the plot width by the number of bars to get a slot, then use a fraction of that slot, such as sixty percent, for the bar itself, leaving the rest as gaps. If the bars look crowded, reduce the fraction; if they look lost, increase it. Do not let bars touch each other, because touching bars merge visually into a single block, and do not make gaps so wide that the chart feels empty.
Axes and gridlines turn a row of rectangles into a readable chart. Draw a baseline along the bottom of the plot area, and horizontal gridlines at a few round values, such as every quarter of the maximum, with small labels at the left. Round values matter: gridlines at 0, 250, 500, and 750 are easier to read than lines at 0, 237, 474, and 711, even if the latter are mathematically tidy. You can compute a nice step by rounding the maximum up to a round number first, then dividing it. Keep gridlines light, using a pale grey and a thin line width, so the bars remain the focus and the grid recedes into the background.
Labels need the same care as the bars. Category labels below the axis should be centred under each bar, using textAlign set to centre. Value labels above each bar help readers who need exact figures, but they can clutter a chart with many bars; in that case, show values only on hover or in a table, and keep the chart itself clean. If a category label is long, shorten it, wrap it, or switch to horizontal bars, rather than letting labels collide. Check the result at the actual display size, not just in a large development window, because a chart that reads well at six hundred pixels wide may fail at three hundred.
Finally, handle the data honestly. If a value is missing, do not draw a zero-height bar as if the value were zero; leave a gap and note the missing data, or the chart will invent a fact. If values change, clear the canvas and redraw the whole chart from the new data, rather than trying to patch individual bars, because full redraws are simple and reliable for charts of this size. When the chart is drawn, compare it against the data by hand for at least one bar: does its height match its value relative to the maximum, and does its label match its category? That single check catches most scaling errors.
Check: Why should bar chart gridlines use round values rather than exact fractions of the maximum?
Section 10 — Drawing a line chart on canvas
A line chart on canvas is a path through scaled points, and its clarity depends on getting the points right before worrying about style. Begin with an array of points, each with an x value, often a date or a category, and a y value. Map the x values across the plot width and the y values through a linear scale, just as in the bar chart, then build a path: move to the first point, draw lines to each subsequent point, and stroke the path once. Stroking once, rather than stroking segment by segment, gives clean joins and a consistent line width, and it keeps the code simple. Add small circular markers at each point if readers need to see individual measurements, but keep markers small so they do not dominate the line.
The x scale deserves attention, because time is not always evenly spaced. If your measurements are daily but a week is missing, spacing points evenly by index will hide the gap and imply a steady progression that did not happen.
Styling a line chart is a balance between emphasis and restraint. Use a line width of two or three pixels for the main series, a colour with good contrast against the background, and round joins and caps so the line looks smooth at its corners. Gridlines and axis labels follow the same rules as the bar chart: light gridlines, round values, and a clear baseline. If you fill the area under the line, you have made an area chart, which emphasises volume; that can be effective for a single series, but it can also imply that the filled area itself is the quantity, so use it deliberately. For multiple series, use distinct hues and, ideally, distinct marker shapes or dash patterns as well, so the series remain distinguishable without colour alone.
Interactivity for a line chart usually starts with a tooltip: when the pointer moves near a point, show its exact value. On canvas, that requires finding the nearest point to the pointer's x position, because there is no element to hover. A simple approach is to convert the pointer's canvas coordinates, find the point with the closest scaled x, and draw a highlight circle and a label box for it. Keep the tooltip inside the canvas bounds, flipping it to the other side of the point when it would overflow. This is more code than an SVG hover, but it is straightforward once the scale functions exist, because the same mapping works in reverse.
Common mistakes include smoothing a line so aggressively that peaks disappear, drawing a line through missing data as if it were present, and using a cropped y-axis that turns a gentle slope into a cliff. Check your line chart by reading its story aloud: does it rise, fall, level off, or spike where the data says it does? Does the steepest segment correspond to the largest change in the data, not merely to a stretched axis? If the story you read from the picture matches the story in the numbers, the chart is doing its job. If it does not, adjust the scale or the styling before adding features.
Check: Why should dates be mapped by their actual time difference rather than by their index in the array?
Section 11 — Pie and donut charts on canvasPie charts on canvas are built from arcs, and the maths is simpler than it looks. Each slice's angle is its share of the total multiplied by a full circle, which is two times pi radians. Start at the top, which in canvas terms is an angle of minus half pi, because zero points to the right, and draw each slice in turn: move to the centre, describe an arc from the current angle to the current angle plus the slice angle, close the path back to the centre, and fill it.
A donut chart is a pie chart with a hole, and on canvas the easiest construction is a thick stroked circle rather than a filled pie with a hole cut out. For each slice, stroke an arc with a large lineWidth, centred on the chart centre, so the stroke itself forms a ring segment.
Labelling is where pie and donut charts most often fail. Placing labels inside thin slices makes text unreadable, and placing labels outside with leader lines can create a tangle. A practical rule is to label only slices large enough to hold their label, and to give the rest a legend with the exact values. Better still, if you have many small slices, group them into an "Other" slice and break that group out in a table or a separate bar chart. A pie with eight slices, three of which are slivers, is not informative; it is decorative, and it asks the reader to judge tiny angles, which humans do poorly. Keep pies to a handful of slices, and let a legend carry the precise numbers.
Colour choice matters more in a pie than in a bar chart, because slices touch each other and share borders. Use colours that are distinct in lightness as well as hue, so adjacent slices do not blend, and consider a thin white stroke around each slice to separate them cleanly. Do not rely on a rainbow of saturated colours, which can look garish and can be hard for colour-blind readers to distinguish. Test the chart in greyscale: if two adjacent slices become indistinguishable, change one of them. Also remember that a pie chart implies parts of a whole, so check that your values actually sum to a meaningful total before drawing one; if they do not, a bar chart is the honest choice.
A final check is to verify the angles by hand for a simple case. Two equal values should give two halves, three equal values should give three thirds, and a value that is half the total should give a semicircle. If your chart fails those checks, the angle maths or the starting angle is wrong, and the error will be visible in every slice. Also confirm that the slices cover the full circle without a gap or an overlap at the join, because a small rounding error at the final slice can leave a noticeable wedge of background. Drawing the last slice to the full circle, rather than to a computed end angle, is a simple way to close that gap.
Check: Why is a donut chart often built from thick stroked arcs rather than filled pie slices?
Section 12 — Animating a canvas chart
Canvas animation works by redrawing the picture repeatedly, usually once per frame, with slightly changed values each time. The browser provides requestAnimationFrame for this purpose: you pass it a function, and the browser calls that function before the next repaint, typically about sixty times per second. Inside the function, you clear the canvas, draw the chart at its current animated state, and request the next frame if the animation is not finished. That loop is the whole mechanism, and it applies to charts, games, and any canvas motion. The key discipline is to keep each frame's drawing fast and to base the animation on elapsed time, not on frame count, so it runs at the same speed on fast and slow devices.
A simple chart animation is a bar chart growing from zero to its final heights. For each bar, compute a progress value between zero and one based on elapsed time divided by the total duration, apply an easing function to that progress, and multiply the final height by the eased progress. Easing changes the feel: a linear progress grows at a constant speed, while an ease-out progress starts fast and slows as it approaches its target, which usually looks more natural for bars settling into place.
The animation loop needs a clear stopping condition, or it will run forever, consuming battery and processor time for no benefit. When elapsed time reaches the duration, draw the final state exactly, and do not request another frame. If the data changes mid-animation, decide whether to restart from the current state or jump to the new final state; restarting from the current animated values usually looks smoother than jumping back to zero. Also handle the case where the chart is removed from the page or hidden: cancel the animation frame when the component is destroyed or the tab is hidden, so you are not drawing a chart nobody can see.
Performance in canvas animation is mostly about avoiding unnecessary work. Clear only what you need to, or clear the whole canvas if the chart is small, which is usually simplest and fast enough. Avoid creating gradients, patterns, or large objects inside the frame loop; create them once outside it and reuse them. Avoid measuring text in every frame if the labels do not change; measure once and reuse the widths. For charts with many points, consider drawing the static parts, such as axes and gridlines, to an offscreen canvas once, and copying that image each frame, while only the animated data layer is redrawn. That separation can turn a sluggish animation into a smooth one.
Accessibility and restraint matter here too. Some readers find motion distracting or uncomfortable, so respect the prefers-reduced-motion setting: when it is active, skip the entrance animation and draw the final chart immediately. Do not animate a chart continuously, such as bars bouncing forever, because that prevents the reader from focusing on values. Use animation for transitions: entrance, data updates, and sorting, where motion helps the reader track what changed. If you cannot explain what the animation helps the reader understand, it is decoration, and it should be removed or shortened.
Check: Why should canvas animation be based on elapsed time rather than on frame count?
Section 13 — Canvas interactivity and hit detectionInteractivity on canvas is manual, because the canvas does not know what it drew. When a user clicks or hovers, the browser gives you a pointer position, and your code must decide what, if anything, that position corresponds to. That decision is called hit detection, and it is the central challenge of canvas interaction. For rectangles, such as bars, hit detection is simple: check whether the pointer's x and y fall within the bar's rectangle, using the same scale functions that drew it. For circles, check whether the distance from the pointer to the centre is less than the radius.
Pointer coordinates need conversion before any of those checks work. The event gives coordinates relative to the viewport, and you need coordinates relative to the canvas, taking into account the canvas's position on the page, its displayed size, and its internal pixel size. The standard approach is to use getBoundingClientRect to find the canvas's position and size on screen, subtract the left and top from the event's client coordinates, and scale the result by the ratio of internal size to displayed size.
Once you know what was hit, you need feedback. The usual pattern is to redraw the chart with the hovered element highlighted, perhaps brighter, with an outline, or slightly enlarged, and to show a tooltip with its exact value. Redrawing on every pointer move can be expensive for large charts, so a common optimisation is to redraw only when the hovered element changes, not on every move event. Also set the canvas cursor style to pointer when hovering something clickable, because that signal tells the user the chart is interactive. Without a cursor change or a highlight, users will not discover that the chart responds at all.
Keyboard interaction deserves equal attention, because a chart that only responds to a mouse excludes keyboard users. Since canvas has no focusable elements, you must provide keyboard support yourself, for example by making the canvas focusable and letting arrow keys move a selection between bars or points, with the selected element highlighted and its value announced. A simpler and often better approach is to provide the same data in a focusable list or table alongside the canvas, so keyboard users can reach every value without custom canvas keyboard handling. That companion table also serves screen reader users, which is why it appears repeatedly in this chapter: it is not an extra, it is part of the chart.
Touch devices add another consideration. Fingers are less precise than mice, so hit areas should be generous, and tooltips should not rely on hover, which does not exist in the same way on touch. A tap should select a point and show its value until another tap or a tap elsewhere dismisses it. Test hit detection on a real phone, not only with a mouse, and check that tapping near a bar selects the intended bar rather than its neighbour. If your bars are narrow, consider a mode where tapping anywhere in a bar's slot selects that bar, which is far more forgiving than requiring a tap on the bar's exact pixels.
Check: Why does canvas hit detection require converting pointer coordinates before checking shapes?
Section 14 — Canvas performance and sharpness
Canvas performance and sharpness share a root cause: the relationship between the canvas's internal pixel size and its displayed size. On a standard display, one CSS pixel equals one device pixel, but on a high-density display, such as a modern phone or a laptop with a scaled screen, one CSS pixel may correspond to two or three device pixels. If your canvas internal size equals its CSS size, the browser stretches a low-resolution bitmap to fill the display, and lines and text look soft. The fix is to size the internal bitmap by the device pixel ratio, scale the drawing context by that ratio, and then draw in CSS pixel coordinates. That gives crisp lines on all screens, at the cost of more pixels to paint, which is usually a good trade for charts.
The setup looks like this in outline: read window.devicePixelRatio, set the canvas width and height attributes to the CSS size multiplied by that ratio, set the CSS size to the intended display size, and call ctx.scale with the ratio. After that, all your existing drawing code, written in CSS pixels, works unchanged, but the output is sharper.
Performance follows from how much you draw and how often. Canvas is fast, but it is not free: thousands of shapes, expensive shadows, and gradients redrawn every frame can still slow a page, especially on a phone. Avoid shadowBlur in animated or large charts, because shadows are costly to compute. Prefer flat fills and simple strokes. Batch drawing by style: set a fill colour once and draw all shapes of that colour, rather than switching colour for every shape, because state changes have a cost, even if it is small. For static charts, none of this matters much, because the chart is drawn once; for animated or interactive charts that redraw on every pointer move, it matters a great deal.
A further technique is to separate layers. Static elements, such as axes, gridlines, and labels, can be drawn once to an offscreen canvas, and each frame can copy that image with drawImage, which is fast, while only the changing data is redrawn. Another technique is to reduce the number of elements: a scatter plot with fifty thousand points may be better served by drawing points as single pixels, by sampling, or by aggregating into a heatmap, rather than drawing fifty thousand circles. The reader cannot distinguish fifty thousand overlapping circles anyway, so the extra work buys nothing. Choose the representation that matches what the eye can actually resolve.
Finally, measure rather than guess. Use the browser's performance tools to check frame times during animation, and watch for dropped frames or long tasks. If a chart redraw takes more than a few milliseconds, profile which part is slow before optimising blindly. Often the culprit is something unexpected, such as measuring text in a loop or creating a gradient per shape. A chart that draws in a few milliseconds and stays crisp on a phone screen is the goal, and it is reachable with the habits in this section: size for the device pixel ratio, avoid expensive effects in hot paths, separate static from changing layers, and reduce data to what the eye can use.
Check: Why does a canvas need an internal size larger than its CSS size on a high-density screen?
Section 15 — Canvas accessibility and its limits
Canvas has a fundamental accessibility limit: its content is pixels, not elements. A screen reader encountering a canvas element finds no bars, lines, or labels inside it, only whatever fallback content or accessible name you provide. That means a canvas chart, on its own, is invisible to many assistive technologies, no matter how well drawn it is. This is not a reason to avoid canvas, because canvas is the right tool for dense or fast charts, but it is a reason to treat the canvas as only the visual layer of the chart, with a text layer alongside it that carries the same information in a form assistive technology can use.
The most robust text layer is a table of the data. A table is natively accessible: screen readers can navigate its rows and columns, users can search it, and it works without JavaScript. Place the table near the chart, perhaps in a collapsible details element if space is tight, and make sure it is kept in sync with the chart data, because a stale table is worse than no table, as it actively misleads. A short summary paragraph can complement the table by stating the main pattern in words, such as which category is largest and whether the trend rises or falls, which helps readers who want the message without reading every row.
For the canvas element itself, provide an accessible name and description. A role of img with an aria-label, or an aria-describedby pointing to the summary, tells assistive technology that the canvas is an image with a description, rather than an empty element. Do not put the full dataset into an aria-label, because that becomes an unreadable wall of text; keep the label short and let the table carry the detail. If the chart is interactive, the interactive controls, such as buttons to switch series or time ranges, should be real HTML buttons outside the canvas, not painted buttons inside it, because painted buttons cannot receive focus or be activated by keyboard.
Colour and contrast limits apply to canvas just as they do to any visual. Do not encode the only difference between two series in colour, because colour-blind readers and greyscale prints will lose it. Use different marker shapes, dash patterns, or direct labels as well. Check contrast between data colours and the background, and between label text and the background, using the same standards you would apply to HTML text. A chart that relies on pale yellow bars on a white background may look elegant, but it fails readers with low vision and anyone viewing it in bright light.
Finally, test accessibility rather than assuming it. Navigate the page with a keyboard alone, and confirm you can reach every value and control. Use a screen reader, even briefly, to hear what the chart announces, and confirm it is the summary and table, not silence. View the chart in greyscale and at high zoom, and confirm it still reads. These checks take minutes and catch problems that are invisible to a sighted mouse user. A canvas chart that passes them is not merely compliant; it is genuinely usable by more people, which is the point of building it in the first place.
Check: Why is a data table the most robust text alternative for a canvas chart?
Section 16 — SVG basics
SVG, Scalable Vector Graphics, is a way of describing pictures as elements in HTML, rather than as pixels painted by code. An SVG document is a set of elements, such as rect, circle, line, path, and text, each with attributes that say where it sits, how large it is, and how it looks. Because those elements live in the page, they can be styled with CSS, inspected in developer tools, given event handlers, and read, in part, by assistive technology. That document-like nature is the core difference from canvas: an SVG chart is a structure you can inspect and interact with element by element, not a flat image you must interpret yourself.
A minimal SVG looks like this:
<svg width="300" height="150" viewBox="0 0 300 150">
<rect x="20" y="20" width="100" height="50" fill="#2563eb"></rect>
<text x="20" y="90">Hello</text>
</svg>
The svg element is the container, and its children are the shapes. The width and height attributes set the displayed size, while the viewBox attribute defines the internal coordinate system, which this chapter explains in detail later. For now, note that shapes are placed with coordinates in that system, and that the browser scales the whole picture to fit the displayed size.
SVG can be used in two main ways: inline in HTML, or as an external image file. Inline SVG, written directly in the page, is the way to build charts, because the elements are part of the page's document, so scripts can create and update them, and styles can target them. External SVG, loaded through an img element, is treated as an image, and its internal elements are not accessible to page scripts or styles. That distinction matters: an icon can be an external image, but a chart that needs interaction or live updates should be inline. Keep the SVG in the same document as the code that updates it, and you keep all the benefits of the element model.
Namespaces are a detail that occasionally confuses beginners. When SVG is written inline in HTML, the browser handles the namespace automatically, and you do not need to declare it. When you create SVG elements in JavaScript, however, you must use createElementNS with the SVG namespace, not the ordinary createElement, or the element will be created in the wrong namespace and will not render. That is a common bug in dynamically generated SVG charts, and it produces the puzzling symptom of elements that appear in developer tools but draw nothing on screen. Remembering the namespace rule saves a frustrating debugging session.
Finally, think of SVG as a language for shapes, not as a charting library. It gives you rectangles, circles, paths, and text, but it does not give you scales, axes, or data binding; you build those yourself, or you use a library that builds them for you. That is a strength, because it keeps SVG charts transparent and customisable, but it also means you must write the mapping from data to coordinates, just as with canvas. The reward is a chart whose parts are real elements, with all the styling, interaction, and accessibility advantages that brings, and that reward is worth the extra structure for most ordinary charts.
Check: Why should an interactive chart use inline SVG rather than SVG loaded as an image?
Section 17 — SVG shapes
SVG provides a set of basic shape elements, each with attributes that describe its geometry. The rect element draws a rectangle, with x and y for its top-left corner, width and height for its size, and optional rx and ry for rounded corners. The circle element draws a circle, with cx and cy for its centre and r for its radius. The ellipse element is like a circle with separate horizontal and vertical radii, rx and ry. The line element draws a straight segment between two points, with x1, y1, x2, and y2. The polyline element draws a connected series of straight segments from a list of points, and the polygon element is like a polyline that closes itself back to the start.
These shapes are styled with presentation attributes and CSS properties. The fill property sets the interior colour, stroke sets the outline colour, and stroke-width sets the outline thickness. Setting fill to none makes a shape hollow, which is useful for outlines and for donut constructions.
Grouping shapes is essential for charts. The g element groups other elements, so you can apply a transform, a style, or an event handler to the whole group at once. A bar chart, for example, might group each bar with its label, so moving or highlighting the group moves or highlights both. Groups can be nested, and transforms on a group affect all its children, which simplifies positioning: you can translate a group to the plot origin once, and then draw all data shapes relative to that origin, rather than adding the margin to every coordinate. That pattern, a translated group for the plot area, appears in almost every hand-built SVG chart, and it is worth adopting early.
Shapes in SVG are also individually addressable, which changes how you think about updates. To change a bar's height, you set its height attribute, or its y attribute, and the browser redraws it; you do not need to clear and redraw the whole chart. To highlight a bar on hover, you can add a class, and CSS handles the rest. To remove a bar, you remove its element. That directness makes SVG charts easy to reason about and easy to debug, because you can inspect a bar in developer tools, see its attributes, and change them live to test a fix. It also makes animations straightforward, because CSS transitions can animate many SVG attributes and properties.
A common mistake is to treat SVG coordinates as if they were CSS layout. SVG elements do not participate in HTML layout in the same way: they are positioned by their coordinates inside the SVG viewport, not by margins or flexbox, and text does not wrap. Another mistake is to forget that SVG's y axis also increases downwards, just like canvas, so the same subtraction trick is needed for bar heights. Keep a consistent mental model: SVG is a drawing surface with its own coordinate system, populated by elements, and your job is to compute coordinates from data, just as with canvas, but with the benefit that each shape remains a real element after it is drawn.
Check: What does the g element let you do to a set of SVG shapes at once?
Section 18 — SVG paths
The path element is the most powerful shape in SVG, because it can describe almost any outline with a compact string of commands. A path's d attribute is a sequence of instructions: M moves to a point, L draws a straight line to a point, H and V draw horizontal and vertical lines, C draws a cubic curve, Q draws a quadratic curve, A draws an arc, and Z closes the path back to its start. Uppercase commands use absolute coordinates, while lowercase commands use coordinates relative to the current point. That compactness makes paths ideal for line charts, area charts, and custom shapes, but it also makes path strings hard to read, so generating them from data with code, rather than writing them by hand, is the normal approach.
A line chart in SVG is a single path whose d attribute is built from scaled points. Start with M and the first point, then L and each subsequent point, joined by spaces, and stroke the path with no fill.
Arcs deserve special attention, because they are how pie and donut slices are built in SVG, and their syntax is the most confusing. An arc command takes two radii, a rotation, two flags, and an end point. The flags decide whether the arc is the large or small way around, and whether it sweeps clockwise or anticlockwise. For a pie slice, you move to the centre, draw a line to the arc start, draw an arc to the arc end, and close back to the centre. Getting the flags wrong produces a slice that wraps the wrong way around the circle, which is a distinctive and easily recognised bug. A useful strategy is to write a helper function that takes a centre, a radius, and two angles, and returns the correct path string, handling the flags internally, so chart code never deals with arc flags directly.
Paths can also be styled and animated in ways that rectangles cannot. The stroke-dasharray property makes a path dashed, which is useful for gridlines and projections, and the stroke-dashoffset property can animate a line drawing itself, a technique later sections explore. The fill-rule property controls how overlapping subpaths are filled, which matters for donut shapes built as a single path with two circles. Because paths are elements, they can receive hover and focus styles, and they can be targeted by CSS, which means a line chart's path can highlight on hover without any JavaScript at all, simply by changing its stroke width or colour in a stylesheet.
Debugging paths is easier if you build them incrementally. Start with two points and confirm the line appears, then add points one at a time, or log the generated d string and inspect it. If a path does not appear, check for a missing M at the start, a typo in a command letter, or a fill and stroke both set to none, which makes the path invisible. If a path appears in the wrong place, check whether its coordinates are in the SVG's coordinate system or whether a group transform is shifting them unexpectedly. Paths reward careful construction, and once you have reliable helpers for lines, areas, and arcs, most SVG chart types become assemblies of those helpers.
Check: How is a line chart path different from an area chart path in SVG?
Section 19 — Groups, transforms, and layers
Transforms in SVG let you move, scale, rotate, and skew elements, and they are the key to clean chart layouts. The transform attribute accepts functions such as translate, scale, rotate, and skewX, which can be combined. The most useful for charts is translate, which shifts an element or group by a given x and y. By wrapping the plot area in a group with a translate transform equal to the margins, you can draw all data shapes relative to a local origin at the plot's bottom-left or top-left, rather than adding margins to every coordinate. That single decision removes a whole class of off-by-margin bugs and makes the code read like the chart's logic rather than its arithmetic.
Transforms compose: a transform on a group applies to all its children, and a transform on a child applies on top of the group's transform. That nesting is powerful, but it can also confuse, because the final position of an element is the result of all its ancestors' transforms. Keep nesting shallow for charts: one group for the whole chart, one for the plot area, and perhaps one per series or per bar-and-label pair. Avoid deep transform chains unless you have a clear reason, because each layer makes debugging harder.
Layers in SVG are determined by document order: later elements are drawn on top of earlier ones. There is no z-index for SVG children in the same way as for HTML, although CSS can influence stacking in some cases, so order matters. Draw gridlines first, then data shapes, then axes, then labels, so labels are never hidden behind bars. If you need to bring an element to the front on hover, you can move it later in the document, or you can rely on a highlight that does not require reordering, such as an outline or a tooltip element that is always last. Planning the draw order at the start prevents the frustrating bug where a label is drawn but hidden behind a bar.
Transforms also enable techniques such as flipping the y axis. Some developers prefer to work in a coordinate system where y increases upwards, as in mathematics, by translating to the plot bottom and scaling y by minus one. That makes bar maths more intuitive, because a value maps directly to a height, but it also flips text upside down, so text elements need a counter-transform. For beginners, the simpler approach is usually to keep the default downward y and subtract from the plot height, as in canvas, because it avoids the flipped-text problem. Either approach is valid, but mixing them in one chart is a recipe for confusion, so choose one and stay consistent.
Finally, remember that transforms are also animatable and interactive. A group can be translated on hover, a bar can be scaled from its baseline, and a whole chart can be zoomed by scaling a group. Those effects are cheap and smooth, because the browser handles them as transforms rather than redrawing shapes. Use them sparingly in data displays, where stability helps reading, and reserve dramatic transforms for transitions between views, where motion helps the reader track what changed. A transform is a tool for layout first and for effect second, and keeping that order will serve your charts well.
Check: Why is document order important for layers in an SVG chart?
Section 20 — SVG text and labels
Text in SVG is drawn with the text element, and it behaves like other SVG elements: it has a position, it can be styled with CSS, and it does not wrap or flow. A text element takes x and y attributes for its position, and its content is the text itself. By default, x sets the start of the text, and y sets its baseline, which is the line the letters sit on, not the top of the letters. That baseline detail matters for alignment: if you position a label by its top, it will sit lower than you expect, because the baseline is inside the text's height. Use the dominant-baseline or alignment-baseline properties, or simply allow for the offset by testing, to place labels precisely.
Alignment attributes give you control similar to canvas text alignment. The text-anchor property sets horizontal alignment: start, middle, or end, relative to the x position. For a value label centred above a bar, use text-anchor set to middle and x set to the bar's centre. For a value label on a vertical axis, use text-anchor set to end and x set just left of the axis, so labels right-align neatly.
SVG text can be styled with the same CSS as HTML text, including font family, size, weight, and fill colour, which sets the text colour in SVG, not the color property used for HTML. That last point is a frequent source of confusion: setting color on an SVG text element does not change its appearance, because SVG uses fill for text. Similarly, letter spacing and line height work, but wrapping does not, so long labels must be shortened, split manually into multiple tspan elements, or handled by switching to a horizontal layout. The tspan element lets you put multiple runs of text inside one text element, each with its own position or style, which is useful for multi-line labels or for emphasising part of a label.
Accessibility for SVG text is better than for canvas text, because SVG text elements are part of the document and can be read by some assistive technologies, especially when the SVG has a title and description. However, do not rely on that alone: provide a table or summary as well, as with canvas, because support varies and because a chart's text elements alone do not convey the data structure. Also give the SVG a title element as its first child, which acts as its accessible name, and a desc element for a longer description, such as the chart's main message. Those elements are not visible, but they are part of the document, and they help tools that do expose SVG content.
Practical label habits carry over from canvas: use one size for most labels, a slightly larger or bolder style for titles and totals, and keep labels short. Check labels at small sizes, check that they do not overlap, and check that they remain legible against their background. If a label overlaps a bar, move it, shorten it, or show it only on hover, rather than shrinking the font until it is unreadable. SVG text is real text in the document, which is a strength, but it still needs the same editorial care as any other text on the page.
Check: Why does setting the CSS color property not change the colour of SVG text?
Section 21 — The viewBox and responsive SVG
The viewBox is what makes SVG scale cleanly, and it is worth understanding precisely. An SVG element has a displayed size, set by width and height or by CSS, and an internal coordinate system, set by viewBox. The viewBox attribute takes four numbers: the minimum x, the minimum y, the width, and the height of the internal system. The browser maps that internal rectangle onto the displayed rectangle, scaling everything inside it. If you set viewBox to "0 0 600 300" and display the SVG at 300 pixels wide, everything shrinks by half, but proportions, line widths, and text all shrink together, so the chart stays correct, just smaller.
That scaling is the reason SVG charts are naturally responsive. You can draw a chart once in a convenient coordinate system, such as 600 by 300, and let CSS control its displayed size, for example width 100 percent and height auto. The chart will fill its container on any screen without any JavaScript resizing, and it will stay sharp, because it is vector. The aspect ratio is preserved by default, which means the SVG will not stretch or squash unless you ask it to. If the container's aspect ratio differs from the viewBox, the browser centres the picture and leaves empty space, rather than distorting it, which is usually what you want for a chart.
The preserveAspectRatio attribute controls that behaviour when the aspect ratios differ. Its default value keeps the picture's proportions and centres it, with the picture meeting the container's edges on one dimension. You can change it to align the picture to a corner, or to stretch it to fill the container exactly, but stretching a chart distorts bar widths, circle shapes, and text, and should be avoided for data displays. For most charts, leave preserveAspectRatio at its default and design the container to match the chart's aspect ratio, or accept the letterboxing. A chart that distorts is a chart that lies about proportions.
A common mistake is to set width and height attributes that conflict with CSS, or to omit viewBox entirely. Without viewBox, SVG coordinates map directly to pixels, and the picture does not scale: it simply clips or leaves empty space when the displayed size changes. With viewBox but without a CSS width, the SVG may display at its attribute size rather than filling its container. The reliable pattern is to set viewBox for the internal system, set width to 100 percent in CSS, and let height follow automatically from the aspect ratio. Test that pattern by resizing the browser window and by viewing the chart on a phone, and confirm the chart scales smoothly without clipping labels.
Text scaling deserves a note. Because viewBox scales everything, text inside a small displayed SVG becomes small too, which can make labels unreadable on a phone. That is a real limit of pure viewBox scaling, and it is why responsive charts sometimes need more than scaling: they may need to hide minor labels, shorten labels, or switch layouts at small sizes, as later sections explain. Scaling alone is not responsiveness; it is the foundation that makes responsiveness possible, and it must be combined with editorial decisions about what to show at each size.
Check: What does the viewBox do that width and height alone do not?
Section 22 — Building a bar chart in SVG
An SVG bar chart is built from rect elements, and its structure mirrors the canvas version, with one important difference: each bar remains an element you can style and interact with. Start with data as an array of labels and values, compute the maximum, and define margins and a plot area. Wrap the plot in a group translated by the margins, so all bar coordinates are relative to the plot origin. For each item, create a rect with an x position from its slot, a y position from its scaled value, a width from the slot fraction, and a height from the scaled value. Because SVG y increases downwards, the y position is the plot height minus the bar height, just as in canvas.
Axes and labels are also elements. Gridlines are line elements drawn across the plot at round values, with text elements for their labels placed to the left of the plot area. Category labels are text elements centred under each bar, using text-anchor middle. Value labels, if shown, are text elements above each bar. Because these are elements, you can give them classes, such as gridline, axis-label, and bar-label, and style them in CSS, which keeps the JavaScript focused on geometry and the stylesheet focused on appearance. That separation also makes it easy to change the chart's look without touching its maths.
Interactivity in SVG is delightfully simple compared with canvas. To highlight a bar on hover, add a CSS rule for rect.bar:hover that changes its fill or opacity; no JavaScript is needed. To show a tooltip, add a title element inside the bar's group, which browsers show as a native tooltip, or use JavaScript to show a styled tooltip element on mouseenter. To make a bar focusable, add a tabindex attribute and a focus style, so keyboard users can tab to it. These are ordinary HTML and CSS techniques applied to SVG elements, which is why SVG charts are often the most accessible choice for typical business data.
Generating the chart in JavaScript requires care with namespaces. Use document.createElementNS with the SVG namespace for every SVG element, and set attributes with setAttribute. A helper function that creates an element, sets attributes from an object, and appends it to a parent will keep the code short and readable. Build the chart by clearing the SVG's plot group and recreating its children from the data, which is simple and reliable for charts of this size. For larger charts, updating existing elements rather than recreating them is faster, but recreation is easier to reason about and is fast enough for dozens or hundreds of bars.
Check the finished chart the same way as the canvas version: compare bar heights against values, check labels against categories, and view the result at small sizes. Also inspect it in developer tools, where you can see each bar as an element with its attributes, which makes debugging far easier than with canvas. If a bar is missing, check whether its element was created in the correct namespace. If a bar is in the wrong place, check its x and y against the scale functions. The element model turns chart debugging into ordinary document debugging, which is a genuine advantage.
Check: Why is hover styling simpler for an SVG bar chart than for a canvas one?
Section 23 — Building a line and area chart in SVG
An SVG line chart is a single path element whose d attribute is generated from scaled points, just as described for paths earlier. Start with points, map x across the plot width and y through a linear scale, and build a string beginning with M and the first point, followed by L and each later point. Set fill to none and stroke to the series colour, with a stroke width of two or three. Add circle elements at each point if markers are wanted, with a small radius and a fill that contrasts with the line. Because the path and circles are elements, they can be styled, hovered, and inspected individually, which makes the chart easy to refine.
An area chart extends the line path down to the baseline. After the last point, add a line to the baseline at the last x, a line back to the baseline at the first x, and close the path. Fill that path with the series colour at low opacity, and draw the line path itself on top with full opacity, so the edge remains crisp. The filled area emphasises volume, but remember the caution from earlier sections: stacked areas mislead, because only the bottom series has a flat baseline. For a single series, an area chart is a legitimate and attractive choice; for multiple series, prefer separate lines or small multiples, unless the stacked total itself is the message.
Multiple series in SVG are straightforward: one path and one set of markers per series, each in a group with a class for its colour. A legend can be built from HTML alongside the SVG, with a coloured swatch and a label per series, which is more accessible and easier to style than a legend drawn inside the SVG. Keep the number of series small, and use marker shapes or dash patterns in addition to colour, so series remain distinguishable without colour. If a series has missing points, break its path into segments rather than joining across the gap, or the chart will invent continuity. A helper that splits points into continuous runs makes that handling clean.
Interactivity follows the SVG pattern: hover on a point's circle to highlight it, focus it with the keyboard if it has a tabindex, and show its value in a tooltip or a details panel. A common refinement is a vertical guide line that follows the pointer across the chart, highlighting the nearest point on each series. That guide line is itself an SVG line element whose x attribute is updated on pointer movement, which is simple once the scale functions exist. Keep the guide subtle, with a light stroke, so it aids reading without dominating the chart.
Finally, check the line chart's honesty. Does the steepest segment match the largest change in the data? Are gaps shown as gaps? Does the y-axis start at a labelled value, and is any cropping explained? Compare the chart against the data for at least two points, and read its story aloud, as with canvas. SVG makes it easy to add features, but features should not distract from that basic test: the picture must tell the same story as the numbers, at a glance and in detail.
Check: How do you turn an SVG line path into an area path?
Section 24 — Pie and donut charts in SVG
SVG pie slices are path elements built from arcs, and the helper function approach from the paths section is essential here. For each slice, compute its start and end angles from its share of the total, convert those angles to points on a circle, and build a path that moves to the centre, lines to the start point, arcs to the end point, and closes back to the centre. The arc flags must be set correctly: the large-arc flag is one when the slice exceeds half a circle, and the sweep flag is one for clockwise drawing. Encapsulating that logic in a helper keeps chart code readable and prevents the wrong-way-around bug that plagues hand-written arcs.
A donut chart in SVG can be built in two ways. One is to draw pie slices and then draw a white circle over the centre, which is simple but fragile, because the centre circle must match the background and will fail on a patterned or dark background. The better way is to use stroke-dasharray on a circle, a technique that deserves explanation. A circle's circumference can be divided into segments by setting stroke-dasharray to a list of dash and gap lengths that correspond to slice shares, with stroke-dashoffset rotating the start point. Each slice is then a circle element with the same radius and stroke width, but a different dash pattern and offset, so slices stack into a ring without overlapping. That construction is elegant, but it requires careful arithmetic for offsets, so test it with two equal slices first.
Labelling follows the same rules as canvas pies: keep slices few, label large slices directly, and use a legend for the rest. In SVG, labels can be text elements positioned at the midpoint angle of each slice, halfway between centre and edge, with text-anchor middle. For donut charts, the centre can hold a text element with the total, which is a natural use of the space. Because labels and slices are elements, they can be grouped, so hovering a legend item can highlight its slice by adding a class, which is a nice interactive touch that canvas requires extra code to achieve.
Colour and separation follow the same guidance as before: distinct lightness as well as hue, a thin stroke or gap between slices, and a greyscale check. In SVG, a stroke on each slice path, in the background colour, creates a clean separation, but be careful: a thick stroke will shrink the visible slice slightly, which is acceptable for separation but should be consistent across slices. Do not use a different stroke width per slice, or the chart will look uneven and may misrepresent shares by a small amount.
Verify the donut or pie by checking that slices sum to a full circle, that no slice overlaps another, and that the legend values match the slice angles. A simple test with two equal values should give two halves, and a value of half the total should give a semicircle, just as in canvas. If a slice is missing or doubled, check the running angle and the dash offsets, which are the usual culprits. SVG's inspectability helps here: you can select a slice in developer tools and see its path or dash attributes directly.
Check: What is the advantage of building an SVG donut with stroke-dasharray over covering a pie centre with a circle?
Section 25 — Scales and axes in depth
Scales are the most reusable idea in charting, and it is worth treating them as proper functions with clear inputs and outputs. A scale maps a data domain to a visual range: a linear scale maps numbers to positions, a categorical scale maps names to slots, and a time scale maps dates to positions. Writing a scale as a function that takes a value and returns a position, with a matching inverse function that takes a position and returns a value, gives you both drawing and interaction for free. The inverse is what makes tooltips, hover guides, and click handling possible, because it turns a pointer position back into a data value.
Axis generation is the companion to scales. An axis is a set of ticks: positions along the scale with labels. Choosing ticks is an editorial decision, not a purely mathematical one. Round numbers, such as 0, 25, 50, and 75, are easier to read than precise divisions of the maximum, so a tick algorithm usually rounds the maximum up to a nice number, then divides it into a small number of equal steps, typically four to six. Too many ticks clutter the axis; too few make values hard to estimate. For a time axis, ticks should fall on meaningful boundaries, such as months or years, not on arbitrary divisions of the time span, because readers think in calendar units.
Inverting a scale is simple for linear scales: given a pixel position, subtract the range start, divide by the range length, multiply by the domain length, and add the domain start. That inverse is used for hit detection, for snapping a tooltip to the nearest point, and for zooming, where a selected pixel range becomes a new domain. If you only ever map data to pixels, you will find interaction awkward; if you build the inverse from the start, interaction becomes a natural extension of drawing. Test both directions with known values: a value mapped to pixels and back should return approximately the original value, within rounding.
Logarithmic and other non-linear scales deserve a warning. A log scale maps equal ratios to equal distances, which is correct when data spans orders of magnitude, but it changes the meaning of slope and distance, and readers may misread it as linear if it is not clearly labelled. Label log axes with their actual values, such as 10, 100, and 1000, and consider whether a different presentation, such as showing growth rates, would be clearer. Similarly, a categorical scale has no arithmetic: the distance between categories is a layout choice, not a data fact, so do not imply order or spacing that the data does not support.
A practical habit is to keep scale code in one place, separate from drawing code, with functions such as scaleX, scaleY, invertX, and invertY. When the data changes, update the domain and redraw; when the chart resizes, update the range and redraw. That separation is the same principle as the model-view separation in applications, applied to charts, and it pays off as soon as a chart gains interaction, animation, or live updates. A chart whose scales are scattered through drawing code is a chart that resists every later change.
Check: Why is an inverse scale function useful in an interactive chart?
Section 26 — Colour in charts
Colour in charts is a code, not decoration, and it should be used sparingly and consistently. The first rule is that colour must mean something, and the same meaning must hold across the whole chart and, ideally, across the whole dashboard. If blue means sales on one chart, it should not mean returns on another, because readers carry colour meanings from chart to chart. Build a small palette for your project: one colour per series, plus neutrals for axes, gridlines, and text, and use it consistently. A palette of four or five series colours is usually enough; beyond that, series become hard to distinguish and the chart becomes noisy.
Different colour jobs need different palettes. A categorical palette assigns a distinct hue to each category, with no implied order. A sequential palette varies lightness within one hue, from pale to dark, to show low to high values, which is ideal for heatmaps and choropleths. A diverging palette runs from one hue through a neutral middle to another hue, to show values below and above a meaningful midpoint, such as profit and loss around zero. Using a categorical palette for ordered data, or a sequential palette for unrelated categories, misleads, because readers infer order or difference from the colour pattern. Choose the palette type to match the data type.
Contrast and lightness matter more than hue. Two colours can look distinct in a palette preview but merge when used as thin lines or small markers, so test colours at the actual sizes they will appear. Check contrast between data colours and the background, and between label text and its background, especially for text placed on coloured bars. A common fix is to place value labels outside bars in dark text, rather than inside bars in white text, because white text on a pale colour fails contrast even when the colour looks pleasant. Also test in greyscale: if two series become identical, add a shape, pattern, or label difference, rather than relying on hue alone.
Colour blindness affects a significant minority of readers, and the most common forms make red and green hard to distinguish. Avoid red-green as the only distinction between two important series, and avoid relying on red for bad and green for good without an additional signal, such as a symbol, a label, or a position difference. Tools that simulate colour blindness can check a palette quickly, and many well-designed palettes are available that are tested for this purpose. Using a tested palette is not copying; it is applying known perceptual research, and it saves you from inventing a palette that fails for some readers.
Finally, restrain the total amount of colour. A chart with a white or pale background, dark text, light gridlines, and a few saturated series colours reads clearly, because the data stands out against a quiet background. A chart with a coloured background, coloured gridlines, and a rainbow of series colours competes with itself, and the reader's eye has nowhere to rest. When in doubt, remove a colour: use grey for secondary series, reserve saturation for the series that matters most, and let the shapes, not the colours, carry the main comparison. Colour should guide the eye to the message, not become the message.
Check: Why should a sequential palette not be used for unrelated categories?
Section 27 — Labels, legends, and tooltips
Labels, legends, and tooltips are three different ways of giving readers exact values, and each suits a different need. Direct labels place values on the chart itself, next to bars or points, so readers can see them without interacting. Legends explain what colours or shapes mean, usually in a box or row beside the chart. Tooltips show details on demand, when a reader hovers or focuses an element. A well-designed chart uses all three judiciously: direct labels for the values that matter most, a legend for series meanings, and tooltips for detail that would clutter the chart if always visible.
Direct labelling is the clearest option when space allows. A bar chart with values above each bar needs no tooltip for those values, and a reader can compare them at a glance. The cost is clutter: with many bars, direct labels collide and the chart becomes hard to read. In that case, label only the largest, smallest, or most important values directly, and let tooltips or a table carry the rest.
Legends need design, not just presence. Place a legend where it does not cover data, usually above or below the chart, and keep its order matching the chart's order, such as the order of bars or the stacking order of series. Make legend swatches large enough to recognise, and include the series name in text, not colour alone. Interactive legends are a useful refinement: clicking a legend item can hide or show its series, which helps readers isolate a trend, but that interaction must be discoverable, with a clear hover or focus style, and it must not hide the only series a reader needs. If a legend is interactive, state that it is, or users will not try.
Tooltips are the most misused of the three. A good tooltip shows the exact value, the category, and the series name, in a consistent format, and it appears near the pointer without covering the element it describes. A bad tooltip shows only a number with no context, appears slowly, flickers as the pointer moves, or overflows off the screen. For SVG, tooltips can be HTML elements positioned over the chart, which are easier to style and make accessible than native title tooltips. For canvas, tooltips are drawn or positioned by your code, using the same hit detection as highlighting. In both cases, tooltips should also be available to keyboard and touch users, not only to mouse hover, or they exclude a large group of readers.
A final principle is redundancy with purpose. The same value may appear as a direct label, in a tooltip, and in a table, and that is not wasteful if each appearance serves a different reading mode: glance, exploration, and precise lookup. What is wasteful is three different formats for the same value, or values that disagree between chart and table. Keep formats consistent, such as the same number of decimal places and the same units everywhere, and keep all representations in sync with the same data source. When labels, legends, and tooltips agree, the chart feels trustworthy, which is the foundation of its usefulness.
Check: When is a direct label better than a tooltip for a value?
Section 28 — Responsive charts
A responsive chart adapts to its container, not merely shrinking to fit it. The simplest adaptation is scaling, which SVG viewBox handles and canvas requires manual resizing for, but scaling alone fails at small sizes, because labels become unreadable and bars become too thin to tap. A truly responsive chart changes what it shows as space shrinks: it may hide minor gridlines, shorten labels, reduce the number of ticks, or switch from vertical bars to horizontal bars. The goal is not to show the same chart smaller, but to show a chart that answers the same question in less space.
Breakpoints for charts are about the chart's container, not the whole page, because a chart may sit in a sidebar on a large screen and full-width on a phone. Modern CSS container queries can help, but in JavaScript you can also observe the chart's container size and choose a layout based on its width. A practical set of layouts might be: above six hundred pixels, a full chart with all labels and a legend; between three hundred and six hundred, a chart with fewer ticks and shortened labels; below three hundred, a simplified chart or a summary with a table.
Canvas responsiveness requires explicit handling. When the container resizes, you must resize the canvas internal bitmap, re-apply the device pixel ratio scaling, recompute scales and margins, and redraw. Debounce the resize handler, so you do not redraw dozens of times during a single drag, and redraw only when the size actually changes. SVG responsiveness is easier for scaling, but the same editorial decisions apply: you may need to hide or change elements at small sizes, which can be done with CSS classes on SVG elements, or by regenerating the chart with fewer labels. In both cases, test resizing by dragging the window slowly, not just by jumping between preset sizes, because intermediate sizes often reveal overlaps that presets hide.
Label handling is the most visible part of responsive charting. Long category labels that fit on a desktop chart will collide on a phone. Options include rotating labels, abbreviating them with a consistent rule, wrapping them over two lines, or switching to horizontal bars where labels have room on the left. Rotated labels are hard to read, so prefer horizontal bars for long labels when space is tight. Also consider the legend: a horizontal legend row may need to become a vertical list, or a compact dropdown, at small sizes. Each of these changes should be tested with real labels, not placeholder text, because real labels are always longer than you expect.
Finally, do not forget touch and interaction at small sizes. Bars that are easy to hover with a mouse may be too narrow to tap with a finger, and tooltips that appear on hover may not appear at all on touch. At small sizes, increase hit areas, make tooltips appear on tap and persist until dismissed, and ensure the chart's controls, such as series toggles, are large enough to tap comfortably. A responsive chart is not just a smaller chart; it is a chart redesigned for a different way of reading and touching, and that redesign is what makes it genuinely usable on a phone.
Check: Why is scaling alone not enough to make a chart responsive?
Section 29 — Updating data live
Charts that update live, such as dashboards showing current sales or sensor readings, introduce problems that static charts do not have. The first is how to update without confusing the reader. If a chart redraws completely on every update, with bars jumping and axes rescaling, the reader cannot track what changed. A better approach is to update in place: keep the same axes and layout where possible, animate the change smoothly, and highlight what moved. If the data range grows, rescale gradually or only when necessary, rather than on every tick, because frequent rescaling makes the chart appear to breathe and undermines trust in its values.
The second problem is data flow. Live data usually arrives as a stream of new points, often through polling, server-sent events, or a websocket, and your chart code should separate receiving data from drawing it. Keep the data in an array or a similar structure, append new points, drop old points beyond a window, such as the last hour, and then redraw or update from that structure. That separation makes the chart testable: you can feed it fake data and check its behaviour without a live server, and you can pause updates, replay history, or switch time ranges without changing the drawing code. It also prevents the common bug where drawing code and network code become tangled and hard to debug.
Performance matters more for live charts, because updates are frequent. For canvas, redrawing a small chart sixty times a second is usually fine, but redrawing a large chart on every data point can waste work. Throttle redraws to a reasonable rate, such as once per animation frame or a few times per second, and batch multiple data arrivals into one redraw. For SVG, updating existing elements' attributes is cheaper than recreating the whole chart, so prefer updates for live charts, even if recreation is simpler for static ones. In both cases, avoid expensive operations in the update path, such as measuring text or creating gradients, unless the labels actually changed.
A further consideration is what the reader should do when data stops arriving. A live chart that silently freezes looks identical to a chart whose data has genuinely flatlined, which is misleading. Show a status indicator, such as a timestamp of the last update and a connection state, and make it visible near the chart, not hidden in a footer. If updates fail, show a clear message and keep the last known data visible, rather than clearing the chart, because the last known data is still useful. If the user pauses updates to examine a value, respect that pause and do not overwrite their view until they resume.
Finally, respect motion and attention. A live chart that animates every update can be distracting, especially if it sits beside text a reader is trying to read. Use subtle transitions for updates, and consider pausing animation when the chart is off-screen or the tab is hidden, to save battery and processor time. Live charts are powerful, but they demand more care than static ones, because they combine data, timing, and interaction, and a failure in any of those areas is immediately visible to the reader.
Check: Why should a live chart avoid rescaling its axes on every update?
Section 30 — Animating chart transitions
Animating a transition between two chart states helps readers track what changed, which is the main purpose of chart animation. When data updates, a bar that grows smoothly from its old height to its new height tells the reader that the same category changed, whereas a bar that instantly jumps or disappears and reappears forces the reader to re-identify every bar. Similarly, when sorting changes, bars that slide to their new positions preserve identity, while bars that instantly reorder require the reader to re-read labels. The principle is object constancy: the same data item should remain recognisably the same visual element across the transition.
Implementing transitions differs between canvas and SVG. In SVG, you can animate attributes with CSS transitions for some properties, or with JavaScript that interpolates values over time and sets attributes each frame. In canvas, you animate by redrawing each frame with interpolated values, using the requestAnimationFrame loop from earlier sections. In both cases, the core technique is interpolation: for each value, compute a start value, an end value, and a progress between zero and one, then set the current value to start plus progress times the difference. Apply an easing function to progress if you want the motion to feel natural, but keep durations short, around half a second, so the transition aids reading rather than delaying it.
Not every change should animate. Small updates, such as a single value changing slightly, may be clearer as an instant change with a brief highlight, rather than a slow morph that draws attention disproportionate to the change. Large structural changes, such as switching from a bar chart to a pie chart, are often clearer as a clean swap than as a morph, because morphing between chart types can produce confusing intermediate shapes that represent neither chart. A useful rule is to animate changes within the same chart type and the same data identity, and to swap cleanly when the chart type or the data identity changes fundamentally.
Interrupted transitions need handling. If new data arrives while a transition is still running, starting from scratch will cause a jump back to an old state. Instead, start the new transition from the current animated values, which are the values actually on screen, not from the previous final values. That requires storing current values separately from target values, and updating targets when new data arrives. The same principle applies to sorting and filtering: animate from what the reader currently sees, not from an idealised previous state. That discipline is what makes transitions feel smooth rather than jittery in a live dashboard.
Accessibility applies to transitions as well. Respect prefers-reduced-motion by skipping transitions and showing the final state immediately, because motion that aids one reader can distract or discomfort another. Also ensure that the final state is correct even if the animation is interrupted or disabled: the chart must end at the true data values, not at an intermediate value left by a cancelled animation. Test transitions by updating data rapidly, by switching series quickly, and by disabling animation, and confirm the chart always lands on the correct values. A transition that sometimes leaves the chart wrong is worse than no transition at all.
Check: What is object constancy, and why does it matter in a chart transition?
Section 31 — Missing, zero, and uncertain data
Missing data is not the same as zero, and confusing the two is one of the most serious chart errors. A missing measurement means you do not know the value; a zero means you measured and the value was zero. Drawing a missing value as a zero-height bar, or as a point at zero on a line, invents a fact and can reverse the story: a sensor outage becomes a collapse in sales, or a holiday becomes a day with no visitors. The correct handling is to show the absence explicitly, with a gap, a break, or a note, so the reader knows the data is missing rather than low.
How you show a gap depends on the chart type. In a bar chart, leave the slot empty and label it as missing, or omit the category with a note, rather than drawing a zero bar. In a line chart, break the line at the gap, so the reader sees two segments with a space between them, rather than a line that dives to zero and climbs back. In a scatter plot, simply have no point for the missing observation. In all cases, add a short note near the chart or in its summary, stating how many values are missing and, if known, why.
Uncertainty is related to missing data but distinct. Many measurements have a margin of error, a forecast range, or a confidence interval, and drawing a single line or bar without showing that uncertainty implies more precision than the data supports. A common way to show uncertainty is a shaded band around a line, representing the range of plausible values, with the central line as the best estimate. For bars, error bars, which are short lines extending above and below the bar's top, can show the range. Label these clearly, and explain what the band or bar means, because readers may otherwise mistake it for a second data series.
Zero itself needs care. A zero on a bar chart is a bar of no height, which is correct, but it can be invisible, so a label or a baseline marker may be needed to show that the category was measured and found to be zero. On a line chart, a zero is a point on the baseline, which is visible if the baseline is drawn. On a pie chart, a zero slice has no angle and should not be drawn, but it should appear in the legend with a zero value, so the reader knows it was considered. These details sound minor, but they are the difference between a chart that a careful reader trusts and one that they must second-guess.
Finally, design your data structures to distinguish missing from zero from the start. Use null or a similar explicit marker for missing values, not zero, and make your scale and drawing code handle null by skipping or breaking, rather than treating it as a number. That discipline in code mirrors the discipline in presentation: if the code cannot distinguish missing from zero, the chart cannot either. Test with a dataset that includes a missing value, a zero, and a normal value, and confirm each is drawn differently and correctly. That single test catches the most common data-handling bug in chart code.
Check: Why must a missing value never be drawn as zero?
Section 32 — Large datasets and sampling
Large datasets challenge charts in two ways: drawing performance and reader comprehension. A chart with a hundred thousand points may be slow to draw, especially in SVG, and even if it draws quickly, the reader cannot distinguish a hundred thousand overlapping points. The solution is not to draw more, but to draw smarter: aggregate, sample, or summarise the data into a form the eye can use, while keeping the full data available in a table or on demand. A chart is a summary, and for large data, the summary must be deliberate.
Aggregation groups data into bins and shows a summary per bin. For time series, that might mean showing daily averages instead of per-second readings, or weekly totals instead of daily ones. For distributions, it means histograms, which count values in ranges. Aggregation reduces the number of drawn elements dramatically, and it often reveals the pattern more clearly than raw points, because noise averages out. The choice of bin size matters: too large, and real patterns disappear; too small, and noise returns. Let the reader change the bin size or time window where possible, so they can explore the data at different resolutions, rather than fixing a single summary that hides detail.
Sampling selects a subset of points to draw, such as every tenth point, or a random sample. Sampling is faster than aggregation to implement, but it risks hiding rare events, such as spikes, because a sampled point may miss them. A safer sampling method for time series is to keep the minimum and maximum within each window, so spikes survive even when most points are dropped. That technique, sometimes called min-max sampling, preserves the visual envelope of the data while reducing its size. Whatever sampling you use, state it, because a reader who thinks they are seeing all points will draw different conclusions from one who knows they are seeing a sample.
Density visualisations are another approach for very large point sets. Instead of drawing each point, draw a heatmap of point density, where colour shows how many points fall in each cell. A scatter plot of a million points becomes a smooth density map that shows clusters and gaps clearly, without the overplotting that makes raw scatter plots unreadable. Similarly, a line chart of a million points can be replaced by a band showing the range and a line showing the average, which conveys the distribution without drawing every point. These are not compromises; they are better charts for large data, because they match the visual channel to what the eye can resolve.
Performance techniques complement these data techniques. For canvas, drawing points as single pixels or small rectangles is faster than drawing circles with strokes. For SVG, reducing element count is the main lever, because each element has a cost. In both cases, avoid redrawing the whole chart for a small change, and consider offscreen rendering for static layers. But remember that performance optimisation without data reduction has limits: a chart that draws a million elements quickly is still a chart nobody can read. Reduce first, optimise second, and always keep the full data reachable for readers who need it.
Check: Why can sampling hide a spike in a time series, and what sampling method preserves it?
Section 33 — Sparklines and small charts
Sparklines are tiny charts, usually the size of a word or a line of text, that show a trend without axes, labels, or legends. They were popularised as a way to put a chart inline with text, such as a stock price trend next to a company name, or a temperature trend in a table row. A sparkline's job is not to give exact values, but to show shape: rising, falling, volatile, or flat. Because they are so small, they must be extremely simple: a single line or a set of small bars, with no gridlines, no axis labels, and at most a dot or a label for the latest value.
Building a sparkline is a scaled-down version of building a full chart. Take the data, map it to a small width and height, such as 100 by 30 pixels, and draw a line or bars. For a line sparkline, use a thin stroke and no markers, except perhaps a dot at the last point. For a bar sparkline, use thin bars with no gaps or tiny gaps. Do not add axes, because there is no room for them, and do not add a title inside the sparkline, because the surrounding text or table header provides context. The sparkline's simplicity is its strength: it adds a trend to a list without turning the list into a dashboard.
Small multiples are the larger cousin of sparklines. They are many small charts, all with the same scale and layout, arranged in a grid, one per category or series. Small multiples let readers compare trends across many items without the tangle of many lines on one chart. Each small chart is simple, but together they reveal patterns, such as which regions rise and which fall, that a single combined chart would hide. Keep the scale identical across all multiples, or the comparison is invalid: a rise that looks steep in one small chart and gentle in another must reflect the data, not different scales. Label each multiple clearly with its category name.
Accessibility for sparklines requires care, because a tiny chart without labels is inaccessible on its own. Provide the underlying values in the surrounding table, or give the sparkline an accessible label summarising its trend, such as "rising over the last month, from 120 to 180". Do not rely on colour alone to convey the trend, and do not use a sparkline as the only place a critical value appears. Sparklines are a supplement to text and tables, not a replacement for them, and they should be treated as such in the page structure.
A practical use of sparklines is in tables and cards, where space is limited but trends matter. A sales table with a sparkline per product lets a reader scan for rising and falling products without opening each product's detail page. A dashboard card with a sparkline and a current value gives both the latest figure and its context in a compact space. Use sparklines where the question is about shape, not exact values, and use full charts where exact values or detailed comparison matter. Choosing the right size for the question is part of chart design, and sparklines are the smallest size in that toolkit.
Check: What question does a sparkline answer, and what does it deliberately not show?
Section 34 — Scatter plots and relationships
A scatter plot shows the relationship between two quantities by placing a point for each observation, with its horizontal position from one quantity and its vertical position from the other. Unlike a line chart, which implies order or time, a scatter plot implies no order: each point is an independent observation, and the pattern of points reveals correlation, clusters, outliers, and gaps. A tight upward cloud suggests that as one quantity rises, the other tends to rise; a scattered cloud with no pattern suggests no relationship; a single point far from the rest suggests an outlier worth investigating. That visual pattern is the scatter plot's message, and it is why scatter plots are essential for exploring data before modelling it.
Building a scatter plot requires two linear scales, one for each axis, and a point marker per observation. Markers are usually small circles, but they can be other shapes, and their size can encode a third quantity, making a bubble chart, where larger bubbles mean larger values of that third quantity. Colour can encode a fourth quantity, such as a category, but be careful: with many points, colour and size quickly become hard to read, and the plot becomes a confetti of overlapping marks. For most scatter plots, keep markers small, semi-transparent, and uniform, so dense areas appear darker through overlap, which naturally shows density.
Overplotting is the main challenge. When many points share similar positions, they stack on top of each other and hide how many points are really there. Solutions include reducing marker size, increasing transparency, jittering points slightly when values are discrete, or switching to a density heatmap for very large sets, as discussed earlier. Jittering, which adds a small random offset, can reveal structure in discrete data, but it must be small and, ideally, disclosed, because it moves points from their true positions. Transparency is usually the safest first step, because it preserves true positions while revealing density through darkness.
A trend line can be added to a scatter plot to summarise its relationship, such as a straight line fitted to the points, but it must be labelled as a summary, not as data, and it must not imply causation. A scatter plot shows that two quantities move together; it does not show that one causes the other, and a trend line does not change that. Avoid drawing a trend line through a cloud with no pattern, because the line will imply a relationship that is not there. Also avoid connecting scatter points with lines, unless the points have a meaningful order, because lines imply sequence and continuity that a scatter plot deliberately does not claim.
Check a scatter plot by asking what pattern a fresh reader would see, and whether that pattern matches the data. Are there clusters that should be labelled? Is there an outlier that needs explanation or investigation? Does the axis range hide or exaggerate the relationship? A scatter plot with cropped axes can make a weak relationship look strong, so include the full range or explain the cropping. When the pattern is clear and honest, a scatter plot is one of the most informative charts available, because it shows the data itself, not a summary of it.
Check: Why should you not connect scatter plot points with lines unless they have a meaningful order?
Section 35 — Histograms and distributionsA histogram shows how values are distributed by counting how many observations fall into each range, or bin, and drawing a bar per bin. Unlike a bar chart, where each bar is a category, a histogram's bars are ranges of a continuous quantity, such as ages, prices, or response times, and the bars touch each other, because the ranges are adjacent with no gaps. The height of each bar is the count, or the proportion, of observations in that range.
Bin choice is the critical decision in a histogram. Too few bins, and the shape becomes a coarse block that hides detail; too many bins, and the shape becomes spiky noise that hides the pattern. A common starting point is to divide the range into about ten to twenty bins, then adjust by looking at the result. If the shape changes dramatically with a small change in bin count, the data may be sparse or the pattern may be fragile, and you should say so rather than picking the bin count that gives the prettiest shape. Also be consistent: bins should be equal width, unless you have a strong reason and you label the unequal widths clearly, because unequal bins make bar heights misleading, as a wider bin naturally contains more observations.
Histograms differ from bar charts in ways that matter for reading. In a bar chart, categories can be reordered, and gaps between bars emphasise that categories are separate. In a histogram, order is fixed by value, gaps would imply missing ranges, and the area of each bar, not just its height, represents the count when bins are equal width. Do not sort histogram bars by height, because that destroys the distribution shape. Do not add gaps between histogram bars, because that implies the ranges are separate categories. These conventions are not arbitrary; they are how readers know they are looking at a distribution rather than a category comparison.
A histogram's summary companions are useful. The mean, median, and mode describe centre, but the histogram shows shape, which is why the two together are powerful. A distribution with two peaks, for example, suggests two subgroups, which a single average would conceal. Outliers appear as isolated bars far from the main shape, which may be errors, special cases, or genuine extremes worth investigating. When you present a histogram, consider annotating its centre, its spread, and any notable peaks or gaps in words, so readers who are not fluent in distribution shapes can still grasp the message.
Building a histogram in code is straightforward: find the minimum and maximum, divide the range into bins, count observations per bin, and draw a bar chart with touching bars. The counting step is the new part, and it should be tested with a small dataset where you can count by hand. Edge cases include values exactly on a bin boundary, which must be assigned consistently to one bin, and empty bins, which should be drawn as zero-height bars, not omitted, because omitting them would hide a gap in the distribution. A histogram that handles those edges correctly is a trustworthy picture of how values are spread.
Check: Why must histogram bars touch each other while bar chart bars have gaps?
Section 36 — Heatmaps and grids
A heatmap shows values in a grid, where each cell's colour represents its value, and position represents its row and column categories. It is ideal for two-dimensional patterns, such as activity by day of week and hour of day, or correlation between many variables, where a table of numbers would be hard to scan and a line chart would be tangled. The eye is good at spotting patterns in colour grids: a bright row, a dark column, or a diagonal pattern will jump out, even when the underlying numbers are many. Heatmaps trade exact values for pattern visibility, which is the right trade when the question is about pattern rather than precision.
Building a heatmap requires a sequential colour scale, as discussed in the colour section, mapping low values to a pale colour and high values to a saturated colour within one hue. That mapping must be consistent and labelled with a colour legend, showing which colours correspond to which values, because without a legend, a heatmap is just a pretty grid. Include the actual value in each cell as text where space allows, or in a tooltip on hover, so readers who need exact figures can get them. For large grids, text in every cell becomes unreadable, so tooltips and a summary become more important, and the heatmap's role shifts firmly to pattern-spotting.
Cell size and grid size matter. A heatmap with too many rows and columns becomes a mosaic of tiny cells that are hard to tap or hover, and whose colours blur together. A heatmap with too few cells may hide detail. A practical limit is a few dozen rows and columns, beyond which aggregation or filtering helps. Sorting rows and columns can reveal patterns: sorting by similarity, for example, can group similar rows together and make clusters visible, whereas alphabetical order may hide them. If you sort, state the sort order, because a sorted heatmap tells a different story from an unsorted one, and readers should know which they are seeing.
Colour blindness and contrast apply strongly to heatmaps, because colour is the only channel carrying value. Use a sequential palette tested for colour blindness, and avoid rainbow palettes, which imply order that is not perceptually uniform: equal steps in data should look like equal steps in colour, which rainbow palettes fail to do. Test the heatmap in greyscale, and consider adding a second channel, such as cell size or a symbol, for critical thresholds, so the pattern survives without colour. A heatmap that only works in full colour is a heatmap that fails for some readers and in print.
Finally, heatmaps have a cousin in calendar views, where each day is a cell coloured by activity, arranged in weeks and months. Calendar heatmaps are familiar from contribution graphs, and they follow the same principles: a sequential scale, a legend, tooltips for exact values, and a clear time structure. Whether the grid is a calendar, a correlation matrix, or a day-hour grid, the design questions are the same: what pattern should jump out, what exact values must remain reachable, and does the colour scale represent the data honestly? Answer those, and a heatmap becomes a powerful tool rather than a decorative grid.
Check: Why does a heatmap need a colour legend more urgently than a bar chart needs one?
Section 37 — Simple maps with SVG
Maps in front-end work are often built with SVG, because geographic shapes are naturally vector paths, and SVG lets you style, hover, and label each region as an element. A simple map, such as regions of a country coloured by a value, is a choropleth: each region's fill colour represents its value on a sequential scale, just like a heatmap cell. That technique is powerful for geographic patterns, such as which regions have higher sales or lower coverage, but it also carries specific risks, because region size can mislead: a large, sparsely populated region will dominate the map visually, even if its value is unremarkable, while a small, dense region may be nearly invisible.
Building a simple SVG map starts with geographic data, which is usually supplied as SVG paths or as a geographic format that must be converted to SVG. You should not draw country outlines by hand from memory, because they will be inaccurate and may be misleading or offensive. Use a reputable geographic dataset, check its licence, and keep its region boundaries as given. Each region becomes a path element with a data attribute for its name and value, and your code colours it by mapping its value through a sequential scale.
Projection is a concept worth knowing, even for simple maps. A map projection turns the curved earth onto a flat surface, and every projection distorts something: area, shape, distance, or direction. For a small region, such as a single country, distortion is minor and often ignorable. For a world map, distortion is major, and the familiar rectangular world map exaggerates areas near the poles dramatically. If your map covers a large area, choose a projection appropriate to your purpose, or use a library that handles projection for you, and be aware that your map's visual impression is partly a product of that choice, not purely of the data.
Interactivity for maps follows the SVG pattern: hover a region to highlight it and show its value, focus it with the keyboard if it is focusable, and allow selection to filter other charts in a dashboard. That last use, called brushing or cross-filtering, is powerful: selecting a region on a map can filter a bar chart to that region's data, linking the views. Implement it by sharing a selected-region state between charts, and updating all charts from that state, rather than letting each chart manage its own selection independently. Keep the interaction simple and predictable, because a map with too many modes becomes hard to use.
Accessibility for maps is challenging, because a map is inherently spatial. Provide a table of region values alongside the map, with the same data, so readers who cannot see the map can still use it. Do not rely on colour alone, and provide region names as text, not only as hover tooltips. Also consider whether a map is the right chart at all: if the question is which region has the highest value, a sorted bar chart will answer it more precisely than a map, and the map may be decorative. Use a map when geography itself matters, such as neighbouring regions, spatial clusters, or coverage gaps, and use a bar chart when rank or exact value matters more.
Check: Why can a large region dominate a choropleth map even when its value is unremarkable?
Section 38 — When to use a chart library
A chart library is a package that builds charts for you, handling scales, axes, tooltips, and updates, so you can focus on data and design rather than geometry. Libraries are valuable, and professional front-end work often uses them, but they are not always the right choice. The decision depends on chart complexity, project size, team skill, and how much customisation you need. A library can save weeks on a complex dashboard with many chart types, interactions, and live updates. It can also be overkill for a single bar chart, where hand-built SVG is shorter, lighter, and easier to understand than learning and configuring a library.
The benefits of a library are real. It handles edge cases you might miss, such as nice tick values, time scales, and transitions, and it is tested by many users, so its bugs are more likely to have been found. It provides a consistent API across chart types, so learning one library gives you many charts. It often includes accessibility features, such as keyboard support and ARIA labels, that would take significant effort to build by hand. For a team, a shared library also creates consistency: charts across the product look and behave similarly, because they share the same underlying code, which is a design system benefit as well as a development one.
The costs are also real. A library adds bundle size, which affects loading time, especially on slow connections. It adds a dependency that must be kept updated, and whose API may change in ways that break your charts. It imposes its own design assumptions, which may fight your design system, requiring overrides or workarounds that are harder than building from scratch. And it can hide understanding: a developer who only ever uses a library may struggle to debug a chart that behaves unexpectedly, because they do not know what scales, axes, or transitions the library is doing internally. That is why this chapter teaches hand-built charts first: understanding the fundamentals makes library use informed rather than magical.
A practical decision framework helps. Use a library when you need many chart types, complex interactions, or live updates at scale, and when the library's design fits your product. Build by hand when you need one or two simple charts, when bundle size matters greatly, when the design is highly custom, or when learning is the goal. In between, consider a hybrid: use a library for scales and helpers, but draw the chart yourself, or use a small, focused library for one chart type rather than a large, general one. That hybrid approach is common in professional work, and it balances speed with control.
Whichever you choose, keep the data layer separate from the chart layer, so switching libraries or moving to hand-built charts later does not require rewriting data fetching and processing. Also keep accessibility in mind: check what the library provides, and add what it lacks, such as a data table or a summary. A library does not absolve you of chart design responsibility; it merely changes where that responsibility is exercised, from geometry to configuration, choice, and verification. The chart must still answer the reader's question honestly, whatever built it.
Check: When is hand-building a chart better than using a library?
Section 39 — Building charts without a library
Building a chart without a library, as this chapter has taught, is a valuable skill even if you later use libraries, because it teaches you what a chart is made of. The process has a consistent shape: define the data, define the scales, define the layout, draw the shapes, add axes and labels, and add interaction and accessibility. That shape repeats across chart types, so learning it once gives you a template for many charts. It also keeps bundle size small and dependencies few, which matters for performance and for long-term maintenance, because there is no library version to update or break.
The key to maintainable hand-built charts is structure. Separate data processing from drawing: a function that cleans and aggregates data should not also draw bars. Separate scales from drawing: scale functions should be pure mappings, testable in isolation. Separate drawing from interaction: hit detection and tooltips should use the same scales as drawing, but they should be their own code. That separation mirrors good application architecture, and it makes charts easier to test, because you can test scales with known values, test data processing with sample inputs, and test drawing by inspecting the resulting elements or pixels, each independently.
A small set of helpers goes a long way. A helper to create SVG elements with attributes, a helper to compute nice tick values, a helper to build a path from points, and a helper to format numbers and dates, together cover most chart needs. Write those helpers once, reuse them across charts, and test them with edge cases, such as empty data, a single point, and equal values. Helpers should be small and honest: a tick helper that returns round values is better than one that tries to be clever and returns confusing values. Over time, your helpers become a personal mini-library, tailored to your projects, which is often the best library of all, because you understand every part of it.
Testing hand-built charts deserves attention. Unit tests can check scales and data processing, but visual testing is also needed, because a chart's correctness is partly visual. A useful practice is to render charts with known data and compare them, by eye or by screenshot, against expected pictures. Another practice is to add a debug mode that shows scale boundaries, hit areas, and label boxes, so you can see the geometry behind the picture. And, as always, check the chart against its data by hand for at least a few values, because a scale error that shifts every bar by a small amount may look plausible but be wrong.
The limits of hand-building appear with complexity. A dashboard with dozens of chart types, brushing, zooming, and live updates may be more reliably built with a library than by hand, because the library has already solved those interactions. Recognising that limit is part of the skill: hand-building is not a moral stance, it is a choice that fits some projects and not others. Build by hand when the chart is simple and the control matters; use a library when complexity or scale makes hand-building fragile. Either way, the fundamentals in this chapter let you judge the result, not merely accept it.
Check: What three separations make a hand-built chart maintainable?
Section 40 — Making charts accessible
Accessible charts are charts whose information is available to everyone, regardless of how they read the page. The foundation is a text alternative, as discussed for canvas and SVG: a table of the data, a summary of the main pattern, or both, kept in sync with the chart. That alternative is not a fallback for a broken chart; it is a first-class part of the chart, and it should be designed with the same care as the visual. A table that is hard to find, out of date, or missing the chart's key values fails the reader just as surely as a missing chart fails a sighted reader.
Beyond the text alternative, accessible charts need keyboard support for interaction. If a chart has interactive elements, such as bars that show tooltips or legend items that toggle series, those elements must be reachable and operable by keyboard, with visible focus indicators. In SVG, that means giving interactive elements a tabindex and a focus style, and handling keyboard events as well as mouse events. In canvas, it means providing focusable controls outside the canvas, or implementing keyboard navigation within the canvas, as discussed earlier. Test keyboard interaction by tabbing through the page and confirming you can reach every chart control and value, without a mouse.
Colour and contrast are accessibility requirements, not aesthetic preferences. Do not use colour as the only way to distinguish series or to convey meaning, such as good versus bad. Use labels, shapes, patterns, or position as well. Check contrast for text, for data colours against the background, and for adjacent series colours against each other. Use patterns, such as hatching, for print and for colour-blind readers where colour alone would fail, but use patterns sparingly, because dense patterns can make a chart busy. The goal is redundancy: the same information available through more than one channel, so losing one channel does not lose the information.
Motion and animation also have accessibility dimensions. Respect prefers-reduced-motion by providing a static final state without entrance animations or continuous motion. Avoid flashing or rapidly changing charts, which can trigger seizures in sensitive readers, and avoid animations that move large areas of the screen quickly. For live charts, provide a way to pause updates, because a constantly changing chart can be distracting or unreadable for readers who need more time to process it. Accessibility is not a checklist applied at the end; it is a set of design decisions made throughout, from data structure to interaction.
Finally, test with real tools and real users where possible. Use a screen reader to hear what the chart announces, use keyboard-only navigation to check operability, and use colour blindness simulators to check palettes. Where possible, ask a reader who uses assistive technology to try the chart and tell you what works and what does not, because automated checks catch only part of the picture. An accessible chart is one that a diverse group of readers can actually use, and that judgement comes from testing, not from intention alone.
Check: Why is a text alternative a first-class part of a chart rather than a fallback?
Section 41 — Colour blindness and contrast in detail
Colour blindness is not a single condition, but a family of conditions that affect how hues are distinguished, most commonly making red and green hard to tell apart, and less commonly affecting blue and yellow. Designing for colour blindness therefore means not relying on hue distinctions that those readers cannot perceive, and testing palettes with simulators that show how a chart appears under different forms. A palette that passes a simulator for the most common forms is a good starting point, but it is not a guarantee, because individual vision varies, so redundancy remains essential: labels, shapes, and patterns should carry information alongside colour.
Contrast is a separate but related concern. Contrast is the difference in lightness between a colour and its background, and it determines legibility for readers with low vision, for readers in bright light, and for printed charts. Text on a chart should meet the same contrast standards as body text, and data colours should contrast sufficiently with the background to be visible as shapes, even if they are not text. A pale colour on a white background may be aesthetically pleasing but functionally invisible, especially as a thin line. When in doubt, darken the colour or add an outline, because a chart that cannot be seen cannot be read, regardless of its design intentions.
A practical workflow for palette checking has four steps. First, choose a palette type appropriate to the data, as in the colour section. Second, check the palette in a colour blindness simulator for the common forms, and adjust any pair that merges. Third, check contrast for text and for thin data elements, and darken or lighten as needed. Fourth, check the chart in greyscale, and confirm that series remain distinguishable by lightness, shape, or label, not by hue alone. That workflow takes minutes with available tools, and it catches most colour problems before they reach readers. Document the checked palette in your design system, so future charts reuse it rather than inventing new, untested colours.
Patterns and textures can help, but they must be used carefully. Hatching, dots, or stripes can distinguish bars or pie slices without colour, which is valuable for print and for colour-blind readers. However, dense patterns can make a chart look busy, can interfere with reading values, and can cause visual vibration when used at small sizes. Use patterns for a small number of series, with light, simple patterns, and test them at actual sizes. For line charts, dash patterns and marker shapes are usually better than textures, because they do not fill the chart with pattern. Choose the redundant channel that fits the chart type, rather than applying patterns everywhere.
Finally, remember that colour choices are also cultural and contextual. Red may mean danger, loss, or simply a series colour, depending on context, and that meaning should be made explicit with labels rather than assumed. A chart that uses red for a neutral series in one place and for a warning in another will confuse readers, even those with full colour vision. Keep colour meanings consistent within a product, document them, and label them in the chart itself where the meaning matters. Colour is a powerful code, but only when its meanings are shared, tested, and consistent.
Check: Why is a greyscale check a useful test for a chart palette?
Section 42 — Exporting and printing charts
Charts often need to leave the page, as images for reports, as files for sharing, or as printed pages, and planning for export and print saves awkward screenshots later. SVG charts have a natural advantage for export: because they are vector, they can be serialised to a standalone SVG file, which scales cleanly and can be edited in vector tools. To export an SVG chart, serialise its markup, include any styles it depends on, because external CSS will not travel with the file, and offer it as a download. Test the exported file by opening it alone, because styles that worked in the page may be missing, and fonts may fall back to defaults, changing the layout.
Canvas charts export as raster images, usually PNG, through the toDataURL or toBlob methods, which produce an image of the canvas at its internal resolution. That image will be sharp if the canvas was sized for high-density screens, as earlier sections recommend, and blurry if it was not. Export at a higher resolution than the display size if the image will be printed or projected, by drawing the chart to a larger offscreen canvas for export, rather than simply scaling up the displayed canvas. Also consider the background: a canvas with a transparent background will export with transparency, which may look wrong on a white report page or a dark slide, so fill the background explicitly for export if the chart was designed for a specific background.
Printing charts introduces its own constraints. Printers use different colour models, and colours that look distinct on screen may merge on paper, especially in greyscale printing. Test printing by actually printing, or by previewing in greyscale, and consider a print stylesheet that adjusts chart colours, hides interactive controls, and ensures the chart fits the page width. For SVG, print is usually straightforward, because vector scales to paper. For canvas, print resolution depends on the canvas internal size, so a low-resolution canvas will print poorly. Also ensure that data tables print alongside charts, because a printed chart without its data is hard to use in a meeting or report.
A related export need is copying data, not just pictures. Readers often want the underlying numbers for their own analysis, so provide a way to download the chart's data as a table or a CSV file, or to copy it to the clipboard. That data export should match the chart exactly, including any filtering or time range applied, because a mismatch between chart and exported data undermines trust. Label the export with the chart's title, date range, and units, so the file is self-explanatory when shared without the page.
Finally, consider accessibility in export. An exported image, whether SVG or PNG, needs the same text alternative as the on-page chart, or it will be inaccessible when shared. For SVG exports, include a title and description in the file. For PNG exports, share the data table alongside the image, or include the key values in the accompanying message. Export is not a separate feature from accessibility; it is the same information travelling to a new context, and it should travel with its meaning intact.
Check: Why must SVG styles be included when exporting an SVG chart as a standalone file?
Section 43 — Testing charts
Testing charts requires both code tests and visual checks, because a chart can be numerically correct and visually wrong, or visually plausible and numerically wrong. Unit tests are valuable for the parts that are pure logic: scales, tick generation, data aggregation, and formatting. A scale test checks that a known value maps to a known position and back. A tick test checks that ticks are round and within range. An aggregation test checks that bin counts sum to the total number of observations. These tests are fast, reliable, and catch regressions when code changes, and they should be written for any chart code that will be maintained over time.
Visual testing catches what unit tests cannot: overlapping labels, clipped axes, colours that merge, and layouts that break at small sizes. A simple visual test is to render the chart with several datasets, including edge cases, and inspect screenshots, either by eye or with a screenshot comparison tool that flags differences from a baseline image. Baseline comparisons can be brittle, because a small intentional change flags every chart, but they are still useful for catching unintended changes, provided baselines are updated deliberately when design changes. For hand-built charts, even manual screenshot review during development, with a checklist of sizes and datasets, is far better than no visual testing at all.
Interaction testing is a third layer. Test hover, focus, click, and touch behaviours with real input, not just by calling functions, because coordinate conversion, event handling, and tooltip positioning have bugs that only appear with real pointers. Test keyboard navigation by tabbing through the chart, and test with a screen reader to hear what is announced. Test resizing by dragging the window, and test live updates by feeding the chart rapidly changing data. These tests are slower than unit tests, but they catch the bugs users actually encounter, which are often interaction bugs rather than calculation bugs.
Data testing deserves emphasis. Test charts with empty data, with a single point, with all equal values, with missing values, with negative values, and with very large values. Each of those cases has broken a real chart, and each is easy to test once you think of it. Also test with realistic data, not only with neat round numbers, because real data is messy, with long labels, uneven gaps, and outliers that neat test data hides. A chart that passes neat tests but fails real data is not a tested chart; it is a chart tested on the wrong data.
Finally, test the chart as a reader would use it, not only as a developer would build it. Can a new reader answer the chart's question in five seconds? Can they find the exact value they need? Can they use it on a phone, with a keyboard, or in print? Those questions are the ultimate tests, and they require stepping back from code and looking at the chart as a communication. A chart that passes code tests and visual tests but fails those reader tests still needs redesign, because its purpose is communication, not merely correct rendering.
Check: Why are unit tests alone not enough to test a chart?
Section 44 — Misleading charts and how to avoid them
Misleading charts are usually not deliberate lies, but the result of choices that exaggerate, hide, or imply what the data does not support. The most common is a cropped axis, especially on a bar chart, where starting the axis above zero makes small differences look huge, because bar length no longer represents value. A related trick is a dual axis, where two series with different scales share a chart, and their relative positions imply a relationship that depends entirely on the chosen scales. Both techniques can be used honestly with clear labelling, but they are so easily misread that they should be avoided unless there is a strong reason and the labelling is explicit.
Cherry-picking is another form of misleading, where a time range or a subset is chosen to show a favourable trend while hiding the broader picture. A line chart that starts just after a dip will show only a rise, even if the longer trend is flat. The defence is to show a meaningful context: a longer time range, a comparison series, or a note stating why the range was chosen. Similarly, omitting data, such as dropping an inconvenient category into "Other" without explanation, or smoothing away a spike, can change the story. If you omit or smooth, say so, and show the omitted or unsmoothed data where it matters.
Visual choices can also mislead. Using area or volume to represent a one-dimensional value exaggerates differences, because doubling a value quadruples an area if both dimensions double. Using a pictogram, such as a person icon scaled in both dimensions to represent population, has the same problem. Using a three-dimensional effect on a bar or pie chart distorts proportions, because the front face and the back face imply different values. The rule is simple: the visual property that encodes value should scale in proportion to value, and no other visual property should imply a different value. Bars scale in length, lines in position, and areas in area, each with care.
Correlation and causation confusion is a subtler misleading pattern. A chart showing two series moving together does not show that one causes the other, but readers often infer causation from co-movement, especially when the chart's title or annotation suggests it. Avoid causal language in titles and annotations unless causation is established, and consider whether a scatter plot, which shows relationship without implying sequence, is more honest than two lines on one chart, which implies a shared story. Also be careful with forecasts: a line extended into the future with the same style as measured data implies certainty that a forecast does not have, so use a dashed line or a shaded band for forecasts, and label them as such.
The defence against misleading charts is a checklist applied before publishing. Does the axis start at a labelled, honest value? Are scales consistent and labelled? Is the time range meaningful and stated? Are omissions and smoothing disclosed? Does the visual encoding scale proportionally? Does the title claim only what the data supports? If any answer is no, fix the chart before it reaches readers, because a misleading chart, once published, is hard to retract, and it damages trust in every chart you publish afterwards.
Check: Why is a dual-axis chart easy to misread even when its data is correct?
Section 45 — Real-time dashboards
A real-time dashboard is a collection of charts that update as new data arrives, and its design challenges are about attention and trust as much as about drawing. The first challenge is layout: a dashboard must show many charts without overwhelming the reader, so it needs hierarchy. The most important metric, such as current sales or system health, should be prominent, with supporting charts smaller and secondary. Use a consistent grid, consistent chart styles, and consistent colour meanings across all charts, so the reader learns the dashboard's language once and applies it everywhere. A dashboard where each chart uses different colours for the same series, or different scales for the same metric, forces the reader to relearn each chart separately.
The second challenge is update cadence. Not every chart needs to update at the same rate, and updating everything constantly can be distracting and wasteful. A key metric might update every second, a trend chart every minute, and a summary table every hour, depending on how fast the underlying data actually changes and how fast decisions need to be made. Match update rates to decision rates: if a reader acts hourly, second-by-second updates add noise, not value. Also provide controls to pause, filter, and change time ranges, because a dashboard that only shows the last minute is useless for understanding a trend, and a dashboard that cannot be paused is useless for examining a specific moment.
The third challenge is state and failure, as discussed for live charts, but at dashboard scale. If one data source fails, the dashboard should show which chart is stale, without clearing the whole dashboard or showing a generic error that hides working charts. If data is delayed, show the delay, because a dashboard that looks live but is actually minutes old can lead to wrong decisions. Keep timestamps visible for each chart or for the dashboard as a whole, and make the data's freshness part of the design, not an afterthought hidden in a tooltip.
Performance at dashboard scale requires care. Many charts updating frequently can strain the browser, especially on a phone, so throttle updates, reuse chart instances rather than recreating them, and pause updates for charts that are off-screen or in hidden tabs. Use canvas for dense or fast charts and SVG for interactive ones, as earlier sections advise, and consider whether every chart needs to be live: a static summary that updates on demand may serve better than a live chart that nobody watches. Measure dashboard performance with realistic data and on realistic devices, because a dashboard that runs smoothly on a developer's machine may stutter on a user's phone.
Finally, a dashboard is a product, not just a collection of charts, so it needs the same design care as any product: onboarding for new readers, explanations for metrics, and feedback from users about what they actually use. Track which charts are viewed and which are ignored, and remove or redesign ignored charts, rather than adding more. A dashboard with five charts that are all used is far more valuable than one with twenty charts that are mostly ignored, because attention is the scarcest resource in a dashboard, and every additional chart taxes it.
Check: Why should dashboard update rates match decision rates rather than data arrival rates?
Section 46 — Combining canvas and SVG
Canvas and SVG are not mutually exclusive, and combining them can give you the strengths of both. A common pattern is to draw dense data on canvas, where performance matters, and to draw axes, labels, and interactive elements in SVG or HTML on top, where element benefits matter. For example, a scatter plot with fifty thousand points can be painted on canvas, while its axes, gridlines, and tooltip are SVG or HTML elements positioned over it, using the same scales. That hybrid gives fast drawing for the bulk of the data and accessible, styleable elements for the parts readers interact with most.
Layering requires careful alignment. The canvas and the SVG overlay must share the same size, the same margins, and the same scales, or the overlay will drift from the data beneath it. A practical structure is a container with relative positioning, a canvas absolutely positioned to fill it, and an SVG absolutely positioned on top, both sized to the container. Use the same plot area calculations for both layers, and test alignment by drawing a test point in both layers and confirming they coincide. If the container resizes, resize both layers together and redraw both, because resizing one without the other breaks alignment immediately.
Interaction in a hybrid chart can use either layer. Pointer events can be handled on the top layer, with coordinates converted to data values through the shared scales, and hit detection for dense canvas data can be done by finding the nearest point, as in pure canvas. Highlights can be drawn in the top SVG layer, which is easier than redrawing the canvas, because the SVG highlight is an element that can be added, moved, and removed without touching the dense data below. Tooltips can be HTML elements positioned over the chart, which are easier to style and make accessible than canvas-drawn tooltips. That division of labour is the hybrid's main benefit: each layer does what it does best.
The costs of a hybrid are complexity and maintenance. Two rendering systems must be kept in sync, two sets of code must be tested, and bugs can arise from mismatches between layers that would not exist in a single-layer chart. A hybrid should therefore be used only when a single layer clearly fails: when data is too dense for SVG alone, but interaction or accessibility needs elements that canvas alone cannot provide. For ordinary charts, a single layer is simpler and better. Do not reach for a hybrid because it sounds sophisticated; reach for it because a measured problem, such as slow SVG rendering with real data, demands it.
A simpler hybrid is also common: an SVG chart with a canvas background, or a canvas chart with HTML controls and tables alongside it. Those are hybrids in a broader sense, because they combine technologies, but they do not require pixel-perfect layer alignment, and they are easier to maintain. The lesson is general: choose technologies per part of the interface, not per project, and let each part use the tool that fits its job. A chart is not a single technology choice; it is a composition of data, drawing, interaction, and text, and each part can use a different tool where that helps.
Check: In a hybrid chart, which layer usually draws dense data, and which draws axes and tooltips?
Section 47 — Chart state and data flow
Chart state is the set of values that determine what a chart shows: the data, the selected series, the time range, the sort order, the highlighted point, and the zoom level. Managing that state clearly is essential, because charts that mix state with drawing code become hard to update and hard to debug. A good pattern is to keep state in one place, such as a plain object, and to derive the chart's appearance from that state by a render function that reads state and draws. When state changes, call render, and the chart updates. That pattern, familiar from application development, applies to charts as well, and it makes interactions predictable: a click changes state, state changes rendering, and rendering never changes state directly.
Data flow in a chart usually has three stages: fetching, processing, and rendering. Fetching gets raw data from an API or a file. Processing cleans it, fills or marks missing values, aggregates or filters it, and computes scales and derived values, such as totals or averages. Rendering turns the processed data into shapes, labels, and interactions. Keeping those stages separate means you can test each stage, reuse processing for multiple charts, and change rendering without touching fetching.
Derived state deserves care. Values such as the maximum, the tick values, and the sorted order are derived from data, and they should be computed from data when rendering, not stored separately and kept in sync by hand, because stored derived values drift out of sync when data changes. If you store a maximum separately, you must remember to update it whenever data changes, and you will eventually forget. Computing it during render is slightly more work per render, but it is always correct, and for charts of ordinary size, the cost is negligible. The same principle applies to filtered data: compute the filtered set from the full set and the filter state at render time, rather than maintaining a separate filtered copy.
Interaction state, such as the hovered or selected element, is also state, but it is often transient and should be handled accordingly. A hovered element changes frequently and does not need to persist, so it can be stored separately from data state and used only for highlighting. A selected element, such as a clicked bar that filters other charts, is more durable and should be part of the main state, because other parts of the interface depend on it. Distinguishing transient from durable state prevents bugs where a hover highlight persists after the pointer leaves, or a selection is lost when data updates.
Finally, make chart state visible for debugging. A debug panel or console log that shows current state, such as data length, selected series, and time range, can save hours when a chart shows the wrong data, because you can see whether the state is wrong or the rendering is wrong. That distinction is the key diagnostic question for any chart bug: is the data wrong, is the state wrong, or is the drawing wrong? A clear state structure lets you answer it quickly, and it makes the chart maintainable as it gains features, which is the difference between a chart that grows gracefully and one that collapses under its own interactions.
Check: Why should derived values like the maximum be computed at render time rather than stored?
Section 48 — Empty, loading, and error statesA chart is not always showing data. It may be waiting for data to load, it may have received no data, or it may have failed to load data, and each of those states needs a designed appearance, not an empty box or a broken chart. An empty chart area with no explanation looks like a bug, even when it is correct, because the reader cannot tell whether data is missing, loading, or failed.
A loading state should show that work is happening, without implying data that does not exist. A skeleton, which is a placeholder shape resembling the chart's layout, is often better than a spinner, because it preserves layout and reduces jarring shifts when data arrives. A spinner is acceptable for short loads, but it should be labelled, such as "Loading sales data", rather than spinning silently. Avoid drawing fake data as a loading placeholder, because readers may mistake it for real data, especially in screenshots. The loading state should also be time-bounded in perception: if loading takes long, show progress or a message, rather than spinning indefinitely without explanation.
An empty state occurs when data loads successfully but contains no values for the current filter or time range. It should say so plainly, such as "No sales in this period", and suggest an action, such as widening the time range or clearing a filter. It should not draw an empty chart with axes and no data, because that implies a zero trend, nor should it show a previous period's data as if it were current. The empty state is also an opportunity to help: a button to reset filters, or a link to a related view with data, turns a dead end into a next step. Keep the empty message in the chart's place, so layout does not collapse and the reader understands what is empty.
An error state occurs when data fails to load, due to a network problem, a server error, or bad data. It should say what happened in plain language, without technical jargon, and offer a retry action. It should preserve any previously loaded data where possible, with a note that it may be stale, rather than clearing the chart entirely, because stale data is often more useful than no data. It should also avoid technical details that do not help the reader, such as stack traces, while logging those details for developers. An error state that says "Could not load sales data. Try again." with a retry button is far more useful than a blank area or a cryptic code.
Testing these states is straightforward: test with a slow network, with an empty dataset, and with a failing endpoint, and confirm each state appears correctly and recovers correctly when data arrives or the retry succeeds. Also test that the chart's accessibility holds in these states: a loading message should be announced, an empty message should be readable, and an error message should be focusable. A chart that handles its non-data states well feels reliable, because readers encounter those states often, and a chart that handles them badly feels broken, even when its data rendering is perfect.
Check: Why should a loading chart not draw fake data as a placeholder?
Section 49 — Choosing a rendering approach for dashboardsChoosing a rendering approach for a dashboard means choosing per chart, not per dashboard, because different charts have different needs. A dashboard may contain a dense scatter plot best drawn on canvas, a bar chart best drawn in SVG, a sparkline best drawn in HTML, and a table for exact values, all in one page. Forcing all of them into one technology for consistency's sake sacrifices the strengths of each, while mixing technologies per chart, with consistent styling and colour meanings, gives each chart its best tool.
A decision checklist helps per chart. First, how much data? Thousands of points push towards canvas; dozens push towards SVG or HTML. Second, how interactive? Hover, focus, and selection per element push towards SVG; simple tooltips can be done in canvas with extra code. Third, how often does it update? Frequent updates push towards canvas or towards SVG attribute updates rather than recreation. Fourth, who needs to use it? High accessibility needs push towards SVG with a table, or HTML, rather than canvas alone. Fifth, how custom is the design? Highly custom designs may be easier by hand in either technology than in a library, while standard designs may be faster in a library. Answering those five questions per chart gives a defensible choice, rather than a fashionable one.
Performance and bundle size are dashboard-level concerns, not just per-chart ones. A dashboard with many libraries, one per chart type, can become heavy, while a dashboard with one library used consistently can be lighter and more maintainable. Similarly, a dashboard with many hand-built charts can become a maintenance burden if each chart reinvents scales and axes, while a dashboard with shared helpers is maintainable. The lesson is to share code where charts share needs: shared scale helpers, shared colour palettes, shared tooltip components, and shared data processing, regardless of whether the rendering is canvas, SVG, or HTML. Sharing at the helper level gives consistency without forcing a single rendering technology.
Team skill also matters. A team fluent in SVG may build and maintain SVG charts faster than canvas charts, even for moderately large data, while a team fluent in canvas may prefer canvas for most charts. That is a legitimate factor, because a chart that the team cannot debug or extend is a liability, however theoretically optimal its technology. Document the choice per chart, with its reasoning, so future maintainers understand why a chart uses canvas or SVG, and do not "fix" it by converting it to the other technology without understanding the trade-off. A short comment stating data size, interaction needs, and accessibility plan is enough.
Finally, revisit choices as dashboards grow. A chart that started as a small SVG bar chart may grow to thousands of bars and need canvas, or a canvas chart may gain interaction needs that make SVG or a hybrid better. Those changes are normal, and they should be made based on measured problems, such as slow rendering or inaccessible interaction, rather than on fashion. A dashboard that evolves its rendering per chart, guided by data size, interaction, accessibility, and team skill, will remain fast, usable, and maintainable, while one that locks into a single technology early will eventually fight that choice.
Check: Why should rendering choice be made per chart rather than per dashboard?
Section 50 — Charts, canvas, and SVG in one paragraph
Charts turn numbers into shapes that readers can grasp at a glance, and this chapter has shown how to build them with canvas, SVG, and plain HTML, each chosen for its strengths. Canvas paints pixels quickly and handles dense, fast, or animated pictures, but it forgets what it drew, so you must handle hit detection, coordinates, and accessibility yourself, with a data table as its text layer. SVG builds charts from elements that can be styled, hovered, focused, and inspected, which makes it the best default for typical interactive charts, with viewBox scaling for responsiveness and paths for lines, areas, and pie slices. Plain HTML and CSS serve simple bars and sparklines with the least code and the best native accessibility. Across all three, the same fundamentals recur: scales that map data to positions and back, axes with round ticks, margins for labels, colours used as a consistent code with redundancy for colour blindness, and labels, legends, and tooltips that give exact values without clutter.
Beyond drawing, this chapter has covered the decisions that make charts trustworthy and usable: handling missing data as gaps rather than zeros, sampling or aggregating large datasets rather than overwhelming the eye, showing uncertainty where it exists, avoiding misleading axes and cherry-picked ranges, and designing empty, loading, and error states so a chart never looks broken when it has no data. It has covered live updates and transitions, where object constancy helps readers track change, and dashboards, where hierarchy, update rates, and shared helpers matter more than any single chart. It has also covered when to use a library and when to build by hand, with the principle that understanding fundamentals makes library use informed, and that state and data flow should be kept clear so charts remain maintainable as they gain interaction.
Carry forward a simple workflow for every chart you build from now on. Start from the reader's question, choose the simplest chart type that makes the required comparison directly, and sketch it with real data before coding. Build scales and axes first, draw shapes through those scales, add labels and a text alternative, and check the result by hand against the data, at small sizes, in greyscale, and with a keyboard. If the chart fails any of those checks, simplify or redesign before adding features, because features cannot fix a chart that misleads or excludes. A chart that passes those checks is not merely a picture of numbers; it is a communication that readers can trust, use, and act on, which is the whole purpose of visualising data in the first place. When you are unsure which tool to pick, start with the simplest one that answers the question, test it with real data and real readers, and only add complexity when a measured problem, such as slow rendering or missing interaction, shows that the simple tool has reached its limit.
Check: In your own words, when should you choose canvas, SVG, and HTML respectively for a chart?
Practical task
Build a small dashboard page with three charts from the same dataset, such as monthly sales for a year: a bar chart in SVG, a line chart on canvas, and a set of sparklines in HTML. For each chart, include a data table with the same values, a title stating its question, and labels that remain legible at phone width. Then test it: check bar heights against values by hand, view it in greyscale, navigate it by keyboard, and resize the window to phone width, fixing any overlap, clipping, or inaccessible values you find.
Chapter questions
- A bar chart's axis starts at 80 instead of zero. What story does it tell that the data does not, and how would you fix it?
- You have 50,000 points to show in a scatter plot. What rendering and data approach would you choose, and why?
- A canvas chart's tooltips work with a mouse but not with a keyboard or touch. What changes would you make?
- Write the steps to build an SVG donut chart, including how slices are calculated and labelled.
- A dashboard chart updates every second but readers act hourly. What is wrong with its update rate, and what would you change?