smartcomment 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.
- smartcomment/__init__.py +49 -0
- smartcomment/analysis/__init__.py +1 -0
- smartcomment/analysis/visualization/__init__.py +43 -0
- smartcomment/analysis/visualization/_utils.py +103 -0
- smartcomment/analysis/visualization/graphviz.py +201 -0
- smartcomment/analysis/visualization/pyvis.py +155 -0
- smartcomment/api/__init__.py +18 -0
- smartcomment/api/_helpers.py +440 -0
- smartcomment/api/_mutation.py +318 -0
- smartcomment/api/public.py +971 -0
- smartcomment/debugging.py +69 -0
- smartcomment/drivers/__init__.py +12 -0
- smartcomment/drivers/base.py +309 -0
- smartcomment/drivers/in_memory.py +323 -0
- smartcomment/identity/__init__.py +6 -0
- smartcomment/identity/registry.py +214 -0
- smartcomment/logging.py +80 -0
- smartcomment/runtime/__init__.py +44 -0
- smartcomment/runtime/context.py +630 -0
- smartcomment/runtime/errors.py +117 -0
- smartcomment/runtime/graph.py +520 -0
- smartcomment/runtime/network.py +1841 -0
- smartcomment/runtime/operation.py +274 -0
- smartcomment/runtime/session.py +133 -0
- smartcomment/runtime/variable.py +237 -0
- smartcomment/schema/__init__.py +1 -0
- smartcomment/schema/base.py +134 -0
- smartcomment/schema/operation.py +96 -0
- smartcomment/schema/session.py +42 -0
- smartcomment/schema/variable.py +126 -0
- smartcomment-0.1.0.dist-info/METADATA +29 -0
- smartcomment-0.1.0.dist-info/RECORD +34 -0
- smartcomment-0.1.0.dist-info/WHEEL +5 -0
- smartcomment-0.1.0.dist-info/top_level.txt +1 -0
smartcomment/__init__.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""smartcomment: A General-Purpose Execution Graph Tracing Package."""
|
|
2
|
+
|
|
3
|
+
from .runtime import (
|
|
4
|
+
current_context,
|
|
5
|
+
current_graph,
|
|
6
|
+
current_op,
|
|
7
|
+
current_session,
|
|
8
|
+
comment_graph,
|
|
9
|
+
comment_op_scope,
|
|
10
|
+
comment_session,
|
|
11
|
+
disable_tracing,
|
|
12
|
+
enable_tracing,
|
|
13
|
+
is_tracing_enabled,
|
|
14
|
+
propagate_attributes,
|
|
15
|
+
)
|
|
16
|
+
from .api import (
|
|
17
|
+
comment_fn,
|
|
18
|
+
comment_link,
|
|
19
|
+
comment_mutation,
|
|
20
|
+
comment_op,
|
|
21
|
+
comment_variable,
|
|
22
|
+
)
|
|
23
|
+
from .identity import IdentityRegistry
|
|
24
|
+
from .logging import logger, setup_logger
|
|
25
|
+
from .debugging import draw_graph
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"IdentityRegistry",
|
|
30
|
+
"logger",
|
|
31
|
+
"setup_logger",
|
|
32
|
+
"comment_graph",
|
|
33
|
+
"comment_op_scope",
|
|
34
|
+
"comment_session",
|
|
35
|
+
"current_context",
|
|
36
|
+
"current_graph",
|
|
37
|
+
"current_op",
|
|
38
|
+
"current_session",
|
|
39
|
+
"disable_tracing",
|
|
40
|
+
"draw_graph",
|
|
41
|
+
"enable_tracing",
|
|
42
|
+
"is_tracing_enabled",
|
|
43
|
+
"propagate_attributes",
|
|
44
|
+
"comment_fn",
|
|
45
|
+
"comment_link",
|
|
46
|
+
"comment_mutation",
|
|
47
|
+
"comment_op",
|
|
48
|
+
"comment_variable",
|
|
49
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Analysis and visualization utilities."""
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Visualization backends for execution graphs.
|
|
2
|
+
|
|
3
|
+
Standalone functions accept user-facing runtime types so callers
|
|
4
|
+
can pass graph data without unwrapping internals.
|
|
5
|
+
|
|
6
|
+
Optional dependencies: ``graphviz``, ``pyvis``, ``matplotlib``.
|
|
7
|
+
Install with ``pip install smartcomment[viz]``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from collections import OrderedDict
|
|
11
|
+
from ...runtime.operation import RuntimeEdge
|
|
12
|
+
from ...runtime.variable import RuntimeVariable
|
|
13
|
+
from .graphviz import render_static, to_dot
|
|
14
|
+
from .pyvis import render_interactive
|
|
15
|
+
from typing import Any, Protocol
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GraphVisualizationBackend(Protocol):
|
|
19
|
+
"""Graph visualization backend protocol."""
|
|
20
|
+
|
|
21
|
+
def __call__(
|
|
22
|
+
self,
|
|
23
|
+
nodes: list[RuntimeVariable[Any]],
|
|
24
|
+
edges: list[RuntimeEdge],
|
|
25
|
+
**kwargs: Any,
|
|
26
|
+
) -> Any:
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_VISUAL_BACKENDS: OrderedDict[str, GraphVisualizationBackend] = OrderedDict(
|
|
31
|
+
(
|
|
32
|
+
("graphviz", render_static),
|
|
33
|
+
("dot", to_dot),
|
|
34
|
+
("pyvis", render_interactive),
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"render_interactive",
|
|
41
|
+
"render_static",
|
|
42
|
+
"to_dot",
|
|
43
|
+
]
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Utility functions for visualization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import TYPE_CHECKING, Iterable
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from matplotlib.colors import Colormap
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _truncate(val: str, max_len: int = 30) -> str:
|
|
12
|
+
"""Truncate a string for graph labels (newlines collapsed to spaces).
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
val (`str`):
|
|
16
|
+
Text to truncate.
|
|
17
|
+
max_len (`int`, defaults to `30`):
|
|
18
|
+
Maximum length of the string.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
`str`:
|
|
22
|
+
Truncated string without newlines.
|
|
23
|
+
"""
|
|
24
|
+
s = val.replace("\n", " ").replace("\r", "")
|
|
25
|
+
if len(s) > max_len:
|
|
26
|
+
return s[: max_len - 3] + "..."
|
|
27
|
+
return s
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _escape_html(s: str) -> str:
|
|
31
|
+
"""Escape string for HTML labels."""
|
|
32
|
+
return s.replace(
|
|
33
|
+
"&",
|
|
34
|
+
"&"
|
|
35
|
+
).replace(
|
|
36
|
+
"<",
|
|
37
|
+
"<"
|
|
38
|
+
).replace(
|
|
39
|
+
">",
|
|
40
|
+
">"
|
|
41
|
+
).replace(
|
|
42
|
+
'"',
|
|
43
|
+
"""
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_color_map(
|
|
48
|
+
categories: Iterable[str],
|
|
49
|
+
cmap: str | Colormap | None = None,
|
|
50
|
+
max_auto: int = 20
|
|
51
|
+
) -> dict[str, str]:
|
|
52
|
+
"""Generate a hex color mapping for a set of categories using Matplotlib.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
categories (`Iterable[str]`):
|
|
56
|
+
Unique categories to colorize.
|
|
57
|
+
cmap (`str | Colormap | None`, optional):
|
|
58
|
+
Matplotlib colormap name or a colormap object.
|
|
59
|
+
If not provided, the default colormap will be used.
|
|
60
|
+
max_auto (`int`):
|
|
61
|
+
If no color map is provided and categories exceed this, return no colors.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
`dict[str, str]`:
|
|
65
|
+
Mapping from category string to hex color string.
|
|
66
|
+
"""
|
|
67
|
+
if not categories:
|
|
68
|
+
raise ValueError("No categories are provided.")
|
|
69
|
+
|
|
70
|
+
if cmap is None and len(categories) > max_auto:
|
|
71
|
+
return {}
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
import matplotlib.pyplot as plt
|
|
75
|
+
import matplotlib.colors as mcolors
|
|
76
|
+
except ImportError as e:
|
|
77
|
+
if cmap is not None:
|
|
78
|
+
raise ImportError(
|
|
79
|
+
"`matplotlib` is required for custom colormaps. "
|
|
80
|
+
"Install it via `pip install matplotlib`."
|
|
81
|
+
) from e
|
|
82
|
+
|
|
83
|
+
# Graceful fallback to no colors if default behavior and no matplotlib.
|
|
84
|
+
warnings.warn(
|
|
85
|
+
"`matplotlib` is required for custom colormaps, but it is not installed. "
|
|
86
|
+
"No colors will be assigned to categories."
|
|
87
|
+
)
|
|
88
|
+
return {}
|
|
89
|
+
|
|
90
|
+
if cmap is None:
|
|
91
|
+
cmap_obj = plt.get_cmap("tab20")
|
|
92
|
+
elif isinstance(cmap, str):
|
|
93
|
+
cmap_obj = plt.get_cmap(cmap)
|
|
94
|
+
else:
|
|
95
|
+
cmap_obj = cmap
|
|
96
|
+
|
|
97
|
+
n = len(categories)
|
|
98
|
+
color_dict = {}
|
|
99
|
+
for i, cat in enumerate(sorted(categories)):
|
|
100
|
+
rgba = cmap_obj(i / max(1, n - 1))
|
|
101
|
+
color_dict[cat] = mcolors.to_hex(rgba)
|
|
102
|
+
|
|
103
|
+
return color_dict
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Graphviz visualization backend."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from ...runtime.operation import RuntimeEdge
|
|
5
|
+
from ...runtime.variable import RuntimeVariable
|
|
6
|
+
from ._utils import (
|
|
7
|
+
_get_color_map,
|
|
8
|
+
_escape_html,
|
|
9
|
+
_truncate,
|
|
10
|
+
)
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from matplotlib.colors import Colormap
|
|
16
|
+
from graphviz import Source
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def to_dot(
|
|
20
|
+
nodes: list[RuntimeVariable[Any]],
|
|
21
|
+
edges: list[RuntimeEdge],
|
|
22
|
+
node_cmap: str | Colormap | None = None,
|
|
23
|
+
edge_cmap: str | Colormap | None = None,
|
|
24
|
+
max_str_len: int = 30,
|
|
25
|
+
**kwargs: Any,
|
|
26
|
+
) -> str:
|
|
27
|
+
"""Convert a node list and an edge list to a `graphviz` DOT string.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
nodes (`list[RuntimeVariable]`):
|
|
31
|
+
Variable nodes to render.
|
|
32
|
+
edges (`list[RuntimeEdge]`):
|
|
33
|
+
Edges to render.
|
|
34
|
+
node_cmap (`str | Colormap | None`, optional):
|
|
35
|
+
Matplotlib colormap (name or object) for coloring nodes by category.
|
|
36
|
+
edge_cmap (`str | Colormap | None`, optional):
|
|
37
|
+
Matplotlib colormap (name or object) for coloring edges by category.
|
|
38
|
+
max_str_len (`int`, defaults to `30`):
|
|
39
|
+
Maximum string length for displayed fields before truncation.
|
|
40
|
+
**kwargs (`Any`):
|
|
41
|
+
These keyword arguments will be ignored.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
`str`:
|
|
45
|
+
DOT-format string.
|
|
46
|
+
"""
|
|
47
|
+
lines = [
|
|
48
|
+
'digraph exec_graph {',
|
|
49
|
+
' rankdir=LR;',
|
|
50
|
+
' node [shape=none, fontname="Helvetica"];',
|
|
51
|
+
' edge [fontname="Helvetica"];'
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# Get unique categories for nodes and edges.
|
|
55
|
+
node_cats = {n.category for n in nodes}
|
|
56
|
+
edge_cats = {e.category for e in edges}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if node_cats:
|
|
60
|
+
node_colors = _get_color_map(node_cats, cmap=node_cmap)
|
|
61
|
+
else:
|
|
62
|
+
node_colors = {}
|
|
63
|
+
if edge_cats:
|
|
64
|
+
edge_colors = _get_color_map(edge_cats, cmap=edge_cmap)
|
|
65
|
+
else:
|
|
66
|
+
edge_colors = {}
|
|
67
|
+
|
|
68
|
+
for node in nodes:
|
|
69
|
+
cat = node.category
|
|
70
|
+
# Default white background.
|
|
71
|
+
color_hex = node_colors.get(cat, "#FFFFFF")
|
|
72
|
+
|
|
73
|
+
node_id_str = _escape_html(
|
|
74
|
+
_truncate(
|
|
75
|
+
node.full_node_id,
|
|
76
|
+
max_len=max_str_len
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
raw_val_str = _escape_html(
|
|
80
|
+
_truncate(
|
|
81
|
+
node.raw_value,
|
|
82
|
+
max_len=max_str_len
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
cat_str = _escape_html(
|
|
86
|
+
_truncate(
|
|
87
|
+
node.category,
|
|
88
|
+
max_len=max_str_len
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
tp_str = _escape_html(
|
|
92
|
+
_truncate(
|
|
93
|
+
node.trigger_point,
|
|
94
|
+
max_len=max_str_len
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
comment_str = _escape_html(
|
|
98
|
+
_truncate(node.comment, max_len=max_str_len) if node.comment else ""
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Pad empty comments with a space to prevent the cell from collapsing.
|
|
102
|
+
comment_display = comment_str if comment_str else " "
|
|
103
|
+
|
|
104
|
+
# Build HTML-like record with strict BALIGN="LEFT" and <BR ALIGN="LEFT"/>.
|
|
105
|
+
label = (
|
|
106
|
+
f'<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" BGCOLOR="{color_hex}">\n'
|
|
107
|
+
f' <TR><TD ALIGN="CENTER"><B>{node_id_str}</B></TD></TR>\n'
|
|
108
|
+
f' <TR><TD ALIGN="LEFT" BALIGN="LEFT">'
|
|
109
|
+
f'{raw_val_str}<BR ALIGN="LEFT"/>'
|
|
110
|
+
f'category: {cat_str}<BR ALIGN="LEFT"/>'
|
|
111
|
+
f'trigger point: {tp_str}<BR ALIGN="LEFT"/>'
|
|
112
|
+
f'</TD></TR>\n'
|
|
113
|
+
f' <TR><TD ALIGN="LEFT" BALIGN="LEFT">{comment_display}<BR ALIGN="LEFT"/></TD></TR>\n'
|
|
114
|
+
f'</TABLE>>'
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
lines.append(f' "{node.full_node_id}" [label={label}];')
|
|
118
|
+
|
|
119
|
+
for edge in edges:
|
|
120
|
+
cat = edge.category
|
|
121
|
+
# Default black for edges.
|
|
122
|
+
color_hex = edge_colors.get(cat, "#000000")
|
|
123
|
+
|
|
124
|
+
label = _escape_html(
|
|
125
|
+
_truncate(
|
|
126
|
+
edge.category,
|
|
127
|
+
max_len=max_str_len
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
if edge.comment:
|
|
131
|
+
label = _escape_html(
|
|
132
|
+
_truncate(
|
|
133
|
+
edge.comment,
|
|
134
|
+
max_len=max_str_len
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
lines.append(
|
|
139
|
+
f' "{edge.source_full_node_id}" -> "{edge.target_full_node_id}" '
|
|
140
|
+
f'[label="{label}", color="{color_hex}", fontcolor="{color_hex}"];'
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
lines.append("}")
|
|
144
|
+
return "\n".join(lines)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def render_static(
|
|
148
|
+
nodes: list[RuntimeVariable[Any]],
|
|
149
|
+
edges: list[RuntimeEdge],
|
|
150
|
+
filename: str = "exec_graph",
|
|
151
|
+
format: str = "png",
|
|
152
|
+
node_cmap: str | Colormap | None = None,
|
|
153
|
+
edge_cmap: str | Colormap | None = None,
|
|
154
|
+
max_str_len: int = 30,
|
|
155
|
+
**kwargs: Any,
|
|
156
|
+
) -> Source:
|
|
157
|
+
"""Render a graph to a static image file via `graphviz`.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
nodes (`list[RuntimeVariable]`):
|
|
161
|
+
Variable nodes to render.
|
|
162
|
+
edges (`list[RuntimeEdge]`):
|
|
163
|
+
Edges to render.
|
|
164
|
+
filename (`str`, defaults to `"exec_graph"`):
|
|
165
|
+
Output filename (without extension).
|
|
166
|
+
format (`str`, defaults to `"png"`):
|
|
167
|
+
Image format (``"png"``, ``"svg"``, ``"pdf"``).
|
|
168
|
+
node_cmap (`str | Colormap | None`, optional):
|
|
169
|
+
Matplotlib colormap (name or object) for coloring nodes by category.
|
|
170
|
+
edge_cmap (`str | Colormap | None`, optional):
|
|
171
|
+
Matplotlib colormap (name or object) for coloring edges by category.
|
|
172
|
+
max_str_len (`int`, defaults to `30`):
|
|
173
|
+
Maximum characters for text display.
|
|
174
|
+
**kwargs (`Any`):
|
|
175
|
+
Additional keyword arguments to be passed to ``graphviz.Source.render``.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
`Source`:
|
|
179
|
+
A `graphviz.Source` object.
|
|
180
|
+
"""
|
|
181
|
+
try:
|
|
182
|
+
import graphviz as gv
|
|
183
|
+
except ImportError as e:
|
|
184
|
+
raise ImportError(
|
|
185
|
+
"`graphviz` Python package is required. "
|
|
186
|
+
"Install it via `pip install graphviz`."
|
|
187
|
+
) from e
|
|
188
|
+
|
|
189
|
+
dot_str = to_dot(
|
|
190
|
+
nodes=nodes,
|
|
191
|
+
edges=edges,
|
|
192
|
+
node_cmap=node_cmap,
|
|
193
|
+
edge_cmap=edge_cmap,
|
|
194
|
+
max_str_len=max_str_len,
|
|
195
|
+
)
|
|
196
|
+
src = gv.Source(dot_str, format=format)
|
|
197
|
+
kwargs.setdefault("cleanup", True)
|
|
198
|
+
kwargs.setdefault("view", False)
|
|
199
|
+
src.render(filename=filename, **kwargs)
|
|
200
|
+
|
|
201
|
+
return src
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Pyvis visualization backend."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from ...runtime.operation import RuntimeEdge
|
|
5
|
+
from ...runtime.variable import RuntimeVariable
|
|
6
|
+
from ._utils import _get_color_map, _truncate
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from pyvis.network import Network
|
|
11
|
+
from matplotlib.colors import Colormap
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DEFAULT_BARNES_HUT = {
|
|
15
|
+
"gravity": -12000,
|
|
16
|
+
"central_gravity": 0.12,
|
|
17
|
+
"spring_length": 400,
|
|
18
|
+
"spring_strength": 0.001,
|
|
19
|
+
"damping": 0.08,
|
|
20
|
+
"overlap": 1,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def render_interactive(
|
|
25
|
+
nodes: list[RuntimeVariable[Any]],
|
|
26
|
+
edges: list[RuntimeEdge],
|
|
27
|
+
filename: str = "exec_graph.html",
|
|
28
|
+
node_cmap: str | Colormap | None = None,
|
|
29
|
+
edge_cmap: str | Colormap | None = None,
|
|
30
|
+
max_str_len: int = 30,
|
|
31
|
+
smooth_type: str = "continuous",
|
|
32
|
+
barnes_hut_config: dict[str, float | int] | None = None,
|
|
33
|
+
**kwargs: Any,
|
|
34
|
+
) -> Network:
|
|
35
|
+
"""Render an interactive HTML graph via `pyvis`.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
nodes (`list[RuntimeVariable]`):
|
|
39
|
+
Variable nodes to render.
|
|
40
|
+
edges (`list[RuntimeEdge]`):
|
|
41
|
+
Edges to render.
|
|
42
|
+
filename (`str`, defaults to `"exec_graph.html"`):
|
|
43
|
+
Output HTML file path.
|
|
44
|
+
node_cmap (`str | Colormap | None`, optional):
|
|
45
|
+
Matplotlib colormap (name or object) for coloring nodes by
|
|
46
|
+
category.
|
|
47
|
+
edge_cmap (`str | Colormap | None`, optional):
|
|
48
|
+
Matplotlib colormap (name or object) for coloring edges by
|
|
49
|
+
category.
|
|
50
|
+
max_str_len (`int`, defaults to `30`):
|
|
51
|
+
Maximum string length for node/edge labels before truncation.
|
|
52
|
+
smooth_type (`str`, defaults to `"continuous"`):
|
|
53
|
+
Edge smoothing algorithm.
|
|
54
|
+
barnes_hut_config (`dict[str, float | int] | None`, optional):
|
|
55
|
+
Override the default BarnesHut physics parameters.
|
|
56
|
+
**kwargs (`Any`):
|
|
57
|
+
Additional keyword arguments to be passed to ``pyvis.network.Network``.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
`Network`:
|
|
61
|
+
A `pyvis.network.Network` object.
|
|
62
|
+
"""
|
|
63
|
+
try:
|
|
64
|
+
from pyvis.network import Network as PyvisNetwork
|
|
65
|
+
except ImportError as e:
|
|
66
|
+
raise ImportError(
|
|
67
|
+
"`pyvis` Python package is required. "
|
|
68
|
+
"Install it via `pip install pyvis`."
|
|
69
|
+
) from e
|
|
70
|
+
|
|
71
|
+
net = PyvisNetwork(**kwargs)
|
|
72
|
+
|
|
73
|
+
node_cats = {n.category for n in nodes}
|
|
74
|
+
edge_cats = {e.category for e in edges}
|
|
75
|
+
|
|
76
|
+
if node_cats:
|
|
77
|
+
node_colors = _get_color_map(node_cats, cmap=node_cmap)
|
|
78
|
+
else:
|
|
79
|
+
node_colors = {}
|
|
80
|
+
if edge_cats:
|
|
81
|
+
edge_colors = _get_color_map(edge_cats, cmap=edge_cmap)
|
|
82
|
+
else:
|
|
83
|
+
edge_colors = {}
|
|
84
|
+
|
|
85
|
+
for node in nodes:
|
|
86
|
+
cat = node.category
|
|
87
|
+
# Pyvis default blueish.
|
|
88
|
+
color_hex = node_colors.get(cat, "#97C2FC")
|
|
89
|
+
|
|
90
|
+
node_id_str = _truncate(node.full_node_id, max_len=max_str_len)
|
|
91
|
+
raw_val_str = _truncate(node.raw_value, max_len=max_str_len)
|
|
92
|
+
cat_str = _truncate(cat, max_len=max_str_len)
|
|
93
|
+
tp_str = _truncate(node.trigger_point, max_len=max_str_len)
|
|
94
|
+
comment_str = (
|
|
95
|
+
_truncate(node.comment, max_len=max_str_len) if node.comment else ""
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
separator = "-" * max(15, min(30, max_str_len))
|
|
99
|
+
label = (
|
|
100
|
+
f"{node_id_str}\n{separator}\n"
|
|
101
|
+
f"{raw_val_str}\ncategory: {cat_str}\n"
|
|
102
|
+
f"trigger point: {tp_str}"
|
|
103
|
+
)
|
|
104
|
+
if comment_str:
|
|
105
|
+
label += f"\n{separator}\n{comment_str}"
|
|
106
|
+
|
|
107
|
+
title = (
|
|
108
|
+
f"ID: {node.full_node_id}\nRaw Value: {node.raw_value}\n"
|
|
109
|
+
f"Category: {cat}\nTrigger Point: {node.trigger_point}"
|
|
110
|
+
)
|
|
111
|
+
if node.comment:
|
|
112
|
+
title += f"\nComment: {node.comment}"
|
|
113
|
+
|
|
114
|
+
# All fields are left aligned.
|
|
115
|
+
net.add_node(
|
|
116
|
+
node.full_node_id,
|
|
117
|
+
label=label,
|
|
118
|
+
title=title,
|
|
119
|
+
shape="box",
|
|
120
|
+
color=color_hex,
|
|
121
|
+
font={"align": "left"},
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
for edge in edges:
|
|
125
|
+
cat = edge.category
|
|
126
|
+
color_hex = edge_colors.get(cat, "#848484")
|
|
127
|
+
tp_str = _truncate(edge.trigger_point, max_len=max_str_len)
|
|
128
|
+
|
|
129
|
+
label_str = _truncate(edge.category, max_str_len)
|
|
130
|
+
if edge.comment:
|
|
131
|
+
label_str = _truncate(edge.comment, max_str_len)
|
|
132
|
+
label_str += f"\n({tp_str})"
|
|
133
|
+
|
|
134
|
+
title_str = f"Category: {cat}\nTrigger Point: {edge.trigger_point}"
|
|
135
|
+
if edge.comment:
|
|
136
|
+
title_str = (
|
|
137
|
+
f"Category: {cat}\nComment: {edge.comment}\n"
|
|
138
|
+
f"Trigger Point: {edge.trigger_point}"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
net.add_edge(
|
|
142
|
+
edge.source_full_node_id,
|
|
143
|
+
edge.target_full_node_id,
|
|
144
|
+
label=label_str,
|
|
145
|
+
title=title_str,
|
|
146
|
+
color=color_hex,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
net.set_edge_smooth(smooth_type)
|
|
150
|
+
|
|
151
|
+
bh = {**_DEFAULT_BARNES_HUT, **(barnes_hut_config or {})}
|
|
152
|
+
net.barnes_hut(**bh)
|
|
153
|
+
net.save_graph(filename)
|
|
154
|
+
|
|
155
|
+
return net
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Public APIs."""
|
|
2
|
+
|
|
3
|
+
from .public import (
|
|
4
|
+
comment_fn,
|
|
5
|
+
comment_link,
|
|
6
|
+
comment_mutation,
|
|
7
|
+
comment_op,
|
|
8
|
+
comment_variable,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"comment_fn",
|
|
14
|
+
"comment_link",
|
|
15
|
+
"comment_mutation",
|
|
16
|
+
"comment_op",
|
|
17
|
+
"comment_variable",
|
|
18
|
+
]
|