Skip to content

Commit 3bf0bca

Browse files
mbostockFil
authored andcommitted
default observable10 scheme (observablehq#1895)
* default observable10 scheme * 2023-11-08 iteration * fix tests * comment --------- Co-authored-by: Philippe Rivière <[email protected]>
1 parent ac7fffa commit 3bf0bca

File tree

105 files changed

+31943
-31903
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+31943
-31903
lines changed

docs/features/facets.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ const olympians = shallowRef([
1313
{weight: 170, height: 2.21, sex: "male"}
1414
]);
1515

16+
const scheme = Plot.scale({color: {type: "categorical"}}).range;
17+
1618
onMounted(() => {
1719
d3.csv("../data/athletes.csv", d3.autoType).then((data) => (olympians.value = data));
1820
});
@@ -23,7 +25,7 @@ onMounted(() => {
2325

2426
Faceting partitions data by ordinal or categorical value and then repeats a plot for each partition (each **facet**), producing [small multiples](https://en.wikipedia.org/wiki/Small_multiple) for comparison. Faceting is typically enabled by declaring the horizontal↔︎ facet channel **fx**, the vertical↕︎ facet channel **fy**, or both for two-dimensional faceting.
2527

26-
For example, below we recreate the Trellis display (“reminiscent of garden trelliswork”) of [Becker *et al.*](https://hci.stanford.edu/courses/cs448b/papers/becker-trellis-jcgs.pdf) using the dot’s **fy** channel to declare vertical↕︎ facets, showing the yields of several varieties of barley across several sites for the years <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[0]}`}">1931</span> and <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[1]}`}">1932</span>.
28+
For example, below we recreate the Trellis display (“reminiscent of garden trelliswork”) of [Becker *et al.*](https://hci.stanford.edu/courses/cs448b/papers/becker-trellis-jcgs.pdf) using the dot’s **fy** channel to declare vertical↕︎ facets, showing the yields of several varieties of barley across several sites for the years <span :style="{borderBottom: `solid 2px ${scheme[0]}`}">1931</span> and <span :style="{borderBottom: `solid 2px ${scheme[1]}`}">1932</span>.
2729

2830
:::plot https://observablehq.com/@observablehq/plot-trellis
2931
```js
@@ -57,7 +59,7 @@ This plot uses the [**sort** mark option](./scales.md#sort-mark-option) to order
5759
Use the [frame mark](../marks/frame.md) for stronger visual separation of facets.
5860
:::
5961

60-
The chart above reveals a likely data collection error: the years appear to be reversed for the Morris site as it is the only site where the yields in <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[1]}`}">1932</span> were higher than in <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[0]}`}">1931</span>. The anomaly in Morris is more obvious if we use directed arrows to show the year-over-year change. The [group transform](../transforms/group.md) groups the observations by site and variety to compute the change.
62+
The chart above reveals a likely data collection error: the years appear to be reversed for the Morris site as it is the only site where the yields in <span :style="{borderBottom: `solid 2px ${scheme[1]}`}">1932</span> were higher than in <span :style="{borderBottom: `solid 2px ${scheme[0]}`}">1931</span>. The anomaly in Morris is more obvious if we use directed arrows to show the year-over-year change. The [group transform](../transforms/group.md) groups the observations by site and variety to compute the change.
6163

6264
:::plot defer https://observablehq.com/@observablehq/plot-trellis-anomaly
6365
```js

docs/features/markers.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ A **marker** defines a graphic drawn on vertices of a [line](../marks/line.md) o
3030
</label>
3131
</p>
3232

33-
:::plot https://observablehq.com/d/cfc5b4e46aa18b57?intent=fork
33+
:::plot https://observablehq.com/@observablehq/plot-line-chart-with-markers?intent=fork
3434
```js-vue
3535
Plot.plot({
3636
marks: [

docs/features/transforms.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const olympians = shallowRef([]);
1010
const traffic = shallowRef(["Saarbrücken-Neuhaus", "Oldenburg (Holstein)", "Holz", "Göttelborn", "Riegelsberg", "Kastel", "Neustadt i. H.-Süd", "Nettersheim", "Hasborn", "Laufeld", "Otzenhausen", "Nonnweiler", "Kirschheck", "AS Eppelborn", "Bierfeld", "Von der Heydt", "Illingen", "Hetzerath", "Groß Ippener", "Bockel", "Ladbergen", "Dibbersen", "Euskirchen/Bliesheim", "Hürth", "Lotte", "Ascheberg", "Bad Schwartau", "Schloss Burg", "Uphusen", "HB-Silbersee", "Barsbüttel", "HB-Mahndorfer See", "Glüsingen", "HB-Weserbrücke", "Hengsen", "Köln-Nord", "Hagen-Vorhalle", "Unna"].map((location, i) => ({location, date: new Date(Date.UTC(2000, 0, 1, i)), vehicles: (10 + i) ** 2.382})));
1111
const bins = computed(() => d3.bin().thresholds(80).value((d) => d.weight)(olympians.value));
1212

13+
const scheme = Plot.scale({color: {type: "categorical"}}).range;
14+
1315
onMounted(() => {
1416
d3.csv("../data/athletes.csv", d3.autoType).then((data) => (olympians.value = data));
1517
d3.csv("../data/bls-metro-unemployment.csv", d3.autoType).then((data) => (bls.value = data));
@@ -116,7 +118,7 @@ If a transform isn’t doing what you expect, try inspecting the options object
116118

117119
Transforms can derive channels (such as **y** above) as well as changing the default options. For example, the bin transform sets default insets for a one-pixel gap between adjacent rects.
118120

119-
Transforms are composable: you can pass *options* through more than one transform before passing it to a mark. For example, above it’s a bit difficult to compare the weight distribution by sex because there are fewer <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[0]}`}">female</span> than <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[1]}`}">male</span> athletes in the data. We can remove this effect using the [normalize transform](../transforms/normalize.md) with the *sum* reducer.
121+
Transforms are composable: you can pass *options* through more than one transform before passing it to a mark. For example, above it’s a bit difficult to compare the weight distribution by sex because there are fewer <span :style="{borderBottom: `solid 2px ${scheme[0]}`}">female</span> than <span :style="{borderBottom: `solid 2px ${scheme[1]}`}">male</span> athletes in the data. We can remove this effect using the [normalize transform](../transforms/normalize.md) with the *sum* reducer.
120122

121123
:::plot defer https://observablehq.com/@observablehq/plot-overlapping-relative-histogram
122124
```js-vue

docs/marks/tip.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ const olympians = shallowRef([
1414
{weight: 170, height: 2.21, sex: "male"}
1515
]);
1616

17+
const scheme = Plot.scale({color: {type: "categorical"}}).range;
18+
1719
onMounted(() => {
1820
d3.csv("../data/aapl.csv", d3.autoType).then((data) => (aapl.value = data));
1921
d3.csv("../data/athletes.csv", d3.autoType).then((data) => (olympians.value = data));
@@ -111,7 +113,7 @@ Plot.dot(olympians, {
111113
The tallest athlete in this dataset, swimmer [Kevin Cordes](https://en.wikipedia.org/wiki/Kevin_Cordes), is likely an error: his official height is 1.96m (6′ 5″) not 2.21m (7′ 3″). Basketball player [Li Muhao](https://en.wikipedia.org/wiki/Li_Muhao) is likely the true tallest.
112114
:::
113115

114-
If a channel is bound to the *color* or *opacity* scale, the tip mark displays a swatch to reinforce the encoding, such as female <span :style="{color: d3.schemeTableau10[0]}">■</span> or male <span :style="{color: d3.schemeTableau10[1]}">■</span>.
116+
If a channel is bound to the *color* or *opacity* scale, the tip mark displays a swatch to reinforce the encoding, such as female <span :style="{color: scheme[0]}">■</span> or male <span :style="{color: scheme[1]}">■</span>.
115117

116118
The tip mark recognizes that **x1** & **x2** and **y1** & **y2** are paired channels. Below, observe that the *weight* shown in the tip is a range such as 64–66 kg; however, the *frequency* is shown as the difference between the **y1** and **y2** channels. The latter is achieved by the stack transform setting a channel hint to indicate that **y1** and **y2** represent a length.
117119

docs/transforms/group.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {shallowRef, onMounted} from "vue";
66

77
const olympians = shallowRef([{weight: 31, height: 1.21, sex: "female"}, {weight: 170, height: 2.21, sex: "male"}]);
88

9+
const scheme = Plot.scale({color: {type: "categorical"}}).range;
10+
911
onMounted(() => {
1012
d3.csv("../data/athletes.csv", d3.autoType).then((data) => (olympians.value = data));
1113
});
@@ -88,7 +90,7 @@ Plot.plot({
8890
```
8991
:::
9092

91-
We aren’t limited to the *count* reducer. We can use the *mode* reducer, for example, to show which sex is more prevalent in each sport: <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[1]}`}">men</span> are represented more often than <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[0]}`}">women</span> in every sport except gymnastics and fencing.
93+
We aren’t limited to the *count* reducer. We can use the *mode* reducer, for example, to show which sex is more prevalent in each sport: <span :style="{borderBottom: `solid 2px ${scheme[1]}`}">men</span> are represented more often than <span :style="{borderBottom: `solid 2px ${scheme[0]}`}">women</span> in every sport except gymnastics and fencing.
9294

9395
:::plot defer https://observablehq.com/@observablehq/plot-group-and-mode-reducer
9496
```js
@@ -162,7 +164,7 @@ Plot.plot({
162164
```
163165
:::
164166

165-
Alternatively, below we use directional arrows (a [link mark](../marks/link.md) with [markers](../features/markers.md)) to indicate the difference in counts of <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[1]}`}">male</span> and <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[0]}`}">female</span> athletes by sport. The color of the arrow indicates which sex is more prevalent, while its length is proportional to the difference.
167+
Alternatively, below we use directional arrows (a [link mark](../marks/link.md) with [markers](../features/markers.md)) to indicate the difference in counts of <span :style="{borderBottom: `solid 2px ${scheme[1]}`}">male</span> and <span :style="{borderBottom: `solid 2px ${scheme[0]}`}">female</span> athletes by sport. The color of the arrow indicates which sex is more prevalent, while its length is proportional to the difference.
166168

167169
:::plot defer https://observablehq.com/@observablehq/plot-difference-arrows
168170
```js

docs/transforms/hexbin.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ const us = shallowRef(null);
1313
const nation = computed(() => us.value ? topojson.feature(us.value, us.value.objects.nation) : {type: null});
1414
const statemesh = computed(() => us.value ? topojson.mesh(us.value, us.value.objects.states, (a, b) => a !== b) : {type: null});
1515

16+
const scheme = Plot.scale({color: {type: "categorical"}}).range;
17+
1618
onMounted(() => {
1719
d3.csv("../data/athletes.csv", d3.autoType).then((data) => (olympians.value = data));
1820
d3.tsv("../data/walmarts.tsv", d3.autoType).then((data) => (walmarts.value = data));
@@ -69,7 +71,7 @@ Plot
6971
Setting a **stroke** ensures that the smallest hexagons are visible.
7072
:::
7173

72-
Alternatively, the **fill** and **r** channels can encode independent (or “bivariate”) dimensions of data. Below, the **r** channel uses *count* as before, while the **fill** channel uses *mode* to show the most frequent sex of athletes in each hexagon. The larger athletes are more likely to be <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[1]}`}">male</span>, while the smaller athletes are more likely to be <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[0]}`}">female</span>.
74+
Alternatively, the **fill** and **r** channels can encode independent (or “bivariate”) dimensions of data. Below, the **r** channel uses *count* as before, while the **fill** channel uses *mode* to show the most frequent sex of athletes in each hexagon. The larger athletes are more likely to be <span :style="{borderBottom: `solid 2px ${scheme[1]}`}">male</span>, while the smaller athletes are more likely to be <span :style="{borderBottom: `solid 2px ${scheme[0]}`}">female</span>.
7375

7476
:::plot defer https://observablehq.com/@observablehq/plot-bivariate-hexbin
7577
```js

docs/transforms/stack.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const order = computed(() => orders.value === "null" ? null : orders.value);
1414
const reverse = ref(true);
1515
const riaa = shallowRef([]);
1616
const survey = shallowRef([]);
17+
const scheme = Plot.scale({color: {type: "categorical"}}).range;
1718

1819
onMounted(() => {
1920
d3.csv("../data/riaa-us-revenue.csv", d3.autoType).then((data) => (riaa.value = data));
@@ -53,7 +54,7 @@ const likert = Likert([
5354

5455
The **stack transform** comes in two orientations: [stackY](#stackY) replaces **y** with **y1** and **y2** to form vertical↑ stacks grouped on **x**, while [stackX](#stackX) replaces **x** with **x1** and **x2** for horizontal→ stacks grouped on **y**. In effect, stacking transforms a *length* into *lower* and *upper* positions: the upper position of each element equals the lower position of the next element in the stack. Stacking makes it easier to perceive a total while still showing its parts.
5556

56-
For example, below is a stacked area chart of [deaths in the Crimean War](https://en.wikipedia.org/wiki/Florence_Nightingale#Crimean_War) — predominantly from <span :style="{borderBottom: `solid ${d3.schemeTableau10[0]} 3px`}">disease</span> — using Florence Nightingale’s data.
57+
For example, below is a stacked area chart of [deaths in the Crimean War](https://en.wikipedia.org/wiki/Florence_Nightingale#Crimean_War) — predominantly from <span :style="{borderBottom: `solid ${scheme[0]} 3px`}">disease</span> — using Florence Nightingale’s data.
5758

5859
:::plot https://observablehq.com/@observablehq/plot-crimean-war-casualties
5960
```js
@@ -128,7 +129,7 @@ Plot.plot({
128129
```
129130
:::
130131

131-
The **order** option controls the order in which the layers are stacked. It defaults to null, meaning to respect the input order of the data. The *appearance* order excels when each series has a prominent peak, as in the chart below of [recording industry](https://en.wikipedia.org/wiki/Recording_Industry_Association_of_America) revenue. <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[0]}`}">Compact disc</span> sales started declining well before the rise of <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[1]}`}">downloads</span> and <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[3]}`}">streaming</span>, suggesting that the industry was slow to provide a convenient digital product and hence lost revenue to piracy.
132+
The **order** option controls the order in which the layers are stacked. It defaults to null, meaning to respect the input order of the data. The *appearance* order excels when each series has a prominent peak, as in the chart below of [recording industry](https://en.wikipedia.org/wiki/Recording_Industry_Association_of_America) revenue. <span :style="{borderBottom: `solid 2px ${scheme[0]}`}">Compact disc</span> sales started declining well before the rise of <span :style="{borderBottom: `solid 2px ${scheme[1]}`}">downloads</span> and <span :style="{borderBottom: `solid 2px ${scheme[3]}`}">streaming</span>, suggesting that the industry was slow to provide a convenient digital product and hence lost revenue to piracy.
132133

133134
<p>
134135
<label class="label-input">
@@ -245,7 +246,7 @@ Plot.plot({
245246
When **offset** is not null, the *y* axis is harder to use because there is no longer a shared baseline at *y* = 0, though it is still useful for eyeballing length.
246247
:::
247248

248-
The *normalize* **offset** is again worth special mention: it scales stacks to fill the interval [0, 1], thereby showing the relative proportion of each layer. Sales of <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[0]}`}">compact discs</span> accounted for over 90% of revenue in the early 2000’s, but now most revenue comes from <span :style="{borderBottom: `solid 2px ${d3.schemeTableau10[3]}`}">streaming</span>.
249+
The *normalize* **offset** is again worth special mention: it scales stacks to fill the interval [0, 1], thereby showing the relative proportion of each layer. Sales of <span :style="{borderBottom: `solid 2px ${scheme[0]}`}">compact discs</span> accounted for over 90% of revenue in the early 2000’s, but now most revenue comes from <span :style="{borderBottom: `solid 2px ${scheme[3]}`}">streaming</span>.
249250

250251
:::plot defer https://observablehq.com/@observablehq/plot-normalized-stack
251252
```js

src/plot.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ export interface PlotOptions extends ScaleDefaults {
234234
* Options for the *color* scale for fill or stroke. The *color* scale
235235
* defaults to a *linear* scale with the *turbo* scheme for quantitative
236236
* (numbers) or temporal (dates) data, and an *ordinal* scale with the
237-
* *tableau10* scheme for categorical (strings or booleans) data.
237+
* *observable10* scheme for categorical (strings or booleans) data.
238238
*
239239
* Plot does not currently render a color legend by default; set the
240240
* **legend** *color* scale option to true to produce a color legend.

src/scales.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ export type ScaleFunctions = {[key in ScaleName]?: (value: any) => any} & {scale
187187
*
188188
* For color, one of:
189189
*
190-
* - *categorical* - equivalent to *ordinal*; defaults to *tableau10*
190+
* - *categorical* - equivalent to *ordinal*; defaults to *observable10*
191191
* - *sequential* - equivalent to *linear*; defaults to *turbo*
192192
* - *cyclical* - equivalent to *linear*; defaults to *rainbow*
193193
* - *threshold* - encodes using discrete thresholds; defaults to *rdylbu*

src/scales/ordinal.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export function createScaleOrdinal(key, channels, {type, interval, domain, range
3939
if (range !== undefined) scheme = undefined; // Don’t re-apply scheme.
4040
}
4141
if (scheme === undefined && range === undefined) {
42-
scheme = type === "ordinal" ? "turbo" : "tableau10";
42+
scheme = type === "ordinal" ? "turbo" : "observable10";
4343
}
4444
if (scheme !== undefined) {
4545
if (range !== undefined) {

src/scales/schemes.js

+15
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,25 @@ import {
7777
schemeYlOrRd
7878
} from "d3";
7979

80+
// TODO https://github.com/d3/d3-scale-chromatic/pull/51
81+
const schemeObservable10 = [
82+
"#4269d0",
83+
"#efb118",
84+
"#ff725c",
85+
"#6cc5b0",
86+
"#a463f2",
87+
"#ff8ab7",
88+
"#9c6b4e",
89+
"#97bbf5",
90+
"#3ca951",
91+
"#9498a0"
92+
];
93+
8094
const categoricalSchemes = new Map([
8195
["accent", schemeAccent],
8296
["category10", schemeCategory10],
8397
["dark2", schemeDark2],
98+
["observable10", schemeObservable10],
8499
["paired", schemePaired],
85100
["pastel1", schemePastel1],
86101
["pastel2", schemePastel2],

0 commit comments

Comments
 (0)