Skip to content

Commit 294baba

Browse files
authored
rule & tick markers; arrow-reverse marker (#1600)
* rule & tick markers; arrow auto-start-reverse * docs edits * arrow-reverse marker
1 parent c4a5d17 commit 294baba

File tree

17 files changed

+307
-63
lines changed

17 files changed

+307
-63
lines changed

docs/features/markers.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ A **marker** defines a graphic drawn on vertices of a [line](../marks/line.md) o
1919
<select v-model="marker">
2020
<option>none</option>
2121
<option>arrow</option>
22+
<option>arrow-reverse</option>
2223
<option>dot</option>
2324
<option>circle</option>
2425
<option>circle-stroke</option>
@@ -47,7 +48,8 @@ The supported marker options are:
4748
The following named markers are supported:
4849

4950
* *none* (default) - no marker
50-
* *arrow* - an arrowhead
51+
* *arrow* - an arrowhead with *auto* orientation
52+
* *arrow-reverse* - an arrowhead with *auto-start-reverse* orientation
5153
* *dot* - a filled *circle* without a stroke and 2.5px radius
5254
* *circle*, equivalent to *circle-fill* - a filled circle with a white stroke and 3px radius
5355
* *circle-stroke* - a hollow circle with a colored stroke and a white fill and 3px radius

docs/marks/rule.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ Rules are also used by the [grid mark](./grid) to draw grid lines.
139139

140140
## Rule options
141141

142-
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*.
142+
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*.
143143

144144
## ruleX(*data*, *options*)
145145

docs/marks/tick.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ Ticks are also used by the [box mark](./box.md) to denote the median value for e
8686

8787
## Tick options
8888

89-
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*.
89+
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*.
9090

9191
## tickX(*data*, *options*)
9292

src/marker.d.ts

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
1+
/**
2+
* The built-in marker implementations; one of:
3+
*
4+
* - *arrow* - an arrowhead with *auto* orientation
5+
* - *arrow-reverse* - an arrowhead with *auto-start-reverse* orientation
6+
* - *dot* - a filled *circle* with no stroke and 2.5px radius
7+
* - *circle-fill* - a filled circle with a white stroke and 3px radius
8+
* - *circle-stroke* - a stroked circle with a white fill and 3px radius
9+
* - *circle* - alias for *circle-fill*
10+
*/
11+
export type MarkerName = "arrow" | "arrow-reverse" | "dot" | "circle" | "circle-fill" | "circle-stroke";
12+
13+
/** A custom marker implementation. */
14+
export type MarkerFunction = (color: string, context: {document: Document}) => SVGMarkerElement;
15+
116
/** How to decorate control points. */
2-
export type Marker =
3-
| "arrow"
4-
| "dot"
5-
| "circle"
6-
| "circle-fill"
7-
| "circle-stroke"
8-
| ((color: string, context: {document: Document}) => SVGMarkerElement);
17+
export type Marker = MarkerName | MarkerFunction;
918

1019
/** Options for marks that support markers, such as lines and links. */
1120
export interface MarkerOptions {
1221
/**
1322
* Shorthand to set the same default for markerStart, markerMid, and
14-
* markerEnd. A marker may be specified as:
23+
* markerEnd; one of:
1524
*
16-
* * *none* (default) - no marker
17-
* * *arrow* - an arrowhead
18-
* * *dot* - a filled *circle* with no stroke and 2.5px radius
19-
* * *circle-fill* - a filled circle with a white stroke and 3px radius
20-
* * *circle-stroke* - a stroked circle with a white fill and 3px radius
21-
* * *circle* - alias for *circle-fill*
25+
* - a marker name such as *arrow* or *circle*
26+
* - *none* (default) - no marker
2227
* * true - alias for *circle-fill*
2328
* * false or null - alias for *none*
2429
* * a function - a custom marker function; see below
@@ -30,15 +35,10 @@ export interface MarkerOptions {
3035
marker?: Marker | "none" | boolean | null;
3136

3237
/**
33-
* The marker for the starting point of a line segment. A marker may be
34-
* specified as:
38+
* The marker for the starting point of a line segment; one of:
3539
*
40+
* - a marker name such as *arrow* or *circle*
3641
* * *none* (default) - no marker
37-
* * *arrow* - an arrowhead
38-
* * *dot* - a filled *circle* with no stroke and 2.5px radius
39-
* * *circle-fill* - a filled circle with a white stroke and 3px radius
40-
* * *circle-stroke* - a stroked circle with a white fill and 3px radius
41-
* * *circle* - alias for *circle-fill*
4242
* * true - alias for *circle-fill*
4343
* * false or null - alias for *none*
4444
* * a function - a custom marker function; see below
@@ -51,15 +51,10 @@ export interface MarkerOptions {
5151

5252
/**
5353
* The marker for any middle (interior) points of a line segment. If the line
54-
* segment only has a start and end point, this option has no effect. A marker
55-
* may be specified as:
54+
* segment only has a start and end point, this option has no effect. One of:
5655
*
56+
* - a marker name such as *arrow* or *circle*
5757
* * *none* (default) - no marker
58-
* * *arrow* - an arrowhead
59-
* * *dot* - a filled *circle* with no stroke and 2.5px radius
60-
* * *circle-fill* - a filled circle with a white stroke and 3px radius
61-
* * *circle-stroke* - a stroked circle with a white fill and 3px radius
62-
* * *circle* - alias for *circle-fill*
6358
* * true - alias for *circle-fill*
6459
* * false or null - alias for *none*
6560
* * a function - a custom marker function; see below
@@ -71,15 +66,10 @@ export interface MarkerOptions {
7166
markerMid?: Marker | "none" | boolean | null;
7267

7368
/**
74-
* The marker for the ending point of a line segment. A marker may be
75-
* specified as:
69+
* The marker for the ending point of a line segment; one of:
7670
*
71+
* - a marker name such as *arrow* or *circle*
7772
* * *none* (default) - no marker
78-
* * *arrow* - an arrowhead
79-
* * *dot* - a filled *circle* with no stroke and 2.5px radius
80-
* * *circle-fill* - a filled circle with a white stroke and 3px radius
81-
* * *circle-stroke* - a stroked circle with a white fill and 3px radius
82-
* * *circle* - alias for *circle-fill*
8373
* * true - alias for *circle-fill*
8474
* * false or null - alias for *none*
8575
* * a function - a custom marker function; see below

src/marker.js

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ function maybeMarker(marker) {
1414
case "none":
1515
return null;
1616
case "arrow":
17-
return markerArrow;
17+
return markerArrow("auto");
18+
case "arrow-reverse":
19+
return markerArrow("auto-start-reverse");
1820
case "dot":
1921
return markerDot;
2022
case "circle":
@@ -26,19 +28,20 @@ function maybeMarker(marker) {
2628
throw new Error(`invalid marker: ${marker}`);
2729
}
2830

29-
function markerArrow(color, context) {
30-
return create("svg:marker", context)
31-
.attr("viewBox", "-5 -5 10 10")
32-
.attr("markerWidth", 6.67)
33-
.attr("markerHeight", 6.67)
34-
.attr("orient", "auto")
35-
.attr("fill", "none")
36-
.attr("stroke", color)
37-
.attr("stroke-width", 1.5)
38-
.attr("stroke-linecap", "round")
39-
.attr("stroke-linejoin", "round")
40-
.call((marker) => marker.append("path").attr("d", "M-1.5,-3l3,3l-3,3"))
41-
.node();
31+
function markerArrow(orient) {
32+
return (color, context) =>
33+
create("svg:marker", context)
34+
.attr("viewBox", "-5 -5 10 10")
35+
.attr("markerWidth", 6.67)
36+
.attr("markerHeight", 6.67)
37+
.attr("orient", orient)
38+
.attr("fill", "none")
39+
.attr("stroke", color)
40+
.attr("stroke-width", 1.5)
41+
.attr("stroke-linecap", "round")
42+
.attr("stroke-linejoin", "round")
43+
.call((marker) => marker.append("path").attr("d", "M-1.5,-3l3,3l-3,3"))
44+
.node();
4245
}
4346

4447
function markerDot(color, context) {

src/marks/rule.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import type {ChannelValueIntervalSpec, ChannelValueSpec} from "../channel.js";
22
import type {InsetOptions} from "../inset.js";
33
import type {Interval} from "../interval.js";
44
import type {Data, MarkOptions, RenderableMark} from "../mark.js";
5+
import type {MarkerOptions} from "../marker.js";
56

67
/** Options for the ruleX and ruleY marks. */
7-
interface RuleOptions extends MarkOptions {
8+
interface RuleOptions extends MarkOptions, MarkerOptions {
89
/**
910
* How to convert a continuous value (**y** for ruleX, or **x** for ruleY)
1011
* into an interval (**y1** and **y2** for ruleX, or **x1** and **x2** for

src/marks/rule.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {create} from "../context.js";
22
import {Mark, withTip} from "../mark.js";
3+
import {applyMarkers, markers} from "../marker.js";
34
import {identity, number} from "../options.js";
45
import {isCollapsed} from "../scales.js";
56
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, offset} from "../style.js";
@@ -26,6 +27,7 @@ export class RuleX extends Mark {
2627
);
2728
this.insetTop = number(insetTop);
2829
this.insetBottom = number(insetBottom);
30+
markers(this, options);
2931
}
3032
render(index, scales, channels, dimensions, context) {
3133
const {x, y} = scales;
@@ -54,6 +56,7 @@ export class RuleX extends Mark {
5456
: height - marginBottom - insetBottom
5557
)
5658
.call(applyChannelStyles, this, channels)
59+
.call(applyMarkers, this, channels, context)
5760
)
5861
.node();
5962
}
@@ -74,6 +77,7 @@ export class RuleY extends Mark {
7477
);
7578
this.insetRight = number(insetRight);
7679
this.insetLeft = number(insetLeft);
80+
markers(this, options);
7781
}
7882
render(index, scales, channels, dimensions, context) {
7983
const {x, y} = scales;
@@ -102,6 +106,7 @@ export class RuleY extends Mark {
102106
.attr("y1", Y ? (i) => Y[i] : (marginTop + height - marginBottom) / 2)
103107
.attr("y2", Y ? (i) => Y[i] : (marginTop + height - marginBottom) / 2)
104108
.call(applyChannelStyles, this, channels)
109+
.call(applyMarkers, this, channels, context)
105110
)
106111
.node();
107112
}

src/marks/tick.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type {ChannelValueSpec} from "../channel.js";
22
import type {InsetOptions} from "../inset.js";
33
import type {Data, MarkOptions, RenderableMark} from "../mark.js";
4+
import type {MarkerOptions} from "../marker.js";
45

56
/** Options for the tickX mark. */
6-
export interface TickXOptions extends MarkOptions, Omit<InsetOptions, "insetLeft" | "insetRight"> {
7+
export interface TickXOptions extends MarkOptions, MarkerOptions, Omit<InsetOptions, "insetLeft" | "insetRight"> {
78
/**
89
* The required horizontal position of the tick; a channel typically bound to
910
* the *x* scale.
@@ -22,7 +23,7 @@ export interface TickXOptions extends MarkOptions, Omit<InsetOptions, "insetLeft
2223
}
2324

2425
/** Options for the tickY mark. */
25-
export interface TickYOptions extends MarkOptions, Omit<InsetOptions, "insetTop" | "insetBottom"> {
26+
export interface TickYOptions extends MarkOptions, MarkerOptions, Omit<InsetOptions, "insetTop" | "insetBottom"> {
2627
/**
2728
* The required vertical position of the tick; a channel typically bound to
2829
* the *y* scale.

src/marks/tick.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {create} from "../context.js";
22
import {identity, number} from "../options.js";
33
import {Mark} from "../mark.js";
4+
import {applyMarkers, markers} from "../marker.js";
45
import {applyDirectStyles, applyIndirectStyles, applyTransform, applyChannelStyles, offset} from "../style.js";
56

67
const defaults = {
@@ -12,6 +13,7 @@ const defaults = {
1213
class AbstractTick extends Mark {
1314
constructor(data, channels, options) {
1415
super(data, channels, options, defaults);
16+
markers(this, options);
1517
}
1618
render(index, scales, channels, dimensions, context) {
1719
return create("svg:g", context)
@@ -29,6 +31,7 @@ class AbstractTick extends Mark {
2931
.attr("y1", this._y1(scales, channels, dimensions))
3032
.attr("y2", this._y2(scales, channels, dimensions))
3133
.call(applyChannelStyles, this, channels)
34+
.call(applyMarkers, this, channels, context)
3235
)
3336
.node();
3437
}

test/output/crimeanWarArrow.svg

Lines changed: 3 additions & 3 deletions
Loading

0 commit comments

Comments
 (0)