These ten questions cover D3 18,089 behaviors that surprise experienced developers: a select() that overwrites data, an index join that keeps stale attributes, a treemap with no values, an ordinal scale that grows, a pie that reorders, CSV numbers that compare as strings, a minus sign that is not a hyphen, a transition that cancels another, links that point to the wrong nodes and a polygon that swallows the globe. Every snippet ran in Chrome 154 1 with D3 7.9.0 through the check.mjs script of Test Yourself!. Write down what each prints and why, then check Appendix H for the real results and the reasons.
Checking your answers
Read the console rather than the page: only question 2's list and question 8's bar appear on screen. None of the ten throws an error, which is the point: each prints a plausible value that is not the one you meant. Question 8 depends on timers, so its width varies by a few pixels between runs; the answer gives the range seen here. Predict the output line by line before you run the pages, including the exact characters of question 7.
node check.mjs questions-1-5.html
node check.mjs questions-6-10.htmlQuestions
<script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js"></script>
<!-- 1. The items carry their own data; then you select the first one through its
parent. What does li.datum() return, and what data do the items hold? (6.2.1) -->
<ul id="shelf"><li></li><li></li></ul>
<!-- 2. Three items joined without a key, with the class set on enter only; then the
reversed array is joined. What classes and texts result? (Sections 6.2.4, 6.2.5) -->
<ul id="list"></ul>
<script>
d3.selectAll('#shelf li').data(['harbor', 'saffron']);
d3.select('#shelf').datum('Fiction');
const li = d3.select('#shelf').select('li');
console.log('1:', li.datum(), d3.selectAll('#shelf li').data().join(' '));
const draw = data => d3.select('#list').selectAll('li').data(data)
.join(enter => enter.append('li').attr('class', d => d)).text(d => d);
draw(['a', 'b', 'c']); draw(['c', 'b', 'a']);
console.log('2:', d3.selectAll('#list li').nodes().map(n => n.className + n.textContent)
.join(' '));
// 3. A treemap of two leaves, laid out without calling sum(). (Section 6.9.3)
const root = d3.hierarchy({ children: [{ v: 3 }, { v: 1 }] });
d3.treemap().size([100, 100])(root);
console.log('3:', root.value, root.leaves().map(n => [n.x0, n.y0, n.x1, n.y1].join())
.join(' '));
// 4. An ordinal scale for two books, asked for a third. (Section 6.3.4)
const color = d3.scaleOrdinal(['harbor', 'deepweb'], ['#1f5f8b', '#5b3f99']);
console.log('4:', color('saffron'), color.domain().join(' '));
// 5. A pie of [1, 3, 2]. Which index does each arc get, and where does the first
// datum's arc start? (Section 6.4.3)
const arcs = d3.pie()([1, 3, 2]);
console.log('5:', arcs.map(a => a.index).join(' '), arcs[0].startAngle.toFixed(2));
</script><script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js"></script>
<!-- 8. A bar grows to 300 px over 1 s; 500 ms in, a second transition recolors it.
How wide is it at 900 ms? (Section 6.6.2) -->
<svg width="320" height="30"><rect id="bar" width="0" height="20" fill="#1f5f8b"/></svg>
<script>
// 6. The largest value in a CSV column, parsed plainly and with autoType. What type
// does the month column get? (Section 6.5.2)
const csv = 'month,units\n2025-01,9\n2025-02,10';
console.log('6:', d3.max(d3.csvParse(csv), d => d.units),
d3.max(d3.csvParse(csv, d3.autoType), d => d.units),
d3.csvParse(csv, d3.autoType)[0].month instanceof Date);
// 7. A formatted negative number, read back as a number. (Section 6.5.2)
const s = d3.format('.1f')(-2.5);
console.log('7:', s, Number(s), s.charCodeAt(0).toString(16));
const bar = d3.select('#bar');
bar.transition().duration(1000).ease(d3.easeLinear).attr('width', 300);
d3.timeout(() => bar.transition().duration(200).attr('fill', '#b5452f'), 500);
d3.timeout(() => console.log('8:', Math.round(bar.attr('width')), bar.attr('fill')), 900);
// 9. Links by book id, and a simulation built without link.id(). (Section 6.10.1)
const nodes = [{ id: 1 }, { id: 2 }, { id: 3 }], links = [{ source: 1, target: 2 }];
d3.forceSimulation(nodes).force('link', d3.forceLink(links)).stop();
console.log('9:', links[0].source.id, links[0].target.id);
// 10. The same 10-degree square, wound counterclockwise (as RFC 7946 asks) and
// clockwise. What share of the sphere does each cover? (Section 6.11.2)
const square = ring => ({ type: 'Polygon', coordinates: [ring] });
const ccw = [[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]];
console.log('10:', (d3.geoArea(square(ccw)) / (4 * Math.PI)).toFixed(4),
(d3.geoArea(square([...ccw].reverse())) / (4 * Math.PI)).toFixed(4));
</script>