564 lines
12 KiB
Markdown
564 lines
12 KiB
Markdown
# 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)
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
d3.schemeTableau10
|
|
```
|
|
|
|
**Characteristics:**
|
|
- 10 colours optimised for data visualisation
|
|
- Professional appearance
|
|
- Excellent distinguishability
|
|
|
|
**Use cases:** Business dashboards, professional reports, presentations
|
|
|
|
### Accent
|
|
|
|
```javascript
|
|
d3.schemeAccent
|
|
// 8 colours with high saturation
|
|
```
|
|
|
|
**Characteristics:**
|
|
- Bright, vibrant colours
|
|
- High contrast
|
|
- Modern aesthetic
|
|
|
|
**Use cases:** Highlighting important categories, modern web applications
|
|
|
|
### Dark2
|
|
|
|
```javascript
|
|
d3.schemeDark2
|
|
// 8 darker, muted colours
|
|
```
|
|
|
|
**Characteristics:**
|
|
- Subdued palette
|
|
- Professional appearance
|
|
- Good for dark backgrounds
|
|
|
|
**Use cases:** Dark mode visualisations, professional contexts
|
|
|
|
### Paired
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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:**
|
|
```javascript
|
|
d3.interpolateBlues
|
|
d3.schemeBlues[9] // 9-step discrete version
|
|
```
|
|
|
|
**Other single-hue options:**
|
|
- `d3.interpolateGreens` / `d3.schemeGreens`
|
|
- `d3.interpolateOranges` / `d3.schemeOranges`
|
|
- `d3.interpolatePurples` / `d3.schemePurples`
|
|
- `d3.interpolateReds` / `d3.schemeReds`
|
|
- `d3.interpolateGreys` / `d3.schemeGreys`
|
|
|
|
**Use cases:**
|
|
- Simple heat maps
|
|
- Choropleth maps
|
|
- Density plots
|
|
- Single-metric visualisations
|
|
|
|
### Multi-hue sequential
|
|
|
|
**Viridis (recommended):**
|
|
```javascript
|
|
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 yellow
|
|
- `d3.interpolateInferno` - Black to white through red/orange
|
|
- `d3.interpolateMagma` - Black to white through purple
|
|
- `d3.interpolateCividis` - Colour-blind optimised
|
|
|
|
**Colour-blind accessible:**
|
|
```javascript
|
|
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:**
|
|
```javascript
|
|
d3.interpolateYlOrRd
|
|
d3.schemeYlOrRd[9]
|
|
```
|
|
|
|
**Yellow-Green-Blue:**
|
|
```javascript
|
|
d3.interpolateYlGnBu
|
|
d3.schemeYlGnBu[9]
|
|
```
|
|
|
|
**Other multi-hue:**
|
|
- `d3.interpolateBuGn` - Blue to green
|
|
- `d3.interpolateBuPu` - Blue to purple
|
|
- `d3.interpolateGnBu` - Green to blue
|
|
- `d3.interpolateOrRd` - Orange to red
|
|
- `d3.interpolatePuBu` - Purple to blue
|
|
- `d3.interpolatePuBuGn` - Purple to blue-green
|
|
- `d3.interpolatePuRd` - Purple to red
|
|
- `d3.interpolateRdPu` - Red to purple
|
|
- `d3.interpolateYlGn` - Yellow to green
|
|
- `d3.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)
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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:**
|
|
```javascript
|
|
d3.interpolateRdYlGn // Red (bad) to green (good)
|
|
```
|
|
|
|
**Spectral (rainbow):**
|
|
```javascript
|
|
d3.interpolateSpectral // Full spectrum
|
|
```
|
|
|
|
**Other options:**
|
|
- `d3.interpolateBrBG` - Brown to blue-green
|
|
- `d3.interpolatePiYG` - Pink to yellow-green
|
|
- `d3.interpolatePRGn` - Purple to green
|
|
- `d3.interpolatePuOr` - Purple to orange
|
|
- `d3.interpolateRdGy` - Red to grey
|
|
|
|
**Use cases:** Choose based on semantic meaning and accessibility needs
|
|
|
|
## Colour-blind friendly palettes
|
|
|
|
### General guidelines
|
|
|
|
1. **Avoid red-green combinations** (most common colour blindness)
|
|
2. **Use blue-orange diverging** instead of red-green
|
|
3. **Add texture or patterns** as redundant encoding
|
|
4. **Test with simulation tools**
|
|
|
|
### Recommended colour-blind safe schemes
|
|
|
|
**Categorical:**
|
|
```javascript
|
|
// 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:**
|
|
```javascript
|
|
// Use Viridis, Cividis, or Blues
|
|
d3.interpolateViridis // Best overall
|
|
d3.interpolateCividis // Optimised for CVD
|
|
d3.interpolateBlues // Simple, safe
|
|
```
|
|
|
|
**Diverging:**
|
|
```javascript
|
|
// Use blue-orange instead of red-green
|
|
d3.interpolateBrBG
|
|
d3.interpolatePuOr
|
|
```
|
|
|
|
## Custom colour palettes
|
|
|
|
### Creating custom sequential
|
|
|
|
```javascript
|
|
const customSequential = d3.scaleLinear()
|
|
.domain([0, 100])
|
|
.range(['#e8f4f8', '#006d9c']) // Light to dark blue
|
|
.interpolate(d3.interpolateLab); // Perceptually uniform
|
|
```
|
|
|
|
### Creating custom diverging
|
|
|
|
```javascript
|
|
const customDiverging = d3.scaleLinear()
|
|
.domain([0, 50, 100])
|
|
.range(['#ca0020', '#f7f7f7', '#0571b0']) // Red, grey, blue
|
|
.interpolate(d3.interpolateLab);
|
|
```
|
|
|
|
### Creating custom categorical
|
|
|
|
```javascript
|
|
// 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:**
|
|
```javascript
|
|
const financialColours = {
|
|
profit: '#27ae60',
|
|
loss: '#e74c3c',
|
|
neutral: '#95a5a6',
|
|
highlight: '#3498db'
|
|
};
|
|
```
|
|
|
|
**Temperature:**
|
|
```javascript
|
|
const temperatureScale = d3.scaleSequential(d3.interpolateRdYlBu)
|
|
.domain([40, -10]); // Hot to cold (reversed)
|
|
```
|
|
|
|
**Traffic/Status:**
|
|
```javascript
|
|
const statusColours = {
|
|
success: '#27ae60',
|
|
warning: '#f39c12',
|
|
error: '#e74c3c',
|
|
info: '#3498db',
|
|
neutral: '#95a5a6'
|
|
};
|
|
```
|
|
|
|
## Accessibility best practices
|
|
|
|
### Contrast ratios
|
|
|
|
Ensure sufficient contrast between colours and backgrounds:
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// Professional, conservative
|
|
const corporatePalette = [
|
|
'#003f5c', // Dark blue
|
|
'#58508d', // Purple
|
|
'#bc5090', // Magenta
|
|
'#ff6361', // Coral
|
|
'#ffa600' // Orange
|
|
];
|
|
```
|
|
|
|
## Dynamic colour selection
|
|
|
|
### Based on data range
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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
|
|
|
|
1. **Rainbow gradients for sequential data**
|
|
- Problem: Not perceptually uniform, hard to read
|
|
- Solution: Use Viridis, Blues, or other uniform schemes
|
|
|
|
2. **Red-green for diverging (colour blindness)**
|
|
- Problem: 8% of males can't distinguish
|
|
- Solution: Use blue-orange or purple-green
|
|
|
|
3. **Too many categorical colours**
|
|
- Problem: Hard to distinguish and remember
|
|
- Solution: Limit to 5-8 categories, use grouping
|
|
|
|
4. **Insufficient contrast**
|
|
- Problem: Poor readability
|
|
- Solution: Test contrast ratios, use darker colours on light backgrounds
|
|
|
|
5. **Culturally inconsistent colours**
|
|
- Problem: Confusing semantic meaning
|
|
- Solution: Research colour associations for target audience
|
|
|
|
6. **Inverted temperature scales**
|
|
- Problem: Counterintuitive (red = cold)
|
|
- Solution: Red/orange = hot, blue = cold
|
|
|
|
## Quick reference guide
|
|
|
|
**Need to show...**
|
|
|
|
- **Categories (≤10):** `d3.schemeCategory10` or `d3.schemeTableau10`
|
|
- **Categories (>10):** `d3.schemePaired` or group categories
|
|
- **Sequential (general):** `d3.interpolateViridis`
|
|
- **Sequential (scientific):** `d3.interpolateViridis` or `d3.interpolatePlasma`
|
|
- **Sequential (temperature):** `d3.interpolateRdYlBu` (inverted)
|
|
- **Diverging (zero):** `d3.interpolateRdBu` or `d3.interpolateBrBG`
|
|
- **Diverging (good/bad):** `d3.interpolateRdYlGn` (inverted)
|
|
- **Colour-blind safe (categorical):** Okabe-Ito palette (shown above)
|
|
- **Colour-blind safe (sequential):** `d3.interpolateCividis` or `d3.interpolateBlues`
|
|
- **Colour-blind safe (diverging):** `d3.interpolatePuOr` or `d3.interpolateBrBG`
|
|
|
|
**Always remember:**
|
|
1. Test for colour-blindness
|
|
2. Ensure sufficient contrast
|
|
3. Use semantic colours appropriately
|
|
4. Add redundant encoding (patterns, labels)
|
|
5. Keep it simple (fewer colours = clearer visualisation) |