anywidget-graph 0.2.8__tar.gz → 0.2.10__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.
- anywidget_graph-0.2.10/CHANGELOG.md +127 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/PKG-INFO +2 -2
- anywidget_graph-0.2.10/examples/test_hierarchy.py +99 -0
- anywidget_graph-0.2.10/examples/test_large_graph.py +91 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/pyproject.toml +2 -2
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/ui/__init__.py +1 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/ui/index.js +430 -66
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/ui/properties.js +5 -4
- anywidget_graph-0.2.10/src/anywidget_graph/ui/schema.js +455 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/ui/styles.css +83 -1
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/widget.py +2 -2
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/tests/test_graph.py +1 -1
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/uv.lock +2 -2
- anywidget_graph-0.2.8/src/anywidget_graph/ui/schema.js +0 -239
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/.github/workflows/ci.yml +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/.github/workflows/pypi.yml +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/.gitignore +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/.python-version +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/LICENSE +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/README.md +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/examples/demo.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/__init__.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/backends/__init__.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/backends/arango.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/backends/cosmosdb.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/backends/grafeo.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/backends/ladybug.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/backends/neo4j.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/converters/__init__.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/converters/base.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/converters/common.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/converters/cypher.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/converters/gql.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/converters/graphql.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/converters/gremlin.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/converters/sparql.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/demo.py +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/py.typed +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/ui/grafeo-embed.js +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/ui/grafeo.js +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/ui/icons.js +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/ui/neo4j.js +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/ui/results.js +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/ui/settings.js +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/src/anywidget_graph/ui/toolbar.js +0 -0
- {anywidget_graph-0.2.8 → anywidget_graph-0.2.10}/tests/__init__.py +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.10
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- **Spring layout (d3-force)**: New default layout using d3-force with charge, link-distance and collision forces; produces clean hub-spoke rings for hierarchical data like Neo4j browser
|
|
8
|
+
- **Degree-based node sizing**: When no explicit `size_field` is set, nodes are sized by their connection count; hub nodes appear larger and push neighbors into natural rings
|
|
9
|
+
- **Filter-aware layouts**: Switching layout or clicking refresh now re-runs the layout using only visible nodes, respecting type filters and search state
|
|
10
|
+
|
|
11
|
+
### Improvements
|
|
12
|
+
|
|
13
|
+
- Spring layout is the new default (renamed to "Default" in the dropdown)
|
|
14
|
+
- Force layout uses `linLogMode` for better community separation
|
|
15
|
+
- Cluster layout filters out hidden node types before packing
|
|
16
|
+
- Filter panel refresh button triggers a layout refresh
|
|
17
|
+
|
|
18
|
+
## 0.2.9
|
|
19
|
+
|
|
20
|
+
### New Features
|
|
21
|
+
|
|
22
|
+
- **Filter panel**: Schema sidebar transformed into a filter panel with per-type checkboxes, counts, color pickers and "all / none" toggle links for both node and edge types
|
|
23
|
+
- **Type-based node coloring**: Nodes automatically colored by their type (first label), matching the filter panel swatches
|
|
24
|
+
- **Color pickers**: Click any type's color swatch in the filter panel to change it; graph updates in real-time
|
|
25
|
+
- **Level-of-detail labels**: Labels auto-hide on small or zoomed-out nodes; thresholds scale with graph size
|
|
26
|
+
- **Circle-packing cluster layout**: Deterministic circle packing with radius proportional to `sqrt(nodeCount)` and mini-force within each cluster for organic internal structure
|
|
27
|
+
- **Edge interaction**: Edges are now clickable with type shown in the properties panel
|
|
28
|
+
|
|
29
|
+
### Bug Fixes
|
|
30
|
+
|
|
31
|
+
- Fixed widget height growing on scroll; wrapper now locked to fixed height
|
|
32
|
+
- Fixed search filtering: now shows matching nodes plus first-degree neighbors and connecting edges, hides everything else
|
|
33
|
+
- Fixed parallel edge crash by using Graphology multi-graph mode
|
|
34
|
+
- Fixed graph disappearing when side panels open by refreshing renderer on resize
|
|
35
|
+
- Fixed dark mode label color: adapts to current theme
|
|
36
|
+
|
|
37
|
+
### Improvements
|
|
38
|
+
|
|
39
|
+
- Zoom controls reordered: fit, zoom-in, zoom-out at top; layout and mode at bottom
|
|
40
|
+
- Edge type shown in properties panel on click
|
|
41
|
+
- Internal attributes (`nodeType`, `edgeType`) hidden from properties panel
|
|
42
|
+
|
|
43
|
+
## 0.2.8
|
|
44
|
+
|
|
45
|
+
### New Features
|
|
46
|
+
|
|
47
|
+
- **Language-aware query dispatch**: GrafeoBackend routes queries to language-specific methods (`execute_cypher`, `execute_gremlin`, etc.) with fallback to generic `execute()`
|
|
48
|
+
- **Results drawer**: Bottom drawer with tabbed nodes/edges tables, pagination and query timing
|
|
49
|
+
- **Grafeo native dict support**: Detection and conversion of Grafeo's internal `_id`/`_labels`/`_source`/`_target`/`_type` dict format so native query results render correctly
|
|
50
|
+
|
|
51
|
+
### Improvements
|
|
52
|
+
|
|
53
|
+
- Added playground link at grafeo.ai to README
|
|
54
|
+
|
|
55
|
+
## 0.2.7
|
|
56
|
+
|
|
57
|
+
### Bug Fixes
|
|
58
|
+
|
|
59
|
+
- Fixed lasso/box selection behavior
|
|
60
|
+
|
|
61
|
+
### Improvements
|
|
62
|
+
|
|
63
|
+
- Quality-of-life improvements to search and toolbar
|
|
64
|
+
|
|
65
|
+
## 0.2.6
|
|
66
|
+
|
|
67
|
+
### New Features
|
|
68
|
+
|
|
69
|
+
- **Browser-side backends**: Added Grafeo server (HTTP), Grafeo WASM (browser-embedded) and Neo4j browser-side driver support
|
|
70
|
+
- **Settings panel**: Full connection UI with backend selector, connection mode, language picker and connect/disconnect
|
|
71
|
+
- **CosmosDB backend**: Added Azure CosmosDB Gremlin backend
|
|
72
|
+
- **Demo mode**: WASM-based demo with pre-populated dataset
|
|
73
|
+
- **Schema browser**: Sidebar with node labels and relationship types; click to query
|
|
74
|
+
- **Properties panel**: Right sidebar showing selected node/edge details
|
|
75
|
+
- **Zoom and pan controls**: Zoom in/out/fit buttons, box select mode, click select mode
|
|
76
|
+
|
|
77
|
+
### Improvements
|
|
78
|
+
|
|
79
|
+
- Updated docs, tests and dependencies
|
|
80
|
+
- HTML export template fix
|
|
81
|
+
- Zoom controls added to graph container
|
|
82
|
+
|
|
83
|
+
## 0.2.5
|
|
84
|
+
|
|
85
|
+
### New Features
|
|
86
|
+
|
|
87
|
+
- **Major UI overhaul**: Modular component architecture with toolbar, schema panel, settings, properties and results drawer
|
|
88
|
+
- **Multiple backends**: Grafeo, Neo4j, LadybugDB, ArangoDB and CosmosDB support
|
|
89
|
+
- **Test suite**: Comprehensive test coverage with 55+ tests
|
|
90
|
+
- **Node interaction**: Click, drag, pin, double-click expand, delete and multi-select
|
|
91
|
+
- **Force layout**: ForceAtlas2 with dynamic parameters scaling by graph size
|
|
92
|
+
- **Keyboard shortcuts**: F to fit, Escape to deselect, Delete to remove selected
|
|
93
|
+
|
|
94
|
+
### Improvements
|
|
95
|
+
|
|
96
|
+
- Consistent code formatting with ruff
|
|
97
|
+
|
|
98
|
+
## 0.2.4
|
|
99
|
+
|
|
100
|
+
### Bug Fixes
|
|
101
|
+
|
|
102
|
+
- Fixed ESM bundling issue in UI init
|
|
103
|
+
|
|
104
|
+
## 0.2.3
|
|
105
|
+
|
|
106
|
+
### Bug Fixes
|
|
107
|
+
|
|
108
|
+
- Fixed rendering issue in graph container
|
|
109
|
+
- Updated dependencies
|
|
110
|
+
|
|
111
|
+
## 0.2.2
|
|
112
|
+
|
|
113
|
+
### Improvements
|
|
114
|
+
|
|
115
|
+
- Added Apache 2.0 license
|
|
116
|
+
- ESM bundle aggregation for widget JS
|
|
117
|
+
|
|
118
|
+
## 0.2.1
|
|
119
|
+
|
|
120
|
+
### Improvements
|
|
121
|
+
|
|
122
|
+
- Updated README with usage examples
|
|
123
|
+
- Code formatting pass
|
|
124
|
+
|
|
125
|
+
## 0.1.0
|
|
126
|
+
|
|
127
|
+
- Initial release of anywidget-graph
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anywidget-graph
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.10
|
|
4
4
|
Summary: Interactive graph visualization for Python notebooks using anywidget
|
|
5
5
|
Project-URL: Homepage, https://grafeo.dev/
|
|
6
6
|
Project-URL: Repository, https://github.com/GrafeoDB/anywidget-graph
|
|
@@ -29,7 +29,7 @@ Requires-Dist: marimo>=0.20; extra == 'dev'
|
|
|
29
29
|
Requires-Dist: prek>=0.3; extra == 'dev'
|
|
30
30
|
Requires-Dist: pytest>=9.0.2; extra == 'dev'
|
|
31
31
|
Requires-Dist: ruff>=0.15; extra == 'dev'
|
|
32
|
-
Requires-Dist: ty>=0.0
|
|
32
|
+
Requires-Dist: ty>=0.0; extra == 'dev'
|
|
33
33
|
Provides-Extra: embed
|
|
34
34
|
Provides-Extra: grafeo
|
|
35
35
|
Requires-Dist: grafeo>=0.5; extra == 'grafeo'
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Marimo demo: hierarchical repo-like graph to test force layout with hub-spoke data."""
|
|
2
|
+
|
|
3
|
+
import marimo
|
|
4
|
+
|
|
5
|
+
__generated_with = "0.19.5"
|
|
6
|
+
app = marimo.App(width="medium")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.cell
|
|
10
|
+
def _():
|
|
11
|
+
import marimo as mo
|
|
12
|
+
|
|
13
|
+
return (mo,)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.cell
|
|
17
|
+
def _():
|
|
18
|
+
from anywidget_graph import Graph
|
|
19
|
+
|
|
20
|
+
return (Graph,)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.cell
|
|
24
|
+
def _(Graph):
|
|
25
|
+
# Simulate a repository structure:
|
|
26
|
+
# 1 Repository -> 4 Directories -> Files -> TypeDefinitions
|
|
27
|
+
nodes = [
|
|
28
|
+
{"id": "repo", "labels": ["Repository"], "name": "my-app"},
|
|
29
|
+
]
|
|
30
|
+
edges = []
|
|
31
|
+
|
|
32
|
+
dirs = ["src", "lib", "tests", "config"]
|
|
33
|
+
file_id = 0
|
|
34
|
+
type_id = 0
|
|
35
|
+
|
|
36
|
+
for d in dirs:
|
|
37
|
+
did = f"dir_{d}"
|
|
38
|
+
nodes.append({"id": did, "labels": ["Directory"], "name": d})
|
|
39
|
+
edges.append({"source": "repo", "target": did, "label": "CONTAINS", "type": "CONTAINS"})
|
|
40
|
+
|
|
41
|
+
# Each directory has 3-8 files
|
|
42
|
+
import random
|
|
43
|
+
|
|
44
|
+
random.seed(hash(d))
|
|
45
|
+
n_files = random.randint(3, 8)
|
|
46
|
+
for i in range(n_files):
|
|
47
|
+
fid = f"file_{file_id}"
|
|
48
|
+
fname = f"{d}/file_{i}.py"
|
|
49
|
+
nodes.append({"id": fid, "labels": ["File"], "name": fname})
|
|
50
|
+
edges.append({"source": did, "target": fid, "label": "CONTAINS", "type": "CONTAINS"})
|
|
51
|
+
file_id += 1
|
|
52
|
+
|
|
53
|
+
# Each file has 1-5 type definitions
|
|
54
|
+
n_types = random.randint(1, 5)
|
|
55
|
+
for _j in range(n_types):
|
|
56
|
+
tid = f"type_{type_id}"
|
|
57
|
+
tname = f"Class_{type_id}"
|
|
58
|
+
nodes.append({"id": tid, "labels": ["TypeDefinition"], "name": tname})
|
|
59
|
+
edges.append({"source": fid, "target": tid, "label": "DEFINES", "type": "DEFINES"})
|
|
60
|
+
type_id += 1
|
|
61
|
+
|
|
62
|
+
# Some types depend on other types (cross-file references)
|
|
63
|
+
if type_id > 3 and random.random() < 0.3:
|
|
64
|
+
dep = f"type_{random.randint(0, type_id - 2)}"
|
|
65
|
+
edges.append({"source": tid, "target": dep, "label": "DEPENDS_ON", "type": "DEPENDS_ON"})
|
|
66
|
+
|
|
67
|
+
# Some cross-directory imports
|
|
68
|
+
if d != "src":
|
|
69
|
+
edges.append(
|
|
70
|
+
{
|
|
71
|
+
"source": did,
|
|
72
|
+
"target": "dir_src",
|
|
73
|
+
"label": "IMPORTS_FROM",
|
|
74
|
+
"type": "IMPORTS_FROM",
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
graph = Graph.from_dict(
|
|
79
|
+
{"nodes": nodes, "edges": edges},
|
|
80
|
+
width=1000,
|
|
81
|
+
height=700,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
graph # noqa: B018
|
|
85
|
+
return (graph,)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.cell
|
|
89
|
+
def _(graph, mo):
|
|
90
|
+
mo.md(f"""
|
|
91
|
+
**Selected node:** {graph.selected_node}
|
|
92
|
+
|
|
93
|
+
Hierarchical repo graph: Repository -> Directories -> Files -> TypeDefinitions
|
|
94
|
+
""")
|
|
95
|
+
return ()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
app.run()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Marimo demo: large graph to test filtering, search, and LOD."""
|
|
2
|
+
|
|
3
|
+
import marimo
|
|
4
|
+
|
|
5
|
+
__generated_with = "0.19.5"
|
|
6
|
+
app = marimo.App(width="medium")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.cell
|
|
10
|
+
def _():
|
|
11
|
+
import marimo as mo
|
|
12
|
+
|
|
13
|
+
return (mo,)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.cell
|
|
17
|
+
def _():
|
|
18
|
+
from anywidget_graph import Graph
|
|
19
|
+
|
|
20
|
+
return (Graph,)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.cell
|
|
24
|
+
def _(Graph):
|
|
25
|
+
import random
|
|
26
|
+
|
|
27
|
+
random.seed(42)
|
|
28
|
+
|
|
29
|
+
node_types = {
|
|
30
|
+
"Person": 40,
|
|
31
|
+
"Company": 15,
|
|
32
|
+
"Product": 25,
|
|
33
|
+
"City": 10,
|
|
34
|
+
"Skill": 20,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
edge_types = ["WORKS_AT", "LIVES_IN", "KNOWS", "MANAGES", "USES", "SELLS", "HAS_SKILL"]
|
|
38
|
+
|
|
39
|
+
nodes = []
|
|
40
|
+
node_ids = []
|
|
41
|
+
for label, count in node_types.items():
|
|
42
|
+
for i in range(count):
|
|
43
|
+
nid = f"{label.lower()}_{i}"
|
|
44
|
+
node_ids.append(nid)
|
|
45
|
+
nodes.append(
|
|
46
|
+
{
|
|
47
|
+
"id": nid,
|
|
48
|
+
"labels": [label],
|
|
49
|
+
"name": f"{label} {i}",
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
edges = []
|
|
54
|
+
for _ in range(250):
|
|
55
|
+
src = random.choice(node_ids)
|
|
56
|
+
tgt = random.choice(node_ids)
|
|
57
|
+
if src != tgt:
|
|
58
|
+
edges.append(
|
|
59
|
+
{
|
|
60
|
+
"source": src,
|
|
61
|
+
"target": tgt,
|
|
62
|
+
"label": random.choice(edge_types),
|
|
63
|
+
"type": random.choice(edge_types),
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
graph = Graph.from_dict(
|
|
68
|
+
{"nodes": nodes, "edges": edges},
|
|
69
|
+
width=1000,
|
|
70
|
+
height=700,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
graph # noqa: B018
|
|
74
|
+
return (graph,)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@app.cell
|
|
78
|
+
def _(graph, mo):
|
|
79
|
+
mo.md(f"""
|
|
80
|
+
**Selected node:** {graph.selected_node}
|
|
81
|
+
|
|
82
|
+
Try:
|
|
83
|
+
- Open the Filter panel (sidebar button) to toggle node/edge types
|
|
84
|
+
- Use the search bar to filter (e.g. type "Person" or "Company")
|
|
85
|
+
- Zoom in/out to see label LOD
|
|
86
|
+
""")
|
|
87
|
+
return ()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
app.run()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "anywidget-graph"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.10"
|
|
4
4
|
description = "Interactive graph visualization for Python notebooks using anywidget"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -30,7 +30,7 @@ dev = [
|
|
|
30
30
|
"prek>=0.3",
|
|
31
31
|
"pytest>=9.0.2",
|
|
32
32
|
"ruff>=0.15",
|
|
33
|
-
"ty>=0.0
|
|
33
|
+
"ty>=0.0",
|
|
34
34
|
"marimo>=0.20",
|
|
35
35
|
]
|
|
36
36
|
networkx = ["networkx>=3.0"]
|
|
@@ -151,6 +151,7 @@ import Sigma from "https://esm.sh/sigma@3.0.0";
|
|
|
151
151
|
import circular from "https://esm.sh/graphology-layout@0.6.1/circular.js";
|
|
152
152
|
import random from "https://esm.sh/graphology-layout@0.6.1/random.js";
|
|
153
153
|
import forceAtlas2 from "https://esm.sh/graphology-layout-forceatlas2@0.10.1";
|
|
154
|
+
import * as d3Force from "https://esm.sh/d3-force@3.0.0";
|
|
154
155
|
import neo4j from "https://cdn.jsdelivr.net/npm/neo4j-driver@5.28.0/lib/browser/neo4j-web.esm.min.js";
|
|
155
156
|
|
|
156
157
|
// === Icons ===
|