neo4j-viz 0.5.0__py3-none-any.whl → 0.6.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.
neo4j_viz/snowflake.py CHANGED
@@ -37,7 +37,7 @@ from snowflake.snowpark.types import (
37
37
  )
38
38
 
39
39
  from neo4j_viz import VisualizationGraph
40
- from neo4j_viz.colors import ColorSpace
40
+ from neo4j_viz.colors import NEO4J_COLORS_DISCRETE, ColorSpace
41
41
  from neo4j_viz.pandas import from_dfs
42
42
 
43
43
 
@@ -312,33 +312,41 @@ def _map_tables(
312
312
  def from_snowflake(
313
313
  session: Session,
314
314
  project_config: dict[str, Any],
315
- node_radius_min_max: Optional[tuple[float, float]] = (3, 60),
316
315
  ) -> VisualizationGraph:
317
- project_model = VizProjectConfig.model_validate(project_config, strict=False, context={"session": session})
318
- node_dfs, rel_dfs, rel_table_names = _map_tables(session, project_model)
316
+ """
317
+ Create a VisualizationGraph from Snowflake tables based on a project configuration.
318
+
319
+ By default:
320
+
321
+ * The caption of the nodes will be set to the table name.
322
+ * The caption of the relationships will be set to the table name.
323
+ * The color of the nodes will be set based on the caption, unless there are more than 12 node tables used.
319
324
 
320
- node_caption_present = False
321
- for node_df in node_dfs:
322
- if "CAPTION" in node_df.columns:
323
- node_caption_present = True
324
- break
325
+ Otherwise, columns will be included as properties on the nodes and relationships.
325
326
 
326
- if not node_caption_present:
327
- for i, node_df in enumerate(node_dfs):
328
- node_df["caption"] = project_model.nodeTables[i].split(".")[-1]
327
+ Args:
328
+ session (Session): An active Snowflake session.
329
+ project_config (dict[str, Any]): A dictionary representing the project configuration.
330
+ Returns:
331
+ VisualizationGraph: The resulting visualization graph.
332
+ """
333
+ project_model = VizProjectConfig.model_validate(project_config, strict=False, context={"session": session})
334
+ node_dfs, rel_dfs, rel_table_names = _map_tables(session, project_model)
329
335
 
330
- rel_caption_present = False
331
- for rel_df in rel_dfs:
332
- if "CAPTION" in rel_df.columns:
333
- rel_caption_present = True
334
- break
336
+ for i, node_df in enumerate(node_dfs):
337
+ node_df["table"] = project_model.nodeTables[i].split(".")[-1]
338
+ for i, rel_df in enumerate(rel_dfs):
339
+ rel_df["table"] = rel_table_names[i].split(".")[-1]
335
340
 
336
- if not rel_caption_present:
337
- for i, rel_df in enumerate(rel_dfs):
338
- rel_df["caption"] = rel_table_names[i].split(".")[-1]
341
+ VG = from_dfs(node_dfs, rel_dfs)
339
342
 
340
- VG = from_dfs(node_dfs, rel_dfs, node_radius_min_max)
343
+ for node in VG.nodes:
344
+ node.caption = node.properties.get("table")
345
+ for rel in VG.relationships:
346
+ rel.caption = rel.properties.get("table")
341
347
 
342
- VG.color_nodes(field="caption", color_space=ColorSpace.DISCRETE)
348
+ number_of_colors = node_df["table"].drop_duplicates().count()
349
+ if number_of_colors <= len(NEO4J_COLORS_DISCRETE):
350
+ VG.color_nodes(field="caption", color_space=ColorSpace.DISCRETE)
343
351
 
344
352
  return VG
@@ -22,9 +22,14 @@ from .options import (
22
22
  from .relationship import Relationship
23
23
 
24
24
 
25
+ # TODO helper for map properties to fields. helper for set caption (simplicity)
25
26
  class VisualizationGraph:
26
27
  """
27
28
  A graph to visualize.
29
+
30
+ The `VisualizationGraph` class represents a collection of nodes and relationships that can be
31
+ rendered as an interactive graph visualization. You can customize the appearance of nodes and
32
+ relationships by setting their properties, colors, sizes, and other visual attributes.
28
33
  """
29
34
 
30
35
  #: "The nodes in the graph"
@@ -33,15 +38,48 @@ class VisualizationGraph:
33
38
  relationships: list[Relationship]
34
39
 
35
40
  def __init__(self, nodes: list[Node], relationships: list[Relationship]) -> None:
36
- """ "
37
- Create a new `VisualizationGraph`.
38
-
41
+ """
39
42
  Parameters
40
43
  ----------
41
- nodes:
44
+ nodes : list[Node]
42
45
  The nodes in the graph.
43
- relationships:
46
+ relationships : list[Relationship]
44
47
  The relationships in the graph.
48
+
49
+ Examples
50
+ --------
51
+ Basic usage with nodes and relationships:
52
+
53
+ >>> from neo4j_viz import Node, Relationship, VisualizationGraph
54
+ >>> nodes = [
55
+ ... Node(id="1", properties={"name": "Alice", "age": 30}),
56
+ ... Node(id="2", properties={"name": "Bob", "age": 25}),
57
+ ... ]
58
+ >>> relationships = [
59
+ ... Relationship(id="r1", source="1", target="2", properties={"type": "KNOWS"})
60
+ ... ]
61
+ >>> VG = VisualizationGraph(nodes=nodes, relationships=relationships)
62
+
63
+ Setting a node field such as captions from properties:
64
+
65
+ >>> # Set caption from a specific property
66
+ >>> for node in VG.nodes:
67
+ ... node.caption = node.properties.get("name")
68
+
69
+ Setting a relationship field such as type from properties:
70
+
71
+ >>> # Set relationship caption from property
72
+ >>> for rel in VG.relationships:
73
+ ... rel.caption = rel.properties.get("type")
74
+
75
+ Using built-in helper methods:
76
+
77
+ >>> # Use the color_nodes method for automatic coloring
78
+ >>> VG.color_nodes(property="age", color_space=ColorSpace.CONTINUOUS)
79
+ >>>
80
+ >>> # Use resize_nodes for automatic sizing
81
+ >>> VG.resize_nodes(property="degree", node_radius_min_max=(10, 50))
82
+
45
83
  """
46
84
  self.nodes = nodes
47
85
  self.relationships = relationships
@@ -90,6 +128,12 @@ class VisualizationGraph:
90
128
  The maximum allowed number of nodes to render.
91
129
  show_hover_tooltip:
92
130
  Whether to show an info tooltip when hovering over nodes and relationships.
131
+
132
+
133
+ Example
134
+ -------
135
+ Basic rendering of a VisualizationGraph:
136
+ >>> from neo4j_viz import Node, Relationship, VisualizationGraph
93
137
  """
94
138
 
95
139
  num_nodes = len(self.nodes)
@@ -154,6 +198,7 @@ class VisualizationGraph:
154
198
  self,
155
199
  sizes: Optional[dict[NodeIdType, RealNumber]] = None,
156
200
  node_radius_min_max: Optional[tuple[RealNumber, RealNumber]] = (3, 60),
201
+ property: Optional[str] = None,
157
202
  ) -> None:
158
203
  """
159
204
  Resize the nodes in the graph.
@@ -163,33 +208,47 @@ class VisualizationGraph:
163
208
  sizes:
164
209
  A dictionary mapping from node ID to the new size of the node.
165
210
  If a node ID is not in the dictionary, the size of the node is not changed.
211
+ Must be None if `property` is provided.
166
212
  node_radius_min_max:
167
213
  Minimum and maximum node size radius as a tuple. To avoid tiny or huge nodes in the visualization, the
168
214
  node sizes are scaled to fit in the given range. If None, the sizes are used as is.
215
+ property:
216
+ The property of the nodes to use for sizing. Must be None if `sizes` is provided.
169
217
  """
170
- if sizes is None and node_radius_min_max is None:
171
- raise ValueError("At least one of `sizes` and `node_radius_min_max` must be given")
218
+ if sizes is not None and property is not None:
219
+ raise ValueError("At most one of the arguments `sizes` and `property` can be provided")
172
220
 
173
- # Gather and verify all node size values we have to work with
174
- all_sizes = {}
175
- for node in self.nodes:
176
- size = None
177
- if sizes is not None:
178
- size = sizes.get(node.id)
221
+ if sizes is None and property is None and node_radius_min_max is None:
222
+ raise ValueError("At least one of `sizes`, `property` or `node_radius_min_max` must be given")
179
223
 
224
+ # Gather node sizes
225
+ all_sizes = {}
226
+ if sizes is not None:
227
+ for node in self.nodes:
228
+ size = sizes.get(node.id, node.size)
180
229
  if size is not None:
181
- if not isinstance(size, (int, float)):
182
- raise ValueError(f"Size for node '{node.id}' must be a real number, but was {size}")
183
-
184
- if size < 0:
185
- raise ValueError(f"Size for node '{node.id}' must be non-negative, but was {size}")
186
-
187
230
  all_sizes[node.id] = size
188
-
189
- if size is None:
231
+ elif property is not None:
232
+ for node in self.nodes:
233
+ size = node.properties.get(property, node.size)
234
+ if size is not None:
235
+ all_sizes[node.id] = size
236
+ else:
237
+ for node in self.nodes:
190
238
  if node.size is not None:
191
239
  all_sizes[node.id] = node.size
192
240
 
241
+ # Validate node sizes
242
+ for id, size in all_sizes.items():
243
+ if size is None:
244
+ continue
245
+
246
+ if not isinstance(size, (int, float)):
247
+ raise ValueError(f"Size for node '{id}' must be a real number, but was {size}")
248
+
249
+ if size < 0:
250
+ raise ValueError(f"Size for node '{id}' must be non-negative, but was {size}")
251
+
193
252
  if node_radius_min_max is not None:
194
253
  verify_radii(node_radius_min_max)
195
254
 
@@ -197,6 +256,7 @@ class VisualizationGraph:
197
256
  else:
198
257
  final_sizes = all_sizes
199
258
 
259
+ # Apply the final sizes to the nodes
200
260
  for node in self.nodes:
201
261
  size = final_sizes.get(node.id)
202
262
 
@@ -264,6 +324,29 @@ class VisualizationGraph:
264
324
  colors are assigned based on unique field/property values or a gradient of the values of the field/property.
265
325
  override:
266
326
  Whether to override existing colors of the nodes, if they have any.
327
+
328
+ Examples
329
+ --------
330
+
331
+ Given a VisualizationGraph `VG`:
332
+
333
+ >>> nodes = [
334
+ ... Node(id="0", properties={"label": "Person", "score": 10}),
335
+ ... Node(id="1", properties={"label": "Person", "score": 20}),
336
+ ... ]
337
+ >>> VG = VisualizationGraph(nodes=nodes)
338
+
339
+ Color nodes based on a discrete field such as "label":
340
+ >>> VG.color_nodes(field="label", color_space=ColorSpace.DISCRETE)
341
+
342
+ Color nodes based on a continuous field such as "score":
343
+
344
+ >>> VG.color_nodes(field="score", color_space=ColorSpace.CONTINUOUS)
345
+
346
+ Color nodes based on a custom colors such as from palettable:
347
+
348
+ >>> from palettable.wesanderson import Moonrise1_5 # type: ignore[import-untyped]
349
+ >>> VG.color_nodes(field="label", colors=Moonrise1_5.colors)
267
350
  """
268
351
  if not ((field is None) ^ (property is None)):
269
352
  raise ValueError(
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neo4j-viz
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: A simple graph visualization tool
5
5
  Author-email: Neo4j <team-gds@neo4j.org>
6
+ License-Expression: GPL-3.0-only
6
7
  Project-URL: Homepage, https://neo4j.com/
7
8
  Project-URL: Repository, https://github.com/neo4j/python-graph-visualization
8
9
  Project-URL: Issues, https://github.com/neo4j/python-graph-visualization/issues
@@ -11,12 +12,10 @@ Keywords: graph,visualization,neo4j
11
12
  Classifier: Development Status :: 3 - Alpha
12
13
  Classifier: Intended Audience :: Developers
13
14
  Classifier: Intended Audience :: Science/Research
14
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
15
15
  Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python
17
17
  Classifier: Programming Language :: Python :: 3
18
18
  Classifier: Programming Language :: Python :: 3 :: Only
19
- Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
@@ -26,18 +25,19 @@ Classifier: Topic :: Scientific/Engineering
26
25
  Classifier: Topic :: Scientific/Engineering :: Visualization
27
26
  Classifier: Topic :: Software Development
28
27
  Classifier: Typing :: Typed
29
- Requires-Python: >=3.9
28
+ Requires-Python: >=3.10
30
29
  Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
31
  Requires-Dist: ipython<10,>=7
32
32
  Requires-Dist: pydantic<3,>=2
33
33
  Requires-Dist: pydantic-extra-types<3,>=2
34
- Requires-Dist: enum-tools==0.12.0
34
+ Requires-Dist: enum-tools==0.13.0
35
35
  Provides-Extra: dev
36
- Requires-Dist: ruff==0.11.8; extra == "dev"
36
+ Requires-Dist: ruff==0.14.1; extra == "dev"
37
37
  Requires-Dist: mypy==1.17.1; extra == "dev"
38
- Requires-Dist: pytest==8.3.4; extra == "dev"
38
+ Requires-Dist: pytest==8.4.2; extra == "dev"
39
39
  Requires-Dist: selenium==4.32.0; extra == "dev"
40
- Requires-Dist: ipykernel==6.29.5; extra == "dev"
40
+ Requires-Dist: ipykernel==6.30.1; extra == "dev"
41
41
  Requires-Dist: palettable==3.3.3; extra == "dev"
42
42
  Requires-Dist: pytest-mock==3.14.0; extra == "dev"
43
43
  Requires-Dist: nbconvert==7.16.6; extra == "dev"
@@ -46,7 +46,7 @@ Requires-Dist: matplotlib>=3.9.4; extra == "dev"
46
46
  Provides-Extra: docs
47
47
  Requires-Dist: sphinx==8.1.3; extra == "docs"
48
48
  Requires-Dist: enum-tools[sphinx]; extra == "docs"
49
- Requires-Dist: nbsphinx==0.9.6; extra == "docs"
49
+ Requires-Dist: nbsphinx==0.9.7; extra == "docs"
50
50
  Requires-Dist: nbsphinx-link==1.3.1; extra == "docs"
51
51
  Provides-Extra: pandas
52
52
  Requires-Dist: pandas<3,>=2; extra == "pandas"
@@ -65,6 +65,7 @@ Requires-Dist: ipywidgets>=8.0.0; extra == "notebook"
65
65
  Requires-Dist: palettable>=3.3.3; extra == "notebook"
66
66
  Requires-Dist: matplotlib>=3.9.4; extra == "notebook"
67
67
  Requires-Dist: snowflake-snowpark-python==1.37.0; extra == "notebook"
68
+ Dynamic: license-file
68
69
 
69
70
  # Graph Visualization for Python by Neo4j
70
71
 
@@ -0,0 +1,27 @@
1
+ neo4j_viz/__init__.py,sha256=Q-VZlJe3_kAow_-F_-9RsHCQfbOfv5on26YD9ihw27o,504
2
+ neo4j_viz/colors.py,sha256=IvOCTmCu7WTMna_wNLZ3GrThTwFyIoKtNkmZYDLdYac,6694
3
+ neo4j_viz/gds.py,sha256=KCnUJ4nTQlM28P-TIVf2JZMlvsvHjneaLROnh95F670,7434
4
+ neo4j_viz/gql_create.py,sha256=2KMHKsOuELV8i-HGXIRAi4SqPrjCdphcMYD8W5VK-Xg,12252
5
+ neo4j_viz/neo4j.py,sha256=5u3mUbQQ0-39NkvpJe-H0BjnUqBOyKOgV_QF3FWCVTc,4643
6
+ neo4j_viz/node.py,sha256=qXjPzsNLksY7yh_Lxe174lOQw0QjHZ9-ka9R9uJpG_A,3843
7
+ neo4j_viz/node_size.py,sha256=c_sMtQSD8eJ_6Y0Kr6ku0LOs9VoEDxfYCUUzUWZ-1Xo,1197
8
+ neo4j_viz/nvl.py,sha256=ZN3tyWar9ugR88r5N6txW3ThfNEWOt5A1KzrrRnLKwk,5262
9
+ neo4j_viz/options.py,sha256=oai-yI03WxWyl6-9cFWEbQkqpXAcI8oG4G6rSVF1Bt0,6495
10
+ neo4j_viz/pandas.py,sha256=gFQW9SlWxiSrVCi2kHGUKpDXDhYtlFkk2AR2DzxYTWE,4759
11
+ neo4j_viz/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ neo4j_viz/relationship.py,sha256=aVj8_6umHSm1rg53CU-oci21W845J91AFKv1AJudmD8,4112
13
+ neo4j_viz/snowflake.py,sha256=GoiFfpzE5fuAorh_PNZCK7QSIv52pIMn_QS2LLsUxCM,12683
14
+ neo4j_viz/visualization_graph.py,sha256=v2OmfGM8t9UDmSDCFLe5uWa6MnPmppby20VK_NjThT4,17161
15
+ neo4j_viz/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ neo4j_viz/resources/icons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ neo4j_viz/resources/icons/screenshot.svg,sha256=Ns9Yi2Iq4lIaiFvzc0pXBmjxt4fcmBO-I4cI8Xiu1HE,311
18
+ neo4j_viz/resources/icons/zoom-in.svg,sha256=PsO5yFkA1JnGM2QV_qxHKG13qmoR-RrlWARpaXNp5qU,415
19
+ neo4j_viz/resources/icons/zoom-out.svg,sha256=OQRADAoe2bxbCeFufg6W22nR41q5NlI8QspT9l5pXUw,400
20
+ neo4j_viz/resources/nvl_entrypoint/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ neo4j_viz/resources/nvl_entrypoint/base.js,sha256=tICNjZLV8sUen2qvosUZuUlovpHcZIqXpdNzSo4phE4,1820159
22
+ neo4j_viz/resources/nvl_entrypoint/styles.css,sha256=JjeTSB9OJT2KMfb8yFUUMLMG7Rzrf3o60hSCD547zTk,1123
23
+ neo4j_viz-0.6.0.dist-info/licenses/LICENSE,sha256=tWSeLI0mRp2u8Pnumaxq51JltfXLVSXl2w_D6kxhRMI,36006
24
+ neo4j_viz-0.6.0.dist-info/METADATA,sha256=A06tmKgiAI93yfZRX-ofAapiQeJldwSCKkCegzf_LG0,7120
25
+ neo4j_viz-0.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
+ neo4j_viz-0.6.0.dist-info/top_level.txt,sha256=jPUM3z8MOtxqDanc2VzqkxG4HJn8aaq4S7rnCFNk_Vs,10
27
+ neo4j_viz-0.6.0.dist-info/RECORD,,