Test Yourself!

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.

Running both question pagesShell
node check.mjs questions-1-5.html
node check.mjs questions-6-10.html

Questions

questions-1-5.html: selections, joins, hierarchies, scales and piesHTMLLive
<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>
questions-6-10.html: parsing, formatting, transitions, forces and geographyHTMLLive
<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>