دروس تعليمية

الألوان في سرد القصص بالبيانات: ما وراء ألوان المخططات الافتراضية

قراءة 10 دقيقة

The default color palettes in charting libraries are designed for one thing: visual distinctiveness. They give adjacent data series colors that are easy to tell apart at a glance. This is a useful starting point. It is also the end of the design thinking baked into those defaults.

Default palettes do not consider your data type, your audience, the story you are telling, the emotional register appropriate for your subject matter, or the experience of users who cannot distinguish red from green. Replacing defaults with intentional color choices is one of the highest-leverage improvements available to anyone creating data visualizations.

This guide covers the three fundamental palette types for data visualization, how to make each one accessible, and how to configure custom color scales in D3.js and Chart.js.


Why Default Chart Colors Fail

They Assume Categorical Data

Most charting library defaults are designed for categorical data — discrete, unordered groups like "Product A, Product B, Product C." They give each category a visually distinct color. If your data is actually sequential (temperature from cold to hot) or diverging (vote margin from strongly Democratic to strongly Republican), a categorical palette is actively misleading. It implies all values are equally different from each other, when actually there is a meaningful order or a meaningful center point.

They Create False Equivalence

When every data series gets a bold, saturated color, every series visually claims equal importance. But most data stories have hierarchy. One line in a chart is the story; the others are context. Treating them identically in color weight forces the reader to parse the data themselves rather than having the color guide their attention.

They Are Not Tested for Color Vision Deficiency

Red-green color blindness (deuteranopia and protanopia combined affect approximately 8% of males) makes the classic red/green combination in default palettes useless as a distinction. D3's "Category10" palette includes both #E15759 (a muted red) and #76B7B2 (a muted teal) — these are distinguishable for most color-blind viewers. But the similar "Tableau10" includes a red and a green that are nearly identical for deuteranopes. These differences matter, and the defaults do not make them obvious.


Sequential Color Scales

Sequential palettes encode quantitative data that ranges from low to high. They work by varying lightness (and sometimes saturation) across a single hue or a controlled multi-hue progression. The convention is: light = low value, dark = high value (on a light background). On a dark background, reverse this: dark = low, light = high.

Single-Hue Sequential

The simplest sequential palette uses one hue across a lightness range:

Light to dark blue:
#EFF6FF → #DBEAFE → #BFDBFE → #93C5FD → #3B82F6 → #1D4ED8 → #1E3A8A

Starting from #EFF6FF through #1E3A8A, this reads as a continuous quantity. A choropleth map using these colors for population density immediately communicates direction — dark regions are denser.

Single-hue palettes are perceptually straightforward because they reduce the viewer's decoding work to a single dimension (lightness). The tradeoff is that fine distinctions between adjacent values require attentive inspection.

Multi-Hue Sequential

Multi-hue sequential palettes vary both hue and lightness, increasing perceptual resolution at the cost of some intuitiveness. A classic example is yellow-green-blue:

#FFFFE0 → #C7E9B4 → #7FCDBB → #41B6C4 → #1D91C0 → #225EA8 → #0C2C84

Starting from pale yellow #FFFFE0 through saturated blue #0C2C84, this palette allows the viewer to distinguish more gradations than a single-hue version would while still reading as ordered. ColorBrewer's YlGnBu is the canonical reference palette for this style.

Multi-hue sequential is especially valuable in choropleths and heatmaps where you need fine distinctions across a continuous range and have space to include a color legend.

Perceptual Uniformity in Sequential Palettes

A critical requirement for sequential palettes is perceptual uniformity — each visual step should feel like an equal change in quantity. RGB interpolation fails this test because the green channel of RGB has disproportionate perceptual weight. A palette interpolated in RGB from #0000FF to #FF0000 passes through an uncomfortably vivid green midpoint that does not represent the midpoint value accurately.

Interpolate sequential palettes in OKLCH or CIELAB instead:

// D3.js — interpolate in OKLCH for perceptual uniformity
import { scaleSequential, interpolateOklch } from 'd3';

const colorScale = scaleSequential()
  .domain([minValue, maxValue])
  .interpolator(d3.interpolateOklch("#EFF6FF", "#1E3A8A"));

Diverging Color Scales

Diverging palettes encode data that has a meaningful midpoint — zero, a target value, a baseline, an average. They use two hues that diverge from a neutral center, making values above and below the midpoint immediately recognizable as directionally different.

When to Use Diverging Palettes

Use a diverging palette when the midpoint is meaningful to the story:

  • Vote margin: red for Republican-leaning, blue for Democratic-leaning, white or light gray for near-even
  • Temperature anomaly: blue for below average, red for above average, near-white for near-average
  • Profit/loss: red for negative, green for positive, white for break-even
  • Sentiment score: cold for negative sentiment, warm for positive, neutral center

Do not use a diverging palette when the midpoint is arbitrary — if you split a 0-100 scale at 50 just because it is in the middle, you are implying that 50 means something, and it probably does not.

Constructing an Accessible Diverging Palette

The two most important requirements for diverging palettes:

  1. The two endpoint hues must be distinguishable by color-blind viewers. Red-green fails this for approximately 8% of viewers. Blue-orange, blue-red, and purple-green all work because they differ in blue channel intensity, which all major forms of color blindness preserve.

  2. The midpoint should be visually neutral — near-white or light gray rather than a bright color that might appear to represent a positive or negative extreme.

A well-structured blue-to-red diverging palette:

#053061 → #2166AC → #4393C3 → #92C5DE → #D1E5F0 → #F7F7F7 → #FDDBC7 → #F4A582 → #D6604D → #B2182B → #67001F

From dark blue #053061 through a neutral light gray #F7F7F7 to dark red #67001F. The midpoint is clearly neutral; both ends have equal visual weight; and because blue and red differ in hue sufficiently, deuteranopes can still distinguish the two directions (though they will perceive the red side differently — always include a legend with value labels).


Accessible Data Visualization Palettes

Simulate Before Shipping

The most effective accessibility practice for data visualization is simulating color vision deficiencies before shipping. Use the color blindness simulator to test your palettes under deuteranopia, protanopia, tritanopia, and achromatopsia conditions. If your chart's color distinctions collapse into indistinguishable shades under simulation, they will collapse for real users.

The Okabe-Ito Palette

Masataka Okabe and Kei Ito's 8-color palette is the most widely recommended color-blind-safe categorical palette for data visualization. It was designed with explicit attention to distinguishability under all major forms of color vision deficiency:

#000000  Black
#E69F00  Orange
#56B4E9  Sky blue
#009E73  Bluish green
#F0E442  Yellow
#0072B2  Blue
#D55E00  Vermilion
#CC79A7  Reddish purple

Starting with #E69F00 and #56B4E9 as the first two distinct colors, this palette achieves visual separation that holds up under deuteranopia because it relies on differences in blue-yellow contrast rather than red-green contrast.

This palette is not maximally beautiful in the default sense — it was optimized for function, not aesthetics. But it is a trustworthy starting point that you can adjust while testing against color vision simulators.

Beyond Color: Shape, Pattern, and Labels

Color is not the only channel available for data distinction. In charts where accessibility is critical, combine color with:

Shape markers: Different point markers (circle, square, triangle, diamond, plus, cross) for scatter plots and line charts. Each data series gets both a unique color and a unique marker shape.

Line patterns: Solid, dashed, dotted, dash-dot — combined with color, these give two orthogonal channels of distinction for line charts.

Direct labels: Placing series labels at the end of each line eliminates the need to cross-reference a color legend entirely, which benefits all users (not just color-blind ones).

Patterns in fills: For bar charts and area charts, texture or crosshatch patterns alongside color provide a second encoding.

// Chart.js — combining color with border dash patterns
const datasets = [
  {
    label: 'Revenue',
    data: revenueData,
    borderColor: '#2166AC',
    borderDash: [],           // solid
    pointStyle: 'circle',
  },
  {
    label: 'Expenses',
    data: expenseData,
    borderColor: '#D6604D',
    borderDash: [6, 3],       // dashed
    pointStyle: 'rect',
  },
  {
    label: 'Profit',
    data: profitData,
    borderColor: '#009E73',
    borderDash: [2, 2],       // dotted
    pointStyle: 'triangle',
  },
];

Color for Emphasis and Narrative

Data visualization color is not only about distinguishing categories. It is also a narrative tool. The most powerful use of color in data storytelling is directing attention: make the data point that matters to your story visually salient, and subordinate everything else.

The One-Bright Rule

A common and effective technique: use a muted, desaturated palette for all context data, and reserve a single bright or saturated color for the data point or series that carries the story.

A bar chart showing revenue by product line, where the story is that Product C is the top performer:

Product A: #CBD5E1  (muted gray-blue)
Product B: #CBD5E1
Product C: #2563EB  (saturated brand blue — the story)
Product D: #CBD5E1
Product E: #CBD5E1

The chart does not shout every value equally. It says: "Here is the context, and here is the point." Readers grasp the narrative faster and retain it more reliably.

Sequential Color for Ordered Rankings

When showing a ranking where order matters (top 10 countries by metric, time series with best-to-worst ordering), a sequential palette reinforces the rank ordering perceptually. The highest value gets the darkest shade; the lowest gets the lightest. Color and position work together rather than independently.

Using Red Carefully

Red is the most culturally loaded color in data visualization. Most viewers interpret red as "bad" before they read any labels or legends. This is powerful when accurate (red for negative values, red for above-danger-threshold) and confusing when incidental (red as the first category color in a categorical palette that has no good/bad axis).

Reserve red for genuinely negative or urgent data. When red is just one color in a categorical series, it creates phantom meaning that the data does not support.


D3.js Color Scale Configuration

D3 provides a comprehensive color scale system through its d3-scale-chromatic module, which includes professionally designed sequential, diverging, and categorical palettes.

Sequential Scales in D3

import { scaleSequential } from 'd3-scale';
import { interpolateBlues, interpolateYlOrRd, interpolateViridis } from 'd3-scale-chromatic';

// Blues — single-hue sequential
const blueScale = scaleSequential()
  .domain([0, 100])
  .interpolator(interpolateBlues);

blueScale(0);   // lightest blue
blueScale(50);  // mid blue
blueScale(100); // darkest blue

// Viridis — perceptually uniform, colorblind-safe
const viridisScale = scaleSequential()
  .domain([minValue, maxValue])
  .interpolator(interpolateViridis);

Viridis is worth special mention: it is a perceptually uniform sequential palette designed to be readable in grayscale and distinguishable under all major forms of color vision deficiency. It is the default in Python's matplotlib for these reasons, and D3's inclusion of it makes it equally accessible in JavaScript visualization work.

Diverging Scales in D3

import { scaleDiverging } from 'd3-scale';
import { interpolateRdBu, interpolatePiYG } from 'd3-scale-chromatic';

// Red-Blue diverging — good for political maps, temperature anomalies
const rdBuScale = scaleDiverging()
  .domain([-1, 0, 1])    // [min, midpoint, max]
  .interpolator(interpolateRdBu);

rdBuScale(-1);   // saturated red
rdBuScale(0);    // near-white neutral
rdBuScale(1);    // saturated blue

// Custom diverging scale with explicit colors
import { interpolateRgb } from 'd3-interpolate';

const customDiverging = scaleDiverging()
  .domain([-100, 0, 100])
  .interpolator(t => {
    // t goes 0 → 1 across the full domain
    if (t < 0.5) {
      return interpolateRgb('#B2182B', '#F7F7F7')(t * 2);
    } else {
      return interpolateRgb('#F7F7F7', '#2166AC')((t - 0.5) * 2);
    }
  });

Categorical Scales in D3

import { scaleOrdinal } from 'd3-scale';
import { schemeTableau10, schemeSet2, schemePastel1 } from 'd3-scale-chromatic';

// Tableau 10 — good general-purpose categorical
const categoricalScale = scaleOrdinal()
  .domain(['A', 'B', 'C', 'D', 'E'])
  .range(schemeTableau10);

// Custom Okabe-Ito for maximum accessibility
const okabeItoScale = scaleOrdinal()
  .domain(categories)
  .range(['#E69F00', '#56B4E9', '#009E73', '#F0E442', '#0072B2', '#D55E00', '#CC79A7', '#000000']);

Chart.js Color Scale Configuration

Chart.js works with color values differently from D3 — it expects arrays of colors rather than scale functions. Generating those arrays for sequential or diverging palettes requires either a scale function (you can use D3's interpolators without D3's full charting library) or manual palette definition.

Custom Categorical Palettes in Chart.js

// Accessible categorical palette (Okabe-Ito)
const accessibleColors = [
  '#E69F00',  // Orange
  '#56B4E9',  // Sky blue
  '#009E73',  // Bluish green
  '#F0E442',  // Yellow
  '#0072B2',  // Blue
  '#D55E00',  // Vermilion
  '#CC79A7',  // Reddish purple
];

const chart = new Chart(ctx, {
  type: 'bar',
  data: {
    labels: categories,
    datasets: [{
      label: 'Values',
      data: values,
      backgroundColor: categories.map((_, i) => accessibleColors[i % accessibleColors.length]),
      borderColor: categories.map((_, i) => accessibleColors[i % accessibleColors.length]),
      borderWidth: 1,
    }],
  },
});

Sequential Colors for Ordered Bar Charts in Chart.js

// Generate a blue sequential palette for N bars
function generateSequentialBlues(n) {
  // D3 interpolator used standalone
  const interpolator = d3.interpolateBlues;
  // Use 0.2 to 0.9 range to avoid too-light and too-dark extremes
  return Array.from({ length: n }, (_, i) => interpolator(0.2 + (0.7 * i / (n - 1))));
}

const chart = new Chart(ctx, {
  type: 'bar',
  data: {
    labels: rankedItems,
    datasets: [{
      data: rankedValues,
      backgroundColor: generateSequentialBlues(rankedItems.length),
    }],
  },
});

Emphasis Coloring in Chart.js

// Highlight one bar, mute all others
const highlightIndex = 2;  // index of the story bar

const chart = new Chart(ctx, {
  type: 'bar',
  data: {
    datasets: [{
      data: values,
      backgroundColor: values.map((_, i) =>
        i === highlightIndex ? '#2563EB' : '#CBD5E1'
      ),
    }],
  },
});

Summary

Replacing default chart colors with intentional choices requires understanding what your data's structure is (categorical, sequential, or diverging), what story you want to tell, and who your audience is. The key principles:

  • Match palette type to data structure: categorical palettes for unordered groups, sequential for quantities with direction, diverging for quantities with a meaningful center.
  • Test every palette under color vision deficiency simulation before shipping.
  • Add shape, pattern, or direct labels as a secondary encoding channel alongside color.
  • Use color weight (saturation and brightness) to direct attention to the data that carries your story.
  • Configure D3's perceptually uniform interpolators or Chart.js custom color arrays to replace defaults that were designed for visual variety, not visual accuracy.

Data visualization color is ultimately a communication decision, not an aesthetic one. The right palette makes the data's structure and story legible without requiring the viewer to decode what the color choices mean. When color works well in data visualization, readers barely notice it — they notice the insight.

الألوان ذات الصلة

العلامات التجارية ذات الصلة

الأدوات ذات الصلة