anywidget-graph 0.2.5__tar.gz → 0.2.7__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.
Files changed (46) hide show
  1. anywidget_graph-0.2.7/PKG-INFO +332 -0
  2. anywidget_graph-0.2.7/README.md +291 -0
  3. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/pyproject.toml +6 -6
  4. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/__init__.py +1 -1
  5. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/ui/__init__.py +3 -0
  6. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/ui/icons.js +4 -0
  7. anywidget_graph-0.2.7/src/anywidget_graph/ui/index.js +810 -0
  8. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/ui/styles.css +155 -0
  9. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/ui/toolbar.js +33 -1
  10. anywidget_graph-0.2.7/src/anywidget_graph/widget.py +1070 -0
  11. anywidget_graph-0.2.7/tests/test_graph.py +589 -0
  12. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/uv.lock +29 -29
  13. anywidget_graph-0.2.5/PKG-INFO +0 -266
  14. anywidget_graph-0.2.5/README.md +0 -225
  15. anywidget_graph-0.2.5/src/anywidget_graph/ui/index.js +0 -262
  16. anywidget_graph-0.2.5/src/anywidget_graph/widget.py +0 -492
  17. anywidget_graph-0.2.5/tests/test_graph.py +0 -55
  18. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/.github/workflows/ci.yml +0 -0
  19. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/.github/workflows/pypi.yml +0 -0
  20. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/.gitignore +0 -0
  21. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/.python-version +0 -0
  22. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/LICENSE +0 -0
  23. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/examples/demo.py +0 -0
  24. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/backends/__init__.py +0 -0
  25. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/backends/arango.py +0 -0
  26. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/backends/cosmosdb.py +0 -0
  27. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/backends/grafeo.py +0 -0
  28. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/backends/ladybug.py +0 -0
  29. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/backends/neo4j.py +0 -0
  30. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/converters/__init__.py +0 -0
  31. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/converters/base.py +0 -0
  32. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/converters/common.py +0 -0
  33. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/converters/cypher.py +0 -0
  34. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/converters/gql.py +0 -0
  35. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/converters/graphql.py +0 -0
  36. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/converters/gremlin.py +0 -0
  37. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/converters/sparql.py +0 -0
  38. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/demo.py +0 -0
  39. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/py.typed +0 -0
  40. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/ui/grafeo-embed.js +0 -0
  41. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/ui/grafeo.js +0 -0
  42. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/ui/neo4j.js +0 -0
  43. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/ui/properties.js +0 -0
  44. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/ui/schema.js +0 -0
  45. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/src/anywidget_graph/ui/settings.js +0 -0
  46. {anywidget_graph-0.2.5 → anywidget_graph-0.2.7}/tests/__init__.py +0 -0
@@ -0,0 +1,332 @@
1
+ Metadata-Version: 2.4
2
+ Name: anywidget-graph
3
+ Version: 0.2.7
4
+ Summary: Interactive graph visualization for Python notebooks using anywidget
5
+ Project-URL: Homepage, https://grafeo.dev/
6
+ Project-URL: Repository, https://github.com/GrafeoDB/anywidget-graph
7
+ Author-email: "S.T. Grond" <dev@grafeo.dev>
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ Keywords: anywidget,cypher,graph,jupyter,marimo,neo4j,visualization
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Framework :: Jupyter
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Scientific/Engineering :: Visualization
21
+ Requires-Python: >=3.12
22
+ Requires-Dist: anywidget>=0.9.21
23
+ Requires-Dist: marimo>=0.19.9
24
+ Provides-Extra: cosmosdb
25
+ Requires-Dist: gremlinpython>=3.7; extra == 'cosmosdb'
26
+ Provides-Extra: demo
27
+ Provides-Extra: dev
28
+ Requires-Dist: marimo>=0.20; extra == 'dev'
29
+ Requires-Dist: prek>=0.3; extra == 'dev'
30
+ Requires-Dist: pytest>=9.0.2; extra == 'dev'
31
+ Requires-Dist: ruff>=0.15; extra == 'dev'
32
+ Requires-Dist: ty>=0.0.20; extra == 'dev'
33
+ Provides-Extra: embed
34
+ Provides-Extra: grafeo
35
+ Requires-Dist: grafeo>=0.5; extra == 'grafeo'
36
+ Provides-Extra: networkx
37
+ Requires-Dist: networkx>=3.0; extra == 'networkx'
38
+ Provides-Extra: pandas
39
+ Requires-Dist: pandas>=2.0; extra == 'pandas'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # anywidget-graph
43
+
44
+ Interactive graph visualization for Python notebooks.
45
+
46
+ Works with Marimo, Jupyter, VS Code, Colab, anywhere [anywidget](https://anywidget.dev/) runs.
47
+
48
+ ## Features
49
+
50
+ - **Universal**: One widget, every notebook environment
51
+ - **Backend-agnostic**: Grafeo, Neo4j, NetworkX, pandas, or raw dicts
52
+ - **Interactive**: Pan, zoom, click, drag, pin, expand neighbors, box select
53
+ - **Customizable**: Colors, sizes, layouts, dark mode
54
+ - **Exportable**: HTML, JSON
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ uv add anywidget-graph
60
+ ```
61
+
62
+ Optional extras:
63
+
64
+ ```bash
65
+ uv add "anywidget-graph[networkx]" # NetworkX support
66
+ uv add "anywidget-graph[pandas]" # pandas support
67
+ uv add "anywidget-graph[grafeo]" # Grafeo backend
68
+ uv add "anywidget-graph[cosmosdb]" # CosmosDB / Gremlin support
69
+ ```
70
+
71
+ ## Quick Start
72
+
73
+ ```python
74
+ from anywidget_graph import Graph
75
+
76
+ graph = Graph.from_dict({
77
+ "nodes": [
78
+ {"id": "alice", "label": "Alice", "group": "person"},
79
+ {"id": "bob", "label": "Bob", "group": "person"},
80
+ {"id": "paper", "label": "Graph Theory", "group": "document"},
81
+ ],
82
+ "edges": [
83
+ {"source": "alice", "target": "bob", "label": "knows"},
84
+ {"source": "alice", "target": "paper", "label": "authored"},
85
+ ]
86
+ })
87
+
88
+ graph
89
+ ```
90
+
91
+ ## Data Sources
92
+
93
+ ### Dictionary
94
+
95
+ ```python
96
+ graph = Graph.from_dict({
97
+ "nodes": [{"id": "a"}, {"id": "b"}],
98
+ "edges": [{"source": "a", "target": "b"}]
99
+ })
100
+ ```
101
+
102
+ ### Direct initialization
103
+
104
+ ```python
105
+ graph = Graph(
106
+ nodes=[{"id": "a", "label": "Alice"}, {"id": "b", "label": "Bob"}],
107
+ edges=[{"source": "a", "target": "b", "label": "KNOWS"}],
108
+ )
109
+ ```
110
+
111
+ ### Cypher results (Neo4j)
112
+
113
+ ```python
114
+ from neo4j import GraphDatabase
115
+
116
+ driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password"))
117
+
118
+ with driver.session() as session:
119
+ result = session.run("MATCH (a)-[r]->(b) RETURN a, r, b LIMIT 100")
120
+ graph = Graph.from_cypher(result)
121
+ ```
122
+
123
+ ### GQL results
124
+
125
+ ```python
126
+ graph = Graph.from_gql(result)
127
+ ```
128
+
129
+ ### SPARQL results
130
+
131
+ ```python
132
+ from rdflib import Graph as RDFGraph
133
+
134
+ g = RDFGraph()
135
+ g.parse("data.ttl")
136
+ result = g.query("SELECT ?s ?p ?o WHERE { ?s ?p ?o }")
137
+ graph = Graph.from_sparql(result)
138
+ ```
139
+
140
+ ### Gremlin results (CosmosDB, TinkerPop)
141
+
142
+ ```python
143
+ graph = Graph.from_gremlin(result)
144
+ ```
145
+
146
+ ### GraphQL results
147
+
148
+ ```python
149
+ graph = Graph.from_graphql(
150
+ response.json(),
151
+ nodes_path="data.characters.results",
152
+ id_field="id",
153
+ label_field="name",
154
+ )
155
+ ```
156
+
157
+ ### NetworkX
158
+
159
+ ```python
160
+ import networkx as nx
161
+
162
+ G = nx.karate_club_graph()
163
+ graph = Graph.from_networkx(G)
164
+ ```
165
+
166
+ ### pandas DataFrames
167
+
168
+ ```python
169
+ import pandas as pd
170
+
171
+ nodes_df = pd.DataFrame({"id": ["alice", "bob"], "group": ["person", "person"]})
172
+ edges_df = pd.DataFrame({"source": ["alice"], "target": ["bob"], "weight": [1.0]})
173
+
174
+ graph = Graph.from_dataframe(nodes_df, edges_df)
175
+ ```
176
+
177
+ ## Interactivity
178
+
179
+ ### Events
180
+
181
+ ```python
182
+ graph = Graph.from_dict(data)
183
+
184
+ @graph.on_node_click
185
+ def handle_node(node_id, node_data):
186
+ print(f"Clicked: {node_id}")
187
+
188
+ @graph.on_edge_click
189
+ def handle_edge(edge_data):
190
+ print(f"Edge: {edge_data['label']}")
191
+
192
+ @graph.on_selection
193
+ def handle_selection(node_ids):
194
+ print(f"Selected: {node_ids}")
195
+ ```
196
+
197
+ ### Selection
198
+
199
+ ```python
200
+ graph.selected_nodes # Current selection (list of IDs)
201
+ graph.selection_mode = "box" # Switch to box-select mode
202
+ ```
203
+
204
+ ### Node expansion
205
+
206
+ ```python
207
+ graph.expand_node("alice") # Fetch and merge neighbors (requires backend)
208
+ ```
209
+
210
+ ### Node pinning
211
+
212
+ ```python
213
+ graph.pin_nodes(["alice", "bob"]) # Pin at current positions
214
+ graph.unpin_nodes(["alice"]) # Release back to layout
215
+ graph.toggle_pin("bob") # Toggle pin state
216
+ graph.unpin_all() # Unpin everything
217
+ ```
218
+
219
+ ### Clear
220
+
221
+ ```python
222
+ graph.clear() # Remove all nodes, edges, pins, and selection
223
+ ```
224
+
225
+ ## Styling
226
+
227
+ ### Property-based coloring
228
+
229
+ ```python
230
+ graph = Graph.from_dict(
231
+ data,
232
+ color_field="group", # Color nodes by field
233
+ color_scale="viridis", # Scale: viridis, plasma, inferno, magma, cividis, turbo
234
+ size_field="score", # Size nodes by field
235
+ size_range=[5, 30], # Min/max node size
236
+ )
237
+ ```
238
+
239
+ ### Edge styling
240
+
241
+ ```python
242
+ graph.edge_color_field = "type"
243
+ graph.edge_color_scale = "plasma"
244
+ graph.edge_size_field = "weight"
245
+ graph.edge_size_range = [1, 8]
246
+ ```
247
+
248
+ ### Layouts
249
+
250
+ ```python
251
+ Graph.from_dict(data, layout="force") # ForceAtlas2 (default)
252
+ Graph.from_dict(data, layout="circular")
253
+ Graph.from_dict(data, layout="random")
254
+ ```
255
+
256
+ ## Options
257
+
258
+ ```python
259
+ graph = Graph(
260
+ nodes=nodes,
261
+ edges=edges,
262
+ width=800, # Widget width (px)
263
+ height=600, # Widget height (px)
264
+ background="#fafafa", # Background color
265
+ show_labels=True, # Node labels
266
+ show_edge_labels=False, # Edge labels
267
+ show_toolbar=True, # Toolbar visibility
268
+ show_settings=True, # Settings panel
269
+ show_query_input=True, # Query input box
270
+ dark_mode=True, # Dark theme
271
+ show_tooltip=True, # Hover tooltips
272
+ tooltip_fields=["label", "id"],
273
+ max_nodes=300, # Limit for node expansion
274
+ )
275
+ ```
276
+
277
+ ## Database Backends
278
+
279
+ ### Grafeo (default)
280
+
281
+ ```python
282
+ import grafeo
283
+ db = grafeo.GrafeoDB()
284
+ graph = Graph(database_backend="grafeo", grafeo_db=db)
285
+ ```
286
+
287
+ ### Neo4j (browser-side)
288
+
289
+ ```python
290
+ graph = Graph(
291
+ database_backend="neo4j",
292
+ connection_uri="neo4j+s://demo.neo4jlabs.com",
293
+ connection_username="neo4j",
294
+ connection_password="password",
295
+ )
296
+ ```
297
+
298
+ ### Generic backend
299
+
300
+ ```python
301
+ graph = Graph(backend=my_backend) # Any object implementing DatabaseBackend protocol
302
+ ```
303
+
304
+ ## Export
305
+
306
+ ```python
307
+ graph.to_json() # JSON string with nodes and edges
308
+ graph.to_html() # Self-contained HTML string
309
+ graph.to_html(title="My Graph") # Custom title
310
+ graph.save_html("graph.html") # Write HTML to file
311
+ ```
312
+
313
+ ## Environment Support
314
+
315
+ | Environment | Supported |
316
+ |-------------|-----------|
317
+ | Marimo | Yes |
318
+ | JupyterLab | Yes |
319
+ | Jupyter Notebook | Yes |
320
+ | VS Code | Yes |
321
+ | Google Colab | Yes |
322
+ | Databricks | Yes |
323
+
324
+ ## Related
325
+
326
+ - [anywidget](https://anywidget.dev/), custom Jupyter widgets made easy
327
+ - [Grafeo](https://github.com/GrafeoDB/grafeo), embeddable graph database
328
+ - [grafeo-web](https://github.com/GrafeoDB/grafeo-web), Grafeo in the browser
329
+
330
+ ## License
331
+
332
+ Apache-2.0
@@ -0,0 +1,291 @@
1
+ # anywidget-graph
2
+
3
+ Interactive graph visualization for Python notebooks.
4
+
5
+ Works with Marimo, Jupyter, VS Code, Colab, anywhere [anywidget](https://anywidget.dev/) runs.
6
+
7
+ ## Features
8
+
9
+ - **Universal**: One widget, every notebook environment
10
+ - **Backend-agnostic**: Grafeo, Neo4j, NetworkX, pandas, or raw dicts
11
+ - **Interactive**: Pan, zoom, click, drag, pin, expand neighbors, box select
12
+ - **Customizable**: Colors, sizes, layouts, dark mode
13
+ - **Exportable**: HTML, JSON
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ uv add anywidget-graph
19
+ ```
20
+
21
+ Optional extras:
22
+
23
+ ```bash
24
+ uv add "anywidget-graph[networkx]" # NetworkX support
25
+ uv add "anywidget-graph[pandas]" # pandas support
26
+ uv add "anywidget-graph[grafeo]" # Grafeo backend
27
+ uv add "anywidget-graph[cosmosdb]" # CosmosDB / Gremlin support
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```python
33
+ from anywidget_graph import Graph
34
+
35
+ graph = Graph.from_dict({
36
+ "nodes": [
37
+ {"id": "alice", "label": "Alice", "group": "person"},
38
+ {"id": "bob", "label": "Bob", "group": "person"},
39
+ {"id": "paper", "label": "Graph Theory", "group": "document"},
40
+ ],
41
+ "edges": [
42
+ {"source": "alice", "target": "bob", "label": "knows"},
43
+ {"source": "alice", "target": "paper", "label": "authored"},
44
+ ]
45
+ })
46
+
47
+ graph
48
+ ```
49
+
50
+ ## Data Sources
51
+
52
+ ### Dictionary
53
+
54
+ ```python
55
+ graph = Graph.from_dict({
56
+ "nodes": [{"id": "a"}, {"id": "b"}],
57
+ "edges": [{"source": "a", "target": "b"}]
58
+ })
59
+ ```
60
+
61
+ ### Direct initialization
62
+
63
+ ```python
64
+ graph = Graph(
65
+ nodes=[{"id": "a", "label": "Alice"}, {"id": "b", "label": "Bob"}],
66
+ edges=[{"source": "a", "target": "b", "label": "KNOWS"}],
67
+ )
68
+ ```
69
+
70
+ ### Cypher results (Neo4j)
71
+
72
+ ```python
73
+ from neo4j import GraphDatabase
74
+
75
+ driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password"))
76
+
77
+ with driver.session() as session:
78
+ result = session.run("MATCH (a)-[r]->(b) RETURN a, r, b LIMIT 100")
79
+ graph = Graph.from_cypher(result)
80
+ ```
81
+
82
+ ### GQL results
83
+
84
+ ```python
85
+ graph = Graph.from_gql(result)
86
+ ```
87
+
88
+ ### SPARQL results
89
+
90
+ ```python
91
+ from rdflib import Graph as RDFGraph
92
+
93
+ g = RDFGraph()
94
+ g.parse("data.ttl")
95
+ result = g.query("SELECT ?s ?p ?o WHERE { ?s ?p ?o }")
96
+ graph = Graph.from_sparql(result)
97
+ ```
98
+
99
+ ### Gremlin results (CosmosDB, TinkerPop)
100
+
101
+ ```python
102
+ graph = Graph.from_gremlin(result)
103
+ ```
104
+
105
+ ### GraphQL results
106
+
107
+ ```python
108
+ graph = Graph.from_graphql(
109
+ response.json(),
110
+ nodes_path="data.characters.results",
111
+ id_field="id",
112
+ label_field="name",
113
+ )
114
+ ```
115
+
116
+ ### NetworkX
117
+
118
+ ```python
119
+ import networkx as nx
120
+
121
+ G = nx.karate_club_graph()
122
+ graph = Graph.from_networkx(G)
123
+ ```
124
+
125
+ ### pandas DataFrames
126
+
127
+ ```python
128
+ import pandas as pd
129
+
130
+ nodes_df = pd.DataFrame({"id": ["alice", "bob"], "group": ["person", "person"]})
131
+ edges_df = pd.DataFrame({"source": ["alice"], "target": ["bob"], "weight": [1.0]})
132
+
133
+ graph = Graph.from_dataframe(nodes_df, edges_df)
134
+ ```
135
+
136
+ ## Interactivity
137
+
138
+ ### Events
139
+
140
+ ```python
141
+ graph = Graph.from_dict(data)
142
+
143
+ @graph.on_node_click
144
+ def handle_node(node_id, node_data):
145
+ print(f"Clicked: {node_id}")
146
+
147
+ @graph.on_edge_click
148
+ def handle_edge(edge_data):
149
+ print(f"Edge: {edge_data['label']}")
150
+
151
+ @graph.on_selection
152
+ def handle_selection(node_ids):
153
+ print(f"Selected: {node_ids}")
154
+ ```
155
+
156
+ ### Selection
157
+
158
+ ```python
159
+ graph.selected_nodes # Current selection (list of IDs)
160
+ graph.selection_mode = "box" # Switch to box-select mode
161
+ ```
162
+
163
+ ### Node expansion
164
+
165
+ ```python
166
+ graph.expand_node("alice") # Fetch and merge neighbors (requires backend)
167
+ ```
168
+
169
+ ### Node pinning
170
+
171
+ ```python
172
+ graph.pin_nodes(["alice", "bob"]) # Pin at current positions
173
+ graph.unpin_nodes(["alice"]) # Release back to layout
174
+ graph.toggle_pin("bob") # Toggle pin state
175
+ graph.unpin_all() # Unpin everything
176
+ ```
177
+
178
+ ### Clear
179
+
180
+ ```python
181
+ graph.clear() # Remove all nodes, edges, pins, and selection
182
+ ```
183
+
184
+ ## Styling
185
+
186
+ ### Property-based coloring
187
+
188
+ ```python
189
+ graph = Graph.from_dict(
190
+ data,
191
+ color_field="group", # Color nodes by field
192
+ color_scale="viridis", # Scale: viridis, plasma, inferno, magma, cividis, turbo
193
+ size_field="score", # Size nodes by field
194
+ size_range=[5, 30], # Min/max node size
195
+ )
196
+ ```
197
+
198
+ ### Edge styling
199
+
200
+ ```python
201
+ graph.edge_color_field = "type"
202
+ graph.edge_color_scale = "plasma"
203
+ graph.edge_size_field = "weight"
204
+ graph.edge_size_range = [1, 8]
205
+ ```
206
+
207
+ ### Layouts
208
+
209
+ ```python
210
+ Graph.from_dict(data, layout="force") # ForceAtlas2 (default)
211
+ Graph.from_dict(data, layout="circular")
212
+ Graph.from_dict(data, layout="random")
213
+ ```
214
+
215
+ ## Options
216
+
217
+ ```python
218
+ graph = Graph(
219
+ nodes=nodes,
220
+ edges=edges,
221
+ width=800, # Widget width (px)
222
+ height=600, # Widget height (px)
223
+ background="#fafafa", # Background color
224
+ show_labels=True, # Node labels
225
+ show_edge_labels=False, # Edge labels
226
+ show_toolbar=True, # Toolbar visibility
227
+ show_settings=True, # Settings panel
228
+ show_query_input=True, # Query input box
229
+ dark_mode=True, # Dark theme
230
+ show_tooltip=True, # Hover tooltips
231
+ tooltip_fields=["label", "id"],
232
+ max_nodes=300, # Limit for node expansion
233
+ )
234
+ ```
235
+
236
+ ## Database Backends
237
+
238
+ ### Grafeo (default)
239
+
240
+ ```python
241
+ import grafeo
242
+ db = grafeo.GrafeoDB()
243
+ graph = Graph(database_backend="grafeo", grafeo_db=db)
244
+ ```
245
+
246
+ ### Neo4j (browser-side)
247
+
248
+ ```python
249
+ graph = Graph(
250
+ database_backend="neo4j",
251
+ connection_uri="neo4j+s://demo.neo4jlabs.com",
252
+ connection_username="neo4j",
253
+ connection_password="password",
254
+ )
255
+ ```
256
+
257
+ ### Generic backend
258
+
259
+ ```python
260
+ graph = Graph(backend=my_backend) # Any object implementing DatabaseBackend protocol
261
+ ```
262
+
263
+ ## Export
264
+
265
+ ```python
266
+ graph.to_json() # JSON string with nodes and edges
267
+ graph.to_html() # Self-contained HTML string
268
+ graph.to_html(title="My Graph") # Custom title
269
+ graph.save_html("graph.html") # Write HTML to file
270
+ ```
271
+
272
+ ## Environment Support
273
+
274
+ | Environment | Supported |
275
+ |-------------|-----------|
276
+ | Marimo | Yes |
277
+ | JupyterLab | Yes |
278
+ | Jupyter Notebook | Yes |
279
+ | VS Code | Yes |
280
+ | Google Colab | Yes |
281
+ | Databricks | Yes |
282
+
283
+ ## Related
284
+
285
+ - [anywidget](https://anywidget.dev/), custom Jupyter widgets made easy
286
+ - [Grafeo](https://github.com/GrafeoDB/grafeo), embeddable graph database
287
+ - [grafeo-web](https://github.com/GrafeoDB/grafeo-web), Grafeo in the browser
288
+
289
+ ## License
290
+
291
+ Apache-2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "anywidget-graph"
3
- version = "0.2.5"
3
+ version = "0.2.7"
4
4
  description = "Interactive graph visualization for Python notebooks using anywidget"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -27,15 +27,15 @@ dependencies = [
27
27
 
28
28
  [project.optional-dependencies]
29
29
  dev = [
30
- "prek>=0.3.2",
30
+ "prek>=0.3",
31
31
  "pytest>=9.0.2",
32
- "ruff>=0.15.0",
33
- "ty>=0.0.16",
34
- "marimo>=0.19.9",
32
+ "ruff>=0.15",
33
+ "ty>=0.0.20",
34
+ "marimo>=0.20",
35
35
  ]
36
36
  networkx = ["networkx>=3.0"]
37
37
  pandas = ["pandas>=2.0"]
38
- grafeo = ["grafeo>=0.5.0"]
38
+ grafeo = ["grafeo>=0.5"]
39
39
  cosmosdb = ["gremlinpython>=3.7"]
40
40
  embed = [] # Marker extra — WASM loads from CDN on demand
41
41
  demo = [] # Marker extra — enables demo mode with pre-populated dataset
@@ -3,4 +3,4 @@
3
3
  from anywidget_graph.widget import Graph
4
4
 
5
5
  __all__ = ["Graph"]
6
- __version__ = "0.2.5"
6
+ __version__ = "0.2.7"
@@ -147,6 +147,9 @@ def get_esm() -> str:
147
147
  // === Auto-generated ESM bundle for anywidget-graph ===
148
148
  import Graph from "https://esm.sh/graphology@0.25.4";
149
149
  import Sigma from "https://esm.sh/sigma@3.0.0";
150
+ import circular from "https://esm.sh/graphology-layout@0.6.1/circular.js";
151
+ import random from "https://esm.sh/graphology-layout@0.6.1/random.js";
152
+ import forceAtlas2 from "https://esm.sh/graphology-layout-forceatlas2@0.10.1";
150
153
  import neo4j from "https://cdn.jsdelivr.net/npm/neo4j-driver@5.28.0/lib/browser/neo4j-web.esm.min.js";
151
154
 
152
155
  // === Icons ===
@@ -12,4 +12,8 @@ export const ICONS = {
12
12
  property: `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h16M4 12h16M4 17h10"/></svg>`,
13
13
  sidebar: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/></svg>`,
14
14
  refresh: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>`,
15
+ zoomIn: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>`,
16
+ zoomOut: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="8" y1="11" x2="14" y2="11"/></svg>`,
17
+ zoomFit: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>`,
18
+ search: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`,
15
19
  };