Skip to content

radius legends #665

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

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ When the *include* or *exclude* facet mode is chosen, the mark data must be para

## Legends

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:
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:

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

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

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:

* *options*.**label** - the scale’s label
* *options*.**ticks** - the desired number of ticks, or an array of tick values
* *options*.**tickFormat** - a function that formats the ticks
* *options*.**strokeWidth** - the stroke width for the connectors, defaults to 0.5
* *options*.**strokeDasharray** - the stroke dash-array for the connectors, defaults to [5, 4]
* *options*.**lineHeight** - the minimum line height, to avoid label occlusion
* *options*.**gap** — the gap between the circles and the tick labels, defaults to 20 pixels
* *options*.**className** - a className for the legend
* *options*.**style** - styles (a string or an object)

### Plot.legend(*options*)

Returns a standalone legend for the given *scale* definition, passing the *options* described in the previous section. For example:
Expand Down
4 changes: 3 additions & 1 deletion src/legends.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import {rgb} from "d3";
import {isObject} from "./options.js";
import {normalizeScale} from "./scales.js";
import {legendRamp} from "./legends/ramp.js";
import {legendRadius} from "./legends/radius.js";
import {legendSwatches, legendSymbols} from "./legends/swatches.js";

const legendRegistry = new Map([
["color", legendColor],
["symbol", legendSymbols],
["opacity", legendOpacity]
["opacity", legendOpacity],
["r", legendRadius]
]);

export function legend(options = {}) {
Expand Down
62 changes: 62 additions & 0 deletions src/legends/radius.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as Plot from "../index.js";
import {maybeClassName} from "../style.js";

export function legendRadius(scale, {
label = scale.label,
ticks = 5,
tickFormat = d => d,
strokeWidth = 0.5,
strokeDasharray = [5, 4],
lineHeight = 8,
gap = 20,
style,
className
}) {
className = maybeClassName(className);
const s = scale.scale;
const r0 = scale.range[1];
const shiftY = label ? 10 : 0;

let h = Infinity;
const values = s.ticks(ticks).reverse()
.filter((t) => h - s(t) > lineHeight / 2 && (h = s(t)));

return Plot.plot({
x: { type: "identity", axis: null },
r: { type: "identity" },
y: { type: "identity", axis: null },
marks: [
Plot.link(values, {
x1: r0 + 2,
y1: (d) => 8 + 2 * r0 - 2 * s(d) + shiftY,
x2: 2 * r0 + 2 + gap,
y2: (d) => 8 + 2 * r0 - 2 * s(d) + shiftY,
strokeWidth: strokeWidth / 2,
strokeDasharray
}),
Plot.dot(values, {
r: s,
x: r0 + 2,
y: (d) => 8 + 2 * r0 - s(d) + shiftY,
strokeWidth
}),
Plot.text(values, {
x: 2 * r0 + 2 + gap,
y: (d) => 8 + 2 * r0 - 2 * s(d) + shiftY,
textAnchor: "start",
dx: 4,
text: tickFormat
}),
Plot.text(label ? [label] : [], {
x: 0,
y: 6,
textAnchor: "start",
fontWeight: "bold",
text: tickFormat
})
],
height: 2 * r0 + 10 + shiftY,
className,
style
});
}
29 changes: 29 additions & 0 deletions test/output/radiusLegend.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/plots/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,4 @@ export {default as wordLengthMobyDick} from "./word-length-moby-dick.js";

export * from "./legend-color.js";
export * from "./legend-opacity.js";
export * from "./legend-radius.js";
5 changes: 5 additions & 0 deletions test/plots/legend-radius.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as Plot from "@observablehq/plot";

export function radiusLegend() {
return Plot.plot({r: {label: "radial", domain: [0, 4500], range: [0, 100]}}).legend("r");
}