Skip to main content

Generate LaTeX tables and figures with glom-style specs

Project description

texer

Generate LaTeX tables and figures (PGFPlots) with Python using a glom-style spec system.

Installation

pip install -e .

Mental Model

texer follows a simple principle: describe your LaTeX structure once, then fill it with any data.

Instead of writing string templates or manually building LaTeX, you define:

  1. The structure of your table or plot using Python classes
  2. Specs that describe where data comes from
  3. Your data as plain Python dicts/lists

texer then evaluates the specs against your data to produce valid LaTeX.

Structure + Specs + Data → LaTeX

Core Concepts

Specs: Describing Data Access

Specs are lazy descriptors that tell texer how to extract and transform data.

from texer import Ref, Iter, Format, Cond, Raw

# Ref: Access data by path (glom-style dot notation)
Ref("name")              # data["name"]
Ref("user.email")        # data["user"]["email"]
Ref("items.0.value")     # data["items"][0]["value"]
Ref("x", default=0)      # with default value

# Iter: Loop over collections
Iter(Ref("rows"), template=Row(...))  # iterate and apply template
Iter(Ref("points"), x=Ref("x"), y=Ref("y"))  # extract coordinates

# Format: Python format specs
Format(Ref("price"), ".2f")   # "3.14"
Format(Ref("ratio"), ".1%")   # "25.0%"

# Cond: Conditional logic
Cond(Ref("x") > 5, "high", "low")
Cond(Ref("active"), Raw(r"\checkmark"), "")

# Raw: Unescaped LaTeX
Raw(r"\textbf{bold}")
Raw(r"\hline")

The Evaluation Flow

1. You define structure with Specs embedded
2. Call evaluate(structure, data)
3. texer walks the structure, resolving Specs against data
4. LaTeX strings are produced with proper escaping

Building Tables

Mental Model for Tables

Think of tables as nested structures:

Table (floating environment)
└── Tabular (the actual grid)
    ├── Header (Row)
    └── Body (list of Rows or Iter)
        └── Cells (values, Refs, or Cell objects)

Basic Table

from texer import Table, Tabular, Row, evaluate

# Define structure
table = Table(
    Tabular(
        columns="lcc",  # left, center, center
        header=Row("Name", "Value", "Unit"),
        rows=[
            Row("Length", "42", "m"),
            Row("Mass", "100", "kg"),
        ],
        toprule=True,
        bottomrule=True,
    ),
    caption="Physical Properties",
    label="tab:properties",
)

# Generate LaTeX (no data needed for static tables)
print(evaluate(table, {}))

Output:

\begin{table}[htbp]
  \centering
  \caption{Physical Properties}
  \label{tab:properties}
  \begin{tabular}{lcc}
    \toprule
    Name & Value & Unit \\
    \midrule
    Length & 42 & m \\
    Mass & 100 & kg \\
    \bottomrule
  \end{tabular}
\end{table}

Data-Driven Table

The power comes from using Specs to pull data dynamically:

from texer import Table, Tabular, Row, Ref, Iter, Format, evaluate

# Define structure with Specs
table = Table(
    Tabular(
        columns="lcc",
        header=Row("Experiment", "Result", "Error"),
        rows=Iter(
            Ref("experiments"),  # iterate over data["experiments"]
            template=Row(
                Ref("name"),                    # each item's "name"
                Format(Ref("result"), ".3f"),   # formatted number
                Format(Ref("error"), ".1%"),    # as percentage
            )
        ),
        toprule=True,
        bottomrule=True,
    ),
    caption=Ref("table_title"),  # dynamic caption
    label="tab:results",
)

# Provide data
data = {
    "table_title": "Experimental Results",
    "experiments": [
        {"name": "Trial A", "result": 3.14159, "error": 0.023},
        {"name": "Trial B", "result": 2.71828, "error": 0.015},
        {"name": "Trial C", "result": 1.41421, "error": 0.042},
    ]
}

print(evaluate(table, data))

Conditional Formatting

Use Cond for conditional styling:

from texer import Tabular, Row, Cell, Ref, Iter, Cond, Raw, Format, evaluate

table = Tabular(
    columns="lcc",
    header=Row("Test", "Score", "Status"),
    rows=Iter(
        Ref("tests"),
        template=Row(
            Ref("name"),
            Format(Ref("score"), ".1f"),
            # Conditional: green PASS if score >= 70, else red FAIL
            Cond(
                Ref("score") >= 70,
                Raw(r"\textcolor{green}{PASS}"),
                Raw(r"\textcolor{red}{FAIL}"),
            ),
        )
    ),
    toprule=True,
    bottomrule=True,
)

data = {
    "tests": [
        {"name": "Unit Tests", "score": 95.5},
        {"name": "Integration", "score": 62.0},
        {"name": "E2E Tests", "score": 88.2},
    ]
}

print(evaluate(table, data))

Cell Formatting

Use Cell for fine-grained control:

from texer import Row, Cell, Ref, Cond

Row(
    Cell(Ref("name"), bold=True),                    # always bold
    Cell(Ref("value"), italic=True),                 # always italic
    Cell(Ref("x"), bold=Cond(Ref("important"), True, False)),  # conditional bold
)

Multi-column and Multi-row

from texer import Row, MultiColumn, MultiRow

# Spanning columns
Row(MultiColumn(3, "c", "Header Spanning 3 Columns"))

# Spanning rows (requires multirow package)
Row(MultiRow(2, "Category"), "Value 1", "Unit 1")

Building PGFPlots

Mental Model for Plots

Think of plots as nested structures:

Single Plot:

PGFPlot (tikzpicture wrapper)
└── Axis (the coordinate system)
    ├── Options (xlabel, ylabel, limits, grid, ...)
    ├── Plots (list of AddPlot)
    │   └── AddPlot
    │       ├── Style (color, mark, line style)
    │       └── Data (Coordinates or expression)
    └── Legend

Multiple Subplots (GroupPlot):

PGFPlot (tikzpicture wrapper)
└── GroupPlot (grid layout container)
    ├── Group Options (size, spacing, label positioning)
    └── Plots (list of NextGroupPlot)
        └── NextGroupPlot (individual subplot)
            ├── Options (xlabel, ylabel, title, ...)
            ├── Plots (list of AddPlot)
            └── Legend

Basic Plot with Static Data

from texer import PGFPlot, Axis, AddPlot, Coordinates, evaluate

plot = PGFPlot(
    Axis(
        xlabel="Time (s)",
        ylabel="Distance (m)",
        plots=[
            AddPlot(
                color="blue",
                mark="*",
                coords=Coordinates([(0, 0), (1, 2), (2, 8), (3, 18)]),
            )
        ],
    )
)

print(evaluate(plot, {}))

Output:

\begin{tikzpicture}
  \begin{axis}[xlabel={Time (s)}, ylabel={Distance (m)}]
    \addplot[color=blue, mark=*] coordinates {(0, 0) (1, 2) (2, 8) (3, 18)};
  \end{axis}
\end{tikzpicture}

Data-Driven Plot

from texer import PGFPlot, Axis, AddPlot, Coordinates, Ref, Iter, evaluate

plot = PGFPlot(
    Axis(
        xlabel=Ref("x_label"),
        ylabel=Ref("y_label"),
        title=Ref("title"),
        legend_pos="north west",
        grid=True,
        plots=[
            AddPlot(
                color="blue",
                mark="*",
                coords=Coordinates(
                    Iter(Ref("measurements"), x=Ref("time"), y=Ref("value"))
                ),
            )
        ],
        legend=[Ref("series_name")],
    )
)

data = {
    "title": "Temperature Over Time",
    "x_label": "Time (hours)",
    "y_label": "Temperature (°C)",
    "series_name": "Sensor 1",
    "measurements": [
        {"time": 0, "value": 20.5},
        {"time": 1, "value": 22.3},
        {"time": 2, "value": 25.1},
        {"time": 3, "value": 23.8},
    ]
}

print(evaluate(plot, data))

Multiple Series

from texer import PGFPlot, Axis, AddPlot, Coordinates, Ref, Iter, evaluate

plot = PGFPlot(
    Axis(
        xlabel="X",
        ylabel="Y",
        legend_pos="south east",
        plots=[
            AddPlot(
                color="blue",
                mark="*",
                coords=Coordinates(
                    Iter(Ref("series_a"), x=Ref("x"), y=Ref("y"))
                ),
            ),
            AddPlot(
                color="red",
                mark="square*",
                style="dashed",
                coords=Coordinates(
                    Iter(Ref("series_b"), x=Ref("x"), y=Ref("y"))
                ),
            ),
        ],
        legend=["Series A", "Series B"],
    )
)

data = {
    "series_a": [{"x": 0, "y": 1}, {"x": 1, "y": 4}, {"x": 2, "y": 9}],
    "series_b": [{"x": 0, "y": 2}, {"x": 1, "y": 3}, {"x": 2, "y": 5}],
}

print(evaluate(plot, data))

Using NumPy Arrays

Coordinates supports direct creation from NumPy arrays (or plain Python lists) using separate x and y parameters:

import numpy as np
from texer import PGFPlot, Axis, AddPlot, Coordinates, evaluate

# Generate data with NumPy
x = np.linspace(0, 2*np.pi, 100)
y1 = np.sin(x)
y2 = np.cos(x)

plot = PGFPlot(
    Axis(
        xlabel="$x$",
        ylabel="$f(x)$",
        grid=True,
        legend_pos="south east",
        plots=[
            AddPlot(
                color="blue",
                thick=True,
                no_marks=True,
                coords=Coordinates(x=x, y=y1),  # Direct from NumPy arrays!
            ),
            AddPlot(
                color="red",
                thick=True,
                no_marks=True,
                style="dashed",
                coords=Coordinates(x=x, y=y2),
            ),
        ],
        legend=[r"$\sin(x)$", r"$\cos(x)$"],
    )
)

print(evaluate(plot, {}))

This also works with plain Python lists:

x = [0, 1, 2, 3, 4]
y = [0, 1, 4, 9, 16]
coords = Coordinates(x=x, y=y)

And for 3D coordinates:

coords = Coordinates(x=[0, 1, 2], y=[0, 1, 2], z=[0, 1, 4])

Mathematical Expressions

For function plots, use expressions instead of coordinates:

from texer import PGFPlot, Axis, AddPlot, evaluate

plot = PGFPlot(
    Axis(
        xlabel="$x$",
        ylabel="$f(x)$",
        xmin=-5, xmax=5,
        ymin=-1.5, ymax=1.5,
        grid=True,
        plots=[
            AddPlot(
                expression="sin(deg(x))",
                domain="-5:5",
                samples=100,
                color="blue",
                thick=True,
                no_marks=True,
            ),
            AddPlot(
                expression="cos(deg(x))",
                domain="-5:5",
                samples=100,
                color="red",
                style="dashed",
                no_marks=True,
            ),
        ],
        legend=[r"$\sin(x)$", r"$\cos(x)$"],
    )
)

print(evaluate(plot, {}))

GroupPlots: Multiple Subplots

Use GroupPlot to create multiple plots in a grid layout:

from texer import PGFPlot, GroupPlot, NextGroupPlot, AddPlot, Coordinates, Ref, Iter, evaluate

plot = PGFPlot(
    GroupPlot(
        group_size="2 by 2",  # 2x2 grid
        width="5cm",
        height="4cm",
        horizontal_sep="1.5cm",
        vertical_sep="1.5cm",
        plots=[
            NextGroupPlot(
                title="Temperature",
                xlabel="Time (s)",
                ylabel="Temp (K)",
                grid=True,
                plots=[
                    AddPlot(
                        color="blue",
                        mark="*",
                        coords=Coordinates(
                            Iter(Ref("temp_data"), x=Ref("t"), y=Ref("temp"))
                        ),
                    )
                ],
            ),
            NextGroupPlot(
                title="Pressure",
                xlabel="Time (s)",
                ylabel="Pressure (Pa)",
                grid=True,
                plots=[
                    AddPlot(
                        color="red",
                        mark="square*",
                        coords=Coordinates(
                            Iter(Ref("pressure_data"), x=Ref("t"), y=Ref("p"))
                        ),
                    )
                ],
            ),
            NextGroupPlot(
                title="Volume",
                plots=[...],
            ),
            NextGroupPlot(
                title="Density",
                plots=[...],
            ),
        ],
    )
)

data = {
    "temp_data": [{"t": 0, "temp": 300}, {"t": 1, "temp": 320}],
    "pressure_data": [{"t": 0, "p": 101325}, {"t": 1, "p": 102000}],
}

print(evaluate(plot, data))

GroupPlot Options

GroupPlot(
    # Grid layout
    group_size="2 by 2",  # "columns by rows"

    # Spacing
    horizontal_sep="1cm",
    vertical_sep="1cm",

    # Label positioning (to avoid repetition)
    xlabels_at="edge bottom",  # only show x labels at bottom edge
    ylabels_at="edge left",    # only show y labels at left edge

    # Common options for all subplots
    width="6cm",
    height="4cm",
    xmin=0, xmax=10,

    # List of subplots
    plots=[NextGroupPlot(...), NextGroupPlot(...), ...],
)

Axis Options Reference

Axis(
    # Labels
    xlabel="X Label",
    ylabel="Y Label",
    zlabel="Z Label",      # for 3D
    title="Plot Title",

    # Limits
    xmin=0, xmax=10,
    ymin=-5, ymax=5,

    # Legend
    legend=["A", "B"],
    legend_pos="north west",  # north west, south east, outer north east, etc.

    # Grid
    grid=True,              # or "major", "minor", "both"

    # Dimensions
    width="10cm",
    height="6cm",

    # Axis type
    axis_type="axis",       # or "semilogxaxis", "semilogyaxis", "loglogaxis"

    # Raw options escape hatch
    _raw_options="some pgfplots option=value",
)

AddPlot Options Reference

AddPlot(
    # Data source (choose one)
    coords=Coordinates([...]),           # List of tuples
    coords=Coordinates(x=[...], y=[...]),  # Separate arrays (NumPy or lists)
    expression="x^2",                    # Mathematical expression

    # Expression options
    domain="0:10",
    samples=100,

    # Style
    color="blue",
    mark="*",               # *, o, square*, triangle*, etc.
    style="dashed",         # dashed, dotted, etc.
    line_width="1pt",
    thick=True,

    # Mark control
    only_marks=True,        # scatter plot
    no_marks=True,          # line only
    smooth=True,            # smooth curve

    # 3D
    surf=True,              # surface plot
    mesh=True,              # mesh plot

    # Raw options escape hatch
    _raw_options="some option",
)

Coordinates Options

# From list of tuples (original method)
Coordinates([(0, 1), (1, 2), (2, 4)])

# From separate x, y arrays (NumPy or lists)
Coordinates(x=[0, 1, 2], y=[1, 2, 4])
Coordinates(x=np.array([...]), y=np.array([...]))

# 3D coordinates
Coordinates([(0, 0, 1), (1, 1, 2)])
Coordinates(x=[0, 1], y=[0, 1], z=[1, 2])

# Dynamic from data
Coordinates(Iter(Ref("points"), x=Ref("x"), y=Ref("y")))

Escape Hatches

When you need raw LaTeX control:

# Raw LaTeX in content
Raw(r"\textbf{bold} and \textit{italic}")

# Raw options on any element
Axis(..., _raw_options="extra pgf option=value")
AddPlot(..., _raw_options="mark options={fill=white}")
Tabular(..., _raw_options="@{}l@{}")

Quick Reference

Specs

Spec Purpose Example
Ref(path) Access data Ref("user.name")
Iter(source, template=...) Loop over list Iter(Ref("items"), template=Row(...))
Iter(source, x=..., y=...) Extract coordinates Iter(Ref("points"), x=Ref("x"), y=Ref("y"))
Format(value, fmt) Format value Format(Ref("x"), ".2f")
Cond(test, if_true, if_false) Conditional Cond(Ref("x") > 0, "pos", "neg")
Raw(latex) Unescaped LaTeX Raw(r"\hline")

Comparison Operators (for Cond)

Ref("x") > 5      # greater than
Ref("x") < 5      # less than
Ref("x") >= 5     # greater or equal
Ref("x") <= 5     # less or equal
Ref("x") == 5     # equal
Ref("x") != 5     # not equal

# Combine with & (and) and | (or)
(Ref("x") > 0) & (Ref("x") < 10)
(Ref("x") < 0) | (Ref("x") > 10)

Table Classes

Class Purpose
Table Floating table environment
Tabular The tabular grid
Row A table row
Cell A cell with formatting options
MultiColumn Cell spanning columns
MultiRow Cell spanning rows

Plot Classes

Class Purpose
PGFPlot tikzpicture wrapper
Axis Axis environment (single plot)
GroupPlot Group of plots in a grid layout
NextGroupPlot Individual subplot within a GroupPlot
AddPlot A single plot/series
Coordinates Data points for a plot
Legend Legend entries

Required LaTeX Packages

For tables with rules:

\usepackage{booktabs}

For colored text:

\usepackage{xcolor}

For plots:

\usepackage{pgfplots}
\pgfplotsset{compat=1.18}

For groupplots (multiple subplots):

\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
\usepgfplotslibrary{groupplots}

For multirow cells:

\usepackage{multirow}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

texer-0.3.0.tar.gz (30.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

texer-0.3.0-py3-none-any.whl (20.4 kB view details)

Uploaded Python 3

File details

Details for the file texer-0.3.0.tar.gz.

File metadata

  • Download URL: texer-0.3.0.tar.gz
  • Upload date:
  • Size: 30.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for texer-0.3.0.tar.gz
Algorithm Hash digest
SHA256 892c89ee61ba70bc5efcb8006d1e3aea8ff06c012b0de328faee9c004b443d51
MD5 0f2933682211866e56392d614813507a
BLAKE2b-256 23b8b80fff38985017a738da571cc8a57efca766c5c4fdecde9e20203bbeff9e

See more details on using hashes here.

File details

Details for the file texer-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: texer-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 20.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for texer-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b2929a558e1bb1b705cef8d6aeb4f814d4c2ef5824a02e57c297aed44ef89187
MD5 564b57c3d8eddcbecfca4d7b5c03979b
BLAKE2b-256 86457448dd4466af9ab498d667f791a7cf991a9c3833a288dcf16252cea57fb3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page