Skip to content

Commit 30f7947

Browse files
committed
ordinal opacity
1 parent 1b7e76a commit 30f7947

11 files changed

+231
-13
lines changed

src/legends.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,21 @@ function legendColor(color, {legend = true, ...options}) {
5252
case "ramp":
5353
return legendRamp(color, options);
5454
default:
55-
throw new Error(`unknown legend type: ${legend}`);
55+
throw new Error(`unknown color legend type: ${legend}`);
5656
}
5757
}
5858

59-
function legendOpacity({type, interpolate, ...scale}, {legend = true, color = rgb(0, 0, 0), ...options}) {
60-
if (!interpolate) throw new Error(`${type} opacity scales are not supported`);
61-
if (legend === true) legend = "ramp";
62-
if (`${legend}`.toLowerCase() !== "ramp") throw new Error(`${legend} opacity legends are not supported`);
63-
return legendColor({type, ...scale, interpolate: interpolateOpacity(color)}, {legend, ...options});
64-
}
65-
66-
function interpolateOpacity(color) {
59+
function legendOpacity(opacity, {legend = true, color = "black", ...options}) {
60+
if (legend === true) legend = opacity.type === "ordinal" ? "swatches" : "ramp";
6761
const {r, g, b} = rgb(color) || rgb(0, 0, 0); // treat invalid color as black
68-
return (t) => `rgba(${r},${g},${b},${t})`;
62+
switch (`${legend}`.toLowerCase()) {
63+
case "swatches":
64+
return legendSwatches({...opacity, scale: (x) => String(rgb(r, g, b, opacity.scale(x)))}, options);
65+
case "ramp":
66+
return legendRamp({...opacity, interpolate: (a) => String(rgb(r, g, b, a))}, options);
67+
default:
68+
throw new Error(`unknown opacity legend type: ${legend}`);
69+
}
6970
}
7071

7172
export function createLegends(scales, context, options) {

src/scales/ordinal.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {scaleBand, scaleOrdinal, scalePoint, scaleImplicit} from "d3";
33
import {ascendingDefined} from "../defined.js";
44
import {isNoneish, map, maybeRangeInterval} from "../options.js";
55
import {maybeSymbol} from "../symbol.js";
6-
import {registry, color, position, symbol} from "./index.js";
6+
import {registry, color, opacity, position, symbol} from "./index.js";
77
import {maybeBooleanRange, ordinalScheme, quantitativeScheme} from "./schemes.js";
88

99
// This denotes an implicitly ordinal color scale: the scale type was not set,
@@ -51,6 +51,10 @@ export function createScaleOrdinal(key, channels, {type, interval, domain, range
5151
range = ordinalScheme(scheme);
5252
}
5353
}
54+
} else if (registry.get(key) === opacity) {
55+
if (range === undefined) {
56+
range = ({length: n}) => quantize((t) => t, n);
57+
}
5458
}
5559
if (unknown === scaleImplicit) {
5660
throw new Error(`implicit unknown on ${key} scale is not supported`);
@@ -96,6 +100,7 @@ function inferDomain(channels, interval, key) {
96100
if (value === undefined) continue;
97101
for (const v of value) values.add(v);
98102
}
103+
if (key === "opacity") values.add(0); // akin to inferZeroDomain
99104
if (interval !== undefined) {
100105
const [min, max] = extent(values).map(interval.floor, interval);
101106
return interval.range(min, interval.offset(max));
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<div class="plot-swatches plot-swatches-wrap">
2+
<style>
3+
:where(.plot-swatches) {
4+
font-family: system-ui, sans-serif;
5+
font-size: 10px;
6+
margin-bottom: 0.5em;
7+
}
8+
9+
:where(.plot-swatch > svg) {
10+
margin-right: 0.5em;
11+
overflow: visible;
12+
}
13+
14+
:where(.plot-swatches-wrap) {
15+
display: flex;
16+
align-items: center;
17+
min-height: 33px;
18+
flex-wrap: wrap;
19+
}
20+
21+
:where(.plot-swatches-wrap .plot-swatch) {
22+
display: inline-flex;
23+
align-items: center;
24+
margin-right: 1em;
25+
}
26+
</style><span class="plot-swatch"><svg width="15" height="15" fill="rgba(0, 0, 0, 0)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
27+
<rect width="100%" height="100%"></rect>
28+
</svg>0</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(0, 0, 0, 0.111)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
29+
<rect width="100%" height="100%"></rect>
30+
</svg>1</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(0, 0, 0, 0.222)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
31+
<rect width="100%" height="100%"></rect>
32+
</svg>2</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(0, 0, 0, 0.333)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
33+
<rect width="100%" height="100%"></rect>
34+
</svg>3</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(0, 0, 0, 0.444)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
35+
<rect width="100%" height="100%"></rect>
36+
</svg>4</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(0, 0, 0, 0.556)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
37+
<rect width="100%" height="100%"></rect>
38+
</svg>5</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(0, 0, 0, 0.667)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
39+
<rect width="100%" height="100%"></rect>
40+
</svg>6</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(0, 0, 0, 0.778)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
41+
<rect width="100%" height="100%"></rect>
42+
</svg>7</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(0, 0, 0, 0.889)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
43+
<rect width="100%" height="100%"></rect>
44+
</svg>8</span><span class="plot-swatch"><svg width="15" height="15" fill="rgb(0, 0, 0)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
45+
<rect width="100%" height="100%"></rect>
46+
</svg>9</span>
47+
</div>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<div class="plot-swatches plot-swatches-wrap">
2+
<style>
3+
:where(.plot-swatches) {
4+
font-family: system-ui, sans-serif;
5+
font-size: 10px;
6+
margin-bottom: 0.5em;
7+
}
8+
9+
:where(.plot-swatch > svg) {
10+
margin-right: 0.5em;
11+
overflow: visible;
12+
}
13+
14+
:where(.plot-swatches-wrap) {
15+
display: flex;
16+
align-items: center;
17+
min-height: 33px;
18+
flex-wrap: wrap;
19+
}
20+
21+
:where(.plot-swatches-wrap .plot-swatch) {
22+
display: inline-flex;
23+
align-items: center;
24+
margin-right: 1em;
25+
}
26+
</style><span class="plot-swatch"><svg width="15" height="15" fill="rgba(255, 0, 0, 0)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
27+
<rect width="100%" height="100%"></rect>
28+
</svg>0</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(255, 0, 0, 0.111)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
29+
<rect width="100%" height="100%"></rect>
30+
</svg>1</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(255, 0, 0, 0.222)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
31+
<rect width="100%" height="100%"></rect>
32+
</svg>2</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(255, 0, 0, 0.333)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
33+
<rect width="100%" height="100%"></rect>
34+
</svg>3</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(255, 0, 0, 0.444)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
35+
<rect width="100%" height="100%"></rect>
36+
</svg>4</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(255, 0, 0, 0.556)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
37+
<rect width="100%" height="100%"></rect>
38+
</svg>5</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(255, 0, 0, 0.667)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
39+
<rect width="100%" height="100%"></rect>
40+
</svg>6</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(255, 0, 0, 0.778)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
41+
<rect width="100%" height="100%"></rect>
42+
</svg>7</span><span class="plot-swatch"><svg width="15" height="15" fill="rgba(255, 0, 0, 0.889)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
43+
<rect width="100%" height="100%"></rect>
44+
</svg>8</span><span class="plot-swatch"><svg width="15" height="15" fill="rgb(255, 0, 0)" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
45+
<rect width="100%" height="100%"></rect>
46+
</svg>9</span>
47+
</div>

test/output/ordinalOpacity.svg

Lines changed: 52 additions & 0 deletions
Loading
Lines changed: 46 additions & 0 deletions
Loading

test/plots/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export * from "./nested-facets.js";
188188
export * from "./npm-versions.js";
189189
export * from "./opacity.js";
190190
export * from "./ordinal-bar.js";
191+
export * from "./ordinal-opacity.js";
191192
export * from "./pairs.js";
192193
export * from "./penguin-annotated.js";
193194
export * from "./penguin-culmen-array.js";

test/plots/legend-color.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import * as d3 from "d3";
21
import * as Plot from "@observablehq/plot";
2+
import * as d3 from "d3";
33

44
export function colorLegendCategorical() {
55
return Plot.legend({color: {domain: "ABCDEFGHIJ"}});

test/plots/legend-opacity.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as Plot from "@observablehq/plot";
2+
import * as d3 from "d3";
23

34
export function opacityLegend() {
45
return Plot.legend({opacity: {domain: [0, 10], label: "Quantitative"}});
@@ -23,3 +24,11 @@ export function opacityLegendLog() {
2324
export function opacityLegendSqrt() {
2425
return Plot.legend({opacity: {type: "sqrt", domain: [0, 1], label: "Sqrt"}});
2526
}
27+
28+
export function opacityLegendSwatches() {
29+
return Plot.legend({opacity: {type: "ordinal", domain: d3.range(10)}});
30+
}
31+
32+
export function opacityLegendSwatchesColor() {
33+
return Plot.legend({opacity: {type: "ordinal", domain: d3.range(10)}, color: "red"});
34+
}

test/plots/opacity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import * as d3 from "d3";
21
import * as Plot from "@observablehq/plot";
2+
import * as d3 from "d3";
33

44
export function opacityDotsFillUnscaled() {
55
return Plot.dotX(d3.ticks(0.3, 0.7, 40), {

0 commit comments

Comments
 (0)