tesserax 0.2.1__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/.github/workflows/release.yaml +56 -0
- tesserax-0.2.1/.github/workflows/tests.yaml +32 -0
- tesserax-0.2.1/.gitignore +11 -0
- tesserax-0.2.1/.python-version +1 -0
- tesserax-0.2.1/.vscode/settings.json +7 -0
- tesserax-0.2.1/AGENT.md +103 -0
- tesserax-0.2.1/LICENSE +21 -0
- tesserax-0.2.1/PKG-INFO +134 -0
- tesserax-0.2.1/README.md +125 -0
- tesserax-0.2.1/docs/.gitignore +4 -0
- tesserax-0.2.1/docs/_quarto.yml +24 -0
- tesserax-0.2.1/docs/core.qmd +143 -0
- tesserax-0.2.1/docs/gallery.qmd +142 -0
- tesserax-0.2.1/docs/index.qmd +123 -0
- tesserax-0.2.1/docs/styles.css +1 -0
- tesserax-0.2.1/examples/basic.py +14 -0
- tesserax-0.2.1/makefile +73 -0
- tesserax-0.2.1/pyproject.toml +24 -0
- tesserax-0.2.1/src/tesserax/__init__.py +5 -0
- tesserax-0.2.1/src/tesserax/base.py +264 -0
- tesserax-0.2.1/src/tesserax/canvas.py +78 -0
- tesserax-0.2.1/src/tesserax/core.py +219 -0
- tesserax-0.2.1/src/tesserax/force.py +98 -0
- tesserax-0.2.1/src/tesserax/layout.py +107 -0
- tesserax-0.2.1/src/tesserax/py.typed +0 -0
- tesserax-0.2.1/tests/test_geometry.py +43 -0
- tesserax-0.2.1/tests/test_layout.py +30 -0
- tesserax-0.2.1/tests/test_shapes.py +27 -0
- tesserax-0.2.1/uv.lock +1827 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Release Pipeline
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- name: Checkout repository
|
|
12
|
+
uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Set up Python
|
|
15
|
+
uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: uv sync --all-extras --all-groups --no-editable
|
|
24
|
+
|
|
25
|
+
- name: Run format check
|
|
26
|
+
run: uv run black --check .
|
|
27
|
+
|
|
28
|
+
- name: Run tests with coverage
|
|
29
|
+
run: uv run pytest --cov=tesserax --cov-report=xml --cov-report=term
|
|
30
|
+
|
|
31
|
+
- name: Upload coverage to Codecov
|
|
32
|
+
uses: codecov/codecov-action@v4
|
|
33
|
+
with:
|
|
34
|
+
files: ./coverage.xml
|
|
35
|
+
fail_ci_if_error: false
|
|
36
|
+
|
|
37
|
+
publish-pypi:
|
|
38
|
+
needs: test
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- name: Checkout repository
|
|
42
|
+
uses: actions/checkout@v4
|
|
43
|
+
|
|
44
|
+
- name: Set up Python
|
|
45
|
+
uses: actions/setup-python@v5
|
|
46
|
+
with:
|
|
47
|
+
python-version: "3.12"
|
|
48
|
+
|
|
49
|
+
- name: Install uv
|
|
50
|
+
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
51
|
+
|
|
52
|
+
- name: Build package
|
|
53
|
+
run: uv build
|
|
54
|
+
|
|
55
|
+
- name: Publish to PyPI
|
|
56
|
+
run: uv publish --token ${{ secrets.PYPI_TOKEN }}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Run Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout repository
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Python
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
|
|
22
|
+
- name: Install uv
|
|
23
|
+
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: uv sync --all-extras --all-groups --no-editable
|
|
27
|
+
|
|
28
|
+
- name: Run format check
|
|
29
|
+
run: uv run black --check .
|
|
30
|
+
|
|
31
|
+
- name: Run tests with coverage
|
|
32
|
+
run: uv run pytest --cov=tesserax --cov-report=xml --cov-report=term
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
tesserax-0.2.1/AGENT.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Code Agent Operating Protocol
|
|
2
|
+
|
|
3
|
+
## Core Directive
|
|
4
|
+
|
|
5
|
+
**YOU ARE AN EXPERT SOFTWARE ENGINEER AND ARCHITECT.** Your primary goal is **NOT** to generate code immediately. Your goal is to produce robust, maintainable, and well-understood solutions. **DO NOT RUSH TO PROVIDE CODE SNIPPETS.** Follow this strict protocol for every request.
|
|
6
|
+
|
|
7
|
+
## Phase 1: Understanding (Default Mode)
|
|
8
|
+
|
|
9
|
+
**Trigger:** Any initial user request, bug report, or feature idea.
|
|
10
|
+
|
|
11
|
+
**Exception (Express Mode):**
|
|
12
|
+
|
|
13
|
+
- If the request is a trivial syntax fix, a simple style change (CSS), or a one-line config update, you may bypass Phase 1 & 2 and provide the code immediately.
|
|
14
|
+
- _Caveat:_ If a "simple" fix implies hidden complexity (e.g., changing a DB column type), revert to Phase 1.
|
|
15
|
+
|
|
16
|
+
1. **Stop and Think:** Do not generate solution code yet.
|
|
17
|
+
2. **Analyze Context:** Assess the user's request. Is it a bug? A new feature? A refactor? An architectural discussion?
|
|
18
|
+
3. **Ask Clarifying Questions:**
|
|
19
|
+
- If the request is vague, ask for constraints.
|
|
20
|
+
- If it's a bug, ask for reproduction steps or recent changes.
|
|
21
|
+
- If it's a feature, ask about the desired API surface and use cases.
|
|
22
|
+
4. **Goal:** You must fully grasp the "Why" and "What" before discussing the "How."
|
|
23
|
+
|
|
24
|
+
## Phase 2: Planning (The Blueprint)
|
|
25
|
+
|
|
26
|
+
**Trigger:** Once the context is understood, but before writing implementation code.
|
|
27
|
+
|
|
28
|
+
1. **Create Artifacts:** You must generate a plan.
|
|
29
|
+
- **For Complex Tasks (Features/Refactors):** Create a dedicated Markdown file (e.g., `docs/plans/feature_name_roadmap.md`).
|
|
30
|
+
- **For Simple Tasks:** Provide a clear Markdown roadmap in the chat.
|
|
31
|
+
2. **Roadmap Content Requirements:**
|
|
32
|
+
- **Context:** Briefly summarize the problem/goal so future agents don't need re-briefing.
|
|
33
|
+
- **API Design:** Show how the user will interact with the new code (signatures, endpoints, data shapes).
|
|
34
|
+
- **Implementation Plan:** A step-by-step list of what needs to happen.
|
|
35
|
+
- Which files need creation?
|
|
36
|
+
- Which files need modification?
|
|
37
|
+
- **NO CODE SNIPPETS** (except for signatures/interfaces).
|
|
38
|
+
3. **User Review:** Present this plan and wait for user approval or feedback.
|
|
39
|
+
|
|
40
|
+
## Phase 3: Development (The Surgeon)
|
|
41
|
+
|
|
42
|
+
**Trigger:** EXPLICIT user command (e.g., "Start coding," "Implement step 1," "Give me the code").
|
|
43
|
+
|
|
44
|
+
1. **Sequential Implementation (Step-by-Step):**
|
|
45
|
+
- **Rule:** Unless trivial (Express Mode), NEVER provide all file changes in a single response.
|
|
46
|
+
- **Action:** Present changes for **one location/file** at a time.
|
|
47
|
+
- **Pause:** Explicitly ask the user, "Are you ready for the next step?" before proceeding to the next logical block.
|
|
48
|
+
2. **Surgical Precision:**
|
|
49
|
+
- **DO NOT** regenerate entire files unless absolutely necessary.
|
|
50
|
+
- Provide **only** the specific function, class, or block that needs changing.
|
|
51
|
+
- Use search/replace blocks or clear "Insert after X" instructions.
|
|
52
|
+
- **Import Awareness:** When providing a snippet, explicitly check if new imports are required. If so, provide the `import` statements separately and instruct the user to add them to the top of the file.
|
|
53
|
+
3. **Contextual Awareness:**
|
|
54
|
+
- Always explain _where_ the code goes.
|
|
55
|
+
- Explain _why_ this implementation was chosen.
|
|
56
|
+
- **Living Documentation:** If the implementation plan changes significantly during coding (e.g., library swap, API change), you MUST pause and ask the user if the `roadmap.md` artifact should be updated to reflect reality.
|
|
57
|
+
4. **Verification:**
|
|
58
|
+
- For every snippet, explain how to verify it works (e.g., "Run test X," "Check log output Y").
|
|
59
|
+
5. **Safety & Side Effects:**
|
|
60
|
+
- Before outputting code, analyze potential risks (breaking changes, security holes, performance hits).
|
|
61
|
+
- Warn the user if a change affects other parts of the system.
|
|
62
|
+
|
|
63
|
+
## Phase 4: Conclusion & Documentation
|
|
64
|
+
|
|
65
|
+
**Trigger:** When all implementation steps are complete.
|
|
66
|
+
|
|
67
|
+
1. **Final Summary:** Generate a comprehensive summary of the session.
|
|
68
|
+
2. **Content Requirements:**
|
|
69
|
+
- **What Changed:** A list of files and specific functions modified.
|
|
70
|
+
- **Key Decisions:** Why certain paths were chosen (context for future agents).
|
|
71
|
+
- **Verification Results:** Confirmation that tests passed (if applicable).
|
|
72
|
+
3. **Artifact Generation:**
|
|
73
|
+
- Format this summary so it can be used directly as a **Commit Message**.
|
|
74
|
+
- Offer to save this as a permanent record in a changelog folder (e.g., `docs/changelogs/YYYY-MM-DD-feature-name.md`) to preserve the "Why" behind the "What" for future reference.
|
|
75
|
+
|
|
76
|
+
## Coding Standards: Python
|
|
77
|
+
|
|
78
|
+
### Versioning
|
|
79
|
+
|
|
80
|
+
- Target **Python 3.12+**.
|
|
81
|
+
- Utilize modern features (pattern matching, new typing syntax).
|
|
82
|
+
|
|
83
|
+
### Typing
|
|
84
|
+
|
|
85
|
+
- **Strict Typing for Interfaces:** All public methods and classes must have type hints.
|
|
86
|
+
- **Native Types:** Use built-in generics.
|
|
87
|
+
- DO: `list[str]`, `dict[str, int]`, `tuple[int, int]`
|
|
88
|
+
- DONT: `List[str]`, `Dict[str, int]`, `Tuple[int, int]` (from `typing`)
|
|
89
|
+
- **Internal Logic:** Looser typing is acceptable inside private methods if it improves readability, but prefer explicit over implicit.
|
|
90
|
+
|
|
91
|
+
### Documentation & Comments
|
|
92
|
+
|
|
93
|
+
- **Docstrings:** Required for all modules, classes, and public functions.
|
|
94
|
+
- **Comment Philosophy:**
|
|
95
|
+
- **NO:** Redundant comments (e.g., `i += 1 # increment i`).
|
|
96
|
+
- **YES:** State and Flow comments. Explain the _state of the application_ at that line.
|
|
97
|
+
- _Example:_ `# At this point, the user payload is validated but not yet persisted to DB.`
|
|
98
|
+
|
|
99
|
+
## Interaction Style
|
|
100
|
+
|
|
101
|
+
- **Be Skeptical:** Do not assume the user's initial prompt covers all edge cases.
|
|
102
|
+
- **Be Agile:** Propose breaking large tasks into smaller, testable deliverables. Avoid making sweeping changes without very careful planning.
|
|
103
|
+
- **Be Educational:** Briefly explain complex decisions without being patronizing.
|
tesserax-0.2.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alejandro Piad
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tesserax-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tesserax
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: A pure-Python library for rendering professional CS graphics.
|
|
5
|
+
Author-email: Alejandro Piad <apiad@apiad.net>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# Tesserax: A Lightweight SVG Rendering Library
|
|
11
|
+
|
|
12
|
+
Tesserax is a modern Python 3.12 library designed for programmatic SVG generation with a focus on ease of use, layout management, and flexible geometric primitives. It is particularly well-suited for visualizing data structures, algorithms, and technical diagrams.
|
|
13
|
+
|
|
14
|
+
> [**Read the full documentation**](https://apiad.github.io/tesserax).
|
|
15
|
+
|
|
16
|
+
## Key Features
|
|
17
|
+
|
|
18
|
+
* **Declarative Layouts**: Effortlessly arrange shapes in `Row` or `Column` containers with automatic alignment and spacing.
|
|
19
|
+
* **Anchor System**: Connect shapes using semantic anchors like `top`, `bottom`, `left`, `right`, and `center`.
|
|
20
|
+
* **Context Manager Support**: Use `with` statements to group shapes naturally within the code.
|
|
21
|
+
* **Smart Canvas**: Automatically fit the canvas viewport to the content with adjustable padding.
|
|
22
|
+
* **Rich Primitives**: Includes `Rect`, `Square`, `Circle`, `Ellipse`, `Line`, `Arrow`, and `Path`.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Tesserax has zero dependencies (literally). It's 100% pure Python, and can be easily installed with `pip`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install tesserax
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or if you're one of the cool kids, using `uv`:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv add tesserax
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
The following example demonstrates how to create two shapes in a row and connect them with an arrow using the anchor system.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from tesserax import Canvas, Rect, Arrow, Circle
|
|
44
|
+
from tesserax.layout import Row
|
|
45
|
+
|
|
46
|
+
# Initialize a canvas
|
|
47
|
+
with Canvas() as canvas:
|
|
48
|
+
# Arrange a circle and a rectangle in a row with a 50px gap
|
|
49
|
+
with Row(gap=50):
|
|
50
|
+
circle = Circle(30, fill="#fee")
|
|
51
|
+
rect = Rect(100, 60, fill="#eef")
|
|
52
|
+
|
|
53
|
+
# Draw an arrow between the two shapes using anchors
|
|
54
|
+
# .dx() provides a small offset for better visual spacing
|
|
55
|
+
Arrow(
|
|
56
|
+
circle.anchor("right").dx(5),
|
|
57
|
+
rect.anchor("left").dx(-5)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Fit the viewport to the shapes and render
|
|
61
|
+
canvas.fit(padding=10).display()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The `display()` method in the `Canvas` class is an IPython/Jupyter/Quarto compatible shortcut to automatically include the rendered SVG (in all its beautiful vectorial glory) directly in a notebook. But you can also use `Canvas.save()` to generate a plain old, boring SVG file on this, and `str(canvas)` to get the actual SVG code as a plain string.
|
|
65
|
+
|
|
66
|
+
## Core Components
|
|
67
|
+
|
|
68
|
+
Tesserax comes with all basic components you need to draw the spectrum of SVG shapes.
|
|
69
|
+
All shapes support standard SVG attributes like `stroke` and `fill`.
|
|
70
|
+
|
|
71
|
+
* **Rect & Square**: Defined by width/height or a single size.
|
|
72
|
+
* **Circle & Ellipse**: Defined by radii.
|
|
73
|
+
* **Groups**: For grouping shapes and applying transforms to them as a single shape.
|
|
74
|
+
* **Arrow**: A specialized line that automatically includes an arrowhead marker.
|
|
75
|
+
* **Path**: Supports a fluent API for complex paths using `move_to`, `line_to`, `cubic_to`, and `close`.
|
|
76
|
+
|
|
77
|
+
### Layouts
|
|
78
|
+
|
|
79
|
+
Layouts are a unique feature of Tesserax to automate the positioning of child elements. We currently have three layouts, but these are very easy to extend:
|
|
80
|
+
|
|
81
|
+
* **Row**: Aligns shapes horizontally. Baselines can be set to `start`, `middle`, or `end`.
|
|
82
|
+
* **Column**: Aligns shapes vertically with `start`, `middle`, or `end` alignment.
|
|
83
|
+
* **ForceLayout**: Typically used to draw graphs.
|
|
84
|
+
|
|
85
|
+
### Transforms
|
|
86
|
+
|
|
87
|
+
Every shape has a `Transform` object allowing for:
|
|
88
|
+
|
|
89
|
+
* **Translation**: `shape.translated(dx, dy)`.
|
|
90
|
+
* **Rotation**: `shape.rotated(degrees)`.
|
|
91
|
+
* **Scaling**: `shape.scaled(factor)`.
|
|
92
|
+
|
|
93
|
+
Groups of shapes also have their own transform, and this can be composed _ad-infinitum_ to create complex drawing.
|
|
94
|
+
|
|
95
|
+
## Why Tesserax?
|
|
96
|
+
|
|
97
|
+
In the Python ecosystem, there is a clear divide between **data visualization** (plotting numbers) and **diagrammatic representation** (drawing concepts). Tesserax is built for the latter.
|
|
98
|
+
|
|
99
|
+
It is designed for researchers, educators, and authors who need the geometric precision of a professional drafting tool combined with the power of a modern programming language.
|
|
100
|
+
|
|
101
|
+
### Tesserax vs. The Alternatives
|
|
102
|
+
|
|
103
|
+
#### Precision over Statistics
|
|
104
|
+
|
|
105
|
+
Libraries like **Matplotlib**, **Seaborn**, or **Altair** are designed to map data points to visual encodings (bars, lines, scatter points).
|
|
106
|
+
|
|
107
|
+
**The Difference**: Tesserax does not compete with these libraries because it does not render data graphs. You wouldn't use Tesserax to plot a CSV. Instead, Tesserax is for "the rest" of the figures in a paper: the schematics, the geometric proofs, the architectural diagrams, and the algorithmic walkthroughs where exact spatial relationships convey the meaning.
|
|
108
|
+
|
|
109
|
+
#### Control over Constraints
|
|
110
|
+
|
|
111
|
+
**Mermaid** and **Graphviz** are excellent for quickly rendering flowcharts using "black-box" layout engines.
|
|
112
|
+
|
|
113
|
+
**The Difference**: These tools sacrifice control for convenience. If you need an arrow to point exactly at the tangent of a rotated ellipse, or a shape to be sized exactly according to a geometric ratio, Mermaid cannot help you. Tesserax is for **Scientific Drawing**—providing the low-level primitives needed for total layout authority.
|
|
114
|
+
|
|
115
|
+
#### The "TikZ for Python" Philosophy
|
|
116
|
+
|
|
117
|
+
**TikZ** is the industry standard for academic figures, but it requires learning a specialized, often cryptic macro language.
|
|
118
|
+
|
|
119
|
+
**The Difference**: Tesserax brings the "low-level, total-control" philosophy of TikZ into **Python 3.12**. You get coordinate-invariant precision and semantic anchoring while using Python’s loops, logic, and types. We are building from the bottom up: starting with geometric atoms and moving toward high-level scientific abstractions (like automated neural network architectures or commutative diagrams) that maintain the ability to "drop down" and tweak a single pixel.
|
|
120
|
+
|
|
121
|
+
### The SVG Advantage
|
|
122
|
+
|
|
123
|
+
While TikZ is the gold standard for LaTeX-based PDF generation, it belongs to a "print-first" era. Tesserax leverages **SVG (Scalable Vector Graphics)** as its native format, offering a portability that TikZ cannot match without significant friction.
|
|
124
|
+
|
|
125
|
+
* **Native Web Rendering**: Tesserax figures are native SVGs. They render instantly in any browser, remain crisp at any zoom level, and can be embedded directly into HTML or Markdown (via Quarto) without conversion.
|
|
126
|
+
* **WYSIWYG Portability**: Converting TikZ to SVG for blog posts or online journals often results in broken fonts or misaligned elements. Because Tesserax *starts* with SVG, what you see in your development notebook is exactly what appears in your final PDF and your website.
|
|
127
|
+
* **Accessibility & Interaction**: Unlike static PDFs, Tesserax SVGs can include metadata and ARIA labels for screen readers. Since they are part of the DOM, they can also be styled with CSS or even animated for interactive educational content.
|
|
128
|
+
* **Perfect Print**: SVG is fully convertible to high-quality, vector-perfect PDF, meeting the highest standards for academic journals and book publishing.
|
|
129
|
+
|
|
130
|
+
## Contribution
|
|
131
|
+
|
|
132
|
+
Tesserax is free as in both free beer and free speech. License is MIT.
|
|
133
|
+
|
|
134
|
+
Contributions are always welcomed! Fork, clone, and submit a pull request.
|
tesserax-0.2.1/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Tesserax: A Lightweight SVG Rendering Library
|
|
2
|
+
|
|
3
|
+
Tesserax is a modern Python 3.12 library designed for programmatic SVG generation with a focus on ease of use, layout management, and flexible geometric primitives. It is particularly well-suited for visualizing data structures, algorithms, and technical diagrams.
|
|
4
|
+
|
|
5
|
+
> [**Read the full documentation**](https://apiad.github.io/tesserax).
|
|
6
|
+
|
|
7
|
+
## Key Features
|
|
8
|
+
|
|
9
|
+
* **Declarative Layouts**: Effortlessly arrange shapes in `Row` or `Column` containers with automatic alignment and spacing.
|
|
10
|
+
* **Anchor System**: Connect shapes using semantic anchors like `top`, `bottom`, `left`, `right`, and `center`.
|
|
11
|
+
* **Context Manager Support**: Use `with` statements to group shapes naturally within the code.
|
|
12
|
+
* **Smart Canvas**: Automatically fit the canvas viewport to the content with adjustable padding.
|
|
13
|
+
* **Rich Primitives**: Includes `Rect`, `Square`, `Circle`, `Ellipse`, `Line`, `Arrow`, and `Path`.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Tesserax has zero dependencies (literally). It's 100% pure Python, and can be easily installed with `pip`:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install tesserax
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or if you're one of the cool kids, using `uv`:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv add tesserax
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
The following example demonstrates how to create two shapes in a row and connect them with an arrow using the anchor system.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from tesserax import Canvas, Rect, Arrow, Circle
|
|
35
|
+
from tesserax.layout import Row
|
|
36
|
+
|
|
37
|
+
# Initialize a canvas
|
|
38
|
+
with Canvas() as canvas:
|
|
39
|
+
# Arrange a circle and a rectangle in a row with a 50px gap
|
|
40
|
+
with Row(gap=50):
|
|
41
|
+
circle = Circle(30, fill="#fee")
|
|
42
|
+
rect = Rect(100, 60, fill="#eef")
|
|
43
|
+
|
|
44
|
+
# Draw an arrow between the two shapes using anchors
|
|
45
|
+
# .dx() provides a small offset for better visual spacing
|
|
46
|
+
Arrow(
|
|
47
|
+
circle.anchor("right").dx(5),
|
|
48
|
+
rect.anchor("left").dx(-5)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Fit the viewport to the shapes and render
|
|
52
|
+
canvas.fit(padding=10).display()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The `display()` method in the `Canvas` class is an IPython/Jupyter/Quarto compatible shortcut to automatically include the rendered SVG (in all its beautiful vectorial glory) directly in a notebook. But you can also use `Canvas.save()` to generate a plain old, boring SVG file on this, and `str(canvas)` to get the actual SVG code as a plain string.
|
|
56
|
+
|
|
57
|
+
## Core Components
|
|
58
|
+
|
|
59
|
+
Tesserax comes with all basic components you need to draw the spectrum of SVG shapes.
|
|
60
|
+
All shapes support standard SVG attributes like `stroke` and `fill`.
|
|
61
|
+
|
|
62
|
+
* **Rect & Square**: Defined by width/height or a single size.
|
|
63
|
+
* **Circle & Ellipse**: Defined by radii.
|
|
64
|
+
* **Groups**: For grouping shapes and applying transforms to them as a single shape.
|
|
65
|
+
* **Arrow**: A specialized line that automatically includes an arrowhead marker.
|
|
66
|
+
* **Path**: Supports a fluent API for complex paths using `move_to`, `line_to`, `cubic_to`, and `close`.
|
|
67
|
+
|
|
68
|
+
### Layouts
|
|
69
|
+
|
|
70
|
+
Layouts are a unique feature of Tesserax to automate the positioning of child elements. We currently have three layouts, but these are very easy to extend:
|
|
71
|
+
|
|
72
|
+
* **Row**: Aligns shapes horizontally. Baselines can be set to `start`, `middle`, or `end`.
|
|
73
|
+
* **Column**: Aligns shapes vertically with `start`, `middle`, or `end` alignment.
|
|
74
|
+
* **ForceLayout**: Typically used to draw graphs.
|
|
75
|
+
|
|
76
|
+
### Transforms
|
|
77
|
+
|
|
78
|
+
Every shape has a `Transform` object allowing for:
|
|
79
|
+
|
|
80
|
+
* **Translation**: `shape.translated(dx, dy)`.
|
|
81
|
+
* **Rotation**: `shape.rotated(degrees)`.
|
|
82
|
+
* **Scaling**: `shape.scaled(factor)`.
|
|
83
|
+
|
|
84
|
+
Groups of shapes also have their own transform, and this can be composed _ad-infinitum_ to create complex drawing.
|
|
85
|
+
|
|
86
|
+
## Why Tesserax?
|
|
87
|
+
|
|
88
|
+
In the Python ecosystem, there is a clear divide between **data visualization** (plotting numbers) and **diagrammatic representation** (drawing concepts). Tesserax is built for the latter.
|
|
89
|
+
|
|
90
|
+
It is designed for researchers, educators, and authors who need the geometric precision of a professional drafting tool combined with the power of a modern programming language.
|
|
91
|
+
|
|
92
|
+
### Tesserax vs. The Alternatives
|
|
93
|
+
|
|
94
|
+
#### Precision over Statistics
|
|
95
|
+
|
|
96
|
+
Libraries like **Matplotlib**, **Seaborn**, or **Altair** are designed to map data points to visual encodings (bars, lines, scatter points).
|
|
97
|
+
|
|
98
|
+
**The Difference**: Tesserax does not compete with these libraries because it does not render data graphs. You wouldn't use Tesserax to plot a CSV. Instead, Tesserax is for "the rest" of the figures in a paper: the schematics, the geometric proofs, the architectural diagrams, and the algorithmic walkthroughs where exact spatial relationships convey the meaning.
|
|
99
|
+
|
|
100
|
+
#### Control over Constraints
|
|
101
|
+
|
|
102
|
+
**Mermaid** and **Graphviz** are excellent for quickly rendering flowcharts using "black-box" layout engines.
|
|
103
|
+
|
|
104
|
+
**The Difference**: These tools sacrifice control for convenience. If you need an arrow to point exactly at the tangent of a rotated ellipse, or a shape to be sized exactly according to a geometric ratio, Mermaid cannot help you. Tesserax is for **Scientific Drawing**—providing the low-level primitives needed for total layout authority.
|
|
105
|
+
|
|
106
|
+
#### The "TikZ for Python" Philosophy
|
|
107
|
+
|
|
108
|
+
**TikZ** is the industry standard for academic figures, but it requires learning a specialized, often cryptic macro language.
|
|
109
|
+
|
|
110
|
+
**The Difference**: Tesserax brings the "low-level, total-control" philosophy of TikZ into **Python 3.12**. You get coordinate-invariant precision and semantic anchoring while using Python’s loops, logic, and types. We are building from the bottom up: starting with geometric atoms and moving toward high-level scientific abstractions (like automated neural network architectures or commutative diagrams) that maintain the ability to "drop down" and tweak a single pixel.
|
|
111
|
+
|
|
112
|
+
### The SVG Advantage
|
|
113
|
+
|
|
114
|
+
While TikZ is the gold standard for LaTeX-based PDF generation, it belongs to a "print-first" era. Tesserax leverages **SVG (Scalable Vector Graphics)** as its native format, offering a portability that TikZ cannot match without significant friction.
|
|
115
|
+
|
|
116
|
+
* **Native Web Rendering**: Tesserax figures are native SVGs. They render instantly in any browser, remain crisp at any zoom level, and can be embedded directly into HTML or Markdown (via Quarto) without conversion.
|
|
117
|
+
* **WYSIWYG Portability**: Converting TikZ to SVG for blog posts or online journals often results in broken fonts or misaligned elements. Because Tesserax *starts* with SVG, what you see in your development notebook is exactly what appears in your final PDF and your website.
|
|
118
|
+
* **Accessibility & Interaction**: Unlike static PDFs, Tesserax SVGs can include metadata and ARIA labels for screen readers. Since they are part of the DOM, they can also be styled with CSS or even animated for interactive educational content.
|
|
119
|
+
* **Perfect Print**: SVG is fully convertible to high-quality, vector-perfect PDF, meeting the highest standards for academic journals and book publishing.
|
|
120
|
+
|
|
121
|
+
## Contribution
|
|
122
|
+
|
|
123
|
+
Tesserax is free as in both free beer and free speech. License is MIT.
|
|
124
|
+
|
|
125
|
+
Contributions are always welcomed! Fork, clone, and submit a pull request.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
project:
|
|
2
|
+
type: website
|
|
3
|
+
output-dir: _site
|
|
4
|
+
|
|
5
|
+
website:
|
|
6
|
+
title: "Tesserax"
|
|
7
|
+
navbar:
|
|
8
|
+
left:
|
|
9
|
+
- href: index.qmd
|
|
10
|
+
text: Home
|
|
11
|
+
- href: core.qmd
|
|
12
|
+
text: Core Concepts
|
|
13
|
+
- href: gallery.qmd
|
|
14
|
+
text: Gallery
|
|
15
|
+
|
|
16
|
+
format:
|
|
17
|
+
html:
|
|
18
|
+
theme: cosmo
|
|
19
|
+
css: styles.css
|
|
20
|
+
toc: true
|
|
21
|
+
code-fold: true
|
|
22
|
+
|
|
23
|
+
execute:
|
|
24
|
+
freeze: auto # Re-render only when source changes
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
This "Core Concepts" guide will walk you through building a scene from scratch, demonstrating how **Tesserax** handles shapes, positioning, and composition.
|
|
2
|
+
|
|
3
|
+
## The Canvas and the First Shape
|
|
4
|
+
|
|
5
|
+
Every drawing starts with a `Canvas`. In Tesserax, the `Canvas` acts as the root container and provides a context manager to automatically add shapes created within its block.
|
|
6
|
+
|
|
7
|
+
We will start by creating a simple 100x50 rectangle.
|
|
8
|
+
|
|
9
|
+
```{python}
|
|
10
|
+
from tesserax import Canvas, Rect
|
|
11
|
+
|
|
12
|
+
# Initialize the canvas
|
|
13
|
+
with Canvas() as canvas:
|
|
14
|
+
# Adding our first shape
|
|
15
|
+
# Primitives support standard SVG attributes like fill
|
|
16
|
+
Rect(100, 50, fill="lightblue")
|
|
17
|
+
|
|
18
|
+
# fit() adjusts the viewport to the content
|
|
19
|
+
# display() renders it directly in the documentation
|
|
20
|
+
canvas.fit(padding=10).display()
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Adding and Transforming Shapes
|
|
25
|
+
|
|
26
|
+
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.
|
|
27
|
+
|
|
28
|
+
```{python}
|
|
29
|
+
from tesserax import Canvas, Rect, Circle
|
|
30
|
+
|
|
31
|
+
with Canvas() as canvas:
|
|
32
|
+
Rect(100, 50, fill="lightblue")
|
|
33
|
+
|
|
34
|
+
# Adding a second shape and moving it manually
|
|
35
|
+
# Circle is defined by its radius
|
|
36
|
+
Circle(30, fill="salmon").translated(150, 25)
|
|
37
|
+
|
|
38
|
+
canvas.fit(padding=10).display()
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Simplifying with Layouts
|
|
43
|
+
|
|
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`.
|
|
45
|
+
|
|
46
|
+
```{python}
|
|
47
|
+
from tesserax import Canvas, Rect, Circle
|
|
48
|
+
from tesserax.layout import Row
|
|
49
|
+
|
|
50
|
+
with Canvas() as canvas:
|
|
51
|
+
# Row is also a context manager
|
|
52
|
+
with Row(gap=20):
|
|
53
|
+
Rect(100, 50, fill="lightblue")
|
|
54
|
+
Circle(30, fill="salmon")
|
|
55
|
+
|
|
56
|
+
canvas.fit(padding=10).display()
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Composing Transforms and Groups
|
|
61
|
+
|
|
62
|
+
Because Layouts are themselves a type of `Group`, you can apply transformations to the entire layout at once. In this step, we will:
|
|
63
|
+
|
|
64
|
+
1. Create a `Row` layout.
|
|
65
|
+
2. Rotate that entire row by 45 degrees.
|
|
66
|
+
3. Add a new shape outside of that row.
|
|
67
|
+
4. Wrap everything in another `Group` to demonstrate hierarchical composition.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
```{python}
|
|
71
|
+
from tesserax import Canvas, Rect, Circle, Group
|
|
72
|
+
from tesserax.layout import Row
|
|
73
|
+
|
|
74
|
+
with Canvas() as canvas:
|
|
75
|
+
# Create and rotate a layout
|
|
76
|
+
with Row(gap=20).rotated(45):
|
|
77
|
+
Rect(100, 50, fill="lightblue")
|
|
78
|
+
Circle(30, fill="salmon")
|
|
79
|
+
|
|
80
|
+
# Add another shape to the scene
|
|
81
|
+
Rect(40, 40, fill="lightgreen").translated(100, -50)
|
|
82
|
+
|
|
83
|
+
# All shapes here are already part of the Canvas group.
|
|
84
|
+
|
|
85
|
+
canvas.fit(padding=10).display()
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
By nesting Layouts and Groups, you can build extremely complex diagrams without ever having to manually compute a single SVG coordinate string.
|
|
90
|
+
|
|
91
|
+
## Bridging Contexts with Anchors
|
|
92
|
+
|
|
93
|
+
One of the most powerful features of Tesserax is the **Anchor System**. When you place a shape inside a `Layout` or a `Group`, its local coordinates change to reflect its position within that container. However, Tesserax allows you to retrieve the "global" position of a shape's anchors, making it trivial to connect objects across different coordinate systems.
|
|
94
|
+
|
|
95
|
+
In this example, we will connect the `Circle` (which is inside a rotated `Row`) to the `Rect` (which is outside) using an `Arrow`, and the two shapes inside the row.
|
|
96
|
+
|
|
97
|
+
```{python}
|
|
98
|
+
from tesserax import Canvas, Rect, Circle, Arrow
|
|
99
|
+
from tesserax.layout import Row
|
|
100
|
+
|
|
101
|
+
with Canvas() as canvas:
|
|
102
|
+
# 1. Create a Row and rotate it
|
|
103
|
+
with Row(gap=40) as row:
|
|
104
|
+
r1 = Rect(80, 40, fill="aliceblue")
|
|
105
|
+
c1 = Circle(30, fill="mistyrose")
|
|
106
|
+
|
|
107
|
+
row.rotated(30).translated(50, 50)
|
|
108
|
+
|
|
109
|
+
# 2. Create a target rectangle outside the layout
|
|
110
|
+
target = Rect(60, 60, fill="honeydew").translated(250, 0)
|
|
111
|
+
|
|
112
|
+
# 3. Connect them!
|
|
113
|
+
# We use .anchor() to get semantic points.
|
|
114
|
+
# Even though c1 is inside a rotated row, c1.anchor("right")
|
|
115
|
+
# returns the correct global coordinate for the arrow.
|
|
116
|
+
Arrow(
|
|
117
|
+
c1.anchor("top").dy(5),
|
|
118
|
+
target.anchor("left").dx(-5),
|
|
119
|
+
stroke="grey",
|
|
120
|
+
width=2,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
Arrow(
|
|
124
|
+
r1.anchor("right").dx(5),
|
|
125
|
+
c1.anchor("left").dx(-5),
|
|
126
|
+
stroke="grey",
|
|
127
|
+
width=2,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
canvas.fit(padding=10).display()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### How Coordinate Mapping Works
|
|
134
|
+
|
|
135
|
+
When you call `shape.anchor(name)`, Tesserax performs the following behind the scenes:
|
|
136
|
+
|
|
137
|
+
1. **Local Geometry**: It identifies the point on the shape (e.g., the rightmost edge of the `Circle`).
|
|
138
|
+
2. **Transformation Path**: It traverses up the hierarchy (from the `Circle` to the `Row`, then to the `Canvas`), applying every rotation, scale, and translation encountered along the way.
|
|
139
|
+
3. **Global Result**: It returns a `Point` that represents exactly where that anchor sits on the final SVG canvas.
|
|
140
|
+
|
|
141
|
+
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
|
+
|
|
143
|
+
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.
|