Line joints and caps¶
Every Line2D joint, cap and gradient side by side.
▶ Run in browserTags: 2d
A Line2D is more than a run of segments. Where it bends it leaves a notch,
and joint_mode decides what fills it; where it ends it stops flat, and
begin_cap_mode / end_cap_mode decide whether it rounds off; and gradient
colours it along its own length instead of flat.
How it works¶
Four rows, each the same zigzag drawn with a different setting so the difference is visible rather than described:
joint_modeis"sharp"(a miter),"bevel"(a flat corner) or"round"(an arc). A miter that would shoot a spike off a near-reversal falls back to a bevel on its own.begin_cap_modeandend_cap_modeare"none"or"round", and each end is set independently.gradientis a list of(t, colour)stops.truns from 0 at the first point to 1 at the last, measured along the line, and the gradient replacescolourrather than tinting it.A
widthof 1.0 or less is a hairline: it rides the one-pixel line pipeline whatever the camera is doing, so it has no width for a joint or a cap to fill and neither is drawn.
The alpha on the top row is deliberately low: a joint fills the outside of a bend only, so a translucent line does not darken along its own corners.
The grid divides the window into cells and scales each zigzag down to fit its cell, so it follows a resize and still fits a small window.
Source¶
1"""Line joints and caps: Every Line2D joint, cap and gradient side by side.
2
3A `Line2D` is more than a run of segments. Where it bends it leaves a notch,
4and `joint_mode` decides what fills it; where it ends it stops flat, and
5`begin_cap_mode` / `end_cap_mode` decide whether it rounds off; and `gradient`
6colours it along its own length instead of flat.
7
8## How it works
9
10Four rows, each the same zigzag drawn with a different setting so the
11difference is visible rather than described:
12
131. `joint_mode` is `"sharp"` (a miter), `"bevel"` (a flat corner) or `"round"`
14 (an arc). A miter that would shoot a spike off a near-reversal falls back to
15 a bevel on its own.
162. `begin_cap_mode` and `end_cap_mode` are `"none"` or `"round"`, and each end
17 is set independently.
183. `gradient` is a list of `(t, colour)` stops. `t` runs from 0 at the first
19 point to 1 at the last, measured along the line, and the gradient replaces
20 `colour` rather than tinting it.
214. A `width` of 1.0 or less is a hairline: it rides the one-pixel line pipeline
22 whatever the camera is doing, so it has no width for a joint or a cap to
23 fill and neither is drawn.
24
25The alpha on the top row is deliberately low: a joint fills the outside of a
26bend only, so a translucent line does not darken along its own corners.
27
28The grid divides the window into cells and scales each zigzag down to fit its
29cell, so it follows a resize and still fits a small window.
30"""
31
32from simvx.core import Line2D, Node2D
33from simvx.graphics import App
34
35#: A right angle, an acute bend and a near-reversal, so every joint mode has
36#: something to show.
37ZIGZAG = [(0, 60), (60, 60), (60, 0), (140, 70), (200, 10), (150, 14)]
38
39#: The zigzag's extent at scale 1 with room for a 14-pixel stroke, and the band
40#: above it that holds a row's title.
41ZIGZAG_SIZE = (220.0, 90.0)
42TITLE_BAND = 36.0
43MARGIN = 40.0
44
45RED = (1.0, 0.2, 0.3, 1.0)
46BLUE = (0.25, 0.4, 1.0, 1.0)
47YELLOW = (1.0, 0.85, 0.2, 1.0)
48GREEN = (0.2, 0.9, 0.5, 1.0)
49CYAN = (0.3, 0.85, 1.0, 0.45)
50AMBER = (1.0, 0.72, 0.25, 1.0)
51WHITE = (0.9, 0.9, 0.95, 1.0)
52TITLE_COLOUR = (0.75, 0.78, 0.85, 1.0)
53
54
55def zigzag(width=14.0, joint_mode="round", **settings) -> Line2D:
56 """The shared zigzag, 14 pixels wide with round joints unless told otherwise."""
57 return Line2D(points=ZIGZAG, width=width, joint_mode=joint_mode, **settings)
58
59
60class LineGallery(Node2D):
61 dynamic = True # the row titles move with the window
62
63 def on_ready(self):
64 caps = (("none", "none"), ("round", "round"), ("none", "round"))
65 gradients = (
66 [(0.0, RED), (1.0, BLUE)],
67 [(0.0, YELLOW), (0.5, YELLOW), (0.5, GREEN), (1.0, GREEN)],
68 [(0.0, RED), (0.33, YELLOW), (0.66, GREEN), (1.0, BLUE)],
69 )
70 self.rows = [
71 (
72 "joint_mode: sharp, bevel, round",
73 [zigzag(colour=CYAN, joint_mode=mode) for mode in ("sharp", "bevel", "round")],
74 ),
75 (
76 "begin_cap_mode / end_cap_mode: none, both, end only",
77 [zigzag(colour=AMBER, begin_cap_mode=begin, end_cap_mode=end) for begin, end in caps],
78 ),
79 (
80 "gradient: a ramp, a hard stop at the midpoint, four stops",
81 [zigzag(gradient=gradient) for gradient in gradients],
82 ),
83 (
84 "width 1.0 or less is a hairline: no joint, no cap",
85 [
86 zigzag(width=width, colour=WHITE, begin_cap_mode="round", end_cap_mode="round")
87 for width in (1.0, 3.0, 14.0)
88 ],
89 ),
90 ]
91 for _, lines in self.rows:
92 for line in lines:
93 self.add_child(line)
94
95 def cell_size(self) -> tuple[float, float]:
96 width, height = self.viewport_size
97 return (width - 2 * MARGIN) / 3, (height - MARGIN) / len(self.rows)
98
99 def row_top(self, row: int) -> float:
100 return MARGIN / 2 + row * self.cell_size()[1]
101
102 def on_update(self, dt):
103 cell_w, cell_h = self.cell_size()
104 scale = min(1.0, cell_w / ZIGZAG_SIZE[0], (cell_h - TITLE_BAND) / ZIGZAG_SIZE[1])
105 inset = 10.0 * scale # half the stroke, so the round caps stay inside the cell
106 for row, (_, lines) in enumerate(self.rows):
107 for column, line in enumerate(lines):
108 line.scale = (scale, scale)
109 line.position = (MARGIN + column * cell_w + inset, self.row_top(row) + TITLE_BAND + inset)
110
111 def on_draw(self, renderer):
112 for row, (title, _) in enumerate(self.rows):
113 renderer.text(title, (MARGIN, self.row_top(row) + 4), fill=TITLE_COLOUR)
114
115
116if __name__ == "__main__":
117 App(title="Line joints and caps", width=900, height=620).run(LineGallery())