Skip to content

Commit

Permalink
tick marker (#1892)
Browse files Browse the repository at this point in the history
* tick marker

* tick marker documentation
  • Loading branch information
mbostock authored Nov 2, 2023
1 parent 51d85c4 commit 32462b1
Show file tree
Hide file tree
Showing 8 changed files with 423 additions and 5 deletions.
6 changes: 6 additions & 0 deletions docs/features/markers.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ A **marker** defines a graphic drawn on vertices of a [line](../marks/line.md) o
<option>dot</option>
<option>circle</option>
<option>circle-stroke</option>
<option>tick</option>
<option>tick-x</option>
<option>tick-y</option>
</select>
</label>
</p>
Expand Down Expand Up @@ -53,6 +56,9 @@ The following named markers are supported:
* *dot* - a filled *circle* without a stroke and 2.5px radius
* *circle*, equivalent to *circle-fill* - a filled circle with a white stroke and 3px radius
* *circle-stroke* - a hollow circle with a colored stroke and a white fill and 3px radius
* *tick* - a small opposing line
* *tick-x* - a small horizontal line
* *tick-y* - a small vertical line

If **marker** is true, it defaults to *circle*. If **marker** is a function, it will be called with a given *color* and must return an [SVG marker element](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker).

Expand Down
21 changes: 18 additions & 3 deletions docs/marks/rule.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,31 @@ Plot.plot({
```
:::

Rules can also be a stylistic choice, as in the lollipop 🍭 chart below, serving the role of a skinny [bar](./bar.md) topped with a [dot](./dot.md).
Rules can indicate uncertainty or error by setting the [**marker** option](../features/markers.md) to *tick*; this draws a small perpendicular line at the start and end of the rule. For example, to simulate ±10% error:

:::plot
```js
Plot.plot({
x: {label: null},
y: {percent: true},
marks: [
Plot.barY(alphabet, {x: "letter", y: "frequency", fill: "blue"}),
Plot.ruleX(alphabet, {x: "letter", y1: (d) => d.frequency * 0.9, y2: (d) => d.frequency * 1.1, marker: "tick"}),
Plot.ruleY([0])
]
})
```
:::

Rules can also be a stylistic choice, as in the lollipop 🍭 chart below, serving the role of a skinny [bar](./bar.md) topped with a [*dot* marker](../features/markers.md).

:::plot https://observablehq.com/@observablehq/plot-lollipop
```js
Plot.plot({
x: {label: null, tickPadding: 6, tickSize: 0},
y: {percent: true},
marks: [
Plot.ruleX(alphabet, {x: "letter", y: "frequency", strokeWidth: 2}),
Plot.dot(alphabet, {x: "letter", y: "frequency", fill: "currentColor", r: 4})
Plot.ruleX(alphabet, {x: "letter", y: "frequency", strokeWidth: 2, markerEnd: "dot"})
]
})
```
Expand Down
14 changes: 13 additions & 1 deletion src/marker.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,20 @@
* - *circle-fill* - a filled circle with a white stroke and 3px radius
* - *circle-stroke* - a stroked circle with a white fill and 3px radius
* - *circle* - alias for *circle-fill*
* - *tick* - a small opposing line
* - *tick-x* - a small horizontal line
* - *tick-y* - a small vertical line
*/
export type MarkerName = "arrow" | "arrow-reverse" | "dot" | "circle" | "circle-fill" | "circle-stroke";
export type MarkerName =
| "arrow"
| "arrow-reverse"
| "dot"
| "circle"
| "circle-fill"
| "circle-stroke"
| "tick"
| "tick-x"
| "tick-y";

/** A custom marker implementation. */
export type MarkerFunction = (color: string, context: {document: Document}) => SVGMarkerElement;
Expand Down
18 changes: 18 additions & 0 deletions src/marker.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ function maybeMarker(marker) {
return markerCircleFill;
case "circle-stroke":
return markerCircleStroke;
case "tick":
return markerTick("auto");
case "tick-x":
return markerTick(90);
case "tick-y":
return markerTick(0);
}
throw new Error(`invalid marker: ${marker}`);
}
Expand Down Expand Up @@ -79,6 +85,18 @@ function markerCircleStroke(color, context) {
.node();
}

function markerTick(orient) {
return (color, context) =>
create("svg:marker", context)
.attr("viewBox", "-3 -3 6 6")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", orient)
.attr("stroke", color)
.call((marker) => marker.append("path").attr("d", "M0,-3v6"))
.node();
}

let nextMarkerId = 0;

export function applyMarkers(path, mark, {stroke: S}, context) {
Expand Down
164 changes: 164 additions & 0 deletions test/output/errorBarX.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 32462b1

Please sign in to comment.