Skip to content

Commit

Permalink
implicit categorical color scale (observablehq#1567)
Browse files Browse the repository at this point in the history
* implicit categorical color scale

* implicit categorical color scale (observablehq#1569)

* add missing test result

* test the scheme after the domain and channels have been checked

* scheme-less asOrdinalType

---------

Co-authored-by: Mike Bostock <mbostock@gmail.com>

---------

Co-authored-by: Philippe Rivière <fil@rezo.net>
  • Loading branch information
mbostock and Fil authored May 17, 2023
1 parent e9e08c2 commit 168e8bf
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 11 deletions.
19 changes: 11 additions & 8 deletions src/scales.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
createScaleDivergingLog,
createScaleDivergingSymlog
} from "./scales/diverging.js";
import {isDivergingScheme} from "./scales/schemes.js";
import {isCategoricalScheme, isDivergingScheme} from "./scales/schemes.js";
import {createScaleTime, createScaleUtc} from "./scales/temporal.js";
import {createScaleOrdinal, createScalePoint, createScaleBand, ordinalImplicit} from "./scales/ordinal.js";
import {maybeSymbol} from "./symbol.js";
Expand Down Expand Up @@ -421,15 +421,18 @@ function inferScaleType(key, channels, {type, domain, range, scheme, pivot, proj
if (domain !== undefined) {
if (isOrdinal(domain)) return asOrdinalType(kind);
if (isTemporal(domain)) return "utc";
if (kind === color && (pivot != null || isDivergingScheme(scheme))) return "diverging";
return "linear";
} else {
const values = channels.map(({value}) => value).filter((value) => value !== undefined);
if (values.some(isOrdinal)) return asOrdinalType(kind);
if (values.some(isTemporal)) return "utc";
}

// For color scales, take a hint from the color scheme and pivot option.
if (kind === color) {
if (pivot != null || isDivergingScheme(scheme)) return "diverging";
if (isCategoricalScheme(scheme)) return "categorical";
}

// If any channel is ordinal or temporal, it takes priority.
const values = channels.map(({value}) => value).filter((value) => value !== undefined);
if (values.some(isOrdinal)) return asOrdinalType(kind);
if (values.some(isTemporal)) return "utc";
if (kind === color && (pivot != null || isDivergingScheme(scheme))) return "diverging";
return "linear";
}

Expand Down
13 changes: 10 additions & 3 deletions src/scales/schemes.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ import {
schemeYlOrRd
} from "d3";

const ordinalSchemes = new Map([
// categorical
const categoricalSchemes = new Map([
["accent", schemeAccent],
["category10", schemeCategory10],
["dark2", schemeDark2],
Expand All @@ -88,7 +87,15 @@ const ordinalSchemes = new Map([
["set1", schemeSet1],
["set2", schemeSet2],
["set3", schemeSet3],
["tableau10", schemeTableau10],
["tableau10", schemeTableau10]
]);

export function isCategoricalScheme(scheme) {
return scheme != null && categoricalSchemes.has(`${scheme}`.toLowerCase());
}

const ordinalSchemes = new Map([
...categoricalSchemes,

// diverging
["brbg", scheme11(schemeBrBG, interpolateBrBG)],
Expand Down
52 changes: 52 additions & 0 deletions test/output/shorthandCellCategorical.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions test/plots/shorthand-cell.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";

export async function shorthandCell() {
const matrix = [
Expand All @@ -18,3 +19,7 @@ export async function shorthandCell() {
];
return Plot.cell(matrix).plot();
}

export async function shorthandCellCategorical() {
return Plot.cellX(d3.range(10)).plot({color: {scheme: "Tableau10"}});
}

0 comments on commit 168e8bf

Please sign in to comment.