509 lines
12 KiB
Markdown
509 lines
12 KiB
Markdown
# D3.js Scale Reference
|
|
|
|
Comprehensive guide to all d3 scale types with examples and use cases.
|
|
|
|
## Continuous scales
|
|
|
|
### Linear scale
|
|
|
|
Maps continuous input domain to continuous output range with linear interpolation.
|
|
|
|
```javascript
|
|
const scale = d3.scaleLinear()
|
|
.domain([0, 100])
|
|
.range([0, 500]);
|
|
|
|
scale(50); // Returns 250
|
|
scale(0); // Returns 0
|
|
scale(100); // Returns 500
|
|
|
|
// Invert scale (get input from output)
|
|
scale.invert(250); // Returns 50
|
|
```
|
|
|
|
**Use cases:**
|
|
- Most common scale for quantitative data
|
|
- Axes, bar lengths, position encoding
|
|
- Temperature, prices, counts, measurements
|
|
|
|
**Methods:**
|
|
- `.domain([min, max])` - Set input domain
|
|
- `.range([min, max])` - Set output range
|
|
- `.invert(value)` - Get domain value from range value
|
|
- `.clamp(true)` - Restrict output to range bounds
|
|
- `.nice()` - Extend domain to nice round values
|
|
|
|
### Power scale
|
|
|
|
Maps continuous input to continuous output with exponential transformation.
|
|
|
|
```javascript
|
|
const sqrtScale = d3.scalePow()
|
|
.exponent(0.5) // Square root
|
|
.domain([0, 100])
|
|
.range([0, 500]);
|
|
|
|
const squareScale = d3.scalePow()
|
|
.exponent(2) // Square
|
|
.domain([0, 100])
|
|
.range([0, 500]);
|
|
|
|
// Shorthand for square root
|
|
const sqrtScale2 = d3.scaleSqrt()
|
|
.domain([0, 100])
|
|
.range([0, 500]);
|
|
```
|
|
|
|
**Use cases:**
|
|
- Perceptual scaling (human perception is non-linear)
|
|
- Area encoding (use square root to map values to circle radii)
|
|
- Emphasising differences in small or large values
|
|
|
|
### Logarithmic scale
|
|
|
|
Maps continuous input to continuous output with logarithmic transformation.
|
|
|
|
```javascript
|
|
const logScale = d3.scaleLog()
|
|
.domain([1, 1000]) // Must be positive
|
|
.range([0, 500]);
|
|
|
|
logScale(1); // Returns 0
|
|
logScale(10); // Returns ~167
|
|
logScale(100); // Returns ~333
|
|
logScale(1000); // Returns 500
|
|
```
|
|
|
|
**Use cases:**
|
|
- Data spanning multiple orders of magnitude
|
|
- Population, GDP, wealth distributions
|
|
- Logarithmic axes
|
|
- Exponential growth visualisations
|
|
|
|
**Important:** Domain values must be strictly positive (>0).
|
|
|
|
### Time scale
|
|
|
|
Specialised linear scale for temporal data.
|
|
|
|
```javascript
|
|
const timeScale = d3.scaleTime()
|
|
.domain([new Date(2020, 0, 1), new Date(2024, 0, 1)])
|
|
.range([0, 800]);
|
|
|
|
timeScale(new Date(2022, 0, 1)); // Returns 400
|
|
|
|
// Invert to get date
|
|
timeScale.invert(400); // Returns Date object for mid-2022
|
|
```
|
|
|
|
**Use cases:**
|
|
- Time series visualisations
|
|
- Timeline axes
|
|
- Temporal animations
|
|
- Date-based interactions
|
|
|
|
**Methods:**
|
|
- `.nice()` - Extend domain to nice time intervals
|
|
- `.ticks(count)` - Generate nicely-spaced tick values
|
|
- All linear scale methods apply
|
|
|
|
### Quantize scale
|
|
|
|
Maps continuous input to discrete output buckets.
|
|
|
|
```javascript
|
|
const quantizeScale = d3.scaleQuantize()
|
|
.domain([0, 100])
|
|
.range(['low', 'medium', 'high']);
|
|
|
|
quantizeScale(25); // Returns 'low'
|
|
quantizeScale(50); // Returns 'medium'
|
|
quantizeScale(75); // Returns 'high'
|
|
|
|
// Get the threshold values
|
|
quantizeScale.thresholds(); // Returns [33.33, 66.67]
|
|
```
|
|
|
|
**Use cases:**
|
|
- Binning continuous data
|
|
- Heat map colours
|
|
- Risk categories (low/medium/high)
|
|
- Age groups, income brackets
|
|
|
|
### Quantile scale
|
|
|
|
Maps continuous input to discrete output based on quantiles.
|
|
|
|
```javascript
|
|
const quantileScale = d3.scaleQuantile()
|
|
.domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20, 24]) // Sample data
|
|
.range(['low', 'medium', 'high']);
|
|
|
|
quantileScale(8); // Returns based on quantile position
|
|
quantileScale.quantiles(); // Returns quantile thresholds
|
|
```
|
|
|
|
**Use cases:**
|
|
- Equal-size groups regardless of distribution
|
|
- Percentile-based categorisation
|
|
- Handling skewed distributions
|
|
|
|
### Threshold scale
|
|
|
|
Maps continuous input to discrete output with custom thresholds.
|
|
|
|
```javascript
|
|
const thresholdScale = d3.scaleThreshold()
|
|
.domain([0, 10, 20])
|
|
.range(['freezing', 'cold', 'warm', 'hot']);
|
|
|
|
thresholdScale(-5); // Returns 'freezing'
|
|
thresholdScale(5); // Returns 'cold'
|
|
thresholdScale(15); // Returns 'warm'
|
|
thresholdScale(25); // Returns 'hot'
|
|
```
|
|
|
|
**Use cases:**
|
|
- Custom breakpoints
|
|
- Grade boundaries (A, B, C, D, F)
|
|
- Temperature categories
|
|
- Air quality indices
|
|
|
|
## Sequential scales
|
|
|
|
### Sequential colour scale
|
|
|
|
Maps continuous input to continuous colour gradient.
|
|
|
|
```javascript
|
|
const colourScale = d3.scaleSequential(d3.interpolateBlues)
|
|
.domain([0, 100]);
|
|
|
|
colourScale(0); // Returns lightest blue
|
|
colourScale(50); // Returns mid blue
|
|
colourScale(100); // Returns darkest blue
|
|
```
|
|
|
|
**Available interpolators:**
|
|
|
|
**Single hue:**
|
|
- `d3.interpolateBlues`, `d3.interpolateGreens`, `d3.interpolateReds`
|
|
- `d3.interpolateOranges`, `d3.interpolatePurples`, `d3.interpolateGreys`
|
|
|
|
**Multi-hue:**
|
|
- `d3.interpolateViridis`, `d3.interpolateInferno`, `d3.interpolateMagma`
|
|
- `d3.interpolatePlasma`, `d3.interpolateWarm`, `d3.interpolateCool`
|
|
- `d3.interpolateCubehelixDefault`, `d3.interpolateTurbo`
|
|
|
|
**Use cases:**
|
|
- Heat maps, choropleth maps
|
|
- Continuous data visualisation
|
|
- Temperature, elevation, density
|
|
|
|
### Diverging colour scale
|
|
|
|
Maps continuous input to diverging colour gradient with a midpoint.
|
|
|
|
```javascript
|
|
const divergingScale = d3.scaleDiverging(d3.interpolateRdBu)
|
|
.domain([-10, 0, 10]);
|
|
|
|
divergingScale(-10); // Returns red
|
|
divergingScale(0); // Returns white/neutral
|
|
divergingScale(10); // Returns blue
|
|
```
|
|
|
|
**Available interpolators:**
|
|
- `d3.interpolateRdBu` - Red to blue
|
|
- `d3.interpolateRdYlBu` - Red, yellow, blue
|
|
- `d3.interpolateRdYlGn` - Red, yellow, green
|
|
- `d3.interpolatePiYG` - Pink, yellow, green
|
|
- `d3.interpolateBrBG` - Brown, blue-green
|
|
- `d3.interpolatePRGn` - Purple, green
|
|
- `d3.interpolatePuOr` - Purple, orange
|
|
- `d3.interpolateRdGy` - Red, grey
|
|
- `d3.interpolateSpectral` - Rainbow spectrum
|
|
|
|
**Use cases:**
|
|
- Data with meaningful midpoint (zero, average, neutral)
|
|
- Positive/negative values
|
|
- Above/below comparisons
|
|
- Correlation matrices
|
|
|
|
### Sequential quantile scale
|
|
|
|
Combines sequential colour with quantile mapping.
|
|
|
|
```javascript
|
|
const sequentialQuantileScale = d3.scaleSequentialQuantile(d3.interpolateBlues)
|
|
.domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20, 24]);
|
|
|
|
// Maps based on quantile position
|
|
```
|
|
|
|
**Use cases:**
|
|
- Perceptually uniform binning
|
|
- Handling outliers
|
|
- Skewed distributions
|
|
|
|
## Ordinal scales
|
|
|
|
### Band scale
|
|
|
|
Maps discrete input to continuous bands (rectangles) with optional padding.
|
|
|
|
```javascript
|
|
const bandScale = d3.scaleBand()
|
|
.domain(['A', 'B', 'C', 'D'])
|
|
.range([0, 400])
|
|
.padding(0.1);
|
|
|
|
bandScale('A'); // Returns start position (e.g., 0)
|
|
bandScale('B'); // Returns start position (e.g., 110)
|
|
bandScale.bandwidth(); // Returns width of each band (e.g., 95)
|
|
bandScale.step(); // Returns total step including padding
|
|
bandScale.paddingInner(); // Returns inner padding (between bands)
|
|
bandScale.paddingOuter(); // Returns outer padding (at edges)
|
|
```
|
|
|
|
**Use cases:**
|
|
- Bar charts (most common use case)
|
|
- Grouped elements
|
|
- Categorical axes
|
|
- Heat map cells
|
|
|
|
**Padding options:**
|
|
- `.padding(value)` - Sets both inner and outer padding (0-1)
|
|
- `.paddingInner(value)` - Padding between bands (0-1)
|
|
- `.paddingOuter(value)` - Padding at edges (0-1)
|
|
- `.align(value)` - Alignment of bands (0-1, default 0.5)
|
|
|
|
### Point scale
|
|
|
|
Maps discrete input to continuous points (no width).
|
|
|
|
```javascript
|
|
const pointScale = d3.scalePoint()
|
|
.domain(['A', 'B', 'C', 'D'])
|
|
.range([0, 400])
|
|
.padding(0.5);
|
|
|
|
pointScale('A'); // Returns position (e.g., 50)
|
|
pointScale('B'); // Returns position (e.g., 150)
|
|
pointScale('C'); // Returns position (e.g., 250)
|
|
pointScale('D'); // Returns position (e.g., 350)
|
|
pointScale.step(); // Returns distance between points
|
|
```
|
|
|
|
**Use cases:**
|
|
- Line chart categorical x-axis
|
|
- Scatter plot with categorical axis
|
|
- Node positions in network graphs
|
|
- Any point positioning for categories
|
|
|
|
### Ordinal colour scale
|
|
|
|
Maps discrete input to discrete output (colours, shapes, etc.).
|
|
|
|
```javascript
|
|
const colourScale = d3.scaleOrdinal(d3.schemeCategory10);
|
|
|
|
colourScale('apples'); // Returns first colour
|
|
colourScale('oranges'); // Returns second colour
|
|
colourScale('apples'); // Returns same first colour (consistent)
|
|
|
|
// Custom range
|
|
const customScale = d3.scaleOrdinal()
|
|
.domain(['cat1', 'cat2', 'cat3'])
|
|
.range(['#FF6B6B', '#4ECDC4', '#45B7D1']);
|
|
```
|
|
|
|
**Built-in colour schemes:**
|
|
|
|
**Categorical:**
|
|
- `d3.schemeCategory10` - 10 colours
|
|
- `d3.schemeAccent` - 8 colours
|
|
- `d3.schemeDark2` - 8 colours
|
|
- `d3.schemePaired` - 12 colours
|
|
- `d3.schemePastel1` - 9 colours
|
|
- `d3.schemePastel2` - 8 colours
|
|
- `d3.schemeSet1` - 9 colours
|
|
- `d3.schemeSet2` - 8 colours
|
|
- `d3.schemeSet3` - 12 colours
|
|
- `d3.schemeTableau10` - 10 colours
|
|
|
|
**Use cases:**
|
|
- Category colours
|
|
- Legend items
|
|
- Multi-series charts
|
|
- Network node types
|
|
|
|
## Scale utilities
|
|
|
|
### Nice domain
|
|
|
|
Extend domain to nice round values.
|
|
|
|
```javascript
|
|
const scale = d3.scaleLinear()
|
|
.domain([0.201, 0.996])
|
|
.nice();
|
|
|
|
scale.domain(); // Returns [0.2, 1.0]
|
|
|
|
// With count (approximate tick count)
|
|
const scale2 = d3.scaleLinear()
|
|
.domain([0.201, 0.996])
|
|
.nice(5);
|
|
```
|
|
|
|
### Clamping
|
|
|
|
Restrict output to range bounds.
|
|
|
|
```javascript
|
|
const scale = d3.scaleLinear()
|
|
.domain([0, 100])
|
|
.range([0, 500])
|
|
.clamp(true);
|
|
|
|
scale(-10); // Returns 0 (clamped)
|
|
scale(150); // Returns 500 (clamped)
|
|
```
|
|
|
|
### Copy scales
|
|
|
|
Create independent copies.
|
|
|
|
```javascript
|
|
const scale1 = d3.scaleLinear()
|
|
.domain([0, 100])
|
|
.range([0, 500]);
|
|
|
|
const scale2 = scale1.copy();
|
|
// scale2 is independent of scale1
|
|
```
|
|
|
|
### Tick generation
|
|
|
|
Generate nice tick values for axes.
|
|
|
|
```javascript
|
|
const scale = d3.scaleLinear()
|
|
.domain([0, 100])
|
|
.range([0, 500]);
|
|
|
|
scale.ticks(10); // Generate ~10 ticks
|
|
scale.tickFormat(10); // Get format function for ticks
|
|
scale.tickFormat(10, ".2f"); // Custom format (2 decimal places)
|
|
|
|
// Time scale ticks
|
|
const timeScale = d3.scaleTime()
|
|
.domain([new Date(2020, 0, 1), new Date(2024, 0, 1)]);
|
|
|
|
timeScale.ticks(d3.timeYear); // Yearly ticks
|
|
timeScale.ticks(d3.timeMonth, 3); // Every 3 months
|
|
timeScale.tickFormat(5, "%Y-%m"); // Format as year-month
|
|
```
|
|
|
|
## Colour spaces and interpolation
|
|
|
|
### RGB interpolation
|
|
|
|
```javascript
|
|
const scale = d3.scaleLinear()
|
|
.domain([0, 100])
|
|
.range(["blue", "red"]);
|
|
// Default: RGB interpolation
|
|
```
|
|
|
|
### HSL interpolation
|
|
|
|
```javascript
|
|
const scale = d3.scaleLinear()
|
|
.domain([0, 100])
|
|
.range(["blue", "red"])
|
|
.interpolate(d3.interpolateHsl);
|
|
// Smoother colour transitions
|
|
```
|
|
|
|
### Lab interpolation
|
|
|
|
```javascript
|
|
const scale = d3.scaleLinear()
|
|
.domain([0, 100])
|
|
.range(["blue", "red"])
|
|
.interpolate(d3.interpolateLab);
|
|
// Perceptually uniform
|
|
```
|
|
|
|
### HCL interpolation
|
|
|
|
```javascript
|
|
const scale = d3.scaleLinear()
|
|
.domain([0, 100])
|
|
.range(["blue", "red"])
|
|
.interpolate(d3.interpolateHcl);
|
|
// Perceptually uniform with hue
|
|
```
|
|
|
|
## Common patterns
|
|
|
|
### Diverging scale with custom midpoint
|
|
|
|
```javascript
|
|
const scale = d3.scaleLinear()
|
|
.domain([min, midpoint, max])
|
|
.range(["red", "white", "blue"])
|
|
.interpolate(d3.interpolateHcl);
|
|
```
|
|
|
|
### Multi-stop gradient scale
|
|
|
|
```javascript
|
|
const scale = d3.scaleLinear()
|
|
.domain([0, 25, 50, 75, 100])
|
|
.range(["#d53e4f", "#fc8d59", "#fee08b", "#e6f598", "#66c2a5"]);
|
|
```
|
|
|
|
### Radius scale for circles (perceptual)
|
|
|
|
```javascript
|
|
const radiusScale = d3.scaleSqrt()
|
|
.domain([0, d3.max(data, d => d.value)])
|
|
.range([0, 50]);
|
|
|
|
// Use with circles
|
|
circle.attr("r", d => radiusScale(d.value));
|
|
```
|
|
|
|
### Adaptive scale based on data range
|
|
|
|
```javascript
|
|
function createAdaptiveScale(data) {
|
|
const extent = d3.extent(data);
|
|
const range = extent[1] - extent[0];
|
|
|
|
// Use log scale if data spans >2 orders of magnitude
|
|
if (extent[1] / extent[0] > 100) {
|
|
return d3.scaleLog()
|
|
.domain(extent)
|
|
.range([0, width]);
|
|
}
|
|
|
|
// Otherwise use linear
|
|
return d3.scaleLinear()
|
|
.domain(extent)
|
|
.range([0, width]);
|
|
}
|
|
```
|
|
|
|
### Colour scale with explicit categories
|
|
|
|
```javascript
|
|
const colourScale = d3.scaleOrdinal()
|
|
.domain(['Low Risk', 'Medium Risk', 'High Risk'])
|
|
.range(['#2ecc71', '#f39c12', '#e74c3c'])
|
|
.unknown('#95a5a6'); // Fallback for unknown values
|
|
``` |