diff --git a/changelog.md b/changelog.md index b30a6779..d0a5590b 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,8 @@ ## Breaking changes +* The `from_gds` methods now fetches all node properties of a given GDS projection by default, instead of none. + ## New features diff --git a/docs/source/integration.rst b/docs/source/integration.rst index 15454c0e..11f59013 100644 --- a/docs/source/integration.rst +++ b/docs/source/integration.rst @@ -110,8 +110,10 @@ and will be used to determine the sizes of the nodes in the visualization. The ``additional_node_properties`` parameter is also optional, and should be a list of additional node properties of the projection that you want to include in the visualization. -For example, these properties could be used to color the nodes, or give captions to them in the visualization, or simply -included in the nodes' `Node.properties` maps without directly impacting the visualization. +The default is `None`, which means that all properties of the nodes in the projection will be included. +Apart from being visible through on-hover tooltips, these properties could be used to color the nodes, or give captions +to them in the visualization, or simply included in the nodes' `Node.properties` maps without directly impacting the +visualization. The last optional property, ``node_radius_min_max``, can be used (and is used by default) to scale the node sizes for the visualization. diff --git a/python-wrapper/src/neo4j_viz/gds.py b/python-wrapper/src/neo4j_viz/gds.py index 2c68ce9b..9bdbebd4 100644 --- a/python-wrapper/src/neo4j_viz/gds.py +++ b/python-wrapper/src/neo4j_viz/gds.py @@ -63,7 +63,8 @@ def from_gds( size_property : str, optional Property to use for node size, by default None. additional_node_properties : list[str], optional - Additional properties to include in the visualization node, by default None. They can be used later for modifying the node appearance. + Additional properties to include in the visualization node, by default None which means that all node + properties will be fetched. node_radius_min_max : tuple[float, float], optional Minimum and maximum node radius, by default (3, 60). To avoid tiny or huge nodes in the visualization, the node sizes are scaled to fit in the given range. @@ -75,7 +76,9 @@ def from_gds( if size_property is not None and size_property not in actual_node_properties: raise ValueError(f"There is no node property '{size_property}' in graph '{G.name()}'") - if additional_node_properties is not None: + if additional_node_properties is None: + additional_node_properties = actual_node_properties + else: for prop in additional_node_properties: if prop not in actual_node_properties: raise ValueError(f"There is no node property '{prop}' in graph '{G.name()}'") diff --git a/python-wrapper/tests/test_gds.py b/python-wrapper/tests/test_gds.py index 76b25acc..0b213851 100644 --- a/python-wrapper/tests/test_gds.py +++ b/python-wrapper/tests/test_gds.py @@ -61,6 +61,58 @@ def test_from_gds_integration(gds: Any) -> None: ] +@pytest.mark.requires_neo4j_and_gds +def test_from_gds_integration_all_properties(gds: Any) -> None: + from neo4j_viz.gds import from_gds + + nodes = pd.DataFrame( + { + "nodeId": [0, 1, 2], + "labels": [["A"], ["C"], ["A", "B"]], + "score": [1337, 42, 3.14], + "component": [1, 4, 2], + "size": [0.1, 0.2, 0.3], + } + ) + rels = pd.DataFrame( + { + "sourceNodeId": [0, 1, 2], + "targetNodeId": [1, 2, 0], + "cost": [1.0, 2.0, 3.0], + "weight": [0.5, 1.5, 2.5], + "relationshipType": ["REL", "REL2", "REL"], + } + ) + + with gds.graph.construct("flo", nodes, rels) as G: + VG = from_gds( + gds, + G, + node_radius_min_max=None, + ) + + assert len(VG.nodes) == 3 + assert sorted(VG.nodes, key=lambda x: x.id) == [ + Node(id=0, size=0.1, properties=dict(labels=["A"], component=float(1), score=1337.0)), + Node(id=1, size=0.2, properties=dict(labels=["C"], component=float(4), score=42.0)), + Node(id=2, size=0.3, properties=dict(labels=["A", "B"], component=float(2), score=3.14)), + ] + + assert len(VG.relationships) == 3 + vg_rels = sorted( + [ + (e.source, e.target, e.properties["relationshipType"], e.properties["cost"], e.properties["weight"]) + for e in VG.relationships + ], + key=lambda x: x[0], + ) + assert vg_rels == [ + (0, 1, "REL", 1.0, 0.5), + (1, 2, "REL2", 2.0, 1.5), + (2, 0, "REL", 3.0, 2.5), + ] + + def test_from_gds_mocked(mocker: MockerFixture) -> None: from graphdatascience import Graph, GraphDataScience