An HTML table presents related data in rows and columns. It can make the underlying values of a visualization available in a human-readable form, giving people an alternative to interpreting a chart through sight or a screen reader description alone. section 10.2.3 section 10.3
Use a table especially when a visualization has many data points or when people may want to inspect or extract its values. For a smaller chart, a descriptive SVG <desc> may be sufficient; a table becomes the more useful alternative as the amount of data grows. section 10.2.3
Build an accessible data table around its meaning, not just its cells:
- Add a <caption> that identifies the dataset, such as “Cetacean species data.” section 10.2.3
- Create column headings with <th scope="col"> so each data column has a clear label. section 10.2.3
- Offer a CSV download link so users can take the displayed dataset with them. section 10.2.3
- Let users switch explicitly between chart and table modes, and update each button’s aria-pressed state to indicate the selected view. section 10.2.3
A chart/table alternative
A cetacean scatterplot can begin in chart mode and provide a “Display Data Table” button. Selecting it hides the chart container, shows a generated table, and exposes columns for common name, population estimate, maximum size, maximum weight, and conservation status. The table caption can also link to the CSV dataset. section 10.2.3
Listing 10.12 Creating the data table (data-table.js)
const createDataTable = (data) => {
const table = d3.select(".data-table-container") #1
.append("table");
table #2
.append("caption")
.text("Cetacean species data")
.append("a")
.attr("href", "./data/data.csv")
.attr("download", "cetaceans_data")
.text("Download the data in CSV format");
const tableHeader = table #3
.append("tr");
tableHeader
.append("th")
.attr("scope", "col")
.text("Common name");
tableHeader
.append("th")
.attr("scope", "col")
.text("Population estimate");
tableHeader
.append("th")
.attr("scope", "col")
.text("Maximum size (meter)");
tableHeader
.append("th")
.attr("scope", "col")
.text("Maximum weight (ton)");
tableHeader
.append("th")
.attr("scope", "col")
.text("Conservation status");
data.forEach(d => { #4
const tableRow = table
.append("tr");
tableRow
.append("td")
.text(d.common_name);
tableRow
.append("td")
.text(d3.format(".2s")
(d.global_population_estimate));
tableRow
.append("td")
.text(d.max_size_m);
tableRow
.append("td")
.text(d.max_weight_t);
tableRow #5
.append("td")
.text(conservationStatuses.find(status =>
status.id === d.status).label);
});
};
This implementation creates a table with D3, then adds a caption and scoped column headers before populating its data. section 10.2.3
Tables can also be a source of data. When a page contains many HTML tables, inspect the markup for a shared selector, select the relevant tables with querySelectorAll(), loop through their rows, and account for inconsistent real-world row structures while extracting values. section 14.1











