Coordinate Systems
A coordinate is a position on the canvas on which the picture is drawn. They take the form of dictionaries and the following sub-sections define the key value pairs for each system. Some systems have a more implicit form as an array of values and CeTZ attempts to infer the system based on the element types.
XYZ
Defines a point x
units right, y
units upward and z
units away.
The implicit form can be given as an array of two or three numbers, as in (x, y)
or (x, y, z)
line((0,0), (x: 1))
line((0,0), (y: 1))
line((0,0), (z: 1))
// Implicit form
line((2, 0), (3, 0))
line((2, 0), (2, 1, 0))
line((2, 0), (2, 0, 1))
Previous
Use this to reference the position of the previous coordinate passed to a draw function. It takes the form of an empty array ()
and the previous position initially will be (0, 0, 0)
. This will never reference the position of a coordinate used to define another coordinate.
line((0,0), (1, 1))
// Draws a circle at (1,1)
circle(())
Relative
Places the given coordinate relative to the previous coordinate. Or in other words, for the given coordinate, the previous coordinate will be used as the origin. Another coordinate can be given to act as the previous coordinate instead.
rel:
The coordinate to place relative to the previous coordinate.
to:
()
The coordinate to treat as the previous coordinate.
In the example below, the red circle is placed one unit to the right of the blue circle. If the blue circle was to be moved a different position, the red circle will move with the blue circle to stay one unit to the right.
circle((0, 0), stroke: blue)
circle((rel: (1, 0)), stroke: red)
Polar
Defines a point that is radius
distance away from the origin at the given angle.
The angle of the coordinate. An value of 0deg
is to the right, a degree of 90deg
is upward.
The distance from the origin. An array of number can be given, in the form (x, y)
, to define the x
and y
radii of an ellipse instead of a circle.
line((0, 0), (angle: 30deg, radius: 1))
The implicit form is an array of the angle then the radius (angle, radius)
or (angle, (x, y))
.
line(
(0, 0),
(30deg, 1),
(60deg, 1),
(90deg, 1),
(120deg, 1),
(150deg, 1),
(180deg, 1)
)
Barycentric
In the barycentric coordinate system a point is expressed as the linear combination of multiple vectors. The idea is that you specify vectors and numbers . Then the barycentric coordinate specified by these vectors and numbers is
bary:
A dictionary where the key is a named element and the value is a
float. The center
anchor of the named element is used as
and the value is used as
circle((90deg, 3), radius: 0, name: "content")
circle((210deg, 3), radius: 0, name: "structure")
circle((-30deg, 3), radius: 0, name: "form")
for (c, a) in (
("content", "south"),
("structure", "north"),
("form", "north")
) {
content(c, align(center, c + [\ oriented]), padding: .1, anchor: a)
}
stroke(gray + 1.2pt)
line("content", "structure", "form", close: true)
for (c, s, f, cont) in (
(0.5, 0.1, 1, "PostScript"),
(1, 0, 0.4, "DVI"),
(0.5, 0.5, 1, "PDF"),
(0, 0.25, 1, "CSS"),
(0.5, 1, 0, "XML"),
(0.5, 1, 0.4, "HTML"),
(1, 0.2, 0.8, "LaTeX"),
(1, 0.6, 0.8, "TeX"),
(0.8, 0.8, 1, "Word"),
(1, 0.05, 0.05, "ASCII")
) {
content(
(bary: (
content: c,
structure: s,
form: f
)),
cont,
fill: rgb(50, 50, 255, 100),
stroke: none,
frame: "circle"
)
}
Anchor
Defines a point relative to a named element using anchors, see Anchors.
The anchor of the element. Strings are named anchors, angles are border
anchors and numbers and ratios are path anchors. If not given, the default
anchor will be used, on most elements this is center
but it can be different or not exist at all!
circle((0,0), name: "circle")
// Anchor at 30 degree
content((name: "circle", anchor: 30deg), box(fill: white, $ 30 degree $))
// Anchor at 30% of the path length
content((name: "circle", anchor: 30%), box(fill: white, $ 30 % $))
// Anchor at 3.14 of the path
content((name: "circle", anchor: 3.14), box(fill: white, $ p = 3.14 $))
You can also use implicit syntax of a dot separated string in the form "name.anchor"
for all anchors.
line((0, 0), (4, 3), name: "line")
circle("line.75%", name: "circle")
rect("line.start", "circle.east")
When using named elements within a group, you can access the element's anchors outside of the group by using the implicit anchor coordinate. e.g. "a.b.north"
group(name: "a", {
circle((), name: "b")
})
circle("a.b.south", radius: 0.2)
circle((name: "a", anchor: "b.north"), radius: 0.2)
Tangent
This system allows you to compute the point that lies tangent to a shape. In detail, consider an element and a point. Now draw a straight line from the point so that it "touches" the element (more formally, so that it is tangent to this element). The point where the line touches the shape is the point referred to by this coordinate system.
point:
The point through which the tangent should go.
A special algorithm is needed in order to compute the tangent for a given shape. Currently it does this by assuming the distance between the center and top anchor (See Anchors) is the radius of a circle.
grid((0,0), (3,2), help-lines: true)
circle((3,2), name: "a", radius: 2pt)
circle((1,1), name: "c", radius: 0.75)
content("c", $ c $, anchor: "north-east", padding: .1)
stroke(red)
line(
// The starting point
"a",
// The tangent coordinate
(element: "c", point: "a", solution: 1),
// The center of the circle
"c",
// The other tangent coordinate
(element: "c", point: "a", solution: 2),
close: true
)
Perpendicular
Can be used to find the intersection of a vertical line going through a point and a horizontal line going through some other point .
horizontal:
The coordinate through which the horizontal line passes.
vertical:
The coordinate through which the vertical line passes.
You can use the implicit syntax of (horizontal, "-|", vertical)
or (vertical, "|-", horizontal)
.
set-style(content: (padding: .05))
content((30deg, 1), $ p_1 $, name: "p1")
content((75deg, 1), $ p_2 $, name: "p2")
line((-0.2, 0), (1.2, 0), name: "xline")
content("xline.end", $ q_1 $, anchor: "west")
line((2, -0.2), (2, 1.2), name: "yline")
content("yline.end", $ q_2 $, anchor: "south")
line("p1.south-east", (horizontal: (), vertical: "xline.end"))
line("p2.south-east", ((), "|-", "xline.end")) // Short form
line("p1.south-east", (vertical: (), horizontal: "yline.end"))
line("p2.south-east", ((), "-|", "yline.end")) // Short form
Interpolation
Use this to linearly interpolate between two coordinates a
and b
with a given distance number
. If number
is a number the position will be at the absolute distance away from a
towards b
, a ratio can be given instead to be the relative distance between a
and b
. An angle can also be given for the general meaning: "First consider the line from a
to b
. Then rotate this line by angle
around point a
. Then the two endpoints of this line will be a
and some point c
. Use the point c
for the subsequent computation."
a:
The coordinate to interpolate from.
b:
The coordinate to interpolate to.
The distance between a
and b
. A ratio will be the relative
distance between the two points, a number will be the absolute
distance between the two points.
Angle between and , where is the resulting coordinate. This can be used to get the normal for a tangent between two points.
Can be used implicitly as an array in the form (a, number, b)
or (a, number, angle, b)
.
grid((0,0), (3,3), help-lines: true)
line((0,0), (2,2), name: "a")
for i in (0%, 20%, 50%, 80%, 100%, 125%) { // Relative distance
content(("a.start", i, "a.end"),
box(fill: white, inset: 1pt, [#i]))
}
line((1,0), (3,2), name: "b")
for i in (0, 0.5, 1, 2) { // Absolute distance
content(("b.start", i, "b.end"),
box(fill: white, inset: 1pt, text(red, [#i])))
}
grid((0,0), (3,3), help-lines: true)
line((1,0), (3,2))
line((1,0), ((1, 0), 1, 10deg, (3,2)))
fill(red)
stroke(none)
circle(((1, 0), 50%, 10deg, (3, 2)), radius: 2pt)
grid((0,0), (4,4), help-lines: true)
fill(black)
stroke(none)
let n = 16
for i in range(0, n+1) {
circle(((2,2), i / 8, i * 22.5deg, (3,2)), radius: 2pt)
}
You can even chain them together!
grid((0,0), (3, 2), help-lines: true)
line((0,0), (3,2))
stroke(red)
line(((0,0), 0.3, (3,2)), (3,0))
fill(red)
stroke(none)
circle(
( // a
(((0, 0), .3, (3, 2))),
0.7,
(3,0)
),
radius: 2pt
)
grid((0,0), (3, 2), help-lines: true)
line((1,0), (3,2))
for (l, c) in ((0cm, "0cm"), (1cm, "1cm"), (15mm, "15mm")) {
content(((1,0), l, (3,2)), box(fill: white, $ #c $))
}
Interpolation coordinates can be used to get the normal on a tangent:
let (a, b) = ((0,0), (3,2))
line(a, b)
// Get normal for tangent from a to () with distance .5, at a
circle(a, radius: .1, fill: black)
line((a, .7, b), (a: (), b: a, number: .5, angle: 90deg), stroke: red)
Function
An array where the first element is a function and the rest are coordinates will cause the function to be called with the resolved coordinates. The resolved coordinates will be given as a vector that represents an xyz point in space.
The example below shows how to use this system to create an offset from an anchor, however this could easily be replaced with a relative coordinate with the to
argument set.
circle((0, 0), name: "c")
fill(red)
circle((v => cetz.vector.add(v, (0, -1)), "c.west"), radius: 0.3)