Skip to content

Commit 138a99b

Browse files
committed
Slim down neo4j api
1 parent 804aaa1 commit 138a99b

File tree

9 files changed

+53
-306
lines changed

9 files changed

+53
-306
lines changed

changelog.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@
44

55
- Do not automatically derive size and caption for `from_neo4j` and `from_gql_create`. Use the `size_property` and `node_caption` parameters to explicitly configure them.
66
- Change API of integrations to only provide basic parameters. Any further configuration should happen ons the Visualization Graph object:
7-
- `from_gds`
8-
- Drop parameters `size_property`, `node_radius_min_max`. Use `VG.resize_nodes(property=...)` instead
9-
- rename additional_node_properties to node_properties
10-
- Don't derive fields from properties. Use `VG.map_properties_to_fields` instead
117
- `from_pandas`
128
- Drop `node_radius_min_max` parameter. `VG.resize_nodes(...)` instead
13-
- `from_gql_create`
9+
- `from_neo4j`, `from_gds`, `from_gql_create`
1410
- Drop parameters `size_property`, `node_radius_min_max`. Use `VG.resize_nodes(property=...)` instead
1511
- rename additional_node_properties to node_properties
1612
- Don't derive fields from properties. Use `VG.map_properties_to_fields` instead

docs/source/integration.rst

Lines changed: 6 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -164,22 +164,9 @@ The ``from_neo4j`` method takes one mandatory positional parameter:
164164
A ``data`` argument representing either a query result in the shape of a ``neo4j.graph.Graph`` or ``neo4j.Result``, or a
165165
``neo4j.Driver`` in which case a simple default query will be executed internally to retrieve the graph data.
166166

167-
We can also provide an optional ``size_property`` parameter, which should refer to a node property,
168-
and will be used to determine the sizes of the nodes in the visualization.
169-
170-
The ``node_caption`` and ``relationship_caption`` parameters are also optional, and indicate the node and relationship
171-
properties to use for the captions of each element in the visualization.
172-
By default, the captions will be set to the node labels relationship types, but you can specify any property that
173-
exists on these entities.
174-
175-
The last optional property, ``node_radius_min_max``, can be used (and is used by default) to scale the node sizes for
176-
the visualization.
177-
It is a tuple of two numbers, representing the radii (sizes) in pixels of the smallest and largest nodes respectively in
178-
the visualization.
179-
The node sizes will be scaled such that the smallest node will have the size of the first value, and the largest node
180-
will have the size of the second value.
181-
The other nodes will be scaled linearly between these two values according to their relative size.
182-
This can be useful if node sizes vary a lot, or are all very small or very big.
167+
The optional ``max_rows`` parameter can be used to limit the number of relationships shown in the visualization.
168+
By default, it is set to 10.000, meaning that if the database has more than 10.000 rows, a warning will be raised.
169+
Note, this only applies if the ``data`` parameter is a ``neo4j.Driver``.
183170

184171

185172
Example
@@ -269,39 +256,14 @@ The ``from_snowflake`` method takes two mandatory positional parameters:
269256
* A `project configuration <https://neo4j.com/docs/snowflake-graph-analytics/current/jobs/#jobs-project>`_ as a dictionary, that specifies how you want your tables to be projected as a graph.
270257
This configuration is the same as the project configuration of the `Neo4j Snowflake Graph Analytics application <https://neo4j.com/docs/snowflake-graph-analytics/current/>`_.
271258

272-
``from_snowflake`` also takes an optional property, ``node_radius_min_max``, that can be used (and is used by default) to
273-
scale the node sizes for the visualization.
274-
It is a tuple of two numbers, representing the radii (sizes) in pixels of the smallest and largest nodes respectively in
275-
the visualization.
276-
The node sizes will be scaled such that the smallest node will have the size of the first value, and the largest node
277-
will have the size of the second value.
278-
The other nodes will be scaled linearly between these two values according to their relative size.
279-
This can be useful if node sizes vary a lot, or are all very small or very big.
280-
281-
282-
Special columns
283-
~~~~~~~~~~~~~~~
284-
285-
It is possible to modify the visualization directly by including columns of certain specific names in the node and relationship tables.
286-
287-
All such special columns can be found :doc:`here <./api-reference/node>` for nodes and :doc:`here <./api-reference/relationship>` for relationships.
288-
Though listed in ``snake_case`` here, ``SCREAMING_SNAKE_CASE`` and ``camelCase`` are also supported.
289-
Some of the most commonly used special columns are:
290-
291-
* **Node sizes**: The sizes of nodes can be controlled by including a column named "SIZE" in node tables.
292-
The values in these columns should be of a numeric type. This can be useful for visualizing the relative importance or size of nodes in the graph, for example using a computed centrality score.
293-
294-
* **Captions**: The caption text of nodes and relationships can be controlled by including a column named "CAPTION" in the tables.
295-
The values in these columns should be of a string type. This can be useful for displaying additional information about the nodes, such as their names or labels. If no "CAPTION" column is provided, the default captions in the visualization will be the names of the corresponding node and relationship tables.
296-
297-
Please also note that you can further customize the visualization after the `VisualizationGraph` has been created, by using the methods described in the :doc:`Customizing the visualization <./customizing>` section.
259+
You can further customize the visualization after the `VisualizationGraph` has been created, by using the methods described in the :doc:`Customizing the visualization <./customizing>` section.
298260

299261

300262
Default behavior
301263
~~~~~~~~~~~~~~~~
302264

303-
Unless there are "CAPTION" columns in the tables, the node and relationship captions will be set to the names of the corresponding tables.
304-
Similarly, if there are are no "COLOR" node table columns, the nodes will be colored be colored so that nodes from the same table have the same color, and different tables have different colors.
265+
The node and relationship captions will be set to the names of the corresponding tables.
266+
The nodes will be colored so that nodes from the same table have the same color, and different tables have different colors.
305267

306268

307269
Example

python-wrapper/src/neo4j_viz/neo4j.py

Lines changed: 29 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from neo4j import Driver, Result, RoutingControl
88
from pydantic import BaseModel, ValidationError
99

10+
from neo4j_viz.colors import NEO4J_COLORS_DISCRETE, ColorSpace
1011
from neo4j_viz.node import Node
1112
from neo4j_viz.relationship import Relationship
1213
from neo4j_viz.visualization_graph import VisualizationGraph
@@ -22,34 +23,25 @@ def _parse_validation_error(e: ValidationError, entity_type: type[BaseModel]) ->
2223

2324
def from_neo4j(
2425
data: Union[neo4j.graph.Graph, Result, Driver],
25-
size_property: Optional[str] = None,
26-
node_caption: Optional[str] = "labels",
27-
relationship_caption: Optional[str] = "type",
28-
node_radius_min_max: Optional[tuple[float, float]] = (3, 60),
2926
row_limit: int = 10_000,
3027
) -> VisualizationGraph:
3128
"""
3229
Create a VisualizationGraph from a Neo4j `Graph`, Neo4j `Result` or Neo4j `Driver`.
3330
34-
All node and relationship properties will be included in the visualization graph.
35-
If the properties are named as the fields of the `Node` or `Relationship` classes, they will be included as
36-
top level fields of the respective objects. Otherwise, they will be included in the `properties` dictionary.
31+
By default:
32+
33+
* the caption of a node will be based on its `labels`.
34+
* the caption of a relationship will be based on its `type`.
35+
* the color of nodes will be set based on their label, unless there are more than 12 unique labels.
36+
37+
All node and relationship properties will be included in the visualization graph under the `properties` field.
3738
Additionally, a "labels" property will be added for nodes and a "type" property for relationships.
3839
3940
Parameters
4041
----------
4142
data : Union[neo4j.graph.Graph, neo4j.Result, neo4j.Driver]
4243
Either a query result in the shape of a `neo4j.graph.Graph` or `neo4j.Result`, or a `neo4j.Driver` in
4344
which case a simple default query will be executed internally to retrieve the graph data.
44-
size_property : str, optional
45-
Property to use for node size, by default None.
46-
node_caption : str, optional
47-
Property to use as the node caption, by default the node labels will be used.
48-
relationship_caption : str, optional
49-
Property to use as the relationship caption, by default the relationship type will be used.
50-
node_radius_min_max : tuple[float, float], optional
51-
Minimum and maximum node radius, by default (3, 60).
52-
To avoid tiny or huge nodes in the visualization, the node sizes are scaled to fit in the given range.
5345
row_limit : int, optional
5446
Maximum number of rows to return from the query, by default 10_000.
5547
This is only used if a `neo4j.Driver` is passed as `result` argument, otherwise the limit is ignored.
@@ -77,117 +69,62 @@ def from_neo4j(
7769
else:
7870
raise ValueError(f"Invalid input type `{type(data)}`. Expected `neo4j.Graph`, `neo4j.Result` or `neo4j.Driver`")
7971

80-
all_node_field_aliases = Node.all_validation_aliases(exempted_fields=["size", "caption"])
81-
all_rel_field_aliases = Relationship.all_validation_aliases(exempted_fields=["caption"])
82-
83-
try:
84-
nodes = [
85-
_map_node(node, all_node_field_aliases, size_property, caption_property=node_caption)
86-
for node in graph.nodes
87-
]
88-
except ValueError as e:
89-
err_msg = str(e)
90-
if ("'size'" in err_msg) and (size_property is not None):
91-
err_msg = err_msg.replace("'size'", f"'{size_property}'")
92-
elif ("'caption'" in err_msg) and (node_caption is not None):
93-
err_msg = err_msg.replace("'caption'", f"'{node_caption}'")
94-
raise ValueError(err_msg)
72+
nodes = [_map_node(node) for node in graph.nodes]
9573

9674
relationships = []
97-
try:
98-
for rel in graph.relationships:
99-
mapped_rel = _map_relationship(rel, all_rel_field_aliases, caption_property=relationship_caption)
100-
if mapped_rel:
101-
relationships.append(mapped_rel)
102-
except ValueError as e:
103-
err_msg = str(e)
104-
if ("'caption'" in err_msg) and (relationship_caption is not None):
105-
err_msg = err_msg.replace("'caption'", f"'{relationship_caption}'")
106-
raise ValueError(err_msg)
75+
76+
for rel in graph.relationships:
77+
mapped_rel = _map_relationship(rel)
78+
if mapped_rel:
79+
relationships.append(mapped_rel)
10780

10881
VG = VisualizationGraph(nodes, relationships)
10982

110-
if (node_radius_min_max is not None) and (size_property is not None):
111-
VG.resize_nodes(node_radius_min_max=node_radius_min_max)
83+
for node in VG.nodes:
84+
node.caption = ":".join(node.properties["labels"])
85+
for r in VG.relationships:
86+
r.caption = r.properties["type"]
87+
88+
number_of_colors = len({n.caption for n in VG.nodes})
89+
if number_of_colors <= len(NEO4J_COLORS_DISCRETE):
90+
VG.color_nodes(field="caption", color_space=ColorSpace.DISCRETE, colors=NEO4J_COLORS_DISCRETE)
11291

11392
return VG
11493

11594

11695
def _map_node(
11796
node: neo4j.graph.Node,
118-
all_node_field_aliases: set[str],
119-
size_property: Optional[str],
120-
caption_property: Optional[str],
12197
) -> Node:
122-
top_level_fields = {"id": node.element_id}
123-
124-
if size_property:
125-
top_level_fields["size"] = node.get(size_property)
126-
12798
labels = sorted([label for label in node.labels])
128-
if caption_property:
129-
if caption_property == "labels":
130-
if len(labels) > 0:
131-
top_level_fields["caption"] = ":".join([label for label in labels])
132-
else:
133-
top_level_fields["caption"] = str(node.get(caption_property))
134-
135-
properties = {}
136-
for prop, value in node.items():
137-
if prop not in all_node_field_aliases:
138-
properties[prop] = value
139-
continue
14099

141-
if prop in top_level_fields:
142-
properties[prop] = value
143-
continue
144-
145-
top_level_fields[prop] = value
100+
properties = {prop: value for prop, value in node.items()}
146101

147102
if "labels" in properties:
148103
properties["__labels"] = properties["labels"]
149104
properties["labels"] = labels
150105

151106
try:
152-
viz_node = Node(**top_level_fields, properties=properties)
107+
viz_node = Node(id=node.element_id, properties=properties)
153108
except ValidationError as e:
154109
_parse_validation_error(e, Node)
155110

156111
return viz_node
157112

158113

159-
def _map_relationship(
160-
rel: neo4j.graph.Relationship, all_rel_field_aliases: set[str], caption_property: Optional[str]
161-
) -> Optional[Relationship]:
114+
def _map_relationship(rel: neo4j.graph.Relationship) -> Optional[Relationship]:
162115
if rel.start_node is None or rel.end_node is None:
163116
return None
164117

165-
top_level_fields = {"id": rel.element_id, "source": rel.start_node.element_id, "target": rel.end_node.element_id}
166-
167-
if caption_property:
168-
if caption_property == "type":
169-
top_level_fields["caption"] = rel.type
170-
else:
171-
top_level_fields["caption"] = str(rel.get(caption_property))
172-
173-
properties = {}
174-
for prop, value in rel.items():
175-
if prop not in all_rel_field_aliases:
176-
properties[prop] = value
177-
continue
178-
179-
if prop in top_level_fields:
180-
properties[prop] = value
181-
continue
182-
183-
top_level_fields[prop] = value
118+
properties = {prop: value for prop, value in rel.items()}
184119

185120
if "type" in properties:
186121
properties["__type"] = properties["type"]
187122
properties["type"] = rel.type
188123

189124
try:
190-
viz_rel = Relationship(**top_level_fields, properties=properties)
125+
viz_rel = Relationship(
126+
id=rel.element_id, source=rel.start_node.element_id, target=rel.end_node.element_id, properties=properties
127+
)
191128
except ValidationError as e:
192129
_parse_validation_error(e, Relationship)
193130

python-wrapper/src/neo4j_viz/node.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,6 @@ def cast_color(cls, color: ColorType) -> Color:
9090
def to_dict(self) -> dict[str, Any]:
9191
return self.model_dump(exclude_none=True, by_alias=True)
9292

93-
@staticmethod
94-
def all_validation_aliases(exempted_fields: Optional[list[str]] = None) -> set[str]:
95-
if exempted_fields is None:
96-
exempted_fields = []
97-
98-
by_field = [v.validation_alias.choices for k, v in Node.model_fields.items() if k not in exempted_fields] # type: ignore
99-
100-
return {str(alias) for aliases in by_field for alias in aliases}
101-
10293
@staticmethod
10394
def basic_fields_validation_aliases() -> set[str]:
10495
mandatory_fields = ["id"]

python-wrapper/src/neo4j_viz/relationship.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,6 @@ def cast_color(cls, color: ColorType) -> Color:
9797
def to_dict(self) -> dict[str, Any]:
9898
return self.model_dump(exclude_none=True, by_alias=True)
9999

100-
@staticmethod
101-
def all_validation_aliases(exempted_fields: Optional[list[str]] = None) -> set[str]:
102-
if exempted_fields is None:
103-
exempted_fields = []
104-
105-
by_field = [
106-
v.validation_alias.choices # type: ignore
107-
for k, v in Relationship.model_fields.items()
108-
if k not in exempted_fields
109-
]
110-
111-
return {str(alias) for aliases in by_field for alias in aliases}
112-
113100
@staticmethod
114101
def basic_fields_validation_aliases() -> set[str]:
115102
basic_fields = ["id", "source", "target"]

python-wrapper/src/neo4j_viz/snowflake.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,11 @@ def from_snowflake(
317317
Create a VisualizationGraph from Snowflake tables based on a project configuration.
318318
319319
By default:
320+
320321
* The caption of the nodes will be set to the table name.
321322
* The caption of the relationships will be set to the table name.
322323
* The color of the nodes will be set based on the caption, unless there are more than 12 node tables used.
324+
323325
Otherwise, columns will be included as properties on the nodes and relationships.
324326
325327
Args:

0 commit comments

Comments
 (0)