Skip to content

Commit ab05bfb

Browse files
committed
radius legends
1 parent 42a7bed commit ab05bfb

File tree

6 files changed

+116
-2
lines changed

6 files changed

+116
-2
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ When the *include* or *exclude* facet mode is chosen, the mark data must be para
498498

499499
## Legends
500500

501-
Plot can generate legends for *color*, *opacity*, and *symbol* [scales](#scale-options). (An opacity scale is treated as a color scale with varying transparency.) For an inline legend, use the *scale*.**legend** option:
501+
Plot can generate legends for *color*, *radius*, *opacity*, and *symbol* [scales](#scale-options). (An opacity scale is treated as a color scale with varying transparency.) For an inline legend, use the *scale*.**legend** option:
502502

503503
* *scale*.**legend** - if truthy, generate a legend for the given scale
504504

@@ -551,6 +551,18 @@ Continuous color legends are rendered as a ramp, and can be configured with the
551551
* *options*.**marginBottom** - the legend’s bottom margin
552552
* *options*.**marginLeft** - the legend’s left margin
553553

554+
Radius legends are rendered as circles sharing a same base, and a line connecting each circle to the corresponding tick label. The ticks are computed in a way that guarantees that they are not occluded. Radius legens can be configured with the following options:
555+
556+
* *options*.**label** - the scale’s label
557+
* *options*.**ticks** - the desired number of ticks, or an array of tick values
558+
* *options*.**tickFormat** - a function that formats the ticks
559+
* *options*.**strokeWidth** - the stroke width for the connectors, defaults to 0.5
560+
* *options*.**strokeDasharray** - the stroke dash-array for the connectors, defaults to [5, 4]
561+
* *options*.**lineHeight** - the minimum line height, to avoid label occlusion
562+
* *options*.**gap** — the gap between the circles and the tick labels, defaults to 20 pixels
563+
* *options*.**className** - a className for the legend
564+
* *options*.**style** - styles (a string or an object)
565+
554566
### Plot.legend(*options*)
555567

556568
Returns a standalone legend for the given *scale* definition, passing the *options* described in the previous section. For example:

src/legends.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import {rgb} from "d3";
22
import {normalizeScale} from "./scales.js";
33
import {legendRamp} from "./legends/ramp.js";
4+
import {legendRadius} from "./legends/radius.js";
45
import {legendSwatches, legendSymbols} from "./legends/swatches.js";
56
import {isObject} from "./mark.js";
67

78
const legendRegistry = new Map([
89
["color", legendColor],
910
["symbol", legendSymbols],
10-
["opacity", legendOpacity]
11+
["opacity", legendOpacity],
12+
["r", legendRadius]
1113
]);
1214

1315
export function legend(options = {}) {

src/legends/radius.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {plot} from "../plot.js";
2+
import {link} from "../marks/link.js";
3+
import {text} from "../marks/text.js";
4+
import {dot} from "../marks/dot.js";
5+
import {maybeClassName} from "../style.js";
6+
7+
export function legendRadius(scale, {
8+
label = scale.label,
9+
ticks = 5,
10+
tickFormat = d => d,
11+
strokeWidth = 0.5,
12+
strokeDasharray = [5, 4],
13+
lineHeight = 8,
14+
gap = 20,
15+
style,
16+
className
17+
}) {
18+
className = maybeClassName(className);
19+
const s = scale.scale;
20+
const r0 = scale.range[1];
21+
const shiftY = label ? 10 : 0;
22+
23+
let h = Infinity;
24+
const values = s.ticks(ticks).reverse()
25+
.filter((t) => h - s(t) > lineHeight / 2 && (h = s(t)));
26+
27+
return plot({
28+
x: { type: "identity", axis: null },
29+
r: { type: "identity" },
30+
y: { type: "identity", axis: null },
31+
marks: [
32+
link(values, {
33+
x1: r0 + 2,
34+
y1: (d) => 8 + 2 * r0 - 2 * s(d) + shiftY,
35+
x2: 2 * r0 + 2 + gap,
36+
y2: (d) => 8 + 2 * r0 - 2 * s(d) + shiftY,
37+
strokeWidth: strokeWidth / 2,
38+
strokeDasharray
39+
}),
40+
dot(values, {
41+
r: s,
42+
x: r0 + 2,
43+
y: (d) => 8 + 2 * r0 - s(d) + shiftY,
44+
strokeWidth
45+
}),
46+
text(values, {
47+
x: 2 * r0 + 2 + gap,
48+
y: (d) => 8 + 2 * r0 - 2 * s(d) + shiftY,
49+
textAnchor: "start",
50+
dx: 4,
51+
text: tickFormat
52+
}),
53+
text(label ? [label] : [], {
54+
x: 0,
55+
y: 6,
56+
textAnchor: "start",
57+
fontWeight: "bold",
58+
text: tickFormat
59+
})
60+
],
61+
height: 2 * r0 + 10 + shiftY,
62+
className,
63+
style
64+
});
65+
}

test/output/radiusLegend.svg

Lines changed: 29 additions & 0 deletions
Loading

test/plots/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,4 @@ export {default as wordLengthMobyDick} from "./word-length-moby-dick.js";
138138

139139
export * from "./legend-color.js";
140140
export * from "./legend-opacity.js";
141+
export * from "./legend-radius.js";

test/plots/legend-radius.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import * as Plot from "@observablehq/plot";
2+
3+
export function radiusLegend() {
4+
return Plot.plot({r: {label: "radial", domain: [0, 4500], range: [0, 100]}}).legend("r");
5+
}

0 commit comments

Comments
 (0)