Marks and Arrows
Arrow marks may be specified like edge(from, "->", to) or edge(..pts, "->") or with the edge.marks option. Some mathematical arrow heads are supported which match the symbols , , , , , and in the default font.
A few other built-in marks are provided, and all marks can be placed at any position along an edge.
Built-in marks and line styles
A mark shorthand such as "<->" consists of mark names "<" and ">" joined with line styles, "-".
The built-in line styles are:
It is possible to rename, redefine, and define your own marks, but the built-in marks are:
Marks can be flipped by appending ' to the name.
Normal #diagram(edge("hook-harpoon", stroke: 1pt))
and flipped #diagram(edge("hook'-harpoon'", stroke: 1pt))Some marks adapt to the edge’s extrusion, including ">", "harpoon", "harpoons", "hook" and "hooks":
#diagram(
edge-stroke: 1pt,
edge((0,0), (1,0), "->"),
edge((0,1), (1,1), "=>"),
edge((0,2), (1,2), "==>"),
edge((2,0), (3,0), "harpoon'-harpoon"),
edge((2,1), (3,1), "harpoon'=harpoon"),
edge((2,2), (3,2), "harpoon'==harpoon"),
)Adjusting marks
While shorthands like "--|>" exist, finer control is possible. Under the hood, shorthands are expanded into full form: for example, edge("--|>") is the same as edge(marks: (none, "|>"), options: (dash: "dashed")). This expansion is done by parse-mark-shorthand():
#fletcher.parsing.parse-mark-shorthand("--|>")For more control, you can use the full form and pass an array of marks to the edge.marks argument. This lets you pass mark objects instead of mark names, which are dictionaries of mark parameters which you can customise. For example, here is a tulip made of arrows:
#diagram(
edge-stroke: 1.5pt,
edge((0,3), (-0.1,0), bend: -8deg, marks: (
(inherit: ">>", size: 6, delta: 70deg, sharpness: 65deg),
(inherit: "head", rev: true, pos: 0.8, sharpness: 0deg, size: 17),
(inherit: "bar", size: 1, pos: 0.3),
(inherit: "solid", size: 12, rev: true, stealth: 0.1, fill: red.mix(purple)),
), stroke: green.darken(50%)),
)In this example, mark objects are based off previously defined marks (using the inherit parameter) with other parameters customised.
Listing mark parameters
You can see the parameters of built-in marks by inspecting the mark object which is stored in the fletcher.marks.DEFAULT_MARKS dictionary:
`>` = #fletcher.marks.DEFAULT_MARKS.at(">")
`head` = #fletcher.marks.DEFAULT_MARKS.at("head")As you can see, the basic head mark (which > and < inherit from) has quite a few parameters:
sizeis the overall size in units of the edge’s stroke thicknesssharpnessis the angle formed at the tipdeltais the angle of the arc spanned by the legs of the arrow- …and other parameters which are common to all marks
Tweaking mark parameters
To adjust a mark, create a mark object that inherits from the mark and overrides any parameters. For example, here is a smaller, pointerer version of the ">" mark:
#let my-mark = (inherit: ">", size: 3, sharpness: 5deg)#diagram(edge(stroke: 2pt, "->"))
#diagram(edge(stroke: 2pt, marks: (none, my-mark)))Mark objects
A mark object is a dictionary of parameters, which must include a draw entry or an inherit entry which points to another mark object. The draw entry eventually contains CeTZ objects which are translated and scaled to fit the edge; the mark should be centered at (0, 0) and pointing right, and the stroke’s thickness is defined as the unit length.
As a minimal example, here is a basic circle mark object:
#import cetz.draw
#let my-mark = (
draw: draw.circle((0,0), radius: 2, fill: none)
)
#diagram(
edge-stroke: 2pt,
edge((0,0), (1,0), marks: (my-mark, my-mark), bend: 30deg),
edge((0,1), (1,1), marks: (none, my-mark), stroke: teal),
)A mark object can contain arbitrary parameters, which can be adjusted to customise the mark. Any entry in a mark object can depend on parameters defined earlier by writing it as a function mark => (..), where mark is a dictionary containing the preceding computed parameter values.
For example, our mark object from above could also be written as:
#let my-mark = (
size: 2,
draw: mark => draw.circle((0,0), radius: mark.size, fill: none)
)The size parameter makes it easy to adjust out new mark:
#diagram(edge(marks: (my-mark + (size: 3), my-mark), stroke: 3pt))Lastly, mark objects may inherit properties from other marks in fletcher.MARKS by containing an inherit entry, for example:
#let my-mark = (
inherit: "stealth",
fill: red,
stroke: none,
extrude: (0, -3),
)
#diagram(edge("rr", stroke: 2pt, marks: (
my-mark, my-mark + (fill: blue))))Internally, marks are passed to resolve-mark(), which resolves all entries to their final values.
Special mark properties
A mark object may contain arbitrary properties, but the following have special functions.
| Name | Description | Default |
|---|---|---|
inherit | The name of a mark in fletcher.marks.DEFAULT_MARKS to inherit properties from. This can be used to make mark aliases, for instance, "<" is defined as (inherit: "head", rev: true). | |
draw | As described above, this contains the final CeTZ objects to be drawn. Objects should be centered at and be scaled so that one unit is the stroke thickness. The default stroke and fill is inherited from the edge’s style. | |
pos | Location of the mark along the edge, from 0 (start) to 1 (end). | auto |
fillstroke | The default fill and stroke styles for CeTZ objects returned by draw. If none, polygons will not be filled/stroked by default, and if auto, the style is inherited from the edge’s stroke style. | auto |
rev | Whether to reverse the mark so it points backwards. | false |
flip | Whether to reflect the mark across the edge; the difference between and , for example. A suffix | false |
scale | Overall scaling factor. See also edge.mark-scale. | 100% |
extrude | Whether to duplicate the mark and draw it offset at each extrude position. For example, ). | (0,) |
tip-origintail-origin | These two properties control the coordinate of the point of the mark, relative to . If the mark is acting as a tip ( or ) then or ). See | 0 |
tip-endtail-end | These control the coordinate at which the edge’s stroke terminates, relative to . See mark-debug(). | 0 |
cap-offset | A function (mark, y) => x returning the coordinate at which the edge’s stroke terminates relative to tip-end or tail-end, as a function of the coordinate. This is relevant for extruded edges. See cap-offset(). |
The last few properties control the fine behaviours of how marks connect to the target point and to the edge’s stroke. Briefly, a mark has four possibly-distinct center points. It is easier to show than to tell:
See mark-debug() and cap-offset() for details.