12 KiB
D3.js Colour Schemes and Palette Recommendations
Comprehensive guide to colour selection in data visualisation with d3.js.
Built-in categorical colour schemes
Category10 (default)
d3.schemeCategory10
// ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd',
// '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']
Characteristics:
- 10 distinct colours
- Good colour-blind accessibility
- Default choice for most categorical data
- Balanced saturation and brightness
Use cases: General purpose categorical encoding, legend items, multiple data series
Tableau10
d3.schemeTableau10
Characteristics:
- 10 colours optimised for data visualisation
- Professional appearance
- Excellent distinguishability
Use cases: Business dashboards, professional reports, presentations
Accent
d3.schemeAccent
// 8 colours with high saturation
Characteristics:
- Bright, vibrant colours
- High contrast
- Modern aesthetic
Use cases: Highlighting important categories, modern web applications
Dark2
d3.schemeDark2
// 8 darker, muted colours
Characteristics:
- Subdued palette
- Professional appearance
- Good for dark backgrounds
Use cases: Dark mode visualisations, professional contexts
Paired
d3.schemePaired
// 12 colours in pairs of similar hues
Characteristics:
- Pairs of light and dark variants
- Useful for nested categories
- 12 distinct colours
Use cases: Grouped bar charts, hierarchical categories, before/after comparisons
Pastel1 & Pastel2
d3.schemePastel1 // 9 colours
d3.schemePastel2 // 8 colours
Characteristics:
- Soft, low-saturation colours
- Gentle appearance
- Good for large areas
Use cases: Background colours, subtle categorisation, calming visualisations
Set1, Set2, Set3
d3.schemeSet1 // 9 colours - vivid
d3.schemeSet2 // 8 colours - muted
d3.schemeSet3 // 12 colours - pastel
Characteristics:
- Set1: High saturation, maximum distinction
- Set2: Professional, balanced
- Set3: Subtle, many categories
Use cases: Varied based on visual hierarchy needs
Sequential colour schemes
Sequential schemes map continuous data from low to high values using a single hue or gradient.
Single-hue sequential
Blues:
d3.interpolateBlues
d3.schemeBlues[9] // 9-step discrete version
Other single-hue options:
d3.interpolateGreens/d3.schemeGreensd3.interpolateOranges/d3.schemeOrangesd3.interpolatePurples/d3.schemePurplesd3.interpolateReds/d3.schemeRedsd3.interpolateGreys/d3.schemeGreys
Use cases:
- Simple heat maps
- Choropleth maps
- Density plots
- Single-metric visualisations
Multi-hue sequential
Viridis (recommended):
d3.interpolateViridis
Characteristics:
- Perceptually uniform
- Colour-blind friendly
- Print-safe
- No visual dead zones
- Monotonically increasing perceived lightness
Other perceptually-uniform options:
d3.interpolatePlasma- Purple to yellowd3.interpolateInferno- Black to white through red/oranged3.interpolateMagma- Black to white through purpled3.interpolateCividis- Colour-blind optimised
Colour-blind accessible:
d3.interpolateTurbo // Rainbow-like but perceptually uniform
d3.interpolateCool // Cyan to magenta
d3.interpolateWarm // Orange to yellow
Use cases:
- Scientific visualisation
- Medical imaging
- Any high-precision data visualisation
- Accessible visualisations
Traditional sequential
Yellow-Orange-Red:
d3.interpolateYlOrRd
d3.schemeYlOrRd[9]
Yellow-Green-Blue:
d3.interpolateYlGnBu
d3.schemeYlGnBu[9]
Other multi-hue:
d3.interpolateBuGn- Blue to greend3.interpolateBuPu- Blue to purpled3.interpolateGnBu- Green to blued3.interpolateOrRd- Orange to redd3.interpolatePuBu- Purple to blued3.interpolatePuBuGn- Purple to blue-greend3.interpolatePuRd- Purple to redd3.interpolateRdPu- Red to purpled3.interpolateYlGn- Yellow to greend3.interpolateYlOrBr- Yellow to orange-brown
Use cases: Traditional data visualisation, familiar colour associations (temperature, vegetation, water)
Diverging colour schemes
Diverging schemes highlight deviations from a central value using two distinct hues.
Red-Blue (temperature)
d3.interpolateRdBu
d3.schemeRdBu[11]
Characteristics:
- Intuitive temperature metaphor
- Strong contrast
- Clear positive/negative distinction
Use cases: Temperature, profit/loss, above/below average, correlation
Red-Yellow-Blue
d3.interpolateRdYlBu
d3.schemeRdYlBu[11]
Characteristics:
- Three-colour gradient
- Softer transition through yellow
- More visual steps
Use cases: When extreme values need emphasis and middle needs visibility
Other diverging schemes
Traffic light:
d3.interpolateRdYlGn // Red (bad) to green (good)
Spectral (rainbow):
d3.interpolateSpectral // Full spectrum
Other options:
d3.interpolateBrBG- Brown to blue-greend3.interpolatePiYG- Pink to yellow-greend3.interpolatePRGn- Purple to greend3.interpolatePuOr- Purple to oranged3.interpolateRdGy- Red to grey
Use cases: Choose based on semantic meaning and accessibility needs
Colour-blind friendly palettes
General guidelines
- Avoid red-green combinations (most common colour blindness)
- Use blue-orange diverging instead of red-green
- Add texture or patterns as redundant encoding
- Test with simulation tools
Recommended colour-blind safe schemes
Categorical:
// Okabe-Ito palette (colour-blind safe)
const okabePalette = [
'#E69F00', // Orange
'#56B4E9', // Sky blue
'#009E73', // Bluish green
'#F0E442', // Yellow
'#0072B2', // Blue
'#D55E00', // Vermillion
'#CC79A7', // Reddish purple
'#000000' // Black
];
const colourScale = d3.scaleOrdinal()
.domain(categories)
.range(okabePalette);
Sequential:
// Use Viridis, Cividis, or Blues
d3.interpolateViridis // Best overall
d3.interpolateCividis // Optimised for CVD
d3.interpolateBlues // Simple, safe
Diverging:
// Use blue-orange instead of red-green
d3.interpolateBrBG
d3.interpolatePuOr
Custom colour palettes
Creating custom sequential
const customSequential = d3.scaleLinear()
.domain([0, 100])
.range(['#e8f4f8', '#006d9c']) // Light to dark blue
.interpolate(d3.interpolateLab); // Perceptually uniform
Creating custom diverging
const customDiverging = d3.scaleLinear()
.domain([0, 50, 100])
.range(['#ca0020', '#f7f7f7', '#0571b0']) // Red, grey, blue
.interpolate(d3.interpolateLab);
Creating custom categorical
// Brand colours
const brandPalette = [
'#FF6B6B', // Primary red
'#4ECDC4', // Secondary teal
'#45B7D1', // Tertiary blue
'#FFA07A', // Accent coral
'#98D8C8' // Accent mint
];
const colourScale = d3.scaleOrdinal()
.domain(categories)
.range(brandPalette);
Semantic colour associations
Universal colour meanings
Red:
- Danger, error, negative
- High temperature
- Debt, loss
Green:
- Success, positive
- Growth, vegetation
- Profit, gain
Blue:
- Trust, calm
- Water, cold
- Information, neutral
Yellow/Orange:
- Warning, caution
- Energy, warmth
- Attention
Grey:
- Neutral, inactive
- Missing data
- Background
Context-specific palettes
Financial:
const financialColours = {
profit: '#27ae60',
loss: '#e74c3c',
neutral: '#95a5a6',
highlight: '#3498db'
};
Temperature:
const temperatureScale = d3.scaleSequential(d3.interpolateRdYlBu)
.domain([40, -10]); // Hot to cold (reversed)
Traffic/Status:
const statusColours = {
success: '#27ae60',
warning: '#f39c12',
error: '#e74c3c',
info: '#3498db',
neutral: '#95a5a6'
};
Accessibility best practices
Contrast ratios
Ensure sufficient contrast between colours and backgrounds:
// Good contrast example
const highContrast = {
background: '#ffffff',
text: '#2c3e50',
primary: '#3498db',
secondary: '#e74c3c'
};
WCAG guidelines:
- Normal text: 4.5:1 minimum
- Large text: 3:1 minimum
- UI components: 3:1 minimum
Redundant encoding
Never rely solely on colour to convey information:
// Add patterns or shapes
const symbols = ['circle', 'square', 'triangle', 'diamond'];
// Add text labels
// Use line styles (solid, dashed, dotted)
// Use size encoding
Testing
Test visualisations for colour blindness:
- Chrome DevTools (Rendering > Emulate vision deficiencies)
- Colour Oracle (free desktop application)
- Coblis (online simulator)
Professional colour recommendations
Data journalism
// Guardian style
const guardianPalette = [
'#005689', // Guardian blue
'#c70000', // Guardian red
'#7d0068', // Guardian pink
'#951c75', // Guardian purple
];
// FT style
const ftPalette = [
'#0f5499', // FT blue
'#990f3d', // FT red
'#593380', // FT purple
'#262a33', // FT black
];
Academic/Scientific
// Nature journal style
const naturePalette = [
'#0071b2', // Blue
'#d55e00', // Vermillion
'#009e73', // Green
'#f0e442', // Yellow
];
// Use Viridis for continuous data
const scientificScale = d3.scaleSequential(d3.interpolateViridis);
Corporate/Business
// Professional, conservative
const corporatePalette = [
'#003f5c', // Dark blue
'#58508d', // Purple
'#bc5090', // Magenta
'#ff6361', // Coral
'#ffa600' // Orange
];
Dynamic colour selection
Based on data range
function selectColourScheme(data) {
const extent = d3.extent(data);
const hasNegative = extent[0] < 0;
const hasPositive = extent[1] > 0;
if (hasNegative && hasPositive) {
// Diverging: data crosses zero
return d3.scaleSequentialSymlog(d3.interpolateRdBu)
.domain([extent[0], 0, extent[1]]);
} else {
// Sequential: all positive or all negative
return d3.scaleSequential(d3.interpolateViridis)
.domain(extent);
}
}
Based on category count
function selectCategoricalScheme(categories) {
const n = categories.length;
if (n <= 10) {
return d3.scaleOrdinal(d3.schemeTableau10);
} else if (n <= 12) {
return d3.scaleOrdinal(d3.schemePaired);
} else {
// For many categories, use sequential with quantize
return d3.scaleQuantize()
.domain([0, n - 1])
.range(d3.quantize(d3.interpolateRainbow, n));
}
}
Common colour mistakes to avoid
-
Rainbow gradients for sequential data
- Problem: Not perceptually uniform, hard to read
- Solution: Use Viridis, Blues, or other uniform schemes
-
Red-green for diverging (colour blindness)
- Problem: 8% of males can't distinguish
- Solution: Use blue-orange or purple-green
-
Too many categorical colours
- Problem: Hard to distinguish and remember
- Solution: Limit to 5-8 categories, use grouping
-
Insufficient contrast
- Problem: Poor readability
- Solution: Test contrast ratios, use darker colours on light backgrounds
-
Culturally inconsistent colours
- Problem: Confusing semantic meaning
- Solution: Research colour associations for target audience
-
Inverted temperature scales
- Problem: Counterintuitive (red = cold)
- Solution: Red/orange = hot, blue = cold
Quick reference guide
Need to show...
- Categories (≤10):
d3.schemeCategory10ord3.schemeTableau10 - Categories (>10):
d3.schemePairedor group categories - Sequential (general):
d3.interpolateViridis - Sequential (scientific):
d3.interpolateViridisord3.interpolatePlasma - Sequential (temperature):
d3.interpolateRdYlBu(inverted) - Diverging (zero):
d3.interpolateRdBuord3.interpolateBrBG - Diverging (good/bad):
d3.interpolateRdYlGn(inverted) - Colour-blind safe (categorical): Okabe-Ito palette (shown above)
- Colour-blind safe (sequential):
d3.interpolateCividisord3.interpolateBlues - Colour-blind safe (diverging):
d3.interpolatePuOrord3.interpolateBrBG
Always remember:
- Test for colour-blindness
- Ensure sufficient contrast
- Use semantic colours appropriately
- Add redundant encoding (patterns, labels)
- Keep it simple (fewer colours = clearer visualisation)