Skip to content

Commit 24e50b4

Browse files
authored
Merge pull request #137 from neo4j/entity_properties
Move non-NVL custom fields into a properties dict
2 parents d0dae64 + b741e24 commit 24e50b4

21 files changed

+455
-317
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.. autoenum:: neo4j_viz.colors.ColorSpace
2+
:members:

docs/source/customizing.rst

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,26 @@ If you have not yet created a ``VisualizationGraph`` object, please refer to one
2323
Coloring nodes
2424
--------------
2525

26-
Nodes can be colored directly by providing them with a color property, upon creation.
26+
Nodes can be colored directly by providing them with a color field, upon creation.
2727
This can for example be done by passing a color as a string to the ``color`` parameter of the
2828
:doc:`Node <./api-reference/node>` object.
2929

30-
Alternatively, you can color nodes based on a property (field) of the nodes after a ``VisualizationGraph`` object has been
30+
Alternatively, you can color nodes based on a field or property of the nodes after a ``VisualizationGraph`` object has been
3131
created.
3232

3333

3434
The ``color_nodes`` method
3535
~~~~~~~~~~~~~~~~~~~~~~~~~~
3636

3737
By calling the :meth:`neo4j_viz.VisualizationGraph.color_nodes` method, you can color nodes based on a
38-
node property (field).
39-
It's possible to color the nodes based on a discrete or continuous property.
40-
In the discrete case, a new color from the ``colors`` provided is assigned to each unique value of the node property.
41-
In the continuous case, the ``colors`` should be a list of colors representing a range that are used to create a gradient of colors based on the values of the node property.
38+
node field or property (members of the `Node.properties` map).
4239

43-
By default the Neo4j color palette that works for both light and dark mode will be used.
40+
It's possible to color the nodes based on a discrete or continuous color space (see :doc:`ColorSpace <./api-reference/colors>`).
41+
In the discrete case, a new color from the `colors` provided is assigned to each unique value of the node field/property.
42+
In the continuous case, the `colors` should be a list of colors representing a range that are used to
43+
create a gradient of colors based on the values of the node field/property.
44+
45+
By default the Neo4j color palette, that works for both light and dark mode, will be used.
4446
If you want to use a different color palette, you can pass a dictionary or iterable of colors as the ``colors``
4547
parameter.
4648
A color value can for example be either strings like "blue", or hexadecimal color codes like "#FF0000", or even a tuple of RGB values like (255, 0, 255).
@@ -49,20 +51,20 @@ If some nodes already have a ``color`` set, you can choose whether or not to ove
4951
parameter.
5052

5153

52-
By discrete node property (field)
53-
*********************************
54+
By discrete color space
55+
***********************
5456

55-
To not use the default colors, we can provide a list of custom colors based on the discrete node property (field) "caption" to the ``color_nodes`` method:
57+
To not use the default colors, we can provide a list of custom colors based on the discrete node field "caption" to the ``color_nodes`` method:
5658

5759
.. code-block:: python
5860
59-
from neo4j_viz.colors import PropertyType
61+
from neo4j_viz.colors import ColorSpace
6062
6163
# VG is a VisualizationGraph object
6264
VG.color_nodes(
63-
"caption",
65+
field="caption",
6466
["red", "#7fffd4", (255, 255, 255, 0.5), "hsl(270, 60%, 70%)"],
65-
property_type=PropertyType.DISCRETE
67+
color_space=ColorSpace.DISCRETE
6668
)
6769
6870
The full set of allowed values for colors are listed `here <https://docs.pydantic.dev/2.0/usage/types/extra_types/color_types/>`_.
@@ -75,18 +77,18 @@ this snippet:
7577
from palettable.wesanderson import Moonrise1_5
7678
7779
# VG is a VisualizationGraph object
78-
VG.color_nodes("caption", Moonrise1_5.colors) # PropertyType.DISCRETE is default
80+
VG.color_nodes(field="caption", Moonrise1_5.colors) # PropertyType.DISCRETE is default
7981
80-
In this case, all nodes with the same caption will get the same color.
82+
In theses cases, all nodes with the same caption will get the same color.
8183

82-
If there are fewer colors that unique values for the node ``property`` provided, the colors will be reused in a cycle.
83-
To avoid that, you could use another palette or extend one with additional colors. Please refer to the
84+
If there are fewer colors than unique values for the node ``field`` or ``property`` provided, the colors will be reused in a cycle.
85+
To avoid that, you could use a larger palette or extend one with additional colors. Please refer to the
8486
:doc:`Visualizing Neo4j Graph Data Science (GDS) Graphs tutorial <./tutorials/gds-example>` for an example on how
8587
to do the latter.
8688

8789

88-
By continuous node property (field)
89-
***********************************
90+
By continuous color space
91+
*************************
9092

9193
To not use the default colors, we can provide a list of custom colors representing a range to the ``color_nodes`` method:
9294

@@ -96,9 +98,9 @@ To not use the default colors, we can provide a list of custom colors representi
9698
9799
# VG is a VisualizationGraph object
98100
VG.color_nodes(
99-
"centrality_score",
101+
property="centrality_score",
100102
[(255, 0, 0), (191, 64, 0), (128, 128, 0), (64, 191, 0), (0, 255, 0)] # From red to green
101-
property_type=PropertyType.CONTINUOUS
103+
color_space=ColorSpace.CONTINUOUS
102104
)
103105
104106
In this case, the nodes will be colored based on the value of the "centrality_score" property, with the lowest values being colored red and the highest values being colored green.
@@ -110,7 +112,7 @@ Since we only provided five colors in the range, the granularity of the gradient
110112
Sizing nodes
111113
------------
112114

113-
Nodes can be given a size directly by providing them with a size property, upon creation.
115+
Nodes can be given a size directly by providing them with a size field, upon creation.
114116
This can for example be done by passing a size as an integer to the ``size`` parameter of the
115117
:doc:`Node <./api-reference/node>` object.
116118

@@ -178,7 +180,7 @@ In the following example, we pin the node with ID 1337 and unpin the node with I
178180
Direct modification of nodes and relationships
179181
----------------------------------------------
180182

181-
Nodes and relationships can also be modified directly by accessing the ``nodes`` and ``relationships`` attributes of an
183+
Nodes and relationships can also be modified directly by accessing the ``nodes`` and ``relationships`` fields of an
182184
existing ``VisualizationGraph`` object.
183185
These attributes list of all the :doc:`Nodes <./api-reference/node>` and
184186
:doc:`Relationships <./api-reference/relationship>` in the graph, respectively.
@@ -189,6 +191,7 @@ Each node and relationship has attributes that can be accessed and modified dire
189191
190192
# VG is a VisualizationGraph object
191193
VG.nodes[0].size = 10
194+
VG.nodes[0].properties["height"] = 170
192195
VG.relationships[4].caption = "BUYS"
193196
194197
Any changes made to the nodes and relationships will be reflected in the next rendering of the graph.

docs/source/integration.rst

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ Integration with other libraries
33

44
In addition to creating graphs from scratch, with ``neo4j-viz`` as is shown in the
55
:doc:`Getting started section <./getting-started>`, you can also import data directly from external sources.
6-
In this section we will cover how to import data from `Pandas DataFrames <https://pandas.pydata.org/>`_ and
7-
`Neo4j Graph Data Science <https://neo4j.com/docs/graph-data-science/current/>`_.
6+
In this section we will cover how to import data from `Pandas DataFrames <https://pandas.pydata.org/>`_,
7+
`Neo4j Graph Data Science <https://neo4j.com/docs/graph-data-science/current/>`_ and
8+
`Neo4j Database <https://neo4j.com/docs/python-manual/current/>`_.
89

910

1011
.. contents:: On this page:
@@ -31,12 +32,18 @@ The ``from_dfs`` method takes two mandatory positional parameters:
3132

3233
* A Pandas ``DataFrame``, or iterable (eg. list) of DataFrames representing the nodes of the graph.
3334
The rows of the DataFrame(s) should represent the individual nodes, and the columns should represent the node
34-
IDs and properties. The columns map directly to fields of :doc:`Node <./api-reference/node>`, and as such
35-
should follow the same naming conventions.
35+
IDs and attributes.
36+
If a column shares the name with a field of :doc:`Node <./api-reference/node>`, the values it contains will be set
37+
on corresponding nodes under that field name.
38+
Otherwise, the column name will be a key in each node's `properties` dictionary, that maps to the node's corresponding
39+
value in the column.
3640
* A Pandas ``DataFrame``, or iterable (eg. list) of DataFrames representing the relationships of the graph.
3741
The rows of the DataFrame(s) should represent the individual relationships, and the columns should represent the
38-
relationship IDs and properties. The columns map directly to fields of
39-
:doc:`Relationship <./api-reference/relationship>`, and as such should follow the same naming conventions.
42+
relationship IDs and attributes.
43+
If a column shares the name with a field of :doc:`Relationship <./api-reference/relationship>`, the values it contains
44+
will be set on corresponding relationships under that field name.
45+
Otherwise, the column name will be a key in each node's `properties` dictionary, that maps to the node's corresponding
46+
value in the column.
4047

4148
``from_dfs`` also takes an optional property, ``node_radius_min_max``, that can be used (and is used by default) to
4249
scale the node sizes for the visualization.
@@ -97,11 +104,12 @@ The ``from_gds`` method takes two mandatory positional parameters:
97104
* A ``Graph`` representing the projection that one wants to import.
98105

99106
We can also provide an optional ``size_property`` parameter, which should refer to a node property of the projection,
100-
and will be used to determine the size of the nodes in the visualization.
107+
and will be used to determine the sizes of the nodes in the visualization.
101108

102109
The ``additional_node_properties`` parameter is also optional, and should be a list of additional node properties of the
103110
projection that you want to include in the visualization.
104-
For example, these properties could be used to color the nodes, or give captions to them in the visualization.
111+
For example, these properties could be used to color the nodes, or give captions to them in the visualization, or simply
112+
included in the nodes' `Node.properties` maps without directly impacting the visualization.
105113

106114
The last optional property, ``node_radius_min_max``, can be used (and is used by default) to scale the node sizes for
107115
the visualization.
@@ -143,7 +151,7 @@ We use the "pagerank" property to determine the size of the nodes, and the "comp
143151
144152
# Color the nodes by the `componentId` property, so that the nodes are
145153
# colored by the connected component they belong to
146-
VG.color_nodes("componentId")
154+
VG.color_nodes(property="componentId")
147155
148156
149157
Please see the :doc:`Visualizing Neo4j Graph Data Science (GDS) Graphs tutorial <./tutorials/gds-example>` for a
@@ -167,10 +175,10 @@ The ``from_neo4j`` method takes one mandatory positional parameters:
167175

168176
* A ``result`` representing the query result either in form of `neo4j.graph.Graph` or `neo4j.Result`.
169177

170-
The ``node_caption`` parameter is also optional, and indicates the value to use for the caption of each node in the visualization.
178+
The ``node_caption`` parameter is also optional, and indicates the node property to use for the caption of each node in the visualization.
171179

172-
We can also provide an optional ``size_property`` parameter, which should refer to a node property of the projection,
173-
and will be used to determine the size of the nodes in the visualization.
180+
We can also provide an optional ``size_property`` parameter, which should refer to a node property,
181+
and will be used to determine the sizes of the nodes in the visualization.
174182

175183
The last optional property, ``node_radius_min_max``, can be used (and is used by default) to scale the node sizes for
176184
the visualization.

examples/gds-example.ipynb

Lines changed: 43 additions & 63 deletions
Large diffs are not rendered by default.

examples/neo4j-example.ipynb

Lines changed: 15 additions & 15 deletions
Large diffs are not rendered by default.

examples/snowpark-example.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@
206206
{
207207
"cell_type": "code",
208208
"execution_count": null,
209-
"id": "2322065c",
209+
"id": "887f41b7a243d439",
210210
"metadata": {},
211211
"outputs": [],
212212
"source": [
@@ -215,7 +215,7 @@
215215
"VG = from_dfs(products_df, parents_df)\n",
216216
"\n",
217217
"# Using the default Neo4j color scheme\n",
218-
"VG.color_nodes(\"CATEGORY\")"
218+
"VG.color_nodes(property=\"CATEGORY\")"
219219
]
220220
},
221221
{

examples/streamlit-example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def create_visualization_graph() -> VisualizationGraph:
2525
nodes_df.drop(columns="features", inplace=True)
2626

2727
VG = from_dfs(nodes_df, rels_df)
28-
VG.color_nodes("subject")
28+
VG.color_nodes(property="subject")
2929

3030
return VG
3131

python-wrapper/src/neo4j_viz/colors.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,29 @@
22
from enum import Enum
33
from typing import Any, Union
44

5+
import enum_tools
56
from pydantic_extra_types.color import ColorType
67

78
ColorsType = Union[dict[Any, ColorType], Iterable[ColorType]]
89

910

10-
class PropertyType(Enum):
11+
@enum_tools.documentation.document_enum
12+
class ColorSpace(Enum):
13+
"""
14+
Describes the type of color space used by a color palette.
15+
"""
16+
1117
DISCRETE = "discrete"
18+
"""
19+
This category describes a color palette that is a collection of different colors that are not necessarily related to
20+
each other. Discrete color spaces are suitable for categorical data, where each unique category is represented by a
21+
different color.
22+
"""
1223
CONTINUOUS = "continuous"
24+
"""
25+
This category describes a color palette that is a range/gradient of colors between two or more colors. Continuous
26+
color spaces are suitable for continuous data (typically floats), where values can change smoothly.
27+
"""
1328

1429

1530
# Comes from https://neo4j.design/40a8cff71/p/5639c0-color/t/page-5639c0-79109681-33

python-wrapper/src/neo4j_viz/gds.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pandas as pd
77
from graphdatascience import Graph, GraphDataScience
88

9-
from .pandas import from_dfs
9+
from .pandas import _from_dfs
1010
from .visualization_graph import VisualizationGraph
1111

1212

@@ -35,6 +35,11 @@ def from_gds(
3535
"""
3636
Create a VisualizationGraph from a GraphDataScience object and a Graph object.
3737
38+
All `additional_node_properties` will be included in the visualization graph.
39+
If the properties are named as the fields of the `Node` class, they will be included as top level fields of the
40+
created `Node` objects. Otherwise, they will be included in the `properties` dictionary.
41+
Additionally, a new "labels" node property will be added, containing the node labels of the node.
42+
3843
Parameters
3944
----------
4045
gds : GraphDataScience
@@ -75,9 +80,13 @@ def from_gds(
7580

7681
node_props_df = pd.concat(node_dfs.values(), ignore_index=True, axis=0).drop_duplicates()
7782
if size_property is not None:
83+
if "size" in actual_node_properties and size_property != "size":
84+
node_props_df.rename(columns={"size": "__size"}, inplace=True)
7885
node_props_df.rename(columns={size_property: "size"}, inplace=True)
7986

8087
for lbl, df in node_dfs.items():
88+
if "labels" in actual_node_properties:
89+
df.rename(columns={"labels": "__labels"}, inplace=True)
8190
df["labels"] = lbl
8291

8392
node_lbls_df = pd.concat([df[["id", "labels"]] for df in node_dfs.values()], ignore_index=True, axis=0)
@@ -88,4 +97,4 @@ def from_gds(
8897
rel_df = _rel_df(gds, G)
8998
rel_df.rename(columns={"sourceNodeId": "source", "targetNodeId": "target"}, inplace=True)
9099

91-
return from_dfs(node_df, rel_df, node_radius_min_max=node_radius_min_max)
100+
return _from_dfs(node_df, rel_df, node_radius_min_max=node_radius_min_max, rename_properties={"__size": "size"})

0 commit comments

Comments
 (0)