Skip to content

Commit 8af3848

Browse files
authored
Merge pull request #245 from neo4j/nodes-2025-demo
nodes 2025 demo
2 parents 22b6377 + 032e204 commit 8af3848

File tree

1 file changed

+395
-0
lines changed

1 file changed

+395
-0
lines changed

examples/nodes_2025_demo.ipynb

Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "8dcd267e",
6+
"metadata": {},
7+
"source": [
8+
"# NODES 2025 Demo\n",
9+
"\n",
10+
"This notebook represents the demo part of the [presentation](https://neo4j.com/nodes-2025/agenda/easy-jupyter-notebook-graph-visualization-in-10-minutes/) at the NODES 2025 conference."
11+
]
12+
},
13+
{
14+
"cell_type": "markdown",
15+
"id": "e15fcce5",
16+
"metadata": {},
17+
"source": [
18+
"## Setup"
19+
]
20+
},
21+
{
22+
"cell_type": "code",
23+
"execution_count": null,
24+
"id": "fb45b182",
25+
"metadata": {},
26+
"outputs": [],
27+
"source": [
28+
"%pip install \"neo4j-viz[gds, neo4j]\" python-dotenv"
29+
]
30+
},
31+
{
32+
"cell_type": "code",
33+
"execution_count": null,
34+
"id": "10b32405",
35+
"metadata": {},
36+
"outputs": [],
37+
"source": [
38+
"from dotenv import load_dotenv\n",
39+
"\n",
40+
"# Load credentials for the neo4j database\n",
41+
"load_dotenv(\"db_creds.env\")"
42+
]
43+
},
44+
{
45+
"cell_type": "markdown",
46+
"id": "cfb884ec",
47+
"metadata": {},
48+
"source": [
49+
"## Building our visualization graph\n",
50+
"\n"
51+
]
52+
},
53+
{
54+
"cell_type": "markdown",
55+
"id": "02304ee4",
56+
"metadata": {},
57+
"source": [
58+
"### From a Create Query string\n",
59+
"\n",
60+
"The most simple way to test out neo4j-viz is by using `from_gql_create`.\n",
61+
"The nodes and relationships are directly parsed from the provided query string. "
62+
]
63+
},
64+
{
65+
"cell_type": "code",
66+
"execution_count": null,
67+
"id": "05eda814",
68+
"metadata": {},
69+
"outputs": [],
70+
"source": [
71+
"from neo4j_viz.gql_create import from_gql_create\n",
72+
"\n",
73+
"VG = from_gql_create(\"\"\"\n",
74+
" CREATE\n",
75+
" (alice:Person {name: 'Alice', age: 30}),\n",
76+
" (bob:Person {name: 'Bob', age: 25}),\n",
77+
" (carol:Person {name: 'Carol', age: 27}),\n",
78+
" (alice)-[:FRIENDS_WITH {since: 2015}]->(bob),\n",
79+
" (bob)-[:FRIENDS_WITH {since: 2018}]->(carol)\n",
80+
"\"\"\")\n",
81+
"\n",
82+
"VG.render(initial_zoom=1.5)"
83+
]
84+
},
85+
{
86+
"cell_type": "markdown",
87+
"id": "32979c03",
88+
"metadata": {},
89+
"source": [
90+
"## From a Neo4j database\n",
91+
"\n",
92+
"Now lets assume, you have a Neo4j database available which you want to inspect.\n",
93+
"In the following, I assume the Movies dataset is imported. You can use `:play movies` in the Neo4j Browser to import the dataset. "
94+
]
95+
},
96+
{
97+
"cell_type": "code",
98+
"execution_count": null,
99+
"id": "72b14cea",
100+
"metadata": {},
101+
"outputs": [],
102+
"source": [
103+
"import os\n",
104+
"import neo4j\n",
105+
"from neo4j_viz.neo4j import from_neo4j\n",
106+
"\n",
107+
"driver = neo4j.GraphDatabase.driver(\n",
108+
" uri=os.getenv(\"NEO4J_URI\"),\n",
109+
" auth=(os.getenv(\"NEO4J_USERNAME\"), os.getenv(\"NEO4J_PASSWORD\")),\n",
110+
")\n",
111+
"\n",
112+
"# Limiting to 20 rows for demo purposes\n",
113+
"VG = from_neo4j(driver, row_limit=20)\n",
114+
"VG.render(initial_zoom=1.0)"
115+
]
116+
},
117+
{
118+
"cell_type": "markdown",
119+
"id": "8c7091b4",
120+
"metadata": {},
121+
"source": [
122+
"## From GDS"
123+
]
124+
},
125+
{
126+
"cell_type": "code",
127+
"execution_count": null,
128+
"id": "153decdb",
129+
"metadata": {},
130+
"outputs": [],
131+
"source": [
132+
"from graphdatascience import GraphDataScience\n",
133+
"\n",
134+
"gds = GraphDataScience(\n",
135+
" endpoint=os.getenv(\"NEO4J_URI\"),\n",
136+
" auth=(os.getenv(\"NEO4J_USERNAME\"), os.getenv(\"NEO4J_PASSWORD\")),\n",
137+
")\n",
138+
"gds.set_database(\"neo4j\")"
139+
]
140+
},
141+
{
142+
"cell_type": "code",
143+
"execution_count": null,
144+
"id": "4c7cdcb4",
145+
"metadata": {},
146+
"outputs": [],
147+
"source": [
148+
"G, _ = gds.graph.cypher.project(\n",
149+
" query=\"\"\"\n",
150+
" MATCH (s:Person)-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(t:Person)\n",
151+
" WITH s, t, count(m) as common_movies\n",
152+
" WHERE s < t\n",
153+
" RETURN gds.graph.project('demo-graph', s, t, {\n",
154+
" sourceNodeLabels: labels(s),\n",
155+
" targetNodeLabels: labels(t),\n",
156+
" relationshipProperties: {weight: common_movies},\n",
157+
" relationshipType: 'CO_ACTED'\n",
158+
" }, {undirectedRelationshipTypes: ['CO_ACTED']})\n",
159+
"\"\"\"\n",
160+
")\n",
161+
"\n",
162+
"str(G)"
163+
]
164+
},
165+
{
166+
"cell_type": "markdown",
167+
"id": "c5652cd6",
168+
"metadata": {},
169+
"source": [
170+
"How do we make sure the projection matches our expectation? Especially if the Cypher queries get more complex"
171+
]
172+
},
173+
{
174+
"cell_type": "code",
175+
"execution_count": null,
176+
"id": "d1b405bd",
177+
"metadata": {},
178+
"outputs": [],
179+
"source": [
180+
"from neo4j_viz.gds import from_gds\n",
181+
"\n",
182+
"# Limit to a couple of nodes to inspect the projection. Sampling via random-walks implemented in GDS\n",
183+
"VG = from_gds(gds, G, db_node_properties=[\"name\"], max_node_count=20)\n",
184+
"VG.render()"
185+
]
186+
},
187+
{
188+
"cell_type": "markdown",
189+
"id": "eabdc19f",
190+
"metadata": {},
191+
"source": [
192+
"Lets run some GDS algorithms to get some more insights about our co-actor graph."
193+
]
194+
},
195+
{
196+
"cell_type": "code",
197+
"execution_count": null,
198+
"id": "7bfd2bc6",
199+
"metadata": {},
200+
"outputs": [],
201+
"source": [
202+
"gds.pageRank.mutate(G, relationshipWeightProperty=\"weight\", mutateProperty=\"pageRank\")\n",
203+
"gds.leiden.mutate(G, mutateProperty=\"component\", relationshipWeightProperty=\"weight\")"
204+
]
205+
},
206+
{
207+
"cell_type": "markdown",
208+
"id": "331f7f79",
209+
"metadata": {},
210+
"source": [
211+
"Other supported datasources include `from_snowflake`, and `from_pandas`."
212+
]
213+
},
214+
{
215+
"cell_type": "markdown",
216+
"id": "4c7da83d",
217+
"metadata": {},
218+
"source": [
219+
"## Customizing the Visualization"
220+
]
221+
},
222+
{
223+
"cell_type": "code",
224+
"execution_count": null,
225+
"id": "8c3e5d7a",
226+
"metadata": {},
227+
"outputs": [],
228+
"source": [
229+
"from neo4j_viz.gds import from_gds\n",
230+
"\n",
231+
"# make sure to include also the newly computed properties such as pageRank\n",
232+
"# by default from_gds includes all properties of G\n",
233+
"VG = from_gds(gds, G, db_node_properties=[\"name\"])\n",
234+
"VG.render()"
235+
]
236+
},
237+
{
238+
"cell_type": "code",
239+
"execution_count": null,
240+
"id": "c5fc8746",
241+
"metadata": {},
242+
"outputs": [],
243+
"source": [
244+
"# Lets first fix the caption of the nodes to show the name.\n",
245+
"for node in VG.nodes:\n",
246+
" node.caption = node.properties.get(\"name\")\n",
247+
"\n",
248+
"VG.render()"
249+
]
250+
},
251+
{
252+
"cell_type": "code",
253+
"execution_count": null,
254+
"id": "5bb837b7",
255+
"metadata": {},
256+
"outputs": [],
257+
"source": [
258+
"# Inspect the computed communities to see which actors are grouped together\n",
259+
"VG.color_nodes(property=\"component\", override=True)\n",
260+
"VG.render()"
261+
]
262+
},
263+
{
264+
"cell_type": "code",
265+
"execution_count": null,
266+
"id": "5c6839b0",
267+
"metadata": {},
268+
"outputs": [],
269+
"source": [
270+
"# Resize nodes based on pageRank property, i.e., more important actors appear larger\n",
271+
"VG.resize_nodes(property=\"pageRank\")\n",
272+
"VG.render()"
273+
]
274+
},
275+
{
276+
"cell_type": "code",
277+
"execution_count": null,
278+
"id": "d8f4e4f0",
279+
"metadata": {},
280+
"outputs": [],
281+
"source": [
282+
"# To make sure certain nodes are always visible, we can pin them. Here we pin \"Keanu Reeves\".\n",
283+
"pinned_nodes = {\n",
284+
" node.id: True\n",
285+
" for node in VG.nodes\n",
286+
" if node.properties.get(\"name\") in [\"Keanu Reeves\"]\n",
287+
"}\n",
288+
"\n",
289+
"VG.toggle_nodes_pinned(pinned_nodes)\n",
290+
"VG.render()"
291+
]
292+
},
293+
{
294+
"cell_type": "markdown",
295+
"id": "4e565d57",
296+
"metadata": {},
297+
"source": [
298+
"Further customization ideas to explore: \n",
299+
"\n",
300+
"* Modify the layout such as by adding coordinates\n",
301+
"* Use custom colors from [`palettable.wesanderson`](https://jiffyclub.github.io/palettable/) \n",
302+
"* Change the size range for nodes"
303+
]
304+
},
305+
{
306+
"cell_type": "markdown",
307+
"id": "311fbb2f",
308+
"metadata": {},
309+
"source": [
310+
"## Saving the Visualization"
311+
]
312+
},
313+
{
314+
"cell_type": "code",
315+
"execution_count": null,
316+
"id": "efd0a82c",
317+
"metadata": {},
318+
"outputs": [],
319+
"source": [
320+
"# Use the save button in the rendered view. This produces a static image.\n",
321+
"VG.render()"
322+
]
323+
},
324+
{
325+
"cell_type": "markdown",
326+
"id": "a5128b85",
327+
"metadata": {},
328+
"source": []
329+
},
330+
{
331+
"cell_type": "code",
332+
"execution_count": null,
333+
"id": "11201815",
334+
"metadata": {},
335+
"outputs": [],
336+
"source": [
337+
"# Save the raw HTML output to a file. This allows to share the interactive visualization.\n",
338+
"\n",
339+
"import os\n",
340+
"from neo4j_viz.options import Renderer\n",
341+
"\n",
342+
"os.makedirs(\"./out\", exist_ok=True)\n",
343+
"\n",
344+
"# Save the visualization to a file\n",
345+
"with open(\"out/co_acted.html\", \"w\") as f:\n",
346+
" f.write(VG.render(renderer=Renderer.CANVAS).data)"
347+
]
348+
},
349+
{
350+
"cell_type": "markdown",
351+
"id": "71c3f41e",
352+
"metadata": {},
353+
"source": [
354+
"## Cleanup"
355+
]
356+
},
357+
{
358+
"cell_type": "code",
359+
"execution_count": null,
360+
"id": "322d137f",
361+
"metadata": {},
362+
"outputs": [],
363+
"source": [
364+
"driver.close()"
365+
]
366+
},
367+
{
368+
"cell_type": "code",
369+
"execution_count": null,
370+
"id": "429dec14",
371+
"metadata": {},
372+
"outputs": [],
373+
"source": [
374+
"gds.graph.get(\"demo-graph\").drop()"
375+
]
376+
},
377+
{
378+
"cell_type": "code",
379+
"execution_count": null,
380+
"id": "010ab463",
381+
"metadata": {},
382+
"outputs": [],
383+
"source": [
384+
"gds.close()"
385+
]
386+
}
387+
],
388+
"metadata": {
389+
"language_info": {
390+
"name": "python"
391+
}
392+
},
393+
"nbformat": 4,
394+
"nbformat_minor": 5
395+
}

0 commit comments

Comments
 (0)