From 81f0a34bdf6d6286e8fdeb26a496813aac903948 Mon Sep 17 00:00:00 2001 From: "HNEU (Henrik Enquist)" Date: Fri, 17 May 2024 10:05:34 +0200 Subject: [PATCH 1/5] Optional autocomplete of relationships --- .../Yfiles_Neo4j_Graphs.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py b/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py index 4a00a95..72e6c5a 100644 --- a/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py +++ b/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py @@ -26,6 +26,7 @@ def __init__(self, driver=None, widget_layout=None, self._overview = overview_enabled self._layout = widget_layout self._context_start_with = context_start_with + self._autocomplete_relationships = False def set_driver(self, driver): """ @@ -44,6 +45,16 @@ def get_driver(self): """ return self._driver + def set_autocomplete_relationships(self, autocomplete_relationships): + """ + Sets the flag to enable or disable autocomplete for relationships. + When autocomplete is enabled, relationships are automatically completed in the graph, + similar to the behavior in Neo4j Browser. + This feature relies on the APOC procedure apoc.algo.cover, so APOC must be installed. + :param autocomplete_relationships: bool + """ + self._autocomplete_relationships = autocomplete_relationships + def show_cypher(self, cypher, **kwargs): """ main function @@ -51,6 +62,17 @@ def show_cypher(self, cypher, **kwargs): **kwargs: variable declarations usable in cypher """ if self._driver is not None: + if self._autocomplete_relationships: + nodes = self._session.run(cypher, **kwargs).graph().nodes + node_ids = [node.element_id for node in nodes] + cypher = f""" + MATCH (n) WHERE elementId(n) IN {node_ids} + RETURN n as start, NULL as rel, NULL as end + UNION ALL + WITH {node_ids} AS all_nodes + CALL apoc.algo.cover(all_nodes) YIELD rel + RETURN startNode(rel) as start, rel, endNode(rel) AS end + """ widget = GraphWidget(overview_enabled=self._overview, context_start_with=self._context_start_with, widget_layout=self._layout, license=self._license, graph=self._session.run(cypher, **kwargs).graph()) From a0facd022fe93aa62bf6bdac219f4c6f6b557aca Mon Sep 17 00:00:00 2001 From: "HNEU (Henrik Enquist)" Date: Fri, 17 May 2024 10:17:52 +0200 Subject: [PATCH 2/5] Mention autocomplete in readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 5ef51da..849dbe1 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,10 @@ The main class `Neo4jGraphWidget` provides the following API: - `**kwargs`: Additional parameters that should be passed to the cypher query (e.g., see the [selection example](https://github.com/yWorks/yfiles-jupyter-graphs-for-neo4j/blob/main/examples/selection_example.ipynb)). +The default behavior is to only show the nodes and relationships returned by the cypher query. +This can be changed to autocomplete relationships like in neo4j browser: +- `set_autocomplete_relationships(autocomplete_relationships)`: Sets whether to autocomplete relationships in the graph or not. + The cypher queries are executed by the provided Neo4j driver. If you have not specified a driver when instantiating the class, you can set a driver afterward: From c3bcfe13624774a83f719cebfea3c3fe0bcf233f Mon Sep 17 00:00:00 2001 From: "HNEU (Henrik Enquist)" Date: Fri, 17 May 2024 15:13:01 +0200 Subject: [PATCH 3/5] No use of apoc, support speciying relationship types --- .../Yfiles_Neo4j_Graphs.py | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py b/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py index 72e6c5a..79a56c9 100644 --- a/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py +++ b/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py @@ -50,10 +50,28 @@ def set_autocomplete_relationships(self, autocomplete_relationships): Sets the flag to enable or disable autocomplete for relationships. When autocomplete is enabled, relationships are automatically completed in the graph, similar to the behavior in Neo4j Browser. - This feature relies on the APOC procedure apoc.algo.cover, so APOC must be installed. - :param autocomplete_relationships: bool + This can be set to True/False to enable or disable for all relationships, + or a single relationship type or a list of relationship types to enable for specific relationships. + :param autocomplete_relationships: bool | str | list[str] """ - self._autocomplete_relationships = autocomplete_relationships + if not isinstance(autocomplete_relationships, (bool, str, list)): + raise ValueError("autocomplete_relationships must be a bool or a list of strings") + if isinstance(autocomplete_relationships, str): + self._autocomplete_relationships = [autocomplete_relationships] + else: + self._autocomplete_relationships = autocomplete_relationships + + def _is_autocomplete_enabled(self): + if isinstance(self._autocomplete_relationships, bool): + return self._autocomplete_relationships + return len(self._autocomplete_relationships) > 0 + + def _get_relationship_types_expression(self): + if self._autocomplete_relationships == True: + return "" + elif len(self._autocomplete_relationships) > 0: + return "AND type(rel) IN $relationship_types" + return "" def show_cypher(self, cypher, **kwargs): """ @@ -62,17 +80,23 @@ def show_cypher(self, cypher, **kwargs): **kwargs: variable declarations usable in cypher """ if self._driver is not None: - if self._autocomplete_relationships: + if self._is_autocomplete_enabled(): nodes = self._session.run(cypher, **kwargs).graph().nodes node_ids = [node.element_id for node in nodes] + reltypes_expr = self._get_relationship_types_expression() cypher = f""" - MATCH (n) WHERE elementId(n) IN {node_ids} + MATCH (n) WHERE elementId(n) IN $node_ids RETURN n as start, NULL as rel, NULL as end UNION ALL - WITH {node_ids} AS all_nodes - CALL apoc.algo.cover(all_nodes) YIELD rel - RETURN startNode(rel) as start, rel, endNode(rel) AS end + MATCH (n)-[rel]-(m) + WHERE elementId(n) IN $node_ids + AND elementId(m) IN $node_ids + {reltypes_expr} + RETURN n as start, rel, m as end """ + kwargs = {"node_ids": node_ids} + if reltypes_expr: + kwargs["relationship_types"] = self._autocomplete_relationships widget = GraphWidget(overview_enabled=self._overview, context_start_with=self._context_start_with, widget_layout=self._layout, license=self._license, graph=self._session.run(cypher, **kwargs).graph()) From f5cb0f1fe4c0180d101a5a9664eefcb8a82eb752 Mon Sep 17 00:00:00 2001 From: "HNEU (Henrik Enquist)" Date: Fri, 17 May 2024 16:02:55 +0200 Subject: [PATCH 4/5] Fix exception message --- src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py b/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py index 79a56c9..df28823 100644 --- a/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py +++ b/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py @@ -55,7 +55,7 @@ def set_autocomplete_relationships(self, autocomplete_relationships): :param autocomplete_relationships: bool | str | list[str] """ if not isinstance(autocomplete_relationships, (bool, str, list)): - raise ValueError("autocomplete_relationships must be a bool or a list of strings") + raise ValueError("autocomplete_relationships must be a bool, a string, or a list of strings") if isinstance(autocomplete_relationships, str): self._autocomplete_relationships = [autocomplete_relationships] else: From 8550d019cc094fc8e97312dd4b52171d34dc6185 Mon Sep 17 00:00:00 2001 From: "HNEU (Henrik Enquist)" Date: Wed, 22 May 2024 11:31:16 +0200 Subject: [PATCH 5/5] Simplify relationship type logic --- .../Yfiles_Neo4j_Graphs.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py b/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py index df28823..2ce5505 100644 --- a/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py +++ b/src/yfiles_jupyter_graphs_for_neo4j/Yfiles_Neo4j_Graphs.py @@ -18,7 +18,8 @@ class Neo4jGraphWidget: _widget = GraphWidget() def __init__(self, driver=None, widget_layout=None, - overview_enabled=None, context_start_with='About', license=None): + overview_enabled=None, context_start_with='About', license=None, + autocomplete_relationships=False): if driver is not None: self._driver = driver self._session = driver.session() @@ -26,7 +27,7 @@ def __init__(self, driver=None, widget_layout=None, self._overview = overview_enabled self._layout = widget_layout self._context_start_with = context_start_with - self._autocomplete_relationships = False + self.set_autocomplete_relationships(autocomplete_relationships) def set_driver(self, driver): """ @@ -67,9 +68,7 @@ def _is_autocomplete_enabled(self): return len(self._autocomplete_relationships) > 0 def _get_relationship_types_expression(self): - if self._autocomplete_relationships == True: - return "" - elif len(self._autocomplete_relationships) > 0: + if isinstance(self._autocomplete_relationships, list) and len(self._autocomplete_relationships) > 0: return "AND type(rel) IN $relationship_types" return ""