tesserax 0.2.1__tar.gz → 0.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {tesserax-0.2.1 → tesserax-0.5.2}/PKG-INFO +3 -2
  2. {tesserax-0.2.1 → tesserax-0.5.2}/README.md +2 -1
  3. {tesserax-0.2.1 → tesserax-0.5.2}/docs/_quarto.yml +1 -1
  4. {tesserax-0.2.1 → tesserax-0.5.2}/docs/core.qmd +112 -4
  5. {tesserax-0.2.1 → tesserax-0.5.2}/docs/gallery.qmd +6 -4
  6. {tesserax-0.2.1 → tesserax-0.5.2}/docs/index.qmd +2 -1
  7. {tesserax-0.2.1 → tesserax-0.5.2}/makefile +1 -1
  8. {tesserax-0.2.1 → tesserax-0.5.2}/pyproject.toml +4 -2
  9. tesserax-0.5.2/src/tesserax/__init__.py +16 -0
  10. tesserax-0.5.2/src/tesserax/base.py +573 -0
  11. {tesserax-0.2.1 → tesserax-0.5.2}/src/tesserax/canvas.py +35 -4
  12. {tesserax-0.2.1 → tesserax-0.5.2}/src/tesserax/core.py +17 -0
  13. tesserax-0.5.2/src/tesserax/layout.py +334 -0
  14. tesserax-0.5.2/src/tesserax/py.typed +0 -0
  15. {tesserax-0.2.1 → tesserax-0.5.2}/uv.lock +115 -1
  16. tesserax-0.2.1/examples/basic.py +0 -14
  17. tesserax-0.2.1/src/tesserax/__init__.py +0 -5
  18. tesserax-0.2.1/src/tesserax/base.py +0 -264
  19. tesserax-0.2.1/src/tesserax/force.py +0 -98
  20. tesserax-0.2.1/src/tesserax/layout.py +0 -107
  21. {tesserax-0.2.1 → tesserax-0.5.2}/.github/workflows/release.yaml +0 -0
  22. {tesserax-0.2.1 → tesserax-0.5.2}/.github/workflows/tests.yaml +0 -0
  23. {tesserax-0.2.1 → tesserax-0.5.2}/.gitignore +0 -0
  24. {tesserax-0.2.1 → tesserax-0.5.2}/.python-version +0 -0
  25. {tesserax-0.2.1 → tesserax-0.5.2}/.vscode/settings.json +0 -0
  26. {tesserax-0.2.1 → tesserax-0.5.2}/AGENT.md +0 -0
  27. {tesserax-0.2.1 → tesserax-0.5.2}/LICENSE +0 -0
  28. {tesserax-0.2.1 → tesserax-0.5.2}/docs/.gitignore +0 -0
  29. {tesserax-0.2.1 → tesserax-0.5.2}/docs/styles.css +0 -0
  30. /tesserax-0.2.1/src/tesserax/py.typed → /tesserax-0.5.2/src/tesserax/align.py +0 -0
  31. {tesserax-0.2.1 → tesserax-0.5.2}/tests/test_geometry.py +0 -0
  32. {tesserax-0.2.1 → tesserax-0.5.2}/tests/test_layout.py +0 -0
  33. {tesserax-0.2.1 → tesserax-0.5.2}/tests/test_shapes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tesserax
3
- Version: 0.2.1
3
+ Version: 0.5.2
4
4
  Summary: A pure-Python library for rendering professional CS graphics.
5
5
  Author-email: Alejandro Piad <apiad@apiad.net>
6
6
  License-File: LICENSE
@@ -80,7 +80,8 @@ Layouts are a unique feature of Tesserax to automate the positioning of child el
80
80
 
81
81
  * **Row**: Aligns shapes horizontally. Baselines can be set to `start`, `middle`, or `end`.
82
82
  * **Column**: Aligns shapes vertically with `start`, `middle`, or `end` alignment.
83
- * **ForceLayout**: Typically used to draw graphs.
83
+ * **HierarchicalLayout**: Useful for drawing trees, DAGs, automata, etc.
84
+ * **ForceLayout**: Typically used to draw arbitrary graphs with a force-directed algorithm.
84
85
 
85
86
  ### Transforms
86
87
 
@@ -71,7 +71,8 @@ Layouts are a unique feature of Tesserax to automate the positioning of child el
71
71
 
72
72
  * **Row**: Aligns shapes horizontally. Baselines can be set to `start`, `middle`, or `end`.
73
73
  * **Column**: Aligns shapes vertically with `start`, `middle`, or `end` alignment.
74
- * **ForceLayout**: Typically used to draw graphs.
74
+ * **HierarchicalLayout**: Useful for drawing trees, DAGs, automata, etc.
75
+ * **ForceLayout**: Typically used to draw arbitrary graphs with a force-directed algorithm.
75
76
 
76
77
  ### Transforms
77
78
 
@@ -9,7 +9,7 @@ website:
9
9
  - href: index.qmd
10
10
  text: Home
11
11
  - href: core.qmd
12
- text: Core Concepts
12
+ text: User Guide
13
13
  - href: gallery.qmd
14
14
  text: Gallery
15
15
 
@@ -1,4 +1,4 @@
1
- This "Core Concepts" guide will walk you through building a scene from scratch, demonstrating how **Tesserax** handles shapes, positioning, and composition.
1
+ This guide will walk you through building a scene from scratch, demonstrating how **Tesserax** handles shapes, positioning, and composition.
2
2
 
3
3
  ## The Canvas and the First Shape
4
4
 
@@ -21,6 +21,20 @@ canvas.fit(padding=10).display()
21
21
 
22
22
  ```
23
23
 
24
+ ## Drawing Text
25
+
26
+ Text behaves exactly like any other shape in Tesserax.
27
+
28
+ ```{python}
29
+ from tesserax import Canvas, Text
30
+
31
+ with Canvas() as canvas:
32
+ Rect(150, 40, fill="lightblue")
33
+ Text("Hello World", size=24, font="serif").translated(0, 22)
34
+
35
+ canvas.align("horizontal").fit(10).display()
36
+ ```
37
+
24
38
  ## Adding and Transforming Shapes
25
39
 
26
40
  While you can add shapes and manually set their coordinates, Tesserax provides a fluent API for transformations. Here, we add a `Circle` and use `translated()` to move it into position.
@@ -39,9 +53,28 @@ canvas.fit(padding=10).display()
39
53
 
40
54
  ```
41
55
 
56
+ ## Grouping and Aligning Shapes
57
+
58
+ The `Group` class acts a container that has control over its children positions, so you can align and layout shapes easily with a procedural API.
59
+
60
+ ```{python}
61
+ from tesserax import Canvas, Rect, Circle, Square, Group
62
+
63
+ with Canvas() as canvas:
64
+ with Group() as g:
65
+ Rect(100, 50, fill="lightblue")
66
+ Circle(30, fill="salmon")
67
+ Square(40, fill="pink")
68
+
69
+ g.align("vertical", "center").distribute("horizontal", gap=10)
70
+
71
+ canvas.fit(padding=10).display()
72
+
73
+ ```
74
+
42
75
  ## Simplifying with Layouts
43
76
 
44
- Manually calculating offsets (like the `150, 25` above) becomes tedious in complex diagrams. **Layouts** automate this positioning. The `Row` layout arranges its children horizontally with an optional `gap`.
77
+ Manually calculating offsets or calling procedural alignment becomes tedious in complex diagrams. **Layouts** automate this positioning. The `Row` layout arranges its children horizontally with an optional `gap`. The `Column` does so vertically.
45
78
 
46
79
  ```{python}
47
80
  from tesserax import Canvas, Rect, Circle
@@ -49,9 +82,10 @@ from tesserax.layout import Row
49
82
 
50
83
  with Canvas() as canvas:
51
84
  # Row is also a context manager
52
- with Row(gap=20):
85
+ with Row(gap=10):
53
86
  Rect(100, 50, fill="lightblue")
54
87
  Circle(30, fill="salmon")
88
+ Square(40, fill="pink")
55
89
 
56
90
  canvas.fit(padding=10).display()
57
91
 
@@ -114,7 +148,7 @@ with Canvas() as canvas:
114
148
  # Even though c1 is inside a rotated row, c1.anchor("right")
115
149
  # returns the correct global coordinate for the arrow.
116
150
  Arrow(
117
- c1.anchor("top").dy(5),
151
+ c1.anchor("top").dy(-5),
118
152
  target.anchor("left").dx(-5),
119
153
  stroke="grey",
120
154
  width=2,
@@ -141,3 +175,77 @@ When you call `shape.anchor(name)`, Tesserax performs the following behind the s
141
175
  This means you never have to manually calculate `sin()` or `cos()` to find where a rotated object's edge is located—you just ask for the anchor.
142
176
 
143
177
  For explicit anchoring, you can use `Shape.resolve(p: Point)` to map a point in local space to the global space, this way you can, e.g., get the point at 2/3rds of the way inside a rectangle and map it to global space.
178
+
179
+ ## Drawing Paths and Lines
180
+
181
+ While shapes like `Rect` and `Circle` cover many use cases, sometimes you need to draw arbitrary lines, custom shapes, or connectors. Tesserax offers two ways to do this: the low-level `Path` for precise control and the high-level `Polyline` for rapid sequences.
182
+
183
+ ### The Low-Level `Path` Object
184
+
185
+ The `Path` class roughly corresponds to the SVG `<path>` element. It operates like a pen: you move it to a location, then draw lines or curves to subsequent points.
186
+
187
+ This is ideal for creating custom glyphs or specific geometric curves.
188
+
189
+ ```{python}
190
+ from tesserax import Canvas, Path
191
+
192
+ with Canvas() as canvas:
193
+ # 1. A simple custom shape (a triangle)
194
+ p = Path()
195
+ p.move_to(0, 0).line_to(50, 50).line_to(0, 50).close()
196
+ p.translated(20, 20)
197
+
198
+ # 2. A curved path using Bezier curves
199
+ curve = Path()
200
+ curve.move_to(100, 20)
201
+ # Cubic Bezier: 2 control points, 1 end point
202
+ curve.cubic_to(
203
+ 150, 20, # Control Point 1
204
+ 150, 80, # Control Point 2
205
+ 200, 80 # End Point
206
+ )
207
+
208
+ # Quadratic Bezier: 1 control point, 1 end point
209
+ curve.quadratic_to(
210
+ 250, 80, # Control Point
211
+ 300, 20 # End Point
212
+ )
213
+
214
+ canvas.fit(padding=10).display()
215
+ ```
216
+
217
+ ### The High-Level `Polyline`
218
+
219
+ For many diagrams, you simply want to connect a sequence of points. The `Polyline` shape automates this.
220
+
221
+ Its most powerful feature is the `smoothness` parameter. Instead of manually calculating Bezier control points to round a corner, you can simply tell `Polyline` to blend the corners for you.
222
+
223
+ * `smoothness=0`: Sharp corners (standard polygon).
224
+ * `smoothness=0.2`: Subtle rounded corners (like a modern UI box).
225
+ * `smoothness=1`: Maximum rounding (spline-like).
226
+
227
+ ```{python}
228
+ from tesserax import Canvas, Polyline, Point
229
+
230
+ # Helper to generate a zigzag pattern
231
+ points = [
232
+ Point(0, 50), Point(50, 0), Point(100, 50),
233
+ Point(150, 0), Point(200, 50)
234
+ ]
235
+
236
+ with Canvas() as canvas:
237
+ # 1. Sharp Polyline (Default)
238
+ Polyline(points, smoothness=0).translated(0, 0)
239
+
240
+ # 2. Slightly Rounded (20% smoothing)
241
+ # Note how it preserves most of the straight line but rounds the tip.
242
+ Polyline(points, smoothness=0.2, stroke="blue").translated(0, 60)
243
+
244
+ # 3. Fully Smooth (100% smoothing)
245
+ # This creates a flowing wave-like appearance.
246
+ Polyline(points, smoothness=1.0, stroke="red").translated(0, 120)
247
+
248
+ canvas.fit(10).display()
249
+ ```
250
+
251
+ This makes `Polyline` an excellent tool for drawing graph edges, wiring diagrams, or "hand-drawn" style annotations where sharp vertices look unnatural.
@@ -58,7 +58,7 @@ This example uses a force layout to draw a simple graph that represents an autom
58
58
  ```{python}
59
59
  import math
60
60
  from tesserax import Canvas, Circle, Arrow
61
- from tesserax.force import ForceLayout
61
+ from tesserax.layout import HierarchicalLayout
62
62
  from tesserax.core import Point
63
63
 
64
64
  def get_boundary_point(center: Point, target: Point, radius: float) -> Point:
@@ -76,10 +76,10 @@ def get_boundary_point(center: Point, target: Point, radius: float) -> Point:
76
76
 
77
77
  with Canvas() as canvas:
78
78
  states: list[Shape] = []
79
- radius = 25
79
+ radius = 20
80
80
 
81
81
  # 1. Define the Graph Structure
82
- with ForceLayout(k=75, iterations=300) as graph:
82
+ with HierarchicalLayout(orientation="horizontal") as graph:
83
83
  # Create 5 states
84
84
  for i in range(4):
85
85
  states.append(Circle(r=radius))
@@ -87,11 +87,13 @@ with Canvas() as canvas:
87
87
  # Connect them (Topology)
88
88
  # q0 -> q1 -> q2
89
89
  graph.connect(states[0], states[1])
90
- graph.connect(states[1], states[2])
90
+ graph.connect(states[0], states[2])
91
91
  # q2 -> q0 (cycle)
92
92
  graph.connect(states[2], states[0])
93
93
  # q2 -> q3 -> q4
94
94
  graph.connect(states[2], states[3])
95
+ # Set the root
96
+ graph.root(states[0])
95
97
 
96
98
  # 2. Draw Transitions (Visuals)
97
99
  # We define edges manually to ensure directionality (ForceLayout is undirected)
@@ -69,7 +69,8 @@ Layouts are a unique feature of Tesserax to automate the positioning of child el
69
69
 
70
70
  * **Row**: Aligns shapes horizontally. Baselines can be set to `start`, `middle`, or `end`.
71
71
  * **Column**: Aligns shapes vertically with `start`, `middle`, or `end` alignment.
72
- * **ForceLayout**: Typically used to draw graphs.
72
+ * **HierarchicalLayout**: Useful for drawing trees, DAGs, automata, etc.
73
+ * **ForceLayout**: Typically used to draw arbitrary graphs with a force-directed algorithm.
73
74
 
74
75
  ### Transforms
75
76
 
@@ -54,7 +54,7 @@ release:
54
54
  @echo Remove backup files
55
55
  @rm pyproject.toml.bak src/tesserax/__init__.py.bak
56
56
 
57
- @uv sync
57
+ @uv sync --all-groups
58
58
 
59
59
  @echo "Committing version bump..."
60
60
  @git add pyproject.toml src/tesserax/__init__.py uv.lock
@@ -1,13 +1,12 @@
1
1
  [project]
2
2
  name = "tesserax"
3
- version = "0.2.1"
3
+ version = "0.5.2"
4
4
  description = "A pure-Python library for rendering professional CS graphics."
5
5
  readme = "README.md"
6
6
  authors = [
7
7
  { name = "Alejandro Piad", email = "apiad@apiad.net" }
8
8
  ]
9
9
  requires-python = ">=3.12"
10
- dependencies = []
11
10
 
12
11
  [build-system]
13
12
  requires = ["hatchling"]
@@ -22,3 +21,6 @@ dev = [
22
21
  "pytest-coverage>=0.0",
23
22
  "ruff>=0.14.14",
24
23
  ]
24
+ export = [
25
+ "cairosvg>=2.8.2",
26
+ ]
@@ -0,0 +1,16 @@
1
+ from .canvas import Canvas
2
+ from .core import Shape, Bounds, Point
3
+ from .base import (
4
+ Rect,
5
+ Square,
6
+ Circle,
7
+ Ellipse,
8
+ Line,
9
+ Arrow,
10
+ Group,
11
+ Path,
12
+ Polyline,
13
+ Text,
14
+ )
15
+
16
+ __version__ = "0.5.2"