figkit 0.1.0__py3-none-any.whl
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.
- figkit/__init__.py +114 -0
- figkit/audit.py +611 -0
- figkit/colors.py +304 -0
- figkit/component.py +110 -0
- figkit/components.py +727 -0
- figkit/connectors.py +689 -0
- figkit/core.py +1021 -0
- figkit/export.py +301 -0
- figkit/figure.py +312 -0
- figkit/fonts.py +462 -0
- figkit/frame.py +376 -0
- figkit/geom.py +497 -0
- figkit/image.py +308 -0
- figkit/layout.py +529 -0
- figkit/mathtext.py +320 -0
- figkit/paint.py +136 -0
- figkit/py.typed +0 -0
- figkit/shapes.py +764 -0
- figkit/style.py +502 -0
- figkit/svgdoc.py +133 -0
- figkit/svgpath.py +470 -0
- figkit/text.py +625 -0
- figkit/themes.py +163 -0
- figkit-0.1.0.dist-info/METADATA +277 -0
- figkit-0.1.0.dist-info/RECORD +28 -0
- figkit-0.1.0.dist-info/WHEEL +5 -0
- figkit-0.1.0.dist-info/licenses/LICENSE +21 -0
- figkit-0.1.0.dist-info/top_level.txt +1 -0
figkit/themes.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Ready-made themes. Start from one and ``.derive(...)`` your own."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .style import DEFAULT_THEME, MONO, SERIF, Style, Theme
|
|
6
|
+
|
|
7
|
+
__all__ = ["PAPER", "SLIDE", "DARK", "BLUEPRINT", "MINIMAL", "SOFT", "THEMES",
|
|
8
|
+
"get_theme"]
|
|
9
|
+
|
|
10
|
+
#: Muted, serif-labelled look for LaTeX papers.
|
|
11
|
+
PAPER = DEFAULT_THEME.derive(
|
|
12
|
+
name="paper",
|
|
13
|
+
font_family=SERIF,
|
|
14
|
+
font_size=12,
|
|
15
|
+
color="#111111",
|
|
16
|
+
stroke_width=1.1,
|
|
17
|
+
radius=3,
|
|
18
|
+
palette={
|
|
19
|
+
"text": "#111111", "muted": "#555555", "line": "#222222",
|
|
20
|
+
"surface": "#ffffff", "surface_alt": "#f0f0f0",
|
|
21
|
+
"primary": "#3B6EA5", "primary_soft": "#cfe0f2",
|
|
22
|
+
"accent": "#B5622A", "accent_soft": "#f2dcc9",
|
|
23
|
+
"good": "#3E7C4B", "good_soft": "#d6e8da", "bad": "#A93B3B",
|
|
24
|
+
},
|
|
25
|
+
box=Style(fill="#ffffff", stroke="#222222", stroke_width=1.1,
|
|
26
|
+
padding=(6, 10), radius=3),
|
|
27
|
+
arrow=Style(stroke="#222222", stroke_width=1.1, head_size=7),
|
|
28
|
+
panel=Style(stroke="#888888", stroke_width=0.9, radius=4),
|
|
29
|
+
label=Style(font_size=10.5, color="#333333"),
|
|
30
|
+
styles={
|
|
31
|
+
"block": Style(fill="#f2f2f2", stroke="#111111", stroke_width=1.2),
|
|
32
|
+
"blue": Style(fill="#cfe0f2", stroke="#3B6EA5"),
|
|
33
|
+
"green": Style(fill="#d6e8da", stroke="#3E7C4B"),
|
|
34
|
+
"warm": Style(fill="#f2dcc9", stroke="#B5622A"),
|
|
35
|
+
"ghost": Style(fill="none", stroke="#888888", stroke_dash="dashed"),
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
#: Bigger type and chunkier strokes, for slides and posters.
|
|
40
|
+
SLIDE = DEFAULT_THEME.derive(
|
|
41
|
+
name="slide",
|
|
42
|
+
font_size=19,
|
|
43
|
+
stroke_width=2.2,
|
|
44
|
+
radius=10,
|
|
45
|
+
box=Style(padding=(12, 18), stroke_width=2.2, radius=10),
|
|
46
|
+
arrow=Style(stroke_width=2.4, head_size=12),
|
|
47
|
+
label=Style(font_size=15),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
#: Light-on-dark, for dark-mode blog posts.
|
|
51
|
+
DARK = DEFAULT_THEME.derive(
|
|
52
|
+
name="dark",
|
|
53
|
+
color="#e8eaed",
|
|
54
|
+
background="#141821",
|
|
55
|
+
stroke_width=1.5,
|
|
56
|
+
palette={
|
|
57
|
+
"text": "#e8eaed", "muted": "#9aa4b2", "line": "#8b95a5",
|
|
58
|
+
"surface": "#1b1f27", "surface_alt": "#252b36",
|
|
59
|
+
"primary": "#7aa2f7", "primary_soft": "#26344f",
|
|
60
|
+
"accent": "#e0af68", "accent_soft": "#3d3222",
|
|
61
|
+
"good": "#9ece6a", "good_soft": "#2b3a25", "bad": "#f7768e",
|
|
62
|
+
},
|
|
63
|
+
box=Style(fill="#252b36", stroke="#8b95a5", color="#e8eaed"),
|
|
64
|
+
text=Style(color="#e8eaed"),
|
|
65
|
+
label=Style(color="#c3cad6"),
|
|
66
|
+
arrow=Style(stroke="#aab4c4"),
|
|
67
|
+
panel=Style(stroke="#4a5364", fill="none"),
|
|
68
|
+
matrix=Style(stroke="#1b1f27"),
|
|
69
|
+
axis=Style(stroke="#9aa4b2"),
|
|
70
|
+
grid=Style(stroke="#333a47"),
|
|
71
|
+
styles={
|
|
72
|
+
"block": Style(fill="#2c3340", stroke="#aab4c4"),
|
|
73
|
+
"blue": Style(fill="#26344f", stroke="#7aa2f7"),
|
|
74
|
+
"green": Style(fill="#2b3a25", stroke="#9ece6a"),
|
|
75
|
+
"warm": Style(fill="#3d3222", stroke="#e0af68"),
|
|
76
|
+
"ghost": Style(fill="none", stroke="#5c6675", stroke_dash="dashed"),
|
|
77
|
+
"card": Style(fill="#222834", stroke="#39414f", radius=12),
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
#: Technical-drawing look: thin monochrome lines on a tinted ground.
|
|
82
|
+
BLUEPRINT = DEFAULT_THEME.derive(
|
|
83
|
+
name="blueprint",
|
|
84
|
+
background="#0b2447",
|
|
85
|
+
font_family=MONO,
|
|
86
|
+
font_size=12,
|
|
87
|
+
color="#dbeafe",
|
|
88
|
+
stroke_width=1.0,
|
|
89
|
+
radius=0,
|
|
90
|
+
palette={"text": "#dbeafe", "line": "#93c5fd", "surface": "#0f2f5c",
|
|
91
|
+
"primary": "#93c5fd", "muted": "#7ea6d8"},
|
|
92
|
+
box=Style(fill="none", stroke="#93c5fd", color="#dbeafe", radius=0),
|
|
93
|
+
arrow=Style(stroke="#93c5fd", head="open", head_size=8),
|
|
94
|
+
panel=Style(stroke="#5b8fc7", stroke_dash="dashed"),
|
|
95
|
+
text=Style(color="#dbeafe"),
|
|
96
|
+
label=Style(color="#bfdbfe", font_size=10),
|
|
97
|
+
styles={
|
|
98
|
+
"block": Style(fill="none", stroke="#93c5fd", stroke_width=1.4),
|
|
99
|
+
"blue": Style(fill="#173f70", stroke="#93c5fd"),
|
|
100
|
+
"green": Style(fill="#134e4a", stroke="#5eead4"),
|
|
101
|
+
"warm": Style(fill="#4a3417", stroke="#fbbf24"),
|
|
102
|
+
"ghost": Style(fill="none", stroke="#5b8fc7", stroke_dash="dashed"),
|
|
103
|
+
"plain": Style(fill="none", stroke="none"),
|
|
104
|
+
"card": Style(fill="none", stroke="#5b8fc7", radius=0),
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
#: No fills, no rounding — just hairlines and text.
|
|
109
|
+
MINIMAL = DEFAULT_THEME.derive(
|
|
110
|
+
name="minimal",
|
|
111
|
+
stroke_width=1.0,
|
|
112
|
+
radius=0,
|
|
113
|
+
box=Style(fill="none", stroke="#111111", stroke_width=1.0, radius=0),
|
|
114
|
+
arrow=Style(stroke="#111111", stroke_width=1.0, head="open", head_size=8),
|
|
115
|
+
panel=Style(stroke="#aaaaaa", stroke_width=0.8, radius=0),
|
|
116
|
+
styles={
|
|
117
|
+
"block": Style(fill="none", stroke="#111111", stroke_width=1.4),
|
|
118
|
+
"blue": Style(fill="none", stroke="#3B6EA5", stroke_width=1.4),
|
|
119
|
+
"green": Style(fill="none", stroke="#3E7C4B", stroke_width=1.4),
|
|
120
|
+
"warm": Style(fill="none", stroke="#B5622A", stroke_width=1.4),
|
|
121
|
+
"ghost": Style(fill="none", stroke="#aaaaaa", stroke_dash="dashed"),
|
|
122
|
+
"plain": Style(fill="none", stroke="none"),
|
|
123
|
+
"card": Style(fill="none", stroke="#cccccc", radius=0),
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
#: Rounded pastel cards with soft shadows, for product / blog diagrams.
|
|
128
|
+
SOFT = DEFAULT_THEME.derive(
|
|
129
|
+
name="soft",
|
|
130
|
+
font_size=14,
|
|
131
|
+
radius=12,
|
|
132
|
+
stroke_width=1.0,
|
|
133
|
+
palette={"surface": "#ffffff", "surface_alt": "#f6f7f9",
|
|
134
|
+
"primary": "#6366f1", "primary_soft": "#e0e7ff",
|
|
135
|
+
"accent": "#f97316", "accent_soft": "#ffedd5",
|
|
136
|
+
"good": "#10b981", "good_soft": "#d1fae5", "line": "#64748b"},
|
|
137
|
+
box=Style(fill="#ffffff", stroke="#e2e5ea", stroke_width=1.0, radius=12,
|
|
138
|
+
padding=(12, 16), shadow={"dy": 2, "blur": 6, "opacity": 0.12}),
|
|
139
|
+
arrow=Style(stroke="#94a3b8", stroke_width=1.6, head="stealth"),
|
|
140
|
+
panel=Style(fill="#f6f7f9", stroke="none", radius=16, padding=20),
|
|
141
|
+
styles={
|
|
142
|
+
"block": Style(fill="#f6f7f9", stroke="#e2e5ea"),
|
|
143
|
+
"blue": Style(fill="#e0e7ff", stroke="#c7d2fe"),
|
|
144
|
+
"green": Style(fill="#d1fae5", stroke="#a7f3d0"),
|
|
145
|
+
"warm": Style(fill="#ffedd5", stroke="#fed7aa"),
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
THEMES = {
|
|
150
|
+
"default": DEFAULT_THEME, "figkit": DEFAULT_THEME,
|
|
151
|
+
"paper": PAPER, "slide": SLIDE, "dark": DARK,
|
|
152
|
+
"blueprint": BLUEPRINT, "minimal": MINIMAL, "soft": SOFT,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_theme(name) -> Theme:
|
|
157
|
+
"""Look up a built-in theme by name (or pass a Theme straight through)."""
|
|
158
|
+
if isinstance(name, Theme):
|
|
159
|
+
return name
|
|
160
|
+
key = str(name).lower()
|
|
161
|
+
if key not in THEMES:
|
|
162
|
+
raise KeyError(f"unknown theme {name!r}; available: {sorted(THEMES)}")
|
|
163
|
+
return THEMES[key]
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: figkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Design figures with Python code — boxes, arrows, LaTeX and data, exported to SVG/PNG/PDF/HTML
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/philippspohn/figkit
|
|
7
|
+
Project-URL: Repository, https://github.com/philippspohn/figkit
|
|
8
|
+
Project-URL: Issues, https://github.com/philippspohn/figkit/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/philippspohn/figkit/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Agent manual, https://github.com/philippspohn/figkit/blob/main/AI_MANUAL.md
|
|
11
|
+
Keywords: svg,figures,diagrams,vector,graphics,latex,publication,illustration,scientific-visualization,matplotlib
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
24
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: fonttools>=4.30
|
|
30
|
+
Provides-Extra: latex
|
|
31
|
+
Requires-Dist: matplotlib>=3.5; extra == "latex"
|
|
32
|
+
Provides-Extra: export
|
|
33
|
+
Requires-Dist: cairosvg>=2.5; extra == "export"
|
|
34
|
+
Provides-Extra: all
|
|
35
|
+
Requires-Dist: matplotlib>=3.5; extra == "all"
|
|
36
|
+
Requires-Dist: cairosvg>=2.5; extra == "all"
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: matplotlib>=3.5; extra == "dev"
|
|
39
|
+
Requires-Dist: cairosvg>=2.5; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
41
|
+
Requires-Dist: pyflakes>=3.0; extra == "dev"
|
|
42
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
43
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
<p align="center">
|
|
47
|
+
<img src="https://raw.githubusercontent.com/philippspohn/figkit/main/docs/logo.png" alt="figkit" width="180">
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
<h1 align="center">figkit</h1>
|
|
51
|
+
|
|
52
|
+
<p align="center">
|
|
53
|
+
<a href="https://pypi.org/project/figkit/"><img alt="PyPI" src="https://img.shields.io/pypi/v/figkit.svg"></a>
|
|
54
|
+
<a href="https://pypi.org/project/figkit/"><img alt="Python versions" src="https://img.shields.io/pypi/pyversions/figkit.svg"></a>
|
|
55
|
+
<a href="https://github.com/philippspohn/figkit/actions/workflows/ci.yml"><img alt="tests" src="https://github.com/philippspohn/figkit/actions/workflows/ci.yml/badge.svg"></a>
|
|
56
|
+
<a href="LICENSE"><img alt="MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
**Design figures with Python code.** Boxes, arrows, LaTeX, images and data —
|
|
60
|
+
laid out precisely, styled by a cascading theme, exported to SVG, PNG, PDF or
|
|
61
|
+
HTML.
|
|
62
|
+
|
|
63
|
+
figkit is for the kind of figure you'd otherwise assemble by hand in Figma or
|
|
64
|
+
draw.io: ML paper pipelines, architecture diagrams, research blog explainers.
|
|
65
|
+
Except it's code, so it's diffable, reproducible, parameterisable — and an LLM
|
|
66
|
+
can write it for you (see [`AI_MANUAL.md`](https://github.com/philippspohn/figkit/blob/main/AI_MANUAL.md)).
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from figkit import *
|
|
70
|
+
|
|
71
|
+
with Figure(pad=24) as fig:
|
|
72
|
+
a = Box("Encoder", style="block", w=120)
|
|
73
|
+
b = Box("Decoder", style="blue").right_of(a, gap=60)
|
|
74
|
+
arrow(a.e, b.w, label="$z$")
|
|
75
|
+
|
|
76
|
+
fig.save("figure.svg")
|
|
77
|
+
fig.save("figure.png", scale=2)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+

|
|
81
|
+
|
|
82
|
+
**Check your work without looking at it.** `fig.audit()` reports the mistakes
|
|
83
|
+
that are obvious in a picture and invisible in the code — and stays quiet about
|
|
84
|
+
deliberate overlap, so a clean report means something:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
>>> print(fig.audit())
|
|
88
|
+
figkit audit: 2 issues (1 overlap, 1 contrast) in 34 elements
|
|
89
|
+
overlap Box('Decoder') covers Text('$z$') (240px², 61% of it) at (196, 41)
|
|
90
|
+
contrast Box('Encoder'): text #dbeafe on #f3f4f6 has contrast 1.1:1 (want 3.0:1) at (60, 17)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Install
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pip install figkit # core: layout, shapes, arrows, SVG/HTML export
|
|
99
|
+
pip install "figkit[latex]" # + inline $math$ (matplotlib)
|
|
100
|
+
pip install "figkit[export]" # + PNG/PDF export (cairosvg)
|
|
101
|
+
pip install "figkit[all]" # everything
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Only `fonttools` is required — it's what makes text measurement (and therefore
|
|
105
|
+
layout) accurate. The extras degrade rather than break: without `matplotlib`
|
|
106
|
+
an inline `$formula$` is set as plain text with a warning, and without a
|
|
107
|
+
rasteriser `save("f.png")` raises while SVG and HTML keep working. PNG/PDF
|
|
108
|
+
export also accepts `rsvg-convert`, `resvg`, `inkscape` or headless Chromium
|
|
109
|
+
if you'd rather not install cairo.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## What it does
|
|
114
|
+
|
|
115
|
+
**Accurate measurement.** figkit reads the actual font file and measures glyph
|
|
116
|
+
advances, so `Box("Feature Extractor")` is exactly as wide as its text plus its
|
|
117
|
+
padding — and everything you place relative to it lands where you expect.
|
|
118
|
+
|
|
119
|
+
**Live anchors.** `box.e` is a reference, not a coordinate. Move the box and
|
|
120
|
+
every arrow attached to it follows.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
b = Box("model").right_of(a, gap=40)
|
|
124
|
+
link = arrow(a.e, b.w)
|
|
125
|
+
b.move(0, 120) # the arrow follows
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Explicit placement, not auto-layout.** You say where things go; figkit does
|
|
129
|
+
the arithmetic.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
fm = Box("$F_M$").right_of(extractor, gap=24, align="top")
|
|
133
|
+
fn = Box("$F_N$").right_of(extractor, gap=24, align="bottom")
|
|
134
|
+
solver = Box("FMap Solver", style="blue").right_of(fm, gap=40)
|
|
135
|
+
|
|
136
|
+
elbow(fm.e, solver.w, stub=12) # -| routing
|
|
137
|
+
mid = between(fm, fn) # vertically between two boxes
|
|
138
|
+
frame = fit(solver, cmn, pad=20, label="Fused operation", dash=True)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Cascading themes.** Base tokens, per-role overrides, CSS-style classes and a
|
|
142
|
+
colour palette — set once, applied everywhere.
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
T = PAPER.derive(
|
|
146
|
+
font_size=13, radius=4,
|
|
147
|
+
palette={"brand": "#3B6EA5"},
|
|
148
|
+
box=Style(fill="#ffffff", stroke="#222222", padding=(9, 13)),
|
|
149
|
+
styles={"solver": Style(fill="@brand", stroke="#1b1b1b")},
|
|
150
|
+
)
|
|
151
|
+
with Figure(theme=T) as fig:
|
|
152
|
+
Box("FMap Solver", classes="solver") # or style="solver block"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Classes resolve lazily against whichever theme is in scope, so the same class
|
|
156
|
+
can mean different things inside a differently themed group.
|
|
157
|
+
|
|
158
|
+
**LaTeX anywhere.** Any `$...$` span in any string is typeset and emitted as
|
|
159
|
+
vector outlines, so exported files don't depend on installed fonts.
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
Box("$C_{\\mathcal{MN}} = \\phi^{\\dagger}_{\\mathcal{N}}\\Pi_{\\mathcal{NM}}\\phi_{\\mathcal{M}}$")
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Data is just Python.** A `Frame` maps data coordinates to figure
|
|
166
|
+
coordinates; every mark it draws is an ordinary element you can anchor to.
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
fr = Frame(w=430, h=240, xlim=(0, 50), ylim=(0.35, 1.0))
|
|
170
|
+
fr.axes(xlabel="epoch", ylabel="accuracy", grid=True)
|
|
171
|
+
fr.line(xs, ys, stroke="@primary", lw=2)
|
|
172
|
+
arrow(note.se, fr.pt(50, 0.94)) # point at a data coordinate
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Gallery
|
|
178
|
+
|
|
179
|
+
Each figure below is produced by the script of the same name in
|
|
180
|
+
[`examples/`](https://github.com/philippspohn/figkit/tree/main/examples). The
|
|
181
|
+
first two are figkit reproductions of published figures, linked as reference —
|
|
182
|
+
they are not affiliated with or endorsed by their authors.
|
|
183
|
+
|
|
184
|
+
### `01_pipeline.py` — an ML paper pipeline
|
|
185
|
+
|
|
186
|
+
Themes, relative placement, live anchors, inline LaTeX, colour matrices,
|
|
187
|
+
elbow routing and hand-built vector graphics. After the pipeline figure in
|
|
188
|
+
[URSSM](https://dongliangcao.github.io/urssm/).
|
|
189
|
+
|
|
190
|
+

|
|
191
|
+
|
|
192
|
+
### `02_attribution.py` — a two-panel blog explainer
|
|
193
|
+
|
|
194
|
+
Programmatic node grids and hundreds of curved edges whose width and opacity
|
|
195
|
+
come straight from the data. After the replacement-model and attribution-graph
|
|
196
|
+
diagram in Anthropic's
|
|
197
|
+
[Circuit Tracing](https://transformer-circuits.pub/2025/attribution-graphs/methods.html).
|
|
198
|
+
|
|
199
|
+

|
|
200
|
+
|
|
201
|
+
### `03_data_and_plots.py` — data-driven graphics
|
|
202
|
+
|
|
203
|
+
A coordinate frame, a legend, a table, a brace and a heatmap with a colour bar.
|
|
204
|
+
|
|
205
|
+

|
|
206
|
+
|
|
207
|
+
### `05_rich_text_and_components.py` — spans, components and formulas
|
|
208
|
+
|
|
209
|
+
Per-word colour and strike-through, a `Component` publishing named ports, and
|
|
210
|
+
`LabelledMatrix` set as an equation with `hstack(align="baseline")`.
|
|
211
|
+
|
|
212
|
+

|
|
213
|
+
|
|
214
|
+
### `04_themes.py` — one diagram, seven themes
|
|
215
|
+
|
|
216
|
+
Not a single colour is set on an element — the theme cascade decides
|
|
217
|
+
everything.
|
|
218
|
+
|
|
219
|
+

|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## API tour
|
|
224
|
+
|
|
225
|
+
| | |
|
|
226
|
+
|---|---|
|
|
227
|
+
| **Canvas** | `Figure(w, h, pad, background, theme)`, `fig.save(...)`, `fig.to_svg()` |
|
|
228
|
+
| **Shapes** | `Box` `Pill` `Ellipse` `Circle` `Diamond` `Triangle` `Hexagon` `Parallelogram` `Chevron` `Star` `Cylinder` `Note` `Callout` |
|
|
229
|
+
| **Geometry** | `Line` `Polyline` `Polygon` `Path` `Dot` `Marker` |
|
|
230
|
+
| **Content** | `Text` `Label` `Span` (per-word colour / strike / underline) `Image` |
|
|
231
|
+
| **Composites** | `Matrix` `LabelledMatrix` `Vector` `ColorBar` `Table` `Legend` `Brace` `Bracket` `Panel` `Group` `Spacer` `Component` |
|
|
232
|
+
| **Connectors** | `arrow` `line` `elbow` `curve` `connect` `double_arrow` `self_loop` |
|
|
233
|
+
| **Placement** | `at` `move` `center_at` `right_of` `left_of` `above_of` `below_of` `inside` `align_to` `span_x` `resize` `rotate` |
|
|
234
|
+
| **Layout** | `align` `distribute_h/v` `spread_h/v` `hstack` (incl. `align="baseline"`) `vstack` `grid` `fit` `brace_around` `between` `center_on` `circular` `same_size` `bbox_of` |
|
|
235
|
+
| **Data** | `Frame` (`pt` `line` `scatter` `bars` `area_fill` `region` `axes` `gridlines`), `nice_ticks` |
|
|
236
|
+
| **Checking** | `fig.audit()` → `Report` (`overlap` `overflow` `contrast` `crossing` `degenerate` `offscreen`), `el.ignore_audit()` |
|
|
237
|
+
| **Style** | `Style` `Theme` `use_theme`, classes (`classes=`, `add_class`), themes `PAPER` `SLIDE` `DARK` `BLUEPRINT` `MINIMAL` `SOFT` |
|
|
238
|
+
| **Colour** | `mix` `lighten` `darken` `alpha` `colormap` `palette` `contrast_color` `to_hex` |
|
|
239
|
+
|
|
240
|
+
Anchors on every element: `n s e w ne nw se sw center`, plus `at_angle(deg)`,
|
|
241
|
+
`uv(u, v)` and offsets like `box.e + (6, 0)`. Connectors add `c.mid` and
|
|
242
|
+
`c.anchor_at(t)`, which stay live as the arrow moves.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Design notes
|
|
247
|
+
|
|
248
|
+
* **Coordinates are px, y grows downward** — the SVG convention. `n` is the top.
|
|
249
|
+
* **The output is clean SVG.** No wrapper `<g>` soup, no embedded raster of
|
|
250
|
+
your text: real `<text>` elements by default, real `<path>` outlines when you
|
|
251
|
+
ask for `text_as_paths=True` (which PNG and PDF export do automatically).
|
|
252
|
+
* **Groups own their children.** `Group(a, b)` and `fit(a, b)` reparent, so the
|
|
253
|
+
cluster moves as a unit. Use `bbox_of([a, b])` when you only want the bounds.
|
|
254
|
+
* **Text is centred optically**, on the cap-height band, which is what looks
|
|
255
|
+
right for short labels in boxes.
|
|
256
|
+
* **No runtime layout engine.** Placement happens when you call it. That makes
|
|
257
|
+
the code easy to reason about and easy to debug — print a `bbox` and you know
|
|
258
|
+
exactly where something is.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Development
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
pip install -e ".[dev]"
|
|
266
|
+
pytest # 277 tests; every example must audit clean
|
|
267
|
+
python -m pyflakes figkit
|
|
268
|
+
python examples/01_pipeline.py
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
See [CONTRIBUTING.md](https://github.com/philippspohn/figkit/blob/main/CONTRIBUTING.md) for what the test suite expects — in
|
|
272
|
+
particular, that new audit checks come with tests for the cases they must
|
|
273
|
+
*not* report.
|
|
274
|
+
|
|
275
|
+
## License
|
|
276
|
+
|
|
277
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
figkit/__init__.py,sha256=P7M60JWyKK3A12mTpluJKB6zrRZxgByRp0c_HPLERyU,5045
|
|
2
|
+
figkit/audit.py,sha256=TYcPxQbz05fR1uRA4Uw2MGMdF1ZHZhivOkom1UnY2t8,23266
|
|
3
|
+
figkit/colors.py,sha256=hHA_DwVSYK0XVzmmckVUe7HTxGYS8YHVkzg6oxLsKy8,11567
|
|
4
|
+
figkit/component.py,sha256=RX8awtDt4xEl1B88CeD3Xgb3rLsbW6Wh7m_d-iiMBZc,4157
|
|
5
|
+
figkit/components.py,sha256=BS__IydHQrKH5m5okVUelQAf-l1qWcGq5zVdnlgR55A,28499
|
|
6
|
+
figkit/connectors.py,sha256=i9OL7YkGR7SKhrdOHU0EsjqmIQfsb9BCk0RXfFK2gKI,26745
|
|
7
|
+
figkit/core.py,sha256=9tGZVefWpKbFFA1Ksoyp0Lpm_6sFbEZT99ixlksscVQ,35372
|
|
8
|
+
figkit/export.py,sha256=A-Cb9xNXZ0FTG-yvPiTBN_hjhbmKQNAA7bNPAPNebaM,11118
|
|
9
|
+
figkit/figure.py,sha256=Nyb4fcaernE2KolJfoMgha9W7WoXRMGCucwMjyIOCbg,11329
|
|
10
|
+
figkit/fonts.py,sha256=X_LakcguGAWEyCync8_f30rT_Pc4aiD3wcwcO_9HBao,17953
|
|
11
|
+
figkit/frame.py,sha256=TLkitIaXNvkLHi6azTKzr9vsHVbH3M2KtgY0d7op5l0,15926
|
|
12
|
+
figkit/geom.py,sha256=01TkzUsz63nqBjOb3q1cK7IsTqNcb1ZTb90d8H8GZow,15502
|
|
13
|
+
figkit/image.py,sha256=b1O96JIOOd8_Ym8qH4aBtu753PAutKP7ig_RVSvvMb0,11159
|
|
14
|
+
figkit/layout.py,sha256=ZEX1NxDQbx4xwcvqxc1q3_qeUbzNtV9yoFJQ1NxLtCI,18702
|
|
15
|
+
figkit/mathtext.py,sha256=06_KvJ1WDjAv2H4RKeLKHypS9Uy8yKAdf0cIGPSiC-o,11190
|
|
16
|
+
figkit/paint.py,sha256=EEMRgmH3044zGIedUyKTjxJPpj3WD7ahSEE4WrZuqdQ,5373
|
|
17
|
+
figkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
figkit/shapes.py,sha256=_ymTRucrImnLFeg4A9fvOHQ52FN2JsLBgSZ8aIl1OFU,27065
|
|
19
|
+
figkit/style.py,sha256=VYE6eE1sv_oC8F85tyWv2GNXKizFcdmm8xotfjoZAYc,18150
|
|
20
|
+
figkit/svgdoc.py,sha256=7LBxzHV_7js2DRKQPqkVAzxNENBYs6CKLrS4XdRCB_o,4301
|
|
21
|
+
figkit/svgpath.py,sha256=fc-QpU8GE5ITk7rzOCyy8Ej3O4tb3aF81IGCfEZQXt8,15770
|
|
22
|
+
figkit/text.py,sha256=bxcvIVqwJArRGd8sr6eeMFnCELQA4eBB9pKETONv_W8,23377
|
|
23
|
+
figkit/themes.py,sha256=OMEP_xoI5x_xEnyukq24NR6tz4uLzKoUXQDPmEBnxGE,6390
|
|
24
|
+
figkit-0.1.0.dist-info/licenses/LICENSE,sha256=sUbQmDzSHkWDvRX1a5lnjsMY4GCRuQr4A4S3eQV1-Fc,1076
|
|
25
|
+
figkit-0.1.0.dist-info/METADATA,sha256=3arboJpYWa0lu0LfPwwx4jmVdfht6zot2e5rBlqEz60,11767
|
|
26
|
+
figkit-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
27
|
+
figkit-0.1.0.dist-info/top_level.txt,sha256=cWFe7ZAY9YdgVfNq7yh-0oVBffEIxVxlikvrtPEQ_bU,7
|
|
28
|
+
figkit-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 figkit contributors
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
figkit
|