Skip to content
Merged
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
4 changes: 3 additions & 1 deletion docs/features/markers.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ A **marker** defines a graphic drawn on vertices of a [line](../marks/line.md) o
<select v-model="marker">
<option>none</option>
<option>arrow</option>
<option>arrow-reverse</option>
<option>dot</option>
<option>circle</option>
<option>circle-stroke</option>
Expand Down Expand Up @@ -47,7 +48,8 @@ The supported marker options are:
The following named markers are supported:

* *none* (default) - no marker
* *arrow* - an arrowhead
* *arrow* - an arrowhead with *auto* orientation
* *arrow-reverse* - an arrowhead with *auto-start-reverse* orientation
* *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
Expand Down
2 changes: 1 addition & 1 deletion docs/marks/rule.md
Original file line number Diff line number Diff line change
Expand Up @@ -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*)

Expand Down
2 changes: 1 addition & 1 deletion docs/marks/tick.md
Original file line number Diff line number Diff line change
Expand Up @@ -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*)

Expand Down
60 changes: 25 additions & 35 deletions src/marker.d.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
/**
* The built-in marker implementations; one of:
*
* - *arrow* - an arrowhead with *auto* orientation
* - *arrow-reverse* - an arrowhead with *auto-start-reverse* orientation
* - *dot* - a filled *circle* with no stroke and 2.5px radius
* - *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*
*/
export type MarkerName = "arrow" | "arrow-reverse" | "dot" | "circle" | "circle-fill" | "circle-stroke";

/** A custom marker implementation. */
export type MarkerFunction = (color: string, context: {document: Document}) => SVGMarkerElement;

/** How to decorate control points. */
export type Marker =
| "arrow"
| "dot"
| "circle"
| "circle-fill"
| "circle-stroke"
| ((color: string, context: {document: Document}) => SVGMarkerElement);
export type Marker = MarkerName | MarkerFunction;

/** Options for marks that support markers, such as lines and links. */
export interface MarkerOptions {
/**
* Shorthand to set the same default for markerStart, markerMid, and
* markerEnd. A marker may be specified as:
* markerEnd; one of:
*
* * *none* (default) - no marker
* * *arrow* - an arrowhead
* * *dot* - a filled *circle* with no stroke and 2.5px radius
* * *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*
* - a marker name such as *arrow* or *circle*
* - *none* (default) - no marker
* * true - alias for *circle-fill*
* * false or null - alias for *none*
* * a function - a custom marker function; see below
Expand All @@ -30,15 +35,10 @@ export interface MarkerOptions {
marker?: Marker | "none" | boolean | null;

/**
* The marker for the starting point of a line segment. A marker may be
* specified as:
* The marker for the starting point of a line segment; one of:
*
* - a marker name such as *arrow* or *circle*
* * *none* (default) - no marker
* * *arrow* - an arrowhead
* * *dot* - a filled *circle* with no stroke and 2.5px radius
* * *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*
* * true - alias for *circle-fill*
* * false or null - alias for *none*
* * a function - a custom marker function; see below
Expand All @@ -51,15 +51,10 @@ export interface MarkerOptions {

/**
* The marker for any middle (interior) points of a line segment. If the line
* segment only has a start and end point, this option has no effect. A marker
* may be specified as:
* segment only has a start and end point, this option has no effect. One of:
*
* - a marker name such as *arrow* or *circle*
* * *none* (default) - no marker
* * *arrow* - an arrowhead
* * *dot* - a filled *circle* with no stroke and 2.5px radius
* * *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*
* * true - alias for *circle-fill*
* * false or null - alias for *none*
* * a function - a custom marker function; see below
Expand All @@ -71,15 +66,10 @@ export interface MarkerOptions {
markerMid?: Marker | "none" | boolean | null;

/**
* The marker for the ending point of a line segment. A marker may be
* specified as:
* The marker for the ending point of a line segment; one of:
*
* - a marker name such as *arrow* or *circle*
* * *none* (default) - no marker
* * *arrow* - an arrowhead
* * *dot* - a filled *circle* with no stroke and 2.5px radius
* * *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*
* * true - alias for *circle-fill*
* * false or null - alias for *none*
* * a function - a custom marker function; see below
Expand Down
31 changes: 17 additions & 14 deletions src/marker.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ function maybeMarker(marker) {
case "none":
return null;
case "arrow":
return markerArrow;
return markerArrow("auto");
case "arrow-reverse":
return markerArrow("auto-start-reverse");
case "dot":
return markerDot;
case "circle":
Expand All @@ -26,19 +28,20 @@ function maybeMarker(marker) {
throw new Error(`invalid marker: ${marker}`);
}

function markerArrow(color, context) {
return create("svg:marker", context)
.attr("viewBox", "-5 -5 10 10")
.attr("markerWidth", 6.67)
.attr("markerHeight", 6.67)
.attr("orient", "auto")
.attr("fill", "none")
.attr("stroke", color)
.attr("stroke-width", 1.5)
.attr("stroke-linecap", "round")
.attr("stroke-linejoin", "round")
.call((marker) => marker.append("path").attr("d", "M-1.5,-3l3,3l-3,3"))
.node();
function markerArrow(orient) {
return (color, context) =>
create("svg:marker", context)
.attr("viewBox", "-5 -5 10 10")
.attr("markerWidth", 6.67)
.attr("markerHeight", 6.67)
.attr("orient", orient)
.attr("fill", "none")
.attr("stroke", color)
.attr("stroke-width", 1.5)
.attr("stroke-linecap", "round")
.attr("stroke-linejoin", "round")
.call((marker) => marker.append("path").attr("d", "M-1.5,-3l3,3l-3,3"))
.node();
}

function markerDot(color, context) {
Expand Down
3 changes: 2 additions & 1 deletion src/marks/rule.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/marks/rule.js
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -54,6 +56,7 @@ export class RuleX extends Mark {
: height - marginBottom - insetBottom
)
.call(applyChannelStyles, this, channels)
.call(applyMarkers, this, channels, context)
)
.node();
}
Expand All @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down
5 changes: 3 additions & 2 deletions src/marks/tick.d.ts
Original file line number Diff line number Diff line change
@@ -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<InsetOptions, "insetLeft" | "insetRight"> {
export interface TickXOptions extends MarkOptions, MarkerOptions, Omit<InsetOptions, "insetLeft" | "insetRight"> {
/**
* The required horizontal position of the tick; a channel typically bound to
* the *x* scale.
Expand All @@ -22,7 +23,7 @@ export interface TickXOptions extends MarkOptions, Omit<InsetOptions, "insetLeft
}

/** Options for the tickY mark. */
export interface TickYOptions extends MarkOptions, Omit<InsetOptions, "insetTop" | "insetBottom"> {
export interface TickYOptions extends MarkOptions, MarkerOptions, Omit<InsetOptions, "insetTop" | "insetBottom"> {
/**
* The required vertical position of the tick; a channel typically bound to
* the *y* scale.
Expand Down
3 changes: 3 additions & 0 deletions src/marks/tick.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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)
Expand All @@ -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();
}
Expand Down
6 changes: 3 additions & 3 deletions test/output/crimeanWarArrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 56 additions & 0 deletions test/output/markerRuleX.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading