Programmable infographic generation powered by sympy and svg.py
Project description
Infogroove
Infogroove converts declarative template definitions into SVG infographics using sympy for formula evaluation and svg.py for SVG generation.
Quick Start
Create the virtual environment and install dependencies with uv:
uv sync
Render any of the bundled examples (each lives in its own subdirectory):
uv run infogroove -f examples/horizontal-bars/def.json -i examples/horizontal-bars/data.json -o examples/horizontal-bars/horizontal-bars.svg
uv run infogroove -f examples/stat-cards/def.json -i examples/stat-cards/data.json -o examples/stat-cards/stat-cards.svg
uv run infogroove -f examples/blue-parallelograms/def.json -i examples/blue-parallelograms/data.json -o examples/blue-parallelograms/blue-parallelograms.svg
uv run infogroove -f examples/arc-circles/def.json -i examples/arc-circles/data.json -o examples/arc-circles/arc-circles.svg
uv run infogroove -f examples/staggered-keywords/def.json -i examples/staggered-keywords/data.json -o examples/staggered-keywords/staggered-keywords.svg
Running Tests
Install development dependencies and execute the test suite with pytest:
uv sync --extra dev
uv run --extra dev pytest
To measure coverage locally you can add the --cov flag:
uv run --extra dev pytest --cov=infogroove --cov=tests
Example Gallery
| Template | Preview |
|---|---|
| Horizontal Bars | |
| Stat Cards | |
| Blue Parallelogram Bands | |
| Arc Circles | |
| Staggered Keywords | |
| Key Messages |
Template Overview
A template definition is a JSON document with these top-level keys. The design aims to keep templates declarative and predictable:
-
Explicit scopes. Global
propertiesestablish shared context, while element-levelletblocks create isolated overlays that run after any repeat bindings. Values never bleed across scope boundaries unless you intentionally rebind them. -
Deterministic evaluation. Element
letbindings resolve lazily the first time they are referenced. Cycles are detected and reported early, preventing runaway recursion and making intent obvious. -
Composable building blocks. Elements remain small, nested structures. Complex layouts emerge from combining scoped bindings and child trees rather than inventing a verbose DSL.
-
properties: Global assignments evaluated before rendering begins. Provide thecanvassize here (width,height) along with reusable constants such aspalette,margin, orfont_family. Values are injected into the rendering context as-is, so strings like"Inter, Arial, sans-serif"remain literal. -
template: A list of element descriptors. Each descriptor has atype, optional attribute map, optionaltext, optionallet, and optionalchildren. Elements render once unless arepeatblock is present. -
numElementsRange(optional): Expected minimum and maximum number of input records for validation.
Each element may declare its own let block. These bindings evaluate against
the current context (including repeat helpers) and the results become available
to the element's attributes and its children.
The repeat block explicitly controls iteration:
{
"type": "text",
"repeat": {
"items": "items",
"as": "row"
},
"let": {
"label": "row.label",
"x": "__index__ * 24"
},
"attributes": {"x": "{x}", "y": "40"},
"text": "{label}"
}
itemsreferences the collection to iterate (any dotted path resolved via the current context).asnames the current element. Use the reserved helpers (e.g.__index__,__count__) inside expressions when you need positional data; when the iterated item is a mapping, those helpers are also exposed on the alias (for example,row.__index__).- Element
letinjects per-iteration bindings scoped to that element. Expressions can reference the current item, previously declared loop bindings, and globals.
During iteration, Infogroove also injects reserved helpers such as __index__,
__first__, __last__, __count__, and __total__ for convenience.
Placeholder syntax supports both {path.to.value} lookups and inline Python
expressions such as {__index__ * 10} or {canvas.width / 2}. Expressions are
evaluated inside the same safe context as loop bindings (global properties,
data fields, derived metrics, and loop-scoped bindings).
Mixed casing identifiers (e.g. {items[0].myValue}) are resolved by
normalising to snake/camel case automatically, but adopting snake_case within
your own datasets keeps templates more predictable.
CLI Options
uv run infogroove --help
Key flags:
-f, --template: Path to the template definition JSON file (e.g.def.json).-i, --input: JSON file containing an array of data objects.-o, --output: Destination SVG path or-for stdout.
Programmatic Usage
Infogroove exposes a loader for integrating templates directly into Python applications:
from infogroove.loader import load
with open("examples/arc-circles/def.json", encoding="utf-8") as fh:
infographic = load(fh)
data = [{"label": "Alpha", "value": 3}]
svg_markup = infographic.render(data)
Prefer infogroove.loader.load for file objects and infogroove.loader.loads
when the template definition is already in memory as a string. Both helpers
return an InfogrooveRenderer, exposing the parsed template via the
template property for metadata inspection.
When you already have the JSON structure as a Python mapping, instantiate an
infographic directly with the Infogroove factory:
from infogroove import Infogroove
infographic = Infogroove({
"properties": {
"canvas": {"width": 200, "height": 40},
"gap": 10,
},
"template": [
{
"type": "circle",
"attributes": {"cx": "{__index__ * gap}", "cy": "20", "r": "5"},
"repeat": {"items": "data", "as": "item"}
}
],
})
svg_inline = infographic.render([{}] * 10)
Developing Templates
- Keep shared constants (including canvas dimensions) under the top-level
propertiesblock. - Use
repeatto make iteration explicit; push derived per-loop values into an element'sletblock so they stay scoped to that element. - Inline expressions handle quick maths (
{__index__ * 10}) while elementletbindings are ideal for shared or multi-step calculations. - Let bindings resolve lazily, so the order you declare keys does not matter.
However, circular definitions (e.g.
total: "max",max: "total") will be rejected with a clear error. Break cycles by lifting shared calculations into a new binding or restructuring the dependency chain.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file infogroove-0.3.1.tar.gz.
File metadata
- Download URL: infogroove-0.3.1.tar.gz
- Upload date:
- Size: 42.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b40cc3990a8727e599321222f1a5c2f2b145095636967ac6f9bdbdc2c923db8
|
|
| MD5 |
f076f0ff98eabedd7f3d06d9821d75db
|
|
| BLAKE2b-256 |
6b6a7d5d77a37c0ecd6ffc4a1a2327ce828ed6113a3bd4470a62fc4b6b27e6a3
|
File details
Details for the file infogroove-0.3.1-py3-none-any.whl.
File metadata
- Download URL: infogroove-0.3.1-py3-none-any.whl
- Upload date:
- Size: 18.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
75aff4d1efa65d89952229fe320b34f028a79de62e1d4c20a654df9cb21e9a93
|
|
| MD5 |
0db0069173850f696590fc71b7e74205
|
|
| BLAKE2b-256 |
99a56680efaa852473af79f7bce2170a03f1e745c0b42ff5d7bcde627e7668fa
|