predictygraph 0.2.4__tar.gz → 0.2.5__tar.gz
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.
- {predictygraph-0.2.4 → predictygraph-0.2.5}/PKG-INFO +1 -1
- {predictygraph-0.2.4 → predictygraph-0.2.5}/pyproject.toml +1 -1
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/layout.py +13 -10
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/scene.py +44 -3
- {predictygraph-0.2.4 → predictygraph-0.2.5}/tests/test_annotations.py +19 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/README.md +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/__init__.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/__main__.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/anim.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/cli.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/default.css +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/Carlito-Bold.ttf +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/Carlito-Regular.ttf +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/DejaVuSans-Bold.ttf +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/DejaVuSans.ttf +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/LiberationMono-Bold.ttf +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/LiberationMono-Regular.ttf +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/LiberationSans-Bold.ttf +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/LiberationSans-Regular.ttf +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/LiberationSerif-Bold.ttf +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/LiberationSerif-Regular.ttf +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/ir.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/model.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/parser.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/render/__init__.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/render/apng.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/render/svg.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/route.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/safe.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/style.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/styleutil.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/text.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/theme.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/catppuccin-frappe.toml +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/catppuccin-latte.toml +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/catppuccin-macchiato.toml +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/catppuccin-mocha.toml +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/default.toml +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/dracula.toml +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/github-light.toml +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/gruvbox-dark.toml +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/hami.toml +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/nord.toml +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/solarized-dark.toml +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/solarized-light.toml +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/tests/test_diagonal.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/tests/test_fixes.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/tests/test_hardening.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/tests/test_predictygraph.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/tests/test_routing.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/tests/test_scene.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/tests/test_stack.py +0 -0
- {predictygraph-0.2.4 → predictygraph-0.2.5}/tests/test_targets.py +0 -0
|
@@ -144,14 +144,13 @@ class LineLayout(Layout):
|
|
|
144
144
|
return (pos, cross) if horizontal else (cross, pos)
|
|
145
145
|
|
|
146
146
|
def label_gaps(self, horizontal: bool) -> dict[int, float]:
|
|
147
|
-
"""Gap needed after child i so
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return {}
|
|
147
|
+
"""Gap needed after child i so what sits on the edges between neighbours i and i+1
|
|
148
|
+
fits. Row: the edge label (plus its step circle) runs along the gap. Column: a step
|
|
149
|
+
circle sits beside the edge, so the gap must be at least one circle plus margins."""
|
|
151
150
|
index = {k.id: i for i, k in enumerate(self.kids)}
|
|
152
151
|
need: dict[int, float] = {}
|
|
153
152
|
for e in self.doc.edges:
|
|
154
|
-
if not e.label:
|
|
153
|
+
if not (e.label if horizontal else e.attrs.get("step")):
|
|
155
154
|
continue
|
|
156
155
|
a, b = direct_child(self.doc.items[e.src], self.g), direct_child(self.doc.items[e.dst], self.g)
|
|
157
156
|
if a is None or b is None or a is b:
|
|
@@ -159,11 +158,15 @@ class LineLayout(Layout):
|
|
|
159
158
|
i, j = sorted((index[a.id], index[b.id]))
|
|
160
159
|
if j != i + 1:
|
|
161
160
|
continue
|
|
162
|
-
|
|
163
|
-
if e.attrs.get("step"): #
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
step = 0.0
|
|
162
|
+
if e.attrs.get("step"): # circle diameter, as drawn by SceneBuilder.step_badge
|
|
163
|
+
sw, sh, _ = text_block(e.attrs["step"][:3], e.step_style or e.style, 11, bold=True)
|
|
164
|
+
step = max(sw, sh) + 6
|
|
165
|
+
if horizontal:
|
|
166
|
+
w, _, _ = text_block(e.label, e.label_style or e.style, 11, bold=False)
|
|
167
|
+
need[i] = max(need.get(i, 0.0), w + (step + 6 if step else 0.0) + 34)
|
|
168
|
+
else:
|
|
169
|
+
need[i] = max(need.get(i, 0.0), step + 14)
|
|
167
170
|
return need
|
|
168
171
|
|
|
169
172
|
|
|
@@ -477,8 +477,10 @@ class SceneBuilder:
|
|
|
477
477
|
return out
|
|
478
478
|
|
|
479
479
|
def step_badge(self, e, poly, label_box=None) -> list[Elem]:
|
|
480
|
-
"""Numbered circle
|
|
481
|
-
|
|
480
|
+
"""Numbered circle for an edge. Next to the label when there is one; on the line a
|
|
481
|
+
third of the way along when the edge is long enough to hold it with room to spare;
|
|
482
|
+
otherwise beside the middle of the edge, so a short arrow between stacked boxes
|
|
483
|
+
stays a visible line and the circle never sits on a box."""
|
|
482
484
|
st = e.step_style or e.style
|
|
483
485
|
st = {**st, "font-weight": st.get("font-weight", "bold")}
|
|
484
486
|
fs = font_size(st, 11)
|
|
@@ -488,12 +490,51 @@ class SceneBuilder:
|
|
|
488
490
|
if label_box:
|
|
489
491
|
cx, cy = label_box["x"] - r - 3, label_box["y"] + label_box["h"] / 2
|
|
490
492
|
else:
|
|
491
|
-
cx, cy =
|
|
493
|
+
cx, cy = self._step_spot(e, poly, r)
|
|
492
494
|
circ = Elem("circle", cls="pdg-step", geom={"cx": cx, "cy": cy, "r": r},
|
|
493
495
|
paint={"fill": st.get("fill", "#374151"), "stroke": self.doc.root.style.get("background", "#ffffff"),
|
|
494
496
|
"stroke-width": 1.5})
|
|
495
497
|
return [circ] + self.text([txt], cx, cy - fs * LINE_H / 2, fs, st)
|
|
496
498
|
|
|
499
|
+
STEP_ROOM = 3.0 # an edge must be this many circle diameters long to carry its step
|
|
500
|
+
|
|
501
|
+
def _step_spot(self, e, poly, r) -> tuple[float, float]:
|
|
502
|
+
"""Where a label-less step circle goes (see step_badge)."""
|
|
503
|
+
length = sum(math.dist(a, b) for a, b in zip(poly, poly[1:]))
|
|
504
|
+
on_line = point_along(poly, 1 / 3)
|
|
505
|
+
if length >= self.STEP_ROOM * 2 * r and not self._on_a_box(on_line, r, e):
|
|
506
|
+
return on_line
|
|
507
|
+
mx, my = point_along(poly, 1 / 2)
|
|
508
|
+
# the segment holding the midpoint gives the edge's direction there
|
|
509
|
+
ax, ay, bx, by = *poly[0], *poly[-1]
|
|
510
|
+
acc, half = 0.0, length / 2
|
|
511
|
+
for a, b in zip(poly, poly[1:]):
|
|
512
|
+
seg = math.dist(a, b)
|
|
513
|
+
if acc + seg >= half:
|
|
514
|
+
(ax, ay), (bx, by) = a, b
|
|
515
|
+
break
|
|
516
|
+
acc += seg
|
|
517
|
+
d = math.hypot(bx - ax, by - ay) or 1.0
|
|
518
|
+
nx, ny = -(by - ay) / d, (bx - ax) / d # unit normal
|
|
519
|
+
off = r + 4
|
|
520
|
+
sides = [(mx - nx * off, my - ny * off), (mx + nx * off, my + ny * off)]
|
|
521
|
+
if abs(by - ay) >= abs(bx - ax): # vertical-ish edge: prefer the left side
|
|
522
|
+
sides.sort(key=lambda p: p[0])
|
|
523
|
+
else: # horizontal-ish edge: prefer above
|
|
524
|
+
sides.sort(key=lambda p: p[1])
|
|
525
|
+
free = [p for p in sides if not self._on_a_box(p, r, e)]
|
|
526
|
+
return (free or sides)[0]
|
|
527
|
+
|
|
528
|
+
def _on_a_box(self, p, r, e) -> bool:
|
|
529
|
+
"""Would a circle of radius r at p overlap any node box (with a small margin)?"""
|
|
530
|
+
x, y = p
|
|
531
|
+
for n in self.doc.walk():
|
|
532
|
+
if isinstance(n, Group):
|
|
533
|
+
continue
|
|
534
|
+
if n.ax - r - 2 < x < n.ax + n.w + r + 2 and n.ay - r - 2 < y < n.ay + n.h + r + 2:
|
|
535
|
+
return True
|
|
536
|
+
return False
|
|
537
|
+
|
|
497
538
|
@staticmethod
|
|
498
539
|
def head(kind, tip, d, hl, color, width, bg="#ffffff") -> Elem:
|
|
499
540
|
"""Arrowhead as plain geometry: tip sits exactly on the target outline.
|
|
@@ -105,3 +105,22 @@ def test_left_label_is_rotated_and_takes_a_column():
|
|
|
105
105
|
rotated.append(e.geom["text"])
|
|
106
106
|
stack += e.children
|
|
107
107
|
assert rotated == ["Kernel space"]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_step_on_short_arrow_sits_beside_it_and_clear_of_boxes():
|
|
111
|
+
doc, sc = scene('graph layout=column gap=4 steps=auto\na "top box"\nb "bottom box"\na -> b\n')
|
|
112
|
+
circles = []
|
|
113
|
+
stack = list(sc.layers)
|
|
114
|
+
while stack:
|
|
115
|
+
e = stack.pop()
|
|
116
|
+
if e.kind == "circle" and "pdg-step" in (e.cls or ""):
|
|
117
|
+
circles.append(e.geom)
|
|
118
|
+
stack += e.children
|
|
119
|
+
(c,) = circles
|
|
120
|
+
for n in (doc.items["a"], doc.items["b"]):
|
|
121
|
+
inside_x = n.ax - c["r"] < c["cx"] < n.ax + n.w + c["r"]
|
|
122
|
+
inside_y = n.ay - c["r"] < c["cy"] < n.ay + n.h + c["r"]
|
|
123
|
+
assert not (inside_x and inside_y)
|
|
124
|
+
# the column gap grew to fit the circle, even though gap=4 was asked for
|
|
125
|
+
a, b = doc.items["a"], doc.items["b"]
|
|
126
|
+
assert b.ay - (a.ay + a.h) >= 2 * c["r"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/LiberationMono-Regular.ttf
RENAMED
|
File without changes
|
|
File without changes
|
{predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/LiberationSans-Regular.ttf
RENAMED
|
File without changes
|
{predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/LiberationSerif-Bold.ttf
RENAMED
|
File without changes
|
{predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/fonts/LiberationSerif-Regular.ttf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{predictygraph-0.2.4 → predictygraph-0.2.5}/src/predictygraph/themes/catppuccin-macchiato.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|