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.
- {tesserax-0.2.1 → tesserax-0.5.2}/PKG-INFO +3 -2
- {tesserax-0.2.1 → tesserax-0.5.2}/README.md +2 -1
- {tesserax-0.2.1 → tesserax-0.5.2}/docs/_quarto.yml +1 -1
- {tesserax-0.2.1 → tesserax-0.5.2}/docs/core.qmd +112 -4
- {tesserax-0.2.1 → tesserax-0.5.2}/docs/gallery.qmd +6 -4
- {tesserax-0.2.1 → tesserax-0.5.2}/docs/index.qmd +2 -1
- {tesserax-0.2.1 → tesserax-0.5.2}/makefile +1 -1
- {tesserax-0.2.1 → tesserax-0.5.2}/pyproject.toml +4 -2
- tesserax-0.5.2/src/tesserax/__init__.py +16 -0
- tesserax-0.5.2/src/tesserax/base.py +573 -0
- {tesserax-0.2.1 → tesserax-0.5.2}/src/tesserax/canvas.py +35 -4
- {tesserax-0.2.1 → tesserax-0.5.2}/src/tesserax/core.py +17 -0
- tesserax-0.5.2/src/tesserax/layout.py +334 -0
- tesserax-0.5.2/src/tesserax/py.typed +0 -0
- {tesserax-0.2.1 → tesserax-0.5.2}/uv.lock +115 -1
- tesserax-0.2.1/examples/basic.py +0 -14
- tesserax-0.2.1/src/tesserax/__init__.py +0 -5
- tesserax-0.2.1/src/tesserax/base.py +0 -264
- tesserax-0.2.1/src/tesserax/force.py +0 -98
- tesserax-0.2.1/src/tesserax/layout.py +0 -107
- {tesserax-0.2.1 → tesserax-0.5.2}/.github/workflows/release.yaml +0 -0
- {tesserax-0.2.1 → tesserax-0.5.2}/.github/workflows/tests.yaml +0 -0
- {tesserax-0.2.1 → tesserax-0.5.2}/.gitignore +0 -0
- {tesserax-0.2.1 → tesserax-0.5.2}/.python-version +0 -0
- {tesserax-0.2.1 → tesserax-0.5.2}/.vscode/settings.json +0 -0
- {tesserax-0.2.1 → tesserax-0.5.2}/AGENT.md +0 -0
- {tesserax-0.2.1 → tesserax-0.5.2}/LICENSE +0 -0
- {tesserax-0.2.1 → tesserax-0.5.2}/docs/.gitignore +0 -0
- {tesserax-0.2.1 → tesserax-0.5.2}/docs/styles.css +0 -0
- /tesserax-0.2.1/src/tesserax/py.typed → /tesserax-0.5.2/src/tesserax/align.py +0 -0
- {tesserax-0.2.1 → tesserax-0.5.2}/tests/test_geometry.py +0 -0
- {tesserax-0.2.1 → tesserax-0.5.2}/tests/test_layout.py +0 -0
- {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
|
|
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
|
-
* **
|
|
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
|
-
* **
|
|
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
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
This
|
|
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
|
|
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=
|
|
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.
|
|
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 =
|
|
79
|
+
radius = 20
|
|
80
80
|
|
|
81
81
|
# 1. Define the Graph Structure
|
|
82
|
-
with
|
|
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[
|
|
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
|
-
* **
|
|
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
|
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tesserax"
|
|
3
|
-
version = "0.2
|
|
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
|
+
]
|