Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

default observable10 scheme #1895

Merged
merged 6 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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