Core Concepts & Primitives

Tesserax is designed to be a “thin layer” of geometric logic over SVG. Unlike plotting libraries that try to decide for you how axes and scales should work, Tesserax gives you a blank canvas and a set of precise drafting tools.

This guide covers the fundamental building blocks: the coordinate system, basic shapes, transformations, the anchor system, and the powerful Polyline API.

The Canvas and Coordinates

Every diagram begins with a Canvas. In Tesserax, the Canvas serves two roles:

  1. Container: It collects all shapes created within its context.
  2. Viewport: It manages the final SVG bounding box and coordinate mapping.

Mathematical Coordinate System

Tesserax follows the standard SVG Coordinate System:

  • The origin \((0, 0)\) is at the top-left corner (initially).
  • The X-axis points to the right (\(+x\)).
  • The Y-axis points down (\(+y\)).

This is crucial to remember when applying rotations. A positive rotation (counter-clockwise) moves the \(+x\) axis towards the \(+y\) axis (downwards on the screen).

Code
from tesserax import Canvas, Rect, Text
from tesserax.color import Colors

with Canvas() as canvas:
    # A simple reference grid
    Rect(100, 100, stroke=Colors.LightGray, fill=Colors.White)

    # Origin marker
    Text("(0,0)", size=20)

    # Positive X/Y indication
    Text("+X", size=20).translated(70, 0)
    Text("+Y", size=20).translated(0, 70)

canvas.fit(5).display()

Basic Primitives

Tesserax provides a suite of geometric atoms. All visual shapes inherit from the Visual class, which standardizes properties like fill, stroke, and width.

Rectangles and Circles

  • Rect(w, h): Defined by width and height. Centered at by default.
  • Square(size): A convenience wrapper for Rect.
  • Circle(r): Defined by radius. Centered at .
  • Ellipse(rx, ry): Defined by horizontal and vertical radii.
Code
from tesserax import Canvas, Rect, Square, Circle, Ellipse

with Canvas() as c:
    # Shapes are placed at (0,0) by default, so we translate them to separate them.
    Square(40, fill=Colors.LightBlue).translated(0, 0)
    Rect(60, 30, fill=Colors.LightGreen).translated(60, 0)
    Circle(20, fill=Colors.Salmon).translated(120, 0)
    Ellipse(20, 10, fill=Colors.Plum).translated(170, 0)

c.fit(10).display()

Text and Alignment

Text in SVG can be tricky due to alignment issues. Tesserax simplifies this by defaulting to center/middle alignment.

You can control this via:

  • anchor: Horizontal alignment ("start", "middle", "end").
  • baseline: Vertical alignment ("top", "middle", "bottom").
Code
from tesserax import Text, Line, Point

with Canvas() as c:
    # Draw a crosshair at the origin to show alignment
    Line(Point(0, -20), Point(0, 20), stroke=Colors.LightGray)
    Line(Point(-50, 0), Point(50, 0), stroke=Colors.LightGray)

    # Default Text (Centered)
    Text("Origin", size=16, fill=Colors.Black)

    # Custom alignment
    Text("Top-Left", anchor="end", baseline="bottom").translated(-20, -20)
    Text("Bottom-Right", anchor="start", baseline="top").translated(20, 20)

c.fit(10).display()

The Transformation Model

Every shape in Tesserax carries a Transform object. This defines an Affine Transformation that maps the shape’s local coordinate space to the parent space.

The operation order is strict: Scale - Rotate - Translate.

  1. Scale: The object is stretched.
  2. Rotate: The object is rotated around the (scaled) origin.
  3. Translate: The object is moved to its final position.

Tesserax provides a fluent API to chain these operations:

Code
from tesserax import Canvas, Rect, deg

with Canvas() as c:
    # 1. Create a rectangle at (0,0)
    r = Rect(50, 30, fill=Colors.LightBlue, stroke=Colors.Blue)

    # 2. Scale it by 1.5x
    # 3. Rotate it by 45 degrees
    # 4. Move it to (50, 50)
    r.scaled(1.5).rotated(deg(45)).translated(50, 50)

    # Show the origin for reference
    Circle(2, fill=Colors.Red).translated(50, 50)

c.fit(20).display()

The Anchor System

One of Tesserax’s most powerful features is semantic anchoring. Because shapes are often nested inside Groups or transformed heavily, calculating the exact coordinate of “the top-right corner of that rotated rectangle” is mathematically tedious.

The .anchor(name) method solves this by resolving the local coordinate through the entire transformation stack.

Available anchors: center, top, bottom, left, right, topleft, topright, bottomleft, bottomright.

Code
from tesserax import Arrow

with Canvas() as c:
    # A generic shape
    obj = Rect(60, 40, fill=Colors.Honeydew, stroke=Colors.Green)
    obj.rotated(deg(30)).translated(50, 50)

    # A target point
    target = Circle(5, fill=Colors.Red).translated(150, 50)

    # Connect the top-right corner of the rotated rect to the target
    # We don't need to know where "top-right" is in global space,
    # .anchor() calculates it.
    Arrow(obj.anchor("topright"), target.anchor("center"))

c.fit(20).display()

Paths and Lines

For arbitrary drawing, Tesserax offers both low-level and high-level path tools.

The Low-Level Path

The Path class maps directly to SVG commands (M, L, C, Q, Z).

Code
from tesserax import Path

with Canvas() as c:
    p = Path(fill=Colors.Transparent, stroke=Colors.Blue, width=2)
    p.jump_to(0, 0)
    p.line_to(50, 50)
    p.quadratic_to(100, 50, 100, 0) # Control point (100,50), End (100,0)

c.fit(10).display()

Lines and Arrows

Line and Arrow are specialized paths that connect two points. They support a curvature parameter to create arcs without manually calculating Bezier control points.

Code
from tesserax import Line

with Canvas() as c:
    start, end = Point(0, 0), Point(100, 0)

    Line(start, end, curvature=0, stroke=Colors.LightGray) # Straight
    Line(start, end, curvature=0.5, stroke=Colors.Blue)   # Arc Up
    Arrow(start, end, curvature=-0.5, stroke=Colors.Red)  # Arc Down

c.fit(30).display()

The Polyline API

The Polyline is a versatile tool for creating complex polygons, wireframes, and smooth curves. It maintains a list of Point objects and renders them sequentially.

Smoothing and Rounding

The smoothness parameter (\(0\) to \(1\)) automatically converts sharp vertices into rounded corners using quadratic Bezier interpolation.

Code
from tesserax import Polyline

pts = [Point(0, 0), Point(50, 50), Point(100, 0), Point(150, 50)]

with Canvas() as c:
    # Sharp (0.0)
    Polyline(pts, smoothness=0, stroke=Colors.Black).translated(0, 0)

    # Rounded (0.5)
    Polyline(pts, smoothness=0.5, stroke=Colors.Blue).translated(0, 60)

    # Fluid (1.0)
    Polyline(pts, smoothness=1.0, stroke=Colors.Red).translated(0, 120)

c.fit(10).display()

Procedural Generation

The Polyline class includes powerful methods to procedurally generate and manipulate shapes.

  • Polyline.poly(n, radius): Creates a regular n-gon (triangle, hexagon, etc.).
  • .subdivide(n): Inserts midpoints between every segment, increasing resolution.
  • .simplify(tolerance): Removes points that are collinear or redundant.
  • .expand(delta): Pushes points away from the origin (inflation).
  • .apply(func): Maps a function over every point.
Code
import math

with Canvas() as c:
    # 1. Create a Hexagon
    hex = Polyline.poly(n=6, radius=40, fill=Colors.AliceBlue)
    hex.translated(50, 50)

    # 2. Create a "Wobbly" Circle
    # Start with a simple polygon, subdivide it to get many vertices,
    # then apply a noise function to the radius.
    blob = Polyline.poly(n=8, radius=40, fill=Colors.MistyRose, smoothness=1)
    blob.subdivide(3) # Increase vertex count 8 -> 16 -> 32 -> 64

    # Apply a wave function to distort it
    def distort(p: Point) -> Point:
        # Get angle of point
        theta = math.atan2(p.y, p.x)
        # Vary radius based on angle
        wave = math.sin(theta * 5) * 5
        return p + p.normalize() * wave

    blob.apply(distort).simplify(0.1)
    blob.translated(150, 50)

    # 3. Contract/Expand
    # Useful for creating outlines or offsets
    base = Polyline.poly(4, 30, stroke=Colors.Black)
    inner = base.clone().contract(5) # Shrink

    base.translated(250, 50)
    inner.translated(250, 50)

c.fit(10).display()

Sketchy Drawings

And since mathematical drawings are incomplete without an XKCD-style sketchy mode, Tesserax also provides a (still kinda ugly) implementation for sketches. You can use it just as a regular group, but everything drawn inside will be sketched (if possible).

Code
from tesserax.sketch import Sketch

with Canvas() as canvas:
    with Sketch() as g:
        r = Rect(50, 100, stroke=Colors.Red, width=2)
        c = Circle(50, stroke=Colors.Green, width=2)
        q = Square(75, stroke=Colors.Blue, width=2)

    g.distribute("horizontal", gap=25)

canvas.fit(10).display()

Not all Tesserax primitives can be sketched. For this to work, we need to be able to compute Paths for a given shape, which is not possible for non-procedural shapes like Text and Sprite. Also, curved shapes like Arrow are not supported yet, but you can subsititute those with custom Polyline with end markers. Also, sketches currently don’t work with fill colors.