mirror of
https://github.com/olehomelchenko/chart-sins.git
synced 2026-08-08 02:22:43 +00:00
Add two sins from wild specimens: category colours, rebinned scales
Both arrived as real charts before the sins existed, which is the better order — the specimen makes the failure mode obvious. "Bar Colours That Contradict Their Labels" (severity 2): categories that already own a colour, drawn in a default palette, so Black is a green bar. Nothing false, which is exactly why it's a 2 under our rubric — but colour is pre-attentive, so the reader pays the cost on every glance. "A Colour Scale That Changed Between Two Charts" (severity 4): the Tagesschau incidence maps, rebinned from 25/50/100/250/500 to 20/35/50/100/200 three weeks apart while being presented as a before-and-after. A colour key is an axis, and moving it mid-comparison is the axis crime we don't police because a legend reads as formatting. The bad chart there needed an explicit independent colour resolve — Vega-Lite merges scales across a concat by default, which silently unified the two legends and destroyed the entire demonstration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ULE5RRxdQE1ebwefEd2eCM
This commit is contained in:
+14
-8
@@ -17,7 +17,7 @@ runs 2–5 on purpose.
|
||||
|
||||
## Sources in play
|
||||
|
||||
`src/lib/references.ts` currently holds 24 entries, 21 of them cited by the 6
|
||||
`src/lib/references.ts` currently holds 24 entries, 22 of them cited by the 8
|
||||
published sins. This table is a **convenience, not a scoreboard** — it's here so
|
||||
you can spot a source that fits a sin you're drafting, not so we can drive the
|
||||
right-hand column to zero blanks. An uncited reference is not a debt.
|
||||
@@ -29,28 +29,28 @@ seven sources, give it seven and add whatever's missing to the file.
|
||||
| --- | --- |
|
||||
| `ftvisvocab` | — |
|
||||
| `healy` | — |
|
||||
| `schwabish` | — |
|
||||
| `fewdualaxes` | dual-axis-correlation |
|
||||
| `huff` | truncated-y-axis |
|
||||
| `isenberg2011` | dual-axis-correlation |
|
||||
| `munzner` | incomplete-pie |
|
||||
| `schwabish` | category-colors |
|
||||
| `shapeparameter` | aspect-ratio |
|
||||
| `showmenumbers` | too-many-pie-slices |
|
||||
| `spurious` | dual-axis-correlation |
|
||||
| `swd` | dual-axis-correlation |
|
||||
| `tufte` | truncated-y-axis |
|
||||
| `callingbullshit` | inverted-y-axis, truncated-y-axis |
|
||||
| `eagereyes` | aspect-ratio, too-many-pie-slices |
|
||||
| `fewpies` | incomplete-pie, too-many-pie-slices |
|
||||
| `junkcharts` | inverted-y-axis, too-many-pie-slices |
|
||||
| `swd` | category-colors, dual-axis-correlation |
|
||||
| `truthfulart` | aspect-ratio, dual-axis-correlation |
|
||||
| `vizwtf` | incomplete-pie, inverted-y-axis |
|
||||
| `wilke` | too-many-pie-slices, truncated-y-axis |
|
||||
| `callingbullshit` | inverted-y-axis, rebinned-colour-scale, truncated-y-axis |
|
||||
| `clevelandmcgill` | aspect-ratio, too-many-pie-slices, truncated-y-axis |
|
||||
| `datatoviz` | dual-axis-correlation, incomplete-pie, too-many-pie-slices |
|
||||
| `datawrapper` | dual-axis-correlation, incomplete-pie, too-many-pie-slices |
|
||||
| `economistmistakes` | dual-axis-correlation, inverted-y-axis, truncated-y-axis |
|
||||
| `howchartslie` | aspect-ratio, dual-axis-correlation, inverted-y-axis, truncated-y-axis |
|
||||
| `munzner` | category-colors, incomplete-pie, rebinned-colour-scale |
|
||||
| `vizwtf` | category-colors, incomplete-pie, inverted-y-axis, rebinned-colour-scale |
|
||||
| `datawrapper` | category-colors, dual-axis-correlation, incomplete-pie, rebinned-colour-scale, too-many-pie-slices |
|
||||
| `howchartslie` | aspect-ratio, dual-axis-correlation, inverted-y-axis, rebinned-colour-scale, truncated-y-axis |
|
||||
|
||||
The citation lists in each entry below are **starting points, not budgets** —
|
||||
add to them while drafting.
|
||||
@@ -237,6 +237,12 @@ rediscovering them:
|
||||
|
||||
## Two new levers, as of the first wild specimen
|
||||
|
||||
Three specimens are live so far (`incomplete-pie`, `category-colors`,
|
||||
`rebinned-colour-scale`), all collected from WTF Visualizations. Each one
|
||||
arrived before its sin existed, which is turning out to be the healthier order:
|
||||
a real chart in hand makes the failure mode obvious and stops us inventing sins
|
||||
nobody commits.
|
||||
|
||||
- **`wild`** — real examples, credited and linked, rendered above the citations.
|
||||
Zero, one or many per sin. A specimen that commits two sins belongs on both
|
||||
pages, with a different note on each; that's a reason to split sins, not merge
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"title": "Cars Sold by Colour",
|
||||
"width": 340,
|
||||
"height": 220,
|
||||
"data": {
|
||||
"values": [
|
||||
{ "colour": "White", "sold": 34 },
|
||||
{ "colour": "Black", "sold": 27 },
|
||||
{ "colour": "Grey", "sold": 18 },
|
||||
{ "colour": "Blue", "sold": 9 },
|
||||
{ "colour": "Red", "sold": 7 },
|
||||
{ "colour": "Green", "sold": 3 },
|
||||
{ "colour": "Yellow", "sold": 2 }
|
||||
]
|
||||
},
|
||||
"mark": "bar",
|
||||
"encoding": {
|
||||
"x": {
|
||||
"field": "colour",
|
||||
"type": "nominal",
|
||||
"sort": null,
|
||||
"axis": { "labelAngle": 0, "title": null }
|
||||
},
|
||||
"y": { "field": "sold", "type": "quantitative", "scale": { "zero": true }, "title": "Cars sold" },
|
||||
"color": {
|
||||
"field": "colour",
|
||||
"type": "nominal",
|
||||
"sort": null,
|
||||
"legend": null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"title": "Cars Sold by Colour",
|
||||
"width": 340,
|
||||
"height": 220,
|
||||
"data": {
|
||||
"values": [
|
||||
{ "colour": "White", "sold": 34 },
|
||||
{ "colour": "Black", "sold": 27 },
|
||||
{ "colour": "Grey", "sold": 18 },
|
||||
{ "colour": "Blue", "sold": 9 },
|
||||
{ "colour": "Red", "sold": 7 },
|
||||
{ "colour": "Green", "sold": 3 },
|
||||
{ "colour": "Yellow", "sold": 2 }
|
||||
]
|
||||
},
|
||||
"mark": { "type": "bar", "stroke": "#8d8d8d", "strokeWidth": 0.6 },
|
||||
"encoding": {
|
||||
"x": {
|
||||
"field": "colour",
|
||||
"type": "nominal",
|
||||
"sort": null,
|
||||
"axis": { "labelAngle": 0, "title": null }
|
||||
},
|
||||
"y": { "field": "sold", "type": "quantitative", "scale": { "zero": true }, "title": "Cars sold" },
|
||||
"color": {
|
||||
"field": "colour",
|
||||
"type": "nominal",
|
||||
"sort": null,
|
||||
"scale": {
|
||||
"domain": ["White", "Black", "Grey", "Blue", "Red", "Green", "Yellow"],
|
||||
"range": ["#ffffff", "#161616", "#8d8d8d", "#0f62fe", "#da1e28", "#198038", "#f1c21b"]
|
||||
},
|
||||
"legend": null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
{
|
||||
"title": "7-Day Incidence by District",
|
||||
"data": {
|
||||
"values": [
|
||||
{
|
||||
"district": "A",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 22
|
||||
},
|
||||
{
|
||||
"district": "B",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 38
|
||||
},
|
||||
{
|
||||
"district": "C",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 45
|
||||
},
|
||||
{
|
||||
"district": "D",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 60
|
||||
},
|
||||
{
|
||||
"district": "E",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 85
|
||||
},
|
||||
{
|
||||
"district": "F",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 110
|
||||
},
|
||||
{
|
||||
"district": "G",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 140
|
||||
},
|
||||
{
|
||||
"district": "H",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 190
|
||||
},
|
||||
{
|
||||
"district": "A",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 45
|
||||
},
|
||||
{
|
||||
"district": "B",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 70
|
||||
},
|
||||
{
|
||||
"district": "C",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 88
|
||||
},
|
||||
{
|
||||
"district": "D",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 120
|
||||
},
|
||||
{
|
||||
"district": "E",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 150
|
||||
},
|
||||
{
|
||||
"district": "F",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 175
|
||||
},
|
||||
{
|
||||
"district": "G",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 210
|
||||
},
|
||||
{
|
||||
"district": "H",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 260
|
||||
}
|
||||
]
|
||||
},
|
||||
"hconcat": [
|
||||
{
|
||||
"title": "17 March",
|
||||
"width": 190,
|
||||
"height": 46,
|
||||
"transform": [
|
||||
{
|
||||
"filter": "datum.when === '17 March'"
|
||||
}
|
||||
],
|
||||
"mark": {
|
||||
"type": "rect",
|
||||
"stroke": "#e0e0e0",
|
||||
"strokeWidth": 0.5
|
||||
},
|
||||
"encoding": {
|
||||
"x": {
|
||||
"field": "district",
|
||||
"type": "nominal",
|
||||
"sort": null,
|
||||
"axis": {
|
||||
"labelAngle": 0,
|
||||
"title": null
|
||||
}
|
||||
},
|
||||
"y": {
|
||||
"field": "row",
|
||||
"type": "nominal",
|
||||
"axis": {
|
||||
"title": null,
|
||||
"labels": false,
|
||||
"ticks": false,
|
||||
"domain": false
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"field": "value",
|
||||
"type": "quantitative",
|
||||
"scale": {
|
||||
"type": "threshold",
|
||||
"domain": [
|
||||
25,
|
||||
50,
|
||||
100,
|
||||
250
|
||||
],
|
||||
"range": [
|
||||
"#fff1f1",
|
||||
"#ffb3b8",
|
||||
"#fa4d56",
|
||||
"#da1e28",
|
||||
"#750e13"
|
||||
]
|
||||
},
|
||||
"legend": {
|
||||
"title": "cases / 100k",
|
||||
"orient": "bottom",
|
||||
"direction": "horizontal"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "9 April",
|
||||
"width": 190,
|
||||
"height": 46,
|
||||
"transform": [
|
||||
{
|
||||
"filter": "datum.when === '9 April'"
|
||||
}
|
||||
],
|
||||
"mark": {
|
||||
"type": "rect",
|
||||
"stroke": "#e0e0e0",
|
||||
"strokeWidth": 0.5
|
||||
},
|
||||
"encoding": {
|
||||
"x": {
|
||||
"field": "district",
|
||||
"type": "nominal",
|
||||
"sort": null,
|
||||
"axis": {
|
||||
"labelAngle": 0,
|
||||
"title": null
|
||||
}
|
||||
},
|
||||
"y": {
|
||||
"field": "row",
|
||||
"type": "nominal",
|
||||
"axis": {
|
||||
"title": null,
|
||||
"labels": false,
|
||||
"ticks": false,
|
||||
"domain": false
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"field": "value",
|
||||
"type": "quantitative",
|
||||
"scale": {
|
||||
"type": "threshold",
|
||||
"domain": [
|
||||
20,
|
||||
35,
|
||||
50,
|
||||
100,
|
||||
200
|
||||
],
|
||||
"range": [
|
||||
"#fff1f1",
|
||||
"#ffd7d9",
|
||||
"#ffb3b8",
|
||||
"#fa4d56",
|
||||
"#da1e28",
|
||||
"#750e13"
|
||||
]
|
||||
},
|
||||
"legend": {
|
||||
"title": "cases / 100k",
|
||||
"orient": "bottom",
|
||||
"direction": "horizontal"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"resolve": {
|
||||
"scale": {
|
||||
"color": "independent"
|
||||
},
|
||||
"legend": {
|
||||
"color": "independent"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
{
|
||||
"title": "7-Day Incidence by District",
|
||||
"data": {
|
||||
"values": [
|
||||
{
|
||||
"district": "A",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 22
|
||||
},
|
||||
{
|
||||
"district": "B",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 38
|
||||
},
|
||||
{
|
||||
"district": "C",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 45
|
||||
},
|
||||
{
|
||||
"district": "D",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 60
|
||||
},
|
||||
{
|
||||
"district": "E",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 85
|
||||
},
|
||||
{
|
||||
"district": "F",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 110
|
||||
},
|
||||
{
|
||||
"district": "G",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 140
|
||||
},
|
||||
{
|
||||
"district": "H",
|
||||
"row": "7-day incidence",
|
||||
"when": "17 March",
|
||||
"value": 190
|
||||
},
|
||||
{
|
||||
"district": "A",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 45
|
||||
},
|
||||
{
|
||||
"district": "B",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 70
|
||||
},
|
||||
{
|
||||
"district": "C",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 88
|
||||
},
|
||||
{
|
||||
"district": "D",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 120
|
||||
},
|
||||
{
|
||||
"district": "E",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 150
|
||||
},
|
||||
{
|
||||
"district": "F",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 175
|
||||
},
|
||||
{
|
||||
"district": "G",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 210
|
||||
},
|
||||
{
|
||||
"district": "H",
|
||||
"row": "7-day incidence",
|
||||
"when": "9 April",
|
||||
"value": 260
|
||||
}
|
||||
]
|
||||
},
|
||||
"hconcat": [
|
||||
{
|
||||
"title": "17 March",
|
||||
"width": 190,
|
||||
"height": 46,
|
||||
"transform": [
|
||||
{
|
||||
"filter": "datum.when === '17 March'"
|
||||
}
|
||||
],
|
||||
"mark": {
|
||||
"type": "rect",
|
||||
"stroke": "#e0e0e0",
|
||||
"strokeWidth": 0.5
|
||||
},
|
||||
"encoding": {
|
||||
"x": {
|
||||
"field": "district",
|
||||
"type": "nominal",
|
||||
"sort": null,
|
||||
"axis": {
|
||||
"labelAngle": 0,
|
||||
"title": null
|
||||
}
|
||||
},
|
||||
"y": {
|
||||
"field": "row",
|
||||
"type": "nominal",
|
||||
"axis": {
|
||||
"title": null,
|
||||
"labels": false,
|
||||
"ticks": false,
|
||||
"domain": false
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"field": "value",
|
||||
"type": "quantitative",
|
||||
"scale": {
|
||||
"type": "threshold",
|
||||
"domain": [
|
||||
25,
|
||||
50,
|
||||
100,
|
||||
250
|
||||
],
|
||||
"range": [
|
||||
"#fff1f1",
|
||||
"#ffb3b8",
|
||||
"#fa4d56",
|
||||
"#da1e28",
|
||||
"#750e13"
|
||||
]
|
||||
},
|
||||
"legend": {
|
||||
"title": "cases / 100k",
|
||||
"orient": "bottom",
|
||||
"direction": "horizontal"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "9 April",
|
||||
"width": 190,
|
||||
"height": 46,
|
||||
"transform": [
|
||||
{
|
||||
"filter": "datum.when === '9 April'"
|
||||
}
|
||||
],
|
||||
"mark": {
|
||||
"type": "rect",
|
||||
"stroke": "#e0e0e0",
|
||||
"strokeWidth": 0.5
|
||||
},
|
||||
"encoding": {
|
||||
"x": {
|
||||
"field": "district",
|
||||
"type": "nominal",
|
||||
"sort": null,
|
||||
"axis": {
|
||||
"labelAngle": 0,
|
||||
"title": null
|
||||
}
|
||||
},
|
||||
"y": {
|
||||
"field": "row",
|
||||
"type": "nominal",
|
||||
"axis": {
|
||||
"title": null,
|
||||
"labels": false,
|
||||
"ticks": false,
|
||||
"domain": false
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"field": "value",
|
||||
"type": "quantitative",
|
||||
"scale": {
|
||||
"type": "threshold",
|
||||
"domain": [
|
||||
25,
|
||||
50,
|
||||
100,
|
||||
250
|
||||
],
|
||||
"range": [
|
||||
"#fff1f1",
|
||||
"#ffb3b8",
|
||||
"#fa4d56",
|
||||
"#da1e28",
|
||||
"#750e13"
|
||||
]
|
||||
},
|
||||
"legend": null
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"resolve": {
|
||||
"scale": {
|
||||
"color": "shared"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: "Bar Colours That Contradict Their Labels"
|
||||
summary: "The categories already have colours. The chart assigned different ones anyway."
|
||||
poke: "Your categories are colours, and you let the software pick different ones. The reader now has to override what they can see in order to read the chart."
|
||||
category: "Sloppy Craft"
|
||||
severity: 2
|
||||
tags: ["bar chart", "colour", "encoding"]
|
||||
badChart: "category-color-bad"
|
||||
fixedChart: "category-color-fixed"
|
||||
related:
|
||||
- too-many-pie-slices
|
||||
date: 2026-08-05
|
||||
wild:
|
||||
- caption: "“Shoe color Frequency” — a survey of shoe colours, plotted in a spreadsheet's default palette."
|
||||
note: "Every category is a colour, and not one bar matches. Black is a green bar. White is a blue bar. Red is orange, grey is teal, brown is navy. The numbers are all correct — you simply cannot use the fastest channel on the chart, because it has been filled with an answer to a different question."
|
||||
image: "shoe-colour-frequency.jpg"
|
||||
alt: "Bar chart titled Shoe color Frequency with categories White, Blue, Yellow, Black, Red, Grey and Brown, each drawn in an unrelated default palette colour."
|
||||
credit: "Spotted in the wild; original author unknown"
|
||||
sourceLabel: "collected by WTF Visualizations"
|
||||
sourceUrl: "https://viz.wtf/post/639589038118125568"
|
||||
citations:
|
||||
- key: munzner
|
||||
note: "Colour is a channel that carries meaning whether or not you meant it to. Spending it on nothing is the cheap version of this mistake; spending it on the wrong thing is this one."
|
||||
- key: swd
|
||||
note: "Knaflic's rule that every colour on a chart should be a decision, made for a reason the reader can recover."
|
||||
- key: datawrapper
|
||||
note: "Their colour writing keeps returning to the same point: a palette is a mapping to data, not decoration."
|
||||
- key: vizwtf
|
||||
note: "Where the specimen above was collected."
|
||||
- key: schwabish
|
||||
note: "Chart-by-chart on matching the encoding to what the reader is actually being asked to do."
|
||||
---
|
||||
|
||||
## The sin
|
||||
|
||||
Some categories arrive with a colour already attached. Colours themselves,
|
||||
obviously — but also political parties, sports teams, brands, traffic-light
|
||||
statuses, the red and black of a balance sheet. The reader knows the mapping
|
||||
before they reach your chart.
|
||||
|
||||
Ignore it and you don't get a neutral chart. You get one that argues with
|
||||
itself: a bar labelled *Black* drawn in green, one labelled *White* drawn in
|
||||
blue. Every value is correct and the chart is still exhausting, because the
|
||||
fastest channel on the page is now pointing somewhere the labels aren't.
|
||||
|
||||
## Why it deceives
|
||||
|
||||
"Deceives" is generous — nothing here is false, which is why this is a two and
|
||||
not a four. But colour is pre-attentive: it lands before you've read a single
|
||||
label. So the chart hands the reader an answer, the labels hand them a different
|
||||
one, and the reader has to consciously suppress the first to trust the second.
|
||||
|
||||
That cost is paid on every glance, by every reader, forever, to save the author
|
||||
one minute. And it doesn't stay merely annoying: anyone skimming, quoting from
|
||||
memory, or looking at a thumbnail will take the colour at face value.
|
||||
|
||||
## The repentance
|
||||
|
||||
If a category has a natural colour, use it. The fix above is not clever — it
|
||||
assigns each bar the colour it is named after, and adds a hairline stroke so
|
||||
white and yellow keep an edge against the canvas.
|
||||
|
||||
If your categories *don't* have natural colours, the other repentance applies:
|
||||
don't colour them at all. Seven bars in seven hues where a single hue would do
|
||||
is the same mistake wearing a nicer coat — colour that encodes nothing but is
|
||||
read as though it encodes something. One colour, or one highlighted bar and the
|
||||
rest in grey.
|
||||
|
||||
The test is simple. Ask what a reader would conclude from the colours alone. If
|
||||
the answer is "something wrong," or "nothing," change them.
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "A Colour Scale That Changed Between Two Charts"
|
||||
summary: "Two snapshots of the same measure, placed side by side, with the legend quietly rebinned in between."
|
||||
poke: "You changed the bins between the before and the after. Whatever the reader sees in that comparison, some of it is your legend, not your data."
|
||||
category: "Misleading Scales"
|
||||
severity: 4
|
||||
tags: ["colour scale", "comparison", "heatmap", "map"]
|
||||
badChart: "rebinned-scale-bad"
|
||||
fixedChart: "rebinned-scale-fixed"
|
||||
compareNote: "The same readings in both, at both dates. Only the legend changed."
|
||||
related:
|
||||
- truncated-y-axis
|
||||
- aspect-ratio
|
||||
date: 2026-08-05
|
||||
wild:
|
||||
- caption: "Tagesschau, 7-day COVID incidence by German district: 17 March 2021 beside 9 April 2021."
|
||||
note: "The two maps are the same measure three weeks apart, and they invite exactly one reading: look how much worse it got. But the legend was rebinned in between. On 17 March the bands ran to 25 / 50 / 100 / 250 / 500; on 9 April, to 20 / 35 / 50 / 100 / 200 / above. A district sitting at 150 is a mid-orange on the left map and near-black on the right, without having moved at all. Incidence did rise over those three weeks — that part is real. What the pair can't tell you is how much of the colour change is the virus and how much is the key."
|
||||
image: "tagesschau-incidence-bins.jpg"
|
||||
alt: "Two choropleth maps of Germany showing 7-day COVID incidence by district, dated 17 March and 9 April 2021, each with a different set of legend thresholds."
|
||||
credit: "Maps by Tagesschau (ARD), data from the Robert Koch-Institut"
|
||||
sourceLabel: "collected by WTF Visualizations"
|
||||
sourceUrl: "https://viz.wtf/post/648282602475798528/german-main-news-website-changed-the-categrories"
|
||||
citations:
|
||||
- key: howchartslie
|
||||
note: "Cairo on how the encoding, not the data, does the arguing when a reader compares two pictures."
|
||||
- key: callingbullshit
|
||||
note: "The axis-crime module, generalised: a colour key is an axis, and rescaling it mid-comparison is the same move."
|
||||
- key: datawrapper
|
||||
note: "Their colour-scale series is explicit that where you put the bin edges changes what readers see — the choice is never neutral."
|
||||
- key: munzner
|
||||
note: "Binning a continuous quantity is an encoding decision, and comparability depends on holding it fixed."
|
||||
- key: vizwtf
|
||||
note: "Where the specimen above was collected."
|
||||
---
|
||||
|
||||
## The sin
|
||||
|
||||
Two panels, same measure, different dates, side by side. The reader is being
|
||||
invited to compare them — that's the only reason to publish them together.
|
||||
|
||||
Then the bins move. The right-hand panel above uses a key that turns darker,
|
||||
sooner, than the left one. Its districts are genuinely worse, but the picture
|
||||
overstates by however much the key was tightened, and there is no way to
|
||||
separate the two by looking.
|
||||
|
||||
## Why it deceives
|
||||
|
||||
A colour key is an axis. It maps a number to something the eye reads directly,
|
||||
and every intuition about not moving the y-axis between two charts applies to it
|
||||
unchanged — we just police it less, because a legend looks like a formatting
|
||||
choice rather than a scale.
|
||||
|
||||
It's also uniquely hard to catch. A shifted y-axis leaves visible evidence: the
|
||||
numbers are right there, and a suspicious reader can check them. Here the reader
|
||||
would have to hold five thresholds from one legend in their head, read the five
|
||||
in the other, and notice they differ — while looking at two attractive maps that
|
||||
are clearly telling a story.
|
||||
|
||||
And rebinning is often *innocent* in origin, which is what keeps it alive. As a
|
||||
number climbs, the old bands stop discriminating — everything saturates at the
|
||||
top colour and the map goes flat. Rescaling genuinely does make each map, on its
|
||||
own, more informative. The sin is doing it and still presenting the pair as a
|
||||
before-and-after.
|
||||
|
||||
## The repentance
|
||||
|
||||
One key for every panel you want compared. That's the whole rule, and the fixed
|
||||
version above does nothing else: same data, same dates, one legend serving both.
|
||||
The rise is still perfectly visible — it just isn't amplified.
|
||||
|
||||
If the range really has outgrown the old bands, you have two honest options.
|
||||
Rebin *both* panels, so the comparison stays like-for-like. Or keep the panels
|
||||
separate, drop the side-by-side framing, and let each map be a snapshot rather
|
||||
than half of an implied trend.
|
||||
|
||||
And if the two panels must carry different keys, say so loudly, in the chart —
|
||||
not in a caption underneath, where it will be cropped off the first time anyone
|
||||
shares it.
|
||||
Reference in New Issue
Block a user