Skip to content

Commit

Permalink
default observable10 scheme (#1895)
Browse files Browse the repository at this point in the history
* default observable10 scheme

* 2023-11-08 iteration

* fix tests

* comment

---------

Co-authored-by: Philippe Rivière <fil@rezo.net>
  • Loading branch information
mbostock and Fil committed Dec 8, 2023
1 parent 6fdb955 commit c8f9f32
Show file tree
Hide file tree
Showing 105 changed files with 31,943 additions and 31,903 deletions.
6 changes: 4 additions & 2 deletions docs/features/facets.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const olympians = shallowRef([
{weight: 170, height: 2.21, sex: "male"}
]);

const scheme = Plot.scale({color: {type: "categorical"}}).range;

onMounted(() => {
d3.csv("../data/athletes.csv", d3.autoType).then((data) => (olympians.value = data));
});
Expand All @@ -23,7 +25,7 @@ onMounted(() => {

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.

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>.
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>.

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

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.
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.

:::plot defer https://observablehq.com/@observablehq/plot-trellis-anomaly
```js
Expand Down
2 changes: 1 addition & 1 deletion docs/features/markers.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ A **marker** defines a graphic drawn on vertices of a [line](../marks/line.md) o
</label>
</p>

:::plot https://observablehq.com/d/cfc5b4e46aa18b57?intent=fork
:::plot https://observablehq.com/@observablehq/plot-line-chart-with-markers?intent=fork
```js-vue
Plot.plot({
marks: [
Expand Down
4 changes: 3 additions & 1 deletion docs/features/transforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const olympians = shallowRef([]);
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})));
const bins = computed(() => d3.bin().thresholds(80).value((d) => d.weight)(olympians.value));

const scheme = Plot.scale({color: {type: "categorical"}}).range;

onMounted(() => {
d3.csv("../data/athletes.csv", d3.autoType).then((data) => (olympians.value = data));
d3.csv("../data/bls-metro-unemployment.csv", d3.autoType).then((data) => (bls.value = data));
Expand Down Expand Up @@ -116,7 +118,7 @@ If a transform isn’t doing what you expect, try inspecting the options object

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.

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.
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.

:::plot defer https://observablehq.com/@observablehq/plot-overlapping-relative-histogram
```js-vue
Expand Down
4 changes: 3 additions & 1 deletion docs/marks/tip.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const olympians = shallowRef([
{weight: 170, height: 2.21, sex: "male"}
]);

const scheme = Plot.scale({color: {type: "categorical"}}).range;

onMounted(() => {
d3.csv("../data/aapl.csv", d3.autoType).then((data) => (aapl.value = data));
d3.csv("../data/athletes.csv", d3.autoType).then((data) => (olympians.value = data));
Expand Down Expand Up @@ -111,7 +113,7 @@ Plot.dot(olympians, {
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.
:::

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>.
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>.

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.

Expand Down
6 changes: 4 additions & 2 deletions docs/transforms/group.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {shallowRef, onMounted} from "vue";

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

const scheme = Plot.scale({color: {type: "categorical"}}).range;

onMounted(() => {
d3.csv("../data/athletes.csv", d3.autoType).then((data) => (olympians.value = data));
});
Expand Down Expand Up @@ -88,7 +90,7 @@ Plot.plot({
```
:::

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.
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.

:::plot defer https://observablehq.com/@observablehq/plot-group-and-mode-reducer
```js
Expand Down Expand Up @@ -162,7 +164,7 @@ Plot.plot({
```
:::

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.
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.

:::plot defer https://observablehq.com/@observablehq/plot-difference-arrows
```js
Expand Down
4 changes: 3 additions & 1 deletion docs/transforms/hexbin.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const us = shallowRef(null);
const nation = computed(() => us.value ? topojson.feature(us.value, us.value.objects.nation) : {type: null});
const statemesh = computed(() => us.value ? topojson.mesh(us.value, us.value.objects.states, (a, b) => a !== b) : {type: null});

const scheme = Plot.scale({color: {type: "categorical"}}).range;

onMounted(() => {
d3.csv("../data/athletes.csv", d3.autoType).then((data) => (olympians.value = data));
d3.tsv("../data/walmarts.tsv", d3.autoType).then((data) => (walmarts.value = data));
Expand Down Expand Up @@ -69,7 +71,7 @@ Plot
Setting a **stroke** ensures that the smallest hexagons are visible.
:::

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>.
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>.

:::plot defer https://observablehq.com/@observablehq/plot-bivariate-hexbin
```js
Expand Down
7 changes: 4 additions & 3 deletions docs/transforms/stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const order = computed(() => orders.value === "null" ? null : orders.value);
const reverse = ref(true);
const riaa = shallowRef([]);
const survey = shallowRef([]);
const scheme = Plot.scale({color: {type: "categorical"}}).range;

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

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.

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.
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.

:::plot https://observablehq.com/@observablehq/plot-crimean-war-casualties
```js
Expand Down Expand Up @@ -128,7 +129,7 @@ Plot.plot({
```
:::

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.
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.

<p>
<label class="label-input">
Expand Down Expand Up @@ -245,7 +246,7 @@ Plot.plot({
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.
:::

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>.
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>.

:::plot defer https://observablehq.com/@observablehq/plot-normalized-stack
```js
Expand Down
2 changes: 1 addition & 1 deletion src/plot.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export interface PlotOptions extends ScaleDefaults {
* Options for the *color* scale for fill or stroke. The *color* scale
* defaults to a *linear* scale with the *turbo* scheme for quantitative
* (numbers) or temporal (dates) data, and an *ordinal* scale with the
* *tableau10* scheme for categorical (strings or booleans) data.
* *observable10* scheme for categorical (strings or booleans) data.
*
* Plot does not currently render a color legend by default; set the
* **legend** *color* scale option to true to produce a color legend.
Expand Down
2 changes: 1 addition & 1 deletion src/scales.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export type ScaleFunctions = {[key in ScaleName]?: (value: any) => any} & {scale
*
* For color, one of:
*
* - *categorical* - equivalent to *ordinal*; defaults to *tableau10*
* - *categorical* - equivalent to *ordinal*; defaults to *observable10*
* - *sequential* - equivalent to *linear*; defaults to *turbo*
* - *cyclical* - equivalent to *linear*; defaults to *rainbow*
* - *threshold* - encodes using discrete thresholds; defaults to *rdylbu*
Expand Down
2 changes: 1 addition & 1 deletion src/scales/ordinal.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function createScaleOrdinal(key, channels, {type, interval, domain, range
if (range !== undefined) scheme = undefined; // Don’t re-apply scheme.
}
if (scheme === undefined && range === undefined) {
scheme = type === "ordinal" ? "turbo" : "tableau10";
scheme = type === "ordinal" ? "turbo" : "observable10";
}
if (scheme !== undefined) {
if (range !== undefined) {
Expand Down
15 changes: 15 additions & 0 deletions src/scales/schemes.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,25 @@ import {
schemeYlOrRd
} from "d3";

// TODO https://github.com/d3/d3-scale-chromatic/pull/51
const schemeObservable10 = [
"#4269d0",
"#efb118",
"#ff725c",
"#6cc5b0",
"#a463f2",
"#ff8ab7",
"#9c6b4e",
"#97bbf5",
"#3ca951",
"#9498a0"
];

const categoricalSchemes = new Map([
["accent", schemeAccent],
["category10", schemeCategory10],
["dark2", schemeDark2],
["observable10", schemeObservable10],
["paired", schemePaired],
["pastel1", schemePastel1],
["pastel2", schemePastel2],
Expand Down
Loading

0 comments on commit c8f9f32

Please sign in to comment.