CS-GY 6313 - Fall 2025
NYU Tandon School of Engineering
2025-10-03
Vasas et al, PLOS Biology, 2024
Image from PLOS Biology
Humans perceive wavelengths from approximately 390-700nm
Visible range: 390-700nm
Luminance has huge dynamic range:
Colors result from spectral curves:
Light passes through cornea, pupil, lens, and reaches the retina
Multiple layers of cells process visual information before sending to brain
Three types of cones: S (short/blue), M (medium/green), L (long/red)
Image from WR Franklin
Highest density in fovea (center of vision)
Rods more sensitive in low light; cones provide color vision
Color perception results from brain’s interpretation of cone responses
Foundation of color theory: any color can be matched with three primaries
Humans perceive colors through three channels
Material from Enrico Bertini
Two primary purposes:
Show numerical values
Distinguish categories
Material from Enrico Bertini
Mapping numerical values to color intensity or hue
Material from Enrico Bertini
Using distinct colors to represent different categories
Material from Enrico Bertini
Human perception is non-linear!
Material from Enrico Bertini
Best for quantitative data with natural ordering
Material from Enrico Bertini
For nominal/categorical data without inherent order
Material from Enrico Bertini
Research suggests: 5-10 distinct categories maximum
Beyond this limit: - Colors become confusable - Need additional encoding (shape, pattern) - Consider grouping categories
Reference: Healey, “Choosing effective colours for data visualization” IEEE Vis 1996
Material from Enrico Bertini
For data with meaningful midpoint (zero, average, neutral)
Sequential scale obscures the critical 50% threshold
Diverging scale clearly shows above/below threshold
Data from County Level Election Results
~10% of males and ~1% of females have color vision deficiencies
Oliveira, “Towards More Accessible Visualizations for Color-Vision-Deficient Individuals” 2013
In RGB space, equal numerical steps ≠ equal perceptual steps
// Single hue
const blueScale = d3.scaleSequential()
.domain([0, 100])
.interpolator(d3.interpolateBlues);
// Multi-hue
const viridisScale = d3.scaleSequential()
.domain([0, 100])
.interpolator(d3.interpolateViridis);
// Diverging scale with custom midpoint
const divergingScale = d3.scaleDiverging()
.domain([−50, 0, 100]) // min, midpoint, max
.interpolator(d3.interpolateRdBu);
// Common diverging schemes:
// RdBu (red-blue), RdYlGn (red-yellow-green),
// BrBG (brown-green), PuOr (purple-orange)
Perfect for: - Temperature anomalies - Election results - Profit/loss - Any data with meaningful zero
// Ordinal scale with color scheme
const categoryScale = d3.scaleOrdinal()
.domain(["A", "B", "C", "D"])
.range(d3.schemeCategory10);
// Available categorical schemes:
// schemeCategory10 - 10 distinct colors
// schemeSet1 - 9 colors (colorblind safe)
// schemeSet2 - 8 colors (print friendly)
// schemeSet3 - 12 colors (pastel)
// schemePaired - 12 colors (paired)
✅ Use single or multi-hue sequential ❌ Don’t use rainbow scales
✅ Use distinct hues with similar saturation/lightness ❌ Don’t use more than ~8 categories
✅ Use when there’s a meaningful midpoint ❌ Don’t use for purely positive data
✅ Test with colorblind simulators ✅ Provide redundant encoding when possible
const timeScale = d3.scaleTime()
.domain([new Date(2020, 0, 1), new Date(2025, 0, 1)])
.range([0, width]);
Transform continuous domains into discrete ranges
// Quantize scale - equal intervals
const quantizeScale = d3.scaleQuantize()
.domain([0, 100])
.range(["low", "medium", "high"]);
// Quantile scale - equal quantities
const quantileScale = d3.scaleQuantile()
.domain(data)
.range(["Q1", "Q2", "Q3", "Q4"]);
// Threshold scale - custom breakpoints
const thresholdScale = d3.scaleThreshold()
.domain([30, 70])
.range(["cold", "comfortable", "hot"]);
D3 provides multiple interpolation methods:
// RGB interpolation (can be muddy)
d3.interpolateRgb("red", "blue")(0.5);
// HSL interpolation (follows hue wheel)
d3.interpolateHsl("red", "blue")(0.5);
// LAB interpolation (perceptually uniform)
d3.interpolateLab("red", "blue")(0.5);
// HCL interpolation (best for many cases)
d3.interpolateHcl("red", "blue")(0.5);
// Cubehelix (rainbow with uniform luminance)
d3.interpolateCubehelix("red", "blue")(0.5);
// Custom sequential scale
const customSequential = d3.scaleSequential()
.domain([0, 100])
.interpolator(t => d3.interpolateHcl("#e8f4f8", "#004c6d")(t));
// Custom diverging scale
const customDiverging = d3.scaleDiverging()
.domain([−1, 0, 1])
.interpolator(t => t < 0.5
? d3.interpolateHcl("#67001f", "#f7f7f7")(t * 2)
: d3.interpolateHcl("#f7f7f7", "#053061")((t - 0.5) * 2));
// Piecewise scale
const piecewise = d3.scaleLi ## Practical Color Guidelines
### For Print
- Consider grayscale reproduction
- Use ColorBrewer schemes
- Test on actual printer
### For Screen
- Consider monitor variations
- Use sufficient contrast
- Test on different devices
### For Accessibility
- Use colorblind-safe palettes
- Provide alternative encodings
- Test with simulators
## Color Tools and Resources
### Online Tools:
- [ColorBrewer](https://colorbrewer2.org) - Color schemes for maps
- [Accessible Colors](https://accessible-colors.com) - WCAG compliance
- [Coblis](https://www.color-blindness.com/coblis-color-blindness-simulator/) - Colorblind simulator
### D3 Resources:
- [D3 Scale Chromatic](https://github.com/d3/d3-scale-chromatic) - All color schemes
- [Observable Color Notebooks](https://observablehq.com/@d3/color-schemes) - Interactive examples
### Research:
- [ColorBrewer Paper](http://colorbrewer2.org/learnmore/schemes_full.html) - Harrower & Brewer
- [Viridis Explanation](https://www.youtube.com/watch?v=xAoljeRJ3lU) - Why we need better colormaps
## Common Color Mistakes
### 1. Rainbow Color Maps
- Non-uniform perceptually
- Create false boundaries
- Poor for colorblind users
### 2. Too Many Categories
- Beyond 7-8, colors become confusable
- Consider grouping or filtering
### 3. Ignoring Context
- Red/green for good/bad in finance
- Cultural color associations vary
### 4. Poor Contrast
- Insufficient luminance difference
- Fails accessibility standards
## Lab Preview: Color and Scales in D3
### Today's Lab Activities:
1. Implement different scale types (linear, log, time)
2. Create color scales for different data types
3. Build a choropleth map with sequential colors
4. Design accessible categorical palettes
5. Test colorblind safety
### Key Concepts to Practice:
- Scale domains and ranges
- Color interpolation methods
- Legends for color scales
- Interactive scale adjustments
## Putting It All Together
### Example: Temperature Visualization
```javascript
// Temperature data with anomalies
const tempScale = d3.scaleDiverging()
.domain([−10, 0, 10]) // Anomaly in degrees
.interpolator(d3.interpolateRdBu)
.clamp(true); // Prevent extrapolation
// Apply to data
svg.selectAll("rect")
.data(temperatureData)
.enter().append("rect")
.attr("fill", d => tempScale(d.anomaly))
.attr("opacity", 0.8); // Slight transparency
// Add legend
const legend = d3.legendColor()
.scale(tempScale)
.title("Temperature Anomaly (°C)");