Skip to content

Commit 7df1c3d

Browse files
Filmbostock
andauthored
opacity legend 2 (#2416)
* upgrade opacity legends: support for display-p3 colors, checkerboard, dark mode * test p3 * only test current color scheme * restore snapshots * remove checkerboard * reindexIri --------- Co-authored-by: Mike Bostock <mbostock@gmail.com>
1 parent 2a2ef29 commit 7df1c3d

11 files changed

Lines changed: 108 additions & 53 deletions

src/legends.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import {rgb} from "d3";
1+
import {select} from "d3";
22
import {createContext} from "./context.js";
33
import {legendRamp} from "./legends/ramp.js";
44
import {isSymbolColorLegend, legendSwatches, legendSymbols} from "./legends/swatches.js";
55
import {inherit, isScaleOptions} from "./options.js";
6+
import {getFilterId} from "./style.js";
67
import {normalizeScale} from "./scales.js";
78

89
const legendRegistry = new Map([
@@ -56,16 +57,23 @@ function legendColor(color, {legend = true, ...options}) {
5657
}
5758
}
5859

59-
function legendOpacity({type, interpolate, ...scale}, {legend = true, color = rgb(0, 0, 0), ...options}) {
60+
function legendOpacity({type, interpolate, ...scale}, {legend = true, color = "currentColor", ...options}) {
6061
if (!interpolate) throw new Error(`${type} opacity scales are not supported`);
6162
if (legend === true) legend = "ramp";
6263
if (`${legend}`.toLowerCase() !== "ramp") throw new Error(`${legend} opacity legends are not supported`);
63-
return legendColor({type, ...scale, interpolate: interpolateOpacity(color)}, {legend, ...options});
64+
const node = legendColor({type, ...scale, interpolate: interpolateOpacity}, {legend, ...options});
65+
if (!node) return;
66+
const fid = getFilterId();
67+
const svg = select(node);
68+
svg.select("image").attr("filter", `url(#${fid})`);
69+
const filter = svg.append("filter").attr("id", fid);
70+
filter.append("feFlood").attr("flood-color", color);
71+
filter.append("feComposite").attr("in2", "SourceGraphic").attr("operator", "in");
72+
return node;
6473
}
6574

66-
function interpolateOpacity(color) {
67-
const {r, g, b} = rgb(color) || rgb(0, 0, 0); // treat invalid color as black
68-
return (t) => `rgba(${r},${g},${b},${t})`;
75+
function interpolateOpacity(t) {
76+
return `rgba(0,0,0,${t})`;
6977
}
7078

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

src/style.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@ export function setOffset(o) {
1313
}
1414

1515
let nextClipId = 0;
16+
let nextFilterId = 0;
1617
let nextPatternId = 0;
1718

1819
export function getClipId() {
1920
return `plot-clip-${++nextClipId}`;
2021
}
2122

23+
export function getFilterId() {
24+
return `plot-filter-${++nextFilterId}`;
25+
}
26+
2227
export function getPatternId() {
2328
return `plot-pattern-${++nextPatternId}`;
2429
}

test/output/opacityLegend.svg

Lines changed: 5 additions & 1 deletion
Loading

test/output/opacityLegendCSS4.svg

Lines changed: 47 additions & 0 deletions
Loading

test/output/opacityLegendColor.svg

Lines changed: 5 additions & 1 deletion
Loading

test/output/opacityLegendLinear.svg

Lines changed: 5 additions & 1 deletion
Loading

test/output/opacityLegendLog.svg

Lines changed: 5 additions & 1 deletion
Loading

test/output/opacityLegendRange.svg

Lines changed: 5 additions & 1 deletion
Loading

test/output/opacityLegendSqrt.svg

Lines changed: 5 additions & 1 deletion
Loading

test/plot-snapshot.js

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ export function test(plot) {
1717
svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
1818
}
1919
reindexStyle(root);
20-
reindexMarker(root);
21-
reindexClip(root);
22-
reindexPattern(root);
20+
reindexIri(root, "clip", ["clip-path"]);
21+
reindexIri(root, "filter", ["filter"]);
22+
reindexIri(root, "marker", ["marker-start", "marker-mid", "marker-end"]);
23+
reindexIri(root, "pattern", ["fill", "stroke"]);
2324
const actual = normalizeHtml(root.outerHTML);
2425
const outfile = join("test", "output", `${name}.${ext}`);
2526
const diffile = join("test", "output", `${name}-changed.${ext}`);
@@ -63,50 +64,16 @@ function reindexStyle(root) {
6364
}
6465
}
6566

66-
function reindexMarker(root) {
67+
function reindexIri(root, name, attributes) {
6768
let index = 0;
6869
const map = new Map();
69-
for (const node of root.querySelectorAll("[id^=plot-marker-]")) {
70+
for (const node of root.querySelectorAll(`[id^=plot-${name}-]`)) {
7071
let id = node.getAttribute("id");
7172
if (map.has(id)) id = map.get(id);
72-
else map.set(id, (id = `plot-marker-${++index}`));
73+
else map.set(id, (id = `plot-${name}-${++index}`));
7374
node.setAttribute("id", id);
7475
}
75-
for (const key of ["marker-start", "marker-mid", "marker-end"]) {
76-
for (const node of root.querySelectorAll(`[${key}]`)) {
77-
let id = node.getAttribute(key).slice(5, -1);
78-
if (map.has(id)) node.setAttribute(key, `url(#${map.get(id)})`);
79-
}
80-
}
81-
}
82-
83-
function reindexClip(root) {
84-
let index = 0;
85-
const map = new Map();
86-
for (const node of root.querySelectorAll("[id^=plot-clip-]")) {
87-
let id = node.getAttribute("id");
88-
if (map.has(id)) id = map.get(id);
89-
else map.set(id, (id = `plot-clip-${++index}`));
90-
node.setAttribute("id", id);
91-
}
92-
for (const key of ["clip-path"]) {
93-
for (const node of root.querySelectorAll(`[${key}]`)) {
94-
let id = node.getAttribute(key).slice(5, -1);
95-
if (map.has(id)) node.setAttribute(key, `url(#${map.get(id)})`);
96-
}
97-
}
98-
}
99-
100-
function reindexPattern(root) {
101-
let index = 0;
102-
const map = new Map();
103-
for (const node of root.querySelectorAll("[id^=plot-pattern-]")) {
104-
let id = node.getAttribute("id");
105-
if (map.has(id)) id = map.get(id);
106-
else map.set(id, (id = `plot-pattern-${++index}`));
107-
node.setAttribute("id", id);
108-
}
109-
for (const key of ["fill", "stroke"]) {
76+
for (const key of attributes) {
11077
for (const node of root.querySelectorAll(`[${key}]`)) {
11178
let id = node.getAttribute(key).slice(5, -1);
11279
if (map.has(id)) node.setAttribute(key, `url(#${map.get(id)})`);

0 commit comments

Comments
 (0)