From ca1f5d749fd78cfb289f3f6ce2aa686c648875be Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 19 May 2023 19:56:08 -0700 Subject: [PATCH 1/3] rule & tick markers; arrow auto-start-reverse --- src/marker.js | 2 +- src/marks/rule.d.ts | 3 +- src/marks/rule.js | 5 ++++ src/marks/tick.d.ts | 5 ++-- src/marks/tick.js | 3 ++ test/output/crimeanWarArrow.svg | 12 ++++---- test/output/driving.svg | 2 +- test/output/flareTree.svg | 4 +-- test/output/geoLink.svg | 2 +- test/output/markerRuleX.svg | 50 +++++++++++++++++++++++++++++++++ test/output/markerRuleY.svg | 50 +++++++++++++++++++++++++++++++++ test/output/markerTickX.svg | 50 +++++++++++++++++++++++++++++++++ test/output/markerTickY.svg | 50 +++++++++++++++++++++++++++++++++ test/plots/crimean-war-arrow.ts | 7 ++--- test/plots/index.ts | 1 + test/plots/markers.ts | 17 +++++++++++ 16 files changed, 244 insertions(+), 19 deletions(-) create mode 100644 test/output/markerRuleX.svg create mode 100644 test/output/markerRuleY.svg create mode 100644 test/output/markerTickX.svg create mode 100644 test/output/markerTickY.svg create mode 100644 test/plots/markers.ts diff --git a/src/marker.js b/src/marker.js index 237da2f88a..0fbc9f7602 100644 --- a/src/marker.js +++ b/src/marker.js @@ -31,7 +31,7 @@ function markerArrow(color, context) { .attr("viewBox", "-5 -5 10 10") .attr("markerWidth", 6.67) .attr("markerHeight", 6.67) - .attr("orient", "auto") + .attr("orient", "auto-start-reverse") .attr("fill", "none") .attr("stroke", color) .attr("stroke-width", 1.5) diff --git a/src/marks/rule.d.ts b/src/marks/rule.d.ts index 3b9fcc41dc..993ee0b835 100644 --- a/src/marks/rule.d.ts +++ b/src/marks/rule.d.ts @@ -2,9 +2,10 @@ import type {ChannelValueIntervalSpec, ChannelValueSpec} from "../channel.js"; import type {InsetOptions} from "../inset.js"; import type {Interval} from "../interval.js"; import type {Data, MarkOptions, RenderableMark} from "../mark.js"; +import type {MarkerOptions} from "../marker.js"; /** Options for the ruleX and ruleY marks. */ -interface RuleOptions extends MarkOptions { +interface RuleOptions extends MarkOptions, MarkerOptions { /** * How to convert a continuous value (**y** for ruleX, or **x** for ruleY) * into an interval (**y1** and **y2** for ruleX, or **x1** and **x2** for diff --git a/src/marks/rule.js b/src/marks/rule.js index 5175e0539e..09e67aebc6 100644 --- a/src/marks/rule.js +++ b/src/marks/rule.js @@ -1,5 +1,6 @@ import {create} from "../context.js"; import {Mark, withTip} from "../mark.js"; +import {applyMarkers, markers} from "../marker.js"; import {identity, number} from "../options.js"; import {isCollapsed} from "../scales.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, offset} from "../style.js"; @@ -26,6 +27,7 @@ export class RuleX extends Mark { ); this.insetTop = number(insetTop); this.insetBottom = number(insetBottom); + markers(this, options); } render(index, scales, channels, dimensions, context) { const {x, y} = scales; @@ -54,6 +56,7 @@ export class RuleX extends Mark { : height - marginBottom - insetBottom ) .call(applyChannelStyles, this, channels) + .call(applyMarkers, this, channels, context) ) .node(); } @@ -74,6 +77,7 @@ export class RuleY extends Mark { ); this.insetRight = number(insetRight); this.insetLeft = number(insetLeft); + markers(this, options); } render(index, scales, channels, dimensions, context) { const {x, y} = scales; @@ -102,6 +106,7 @@ export class RuleY extends Mark { .attr("y1", Y ? (i) => Y[i] : (marginTop + height - marginBottom) / 2) .attr("y2", Y ? (i) => Y[i] : (marginTop + height - marginBottom) / 2) .call(applyChannelStyles, this, channels) + .call(applyMarkers, this, channels, context) ) .node(); } diff --git a/src/marks/tick.d.ts b/src/marks/tick.d.ts index 8536b3cab0..947bf61f43 100644 --- a/src/marks/tick.d.ts +++ b/src/marks/tick.d.ts @@ -1,9 +1,10 @@ import type {ChannelValueSpec} from "../channel.js"; import type {InsetOptions} from "../inset.js"; import type {Data, MarkOptions, RenderableMark} from "../mark.js"; +import type {MarkerOptions} from "../marker.js"; /** Options for the tickX mark. */ -export interface TickXOptions extends MarkOptions, Omit { +export interface TickXOptions extends MarkOptions, MarkerOptions, Omit { /** * The required horizontal position of the tick; a channel typically bound to * the *x* scale. @@ -22,7 +23,7 @@ export interface TickXOptions extends MarkOptions, Omit { +export interface TickYOptions extends MarkOptions, MarkerOptions, Omit { /** * The required vertical position of the tick; a channel typically bound to * the *y* scale. diff --git a/src/marks/tick.js b/src/marks/tick.js index 908999b373..42ef1b56d6 100644 --- a/src/marks/tick.js +++ b/src/marks/tick.js @@ -1,6 +1,7 @@ import {create} from "../context.js"; import {identity, number} from "../options.js"; import {Mark} from "../mark.js"; +import {applyMarkers, markers} from "../marker.js"; import {applyDirectStyles, applyIndirectStyles, applyTransform, applyChannelStyles, offset} from "../style.js"; const defaults = { @@ -12,6 +13,7 @@ const defaults = { class AbstractTick extends Mark { constructor(data, channels, options) { super(data, channels, options, defaults); + markers(this, options); } render(index, scales, channels, dimensions, context) { return create("svg:g", context) @@ -29,6 +31,7 @@ class AbstractTick extends Mark { .attr("y1", this._y1(scales, channels, dimensions)) .attr("y2", this._y2(scales, channels, dimensions)) .call(applyChannelStyles, this, channels) + .call(applyMarkers, this, channels, context) ) .node(); } diff --git a/test/output/crimeanWarArrow.svg b/test/output/crimeanWarArrow.svg index 6cbdd9b0e8..abbaa64706 100644 --- a/test/output/crimeanWarArrow.svg +++ b/test/output/crimeanWarArrow.svg @@ -72,17 +72,17 @@ - + - - + + - - + + - + \ No newline at end of file diff --git a/test/output/driving.svg b/test/output/driving.svg index eacb3ff226..389e6c1360 100644 --- a/test/output/driving.svg +++ b/test/output/driving.svg @@ -83,7 +83,7 @@ Miles driven (per person-year) → - + diff --git a/test/output/flareTree.svg b/test/output/flareTree.svg index 2d9271f72a..2466de415c 100644 --- a/test/output/flareTree.svg +++ b/test/output/flareTree.svg @@ -14,7 +14,7 @@ } - + @@ -30,7 +30,7 @@ - + diff --git a/test/output/geoLink.svg b/test/output/geoLink.svg index 6a5ecbeb61..0a3f15cac8 100644 --- a/test/output/geoLink.svg +++ b/test/output/geoLink.svg @@ -23,7 +23,7 @@ - + diff --git a/test/output/markerRuleX.svg b/test/output/markerRuleX.svg new file mode 100644 index 0000000000..b77130552c --- /dev/null +++ b/test/output/markerRuleX.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + 1.0 + 1.2 + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + 2.4 + 2.6 + 2.8 + 3.0 + + + + + + + + + + \ No newline at end of file diff --git a/test/output/markerRuleY.svg b/test/output/markerRuleY.svg new file mode 100644 index 0000000000..f297f94acb --- /dev/null +++ b/test/output/markerRuleY.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + 1.0 + 1.2 + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + 2.4 + 2.6 + 2.8 + 3.0 + + + + + + + + + + \ No newline at end of file diff --git a/test/output/markerTickX.svg b/test/output/markerTickX.svg new file mode 100644 index 0000000000..30ae4700a4 --- /dev/null +++ b/test/output/markerTickX.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + 1.0 + 1.2 + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + 2.4 + 2.6 + 2.8 + 3.0 + + + + + + + + + + \ No newline at end of file diff --git a/test/output/markerTickY.svg b/test/output/markerTickY.svg new file mode 100644 index 0000000000..55f89ad4ea --- /dev/null +++ b/test/output/markerTickY.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + 1.0 + 1.2 + 1.4 + 1.6 + 1.8 + 2.0 + 2.2 + 2.4 + 2.6 + 2.8 + 3.0 + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/crimean-war-arrow.ts b/test/plots/crimean-war-arrow.ts index 9ef497a032..3a1769dfac 100644 --- a/test/plots/crimean-war-arrow.ts +++ b/test/plots/crimean-war-arrow.ts @@ -6,10 +6,7 @@ export async function crimeanWarArrow() { const causes = crimea.columns.slice(2); const data = causes.flatMap((cause) => crimea.map(({date, [cause]: deaths}) => ({date, cause, deaths}))); return Plot.plot({ - x: { - tickFormat: "%b", - label: null - }, - marks: [Plot.ruleY([0]), Plot.lineY(data, {x: "date", y: "deaths", stroke: "cause", marker: "arrow"})] + x: {tickFormat: "%b", label: null}, + marks: [Plot.ruleY([0]), Plot.lineY(data, {x: "date", y: "deaths", stroke: "cause", markerMid: "arrow"})] }); } diff --git a/test/plots/index.ts b/test/plots/index.ts index 39d3b5b461..baa80ae0ff 100644 --- a/test/plots/index.ts +++ b/test/plots/index.ts @@ -141,6 +141,7 @@ export * from "./linear-regression-mtcars.js"; export * from "./linear-regression-penguins.js"; export * from "./log-degenerate.js"; export * from "./long-labels.js"; +export * from "./markers.js"; export * from "./markov-chain.js"; export * from "./metro-inequality-change.js"; export * from "./metro-inequality.js"; diff --git a/test/plots/markers.ts b/test/plots/markers.ts new file mode 100644 index 0000000000..113d069d02 --- /dev/null +++ b/test/plots/markers.ts @@ -0,0 +1,17 @@ +import * as Plot from "@observablehq/plot"; + +export async function markerRuleX() { + return Plot.ruleX([1, 2, 3], {marker: "arrow", inset: 3}).plot(); +} + +export async function markerRuleY() { + return Plot.ruleY([1, 2, 3], {marker: "arrow", inset: 3}).plot(); +} + +export async function markerTickX() { + return Plot.tickX([1, 2, 3], {marker: "arrow", inset: 3}).plot(); +} + +export async function markerTickY() { + return Plot.tickY([1, 2, 3], {marker: "arrow", inset: 3}).plot(); +} From 99eeefd5b5bb5bb479ff256b085331c03e25c120 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 19 May 2023 20:06:18 -0700 Subject: [PATCH 2/3] docs edits --- docs/marks/rule.md | 2 +- docs/marks/tick.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/marks/rule.md b/docs/marks/rule.md index dc9af9e93d..5f60766c66 100644 --- a/docs/marks/rule.md +++ b/docs/marks/rule.md @@ -139,7 +139,7 @@ Rules are also used by the [grid mark](./grid) to draw grid lines. ## Rule options -For the required channels, see [ruleX](#rulex-data-options) and [ruleY](#ruley-data-options). The rule mark supports the [standard mark options](../features/marks.md#mark-options), including insets along its secondary dimension. The **stroke** defaults to *currentColor*. +For the required channels, see [ruleX](#rulex-data-options) and [ruleY](#ruley-data-options). The rule mark supports the [standard mark options](../features/marks.md#mark-options), including insets along its secondary dimension, and [marker options](../features/markers.md) to add a marker (such as a dot or an arrowhead) to the start or end of the rule. The **stroke** defaults to *currentColor*. ## ruleX(*data*, *options*) diff --git a/docs/marks/tick.md b/docs/marks/tick.md index fbbf0d6998..b0b467a37c 100644 --- a/docs/marks/tick.md +++ b/docs/marks/tick.md @@ -86,7 +86,7 @@ Ticks are also used by the [box mark](./box.md) to denote the median value for e ## Tick options -For the required channels, see [tickX](#tickx-data-options) and [tickY](#ticky-data-options). The tick mark supports the [standard mark options](../features/marks.md#mark-options), including insets. The **stroke** defaults to *currentColor*. +For the required channels, see [tickX](#tickx-data-options) and [tickY](#ticky-data-options). The tick mark supports the [standard mark options](../features/marks.md#mark-options), including insets, and [marker options](../features/markers.md) to add a marker (such as a dot or an arrowhead) to the start or end of the rule. The **stroke** defaults to *currentColor*. ## tickX(*data*, *options*) From a4b4435e67f12965c99d7181bc15368001f3f400 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sat, 20 May 2023 08:07:22 -0700 Subject: [PATCH 3/3] arrow-reverse marker --- docs/features/markers.md | 4 ++- src/marker.d.ts | 60 ++++++++++++++------------------- src/marker.js | 31 +++++++++-------- test/output/crimeanWarArrow.svg | 6 ++-- test/output/driving.svg | 2 +- test/output/flareTree.svg | 4 +-- test/output/geoLink.svg | 2 +- test/output/markerRuleX.svg | 12 +++++-- test/output/markerRuleY.svg | 12 +++++-- test/output/markerTickX.svg | 12 +++++-- test/output/markerTickY.svg | 12 +++++-- test/plots/markers.ts | 8 ++--- 12 files changed, 92 insertions(+), 73 deletions(-) diff --git a/docs/features/markers.md b/docs/features/markers.md index bd4d8a9ade..cea6193b4b 100644 --- a/docs/features/markers.md +++ b/docs/features/markers.md @@ -19,6 +19,7 @@ A **marker** defines a graphic drawn on vertices of a [line](../marks/line.md) o