Last Updated: 4/7/2026
D3 Style Guide & Conventions
This guide documents the D3.js coding conventions used in the βWhere Did They Go?β project. Following these standards ensures consistent, readable code across all map visualization components.
Indentation Rules for Selection Chains
The project uses a two-space vs. one-space indentation convention to distinguish between different types of D3 selection methods:
- Two-space indent (
.): Methods that return the current selection (modify in place) - One-space indent (
.): Methods that return a new selection (filter or traverse)
This visual distinction helps developers quickly understand the data flow in complex D3 chains.
Example from the Codebase
selection = gTag
.selectAll('.border') // new selection (1 space)
.data(d.features, (d) => d.properties.id)
.join(
(enter) => {
let s = enter
.append('path') // new selection (1 space)
.attr('d', (d) => { // current selection (2 spaces)
d.geometry.coordinates[0].reverse();
return pathGen(d);
})
.attr('stroke', 'black') // current selection (2 spaces)
.attr('stroke-width', strokeWidth)
.attr('opacity', '0%')
.attr('fill-opacity', '0%')
.classed('border', true) // current selection (2 spaces)
.on('click', onBorderClick);
fadeIn(s, { duration: fadeDuration })
return s;
},
(update) => update,
(exit) => fadeOut(exit, { duration: fadeDuration }).remove()
);Selection Method Categories
Methods Returning Current Selection (2 spaces)
These methods modify the current selection and return it for further chaining:
.attr(name, value)β Set attribute.style(name, value)β Set CSS style.property(name, value)β Set DOM property.classed(name, boolean)β Add/remove CSS class.text(value)β Set text content.html(value)β Set HTML content.on(event, handler)β Attach event listener.each(function)β Invoke function for each element.call(function)β Invoke function once with selection.transition()β Create transition (returns transition object)
Methods Returning New Selection (1 space)
These methods filter or traverse the DOM, returning a new selection:
.select(selector)β Select first descendant.selectAll(selector)β Select all descendants.filter(predicate)β Filter selection.data(data, key)β Join data (returns update selection).enter()β Enter selection.exit()β Exit selection.merge(selection)β Merge selections.append(element)β Append new element.insert(element, before)β Insert new element.remove()β Remove elements
Why This Convention Matters
D3 selection chains can become long and complex. The indentation convention provides visual cues about the selectionβs state:
- Readability: Quickly identify where the selection changes vs. where itβs being modified
- Debugging: Easier to trace which selection is being operated on
- Consistency: All developers follow the same pattern across components
Using the Interpolators Utility
The project provides color and value interpolation utilities in client/src/utility/Interpolators.js:
normalize(value, min, max)
Normalizes a numeric value to a 0β1 range:
import { normalize } from '@/utility/Interpolators';
const t = normalize(yearData[year], normData[year].min, normData[year].max);
// t is now between 0 and 1Returns 0 if min and max are equal (prevents division by zero).
interpolateColor(color1, color2, t)
Calculates an intermediate color between two RGB colors:
import { interpolateColor } from '@/utility/Interpolators';
const lightColor = { r: 198, g: 219, b: 239 }; // light blue
const darkColor = { r: 8, g: 48, b: 108 }; // dark blue
const color = interpolateColor(lightColor, darkColor, 0.5);
// Returns: "rgb(103, 133, 173)"The t parameter (typically 0β1) controls the interpolation:
t = 0returnscolor1t = 1returnscolor2t = 0.5returns the midpoint color
Example: Heat Map Coloring
The BorderData.vue component uses both utilities to create a population heat map:
import { normalize, interpolateColor } from '@/utility/Interpolators';
const lightColor = { r: 198, g: 219, b: 239 };
const darkColor = { r: 8, g: 48, b: 108 };
function getCountyColor(d, year) {
const yearData = d.properties["pop-by-year"];
if (Object.hasOwn(yearData, year)) {
const t = normalize(yearData[year], normData[year].min, normData[year].max);
return interpolateColor(lightColor, darkColor, t);
} else {
return 'rgb(240, 240, 240)'; // gray for missing data
}
}
createTransition(selection)
.attr('fill', (d) => getCountyColor(d, year))
.attr('fill-opacity', '100%');Transition Helpers
The project provides transition utilities in client/src/d3/transitions/:
createTransition(selection)β Creates a standard transition with consistent durationfadeIn(selection, options)β Fades elements to 100% opacityfadeOut(selection, options)β Fades elements to 0% opacity
Use these instead of creating raw D3 transitions to maintain consistent animation timing across the application.
Whatβs Next
- Fetching Data β Learn how to load GeoJSON and JSON data
- Data Sources β Reference for all data files
- Adding A Data Component β See D3 selection patterns used in a complete component example