codefig 0.17.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.
- codefig/__init__.py +210 -0
- codefig/_validate.py +12 -0
- codefig/autowrap.py +277 -0
- codefig/charts.py +1806 -0
- codefig/code.py +602 -0
- codefig/compose.py +374 -0
- codefig/constraints.py +1571 -0
- codefig/dim.py +214 -0
- codefig/edge_align.py +231 -0
- codefig/edges.py +2824 -0
- codefig/export.py +204 -0
- codefig/figure.py +1923 -0
- codefig/fills.py +146 -0
- codefig/flows.py +102 -0
- codefig/glyphs.py +395 -0
- codefig/icon.py +288 -0
- codefig/icons/LICENSE-tabler-icons.txt +21 -0
- codefig/icons/__init__.py +101 -0
- codefig/icons/tabler.json +1 -0
- codefig/legend.py +483 -0
- codefig/lint.py +1737 -0
- codefig/lint_geom.py +528 -0
- codefig/node.py +864 -0
- codefig/notices.py +34 -0
- codefig/overlaps.py +392 -0
- codefig/policy.py +137 -0
- codefig/primitives.py +2721 -0
- codefig/py.typed +0 -0
- codefig/regions.py +727 -0
- codefig/render_svg.py +512 -0
- codefig/routing.py +932 -0
- codefig/shapes.py +642 -0
- codefig/solids.py +514 -0
- codefig/steps.py +203 -0
- codefig/styles.py +893 -0
- codefig/textutil.py +889 -0
- codefig/timeline.py +1288 -0
- codefig/tree.py +277 -0
- codefig/venues.py +93 -0
- codefig-0.17.0.dist-info/METADATA +542 -0
- codefig-0.17.0.dist-info/RECORD +44 -0
- codefig-0.17.0.dist-info/WHEEL +5 -0
- codefig-0.17.0.dist-info/licenses/LICENSE +21 -0
- codefig-0.17.0.dist-info/top_level.txt +1 -0
codefig/steps.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Circled / squared step numbers (①②③) drawn as shapes, not font glyphs.
|
|
2
|
+
|
|
3
|
+
* :class:`Step` — one numbered badge; a normal node (layering role **hard**) you can
|
|
4
|
+
put in any Row/Col, attach to a :class:`~codefig.primitives.Box` corner with
|
|
5
|
+
``Box(..., step=2)`` or place on an edge path with ``Edge(..., step=2)``.
|
|
6
|
+
* :class:`StepCounter` — hands out consecutive :class:`Step` badges that share one
|
|
7
|
+
style (``steps.next()`` → ①, ②, ③ …).
|
|
8
|
+
|
|
9
|
+
The digits are ordinary text centred in a vector circle / rounded square, so they
|
|
10
|
+
render identically in every font and at every scale (unlike the Unicode ①
|
|
11
|
+
glyphs, which many fonts lack).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any, List, Optional, Tuple, Union
|
|
17
|
+
|
|
18
|
+
from .node import Node
|
|
19
|
+
from .styles import Theme
|
|
20
|
+
|
|
21
|
+
STEP_STYLES = ("circle", "square")
|
|
22
|
+
|
|
23
|
+
StepLike = Union[int, str, "Step"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Step(Node):
|
|
27
|
+
"""A numbered step badge (①-style), drawn as a filled or outlined shape.
|
|
28
|
+
|
|
29
|
+
Layering role: **hard** (edges route around it; check_overlaps() reports
|
|
30
|
+
anything it covers). On an edge (``Edge(step=…)``) it is checked like an
|
|
31
|
+
edge label.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
n: The number or short text shown inside (``1``, ``"2a"``, ``"A"``).
|
|
35
|
+
style: ``"circle"`` (default) or ``"square"`` (rounded square).
|
|
36
|
+
color: Badge colour (px-independent CSS colour). Default: ``theme.highlight``
|
|
37
|
+
(``"#c0392b"``).
|
|
38
|
+
filled: ``True`` (default) → solid ``color`` disc with white digits;
|
|
39
|
+
``False`` → white disc with a ``color`` outline and ``color`` digits.
|
|
40
|
+
size: Diameter / side length in px. Default: ``1.5 × font_size`` (≈ 16 px).
|
|
41
|
+
font_size: Digit size in px. Default: ``0.82 × theme.font_size``.
|
|
42
|
+
text_color: Digit colour override (default white when filled, else ``color``).
|
|
43
|
+
stroke_width: Outline width in px for ``filled=False`` (default 1.4).
|
|
44
|
+
**kwargs: Common :class:`~codefig.node.Node` options (``id``, ``obstacle``,
|
|
45
|
+
``legend`` …).
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
>>> from codefig import Figure, Row, Box, Step
|
|
49
|
+
>>> fig = Figure(size="auto", cols=1)
|
|
50
|
+
>>> with fig.panel("a") as p:
|
|
51
|
+
... p += Row([Step(1), Box("load data"), Step(2, filled=False), Box("train")])
|
|
52
|
+
>>> svg = fig.to_svg()
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
n: Union[int, str],
|
|
58
|
+
*,
|
|
59
|
+
style: str = "circle",
|
|
60
|
+
color: Optional[str] = None,
|
|
61
|
+
filled: bool = True,
|
|
62
|
+
size: Optional[float] = None,
|
|
63
|
+
font_size: Optional[float] = None,
|
|
64
|
+
text_color: Optional[str] = None,
|
|
65
|
+
stroke_width: float = 1.4,
|
|
66
|
+
**kwargs: Any,
|
|
67
|
+
) -> None:
|
|
68
|
+
super().__init__(**kwargs)
|
|
69
|
+
if style not in STEP_STYLES:
|
|
70
|
+
raise ValueError(f"Step style must be one of {STEP_STYLES}, got {style!r}")
|
|
71
|
+
self.n = n
|
|
72
|
+
self.text = str(n)
|
|
73
|
+
self.style = style
|
|
74
|
+
self.color = color
|
|
75
|
+
self.filled = bool(filled)
|
|
76
|
+
self.size = None if size is None else float(size)
|
|
77
|
+
self.font_size = None if font_size is None else float(font_size)
|
|
78
|
+
self.text_color = text_color
|
|
79
|
+
self.stroke_width = float(stroke_width)
|
|
80
|
+
|
|
81
|
+
# ---- metrics ---------------------------------------------------------------
|
|
82
|
+
def _fs(self) -> float:
|
|
83
|
+
return self.font_size if self.font_size is not None else round(self.theme.font_size * 0.82, 2)
|
|
84
|
+
|
|
85
|
+
def diameter(self) -> float:
|
|
86
|
+
"""Badge diameter / side in px (grows for multi-character labels)."""
|
|
87
|
+
from .textutil import measure_text
|
|
88
|
+
|
|
89
|
+
d = self.size if self.size is not None else round(self._fs() * 1.75, 1)
|
|
90
|
+
tw, _ = measure_text(self.text, self._fs(), weight="bold")
|
|
91
|
+
return max(d, tw + self._fs() * 0.7)
|
|
92
|
+
|
|
93
|
+
def _measure(self) -> Tuple[float, float]:
|
|
94
|
+
d = self.diameter()
|
|
95
|
+
return d, d
|
|
96
|
+
|
|
97
|
+
def _color(self) -> str:
|
|
98
|
+
return self.color or self.theme.highlight
|
|
99
|
+
|
|
100
|
+
# ---- drawing ---------------------------------------------------------------
|
|
101
|
+
def paint(self, buf: List[str], cx: float, cy: float, d: Optional[float] = None) -> None:
|
|
102
|
+
"""Draw the badge centred at page coordinates (``cx``, ``cy``)."""
|
|
103
|
+
from .render_svg import draw_circle, draw_rect, draw_text
|
|
104
|
+
from .textutil import measure_text
|
|
105
|
+
|
|
106
|
+
d = self.diameter() if d is None else d
|
|
107
|
+
col = self._color()
|
|
108
|
+
fill = col if self.filled else "#ffffff"
|
|
109
|
+
stroke = "none" if self.filled else col
|
|
110
|
+
sw = 0.0 if self.filled else self.stroke_width
|
|
111
|
+
# marked as a glyph so lint does not count the bold numeral as a bold label
|
|
112
|
+
buf.append('<g data-cf-glyph="step">')
|
|
113
|
+
if self.style == "circle":
|
|
114
|
+
draw_circle(buf, cx, cy, d / 2 - sw / 2, fill=fill, stroke=stroke, stroke_width=sw)
|
|
115
|
+
else:
|
|
116
|
+
s = d - sw
|
|
117
|
+
draw_rect(buf, cx - s / 2, cy - s / 2, s, s, fill=fill, stroke=stroke,
|
|
118
|
+
stroke_width=sw, radius=round(d * 0.2, 2))
|
|
119
|
+
fs = self._fs()
|
|
120
|
+
tc = self.text_color or ("#ffffff" if self.filled else col)
|
|
121
|
+
tw, _ = measure_text(self.text, fs, weight="bold")
|
|
122
|
+
draw_text(buf, self.text, cx - tw / 2, cy + fs * 0.36, font_size=fs, weight="bold",
|
|
123
|
+
fill=tc)
|
|
124
|
+
buf.append("</g>")
|
|
125
|
+
|
|
126
|
+
def draw(self, buf: List[str], ox: float = 0.0, oy: float = 0.0) -> None:
|
|
127
|
+
g = self.geom
|
|
128
|
+
self.paint(buf, g.cx + ox, g.cy + oy, min(g.w, g.h))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class StepCounter:
|
|
132
|
+
"""Hands out consecutive :class:`Step` badges that share one style.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
style: ``"circle"`` (default) or ``"square"``.
|
|
136
|
+
color: Badge colour (default ``theme.highlight``).
|
|
137
|
+
filled: Solid (default) or outlined badges.
|
|
138
|
+
start: First number (default 1).
|
|
139
|
+
size: Badge diameter in px (default ≈ 1.5 × font size).
|
|
140
|
+
font_size: Digit size in px.
|
|
141
|
+
theme: Theme for defaults.
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
>>> from codefig import Box, Edge, StepCounter
|
|
145
|
+
>>> steps = StepCounter(color="#1f6fb2")
|
|
146
|
+
>>> a, b, c = Box("parse"), Box("plan"), Box("run")
|
|
147
|
+
>>> e1 = Edge(a, b, step=steps.next()) # ①
|
|
148
|
+
>>> e2 = Edge(b, c, step=steps.next()) # ②
|
|
149
|
+
>>> steps.peek()
|
|
150
|
+
3
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
*,
|
|
156
|
+
style: str = "circle",
|
|
157
|
+
color: Optional[str] = None,
|
|
158
|
+
filled: bool = True,
|
|
159
|
+
start: int = 1,
|
|
160
|
+
size: Optional[float] = None,
|
|
161
|
+
font_size: Optional[float] = None,
|
|
162
|
+
theme: Optional[Theme] = None,
|
|
163
|
+
) -> None:
|
|
164
|
+
if style not in STEP_STYLES:
|
|
165
|
+
raise ValueError(f"StepCounter style must be one of {STEP_STYLES}, got {style!r}")
|
|
166
|
+
self.style = style
|
|
167
|
+
self.color = color
|
|
168
|
+
self.filled = bool(filled)
|
|
169
|
+
self.size = size
|
|
170
|
+
self.font_size = font_size
|
|
171
|
+
self.theme = theme
|
|
172
|
+
self._next = int(start)
|
|
173
|
+
|
|
174
|
+
def next(self, **overrides: Any) -> Step:
|
|
175
|
+
"""Return a new :class:`Step` with the next number (``overrides`` → Step kwargs)."""
|
|
176
|
+
kw = dict(style=self.style, color=self.color, filled=self.filled, size=self.size,
|
|
177
|
+
font_size=self.font_size, theme=self.theme)
|
|
178
|
+
kw.update(overrides)
|
|
179
|
+
s = Step(self._next, **kw)
|
|
180
|
+
self._next += 1
|
|
181
|
+
return s
|
|
182
|
+
|
|
183
|
+
def peek(self) -> int:
|
|
184
|
+
"""The number the next :meth:`next` call will use."""
|
|
185
|
+
return self._next
|
|
186
|
+
|
|
187
|
+
def reset(self, start: int = 1) -> None:
|
|
188
|
+
"""Restart numbering at ``start``."""
|
|
189
|
+
self._next = int(start)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def as_step(v: Optional[StepLike], theme: Optional[Theme] = None) -> Optional[Step]:
|
|
193
|
+
"""Coerce ``None`` / an int / a str / a :class:`Step` to a Step (or None)."""
|
|
194
|
+
if v is None or v is False:
|
|
195
|
+
return None
|
|
196
|
+
if isinstance(v, Step):
|
|
197
|
+
return v
|
|
198
|
+
if isinstance(v, (int, str)):
|
|
199
|
+
return Step(v, theme=theme)
|
|
200
|
+
raise TypeError(f"step must be an int, str or Step, got {type(v).__name__}")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
__all__ = ["Step", "StepCounter", "as_step", "STEP_STYLES"]
|