diff --git a/neographviz/__init__.py b/neographviz/__init__.py index 5c9c519..40da893 100644 --- a/neographviz/__init__.py +++ b/neographviz/__init__.py @@ -1,9 +1,8 @@ import py2neo -version = py2neo.__version__[0] -if int(version) == 4: - from .vis import * # as graphviz -elif int(version) == 3: - from .vis_old import * # vis_old as graphviz -elif int(version) < 3: - raise NotImplementedError(f"Not defined for py2neo version {version} ") +__version__ = 0.3 + +if py2neo.__version__[0] >= 4: + from .vis import plot_query, get_edges, get_nodes, draw # as graphviz +else: + raise NotImplementedError("Requires py2neo v4 or (possibly) newer") diff --git a/neographviz/templates/vis.html b/neographviz/templates/vis.html index c981366..65808e3 100644 --- a/neographviz/templates/vis.html +++ b/neographviz/templates/vis.html @@ -6,8 +6,9 @@
- - + + +
diff --git a/neographviz/vis.py b/neographviz/vis.py index 1d3ae83..d3814f7 100644 --- a/neographviz/vis.py +++ b/neographviz/vis.py @@ -186,7 +186,6 @@ def draw( cache = {} nodes = [] edges = [] - for row in data: source_node = row[0] source_id = row[1] @@ -198,7 +197,6 @@ def draw( if source_info not in nodes: nodes.append(source_info) - # smooth: {type: 'curvedCW', roundness: 0.2} if rel is not None: target_info = get_vis_info(target_node, target_id, options) @@ -208,18 +206,13 @@ def draw( label = "".join([f"{name} " for name in rel.types()]).strip() if len(rel.keys()): # we have keys get the details - try: - title = "".join( - [f"{key}:{str(rel[key])} " for key in list(rel.keys())] - ).strip() - except: - breakpoint() + title = "".join( + [f"{key}:{str(rel[key])} " for key in list(rel.keys())] + ).strip() else: # there is nothing more to it title = "".join([f"{name} " for name in rel.types()]).strip() - # try: - # title = "".join([f"{name}:{value} " for name, value in rel.items()]).strip() - # except: + rdns, cache = roundness(source_info["id"], target_info["id"], cache) edges.append( { diff --git a/neographviz/vis_new.backup.py b/neographviz/vis_new.backup.py deleted file mode 100644 index 872e06f..0000000 --- a/neographviz/vis_new.backup.py +++ /dev/null @@ -1,294 +0,0 @@ -from IPython.display import IFrame -import json -import uuid -import os - - -def vis_network( - nodes, - edges, - physics=False, - height=400, - node_size=25, - font_size=14, - filename=None, - config=False, - jsoptions=None, -): - html = """ - - - - - - - - - - -
- - - - - """ - - unique_id = str(uuid.uuid4()) - if not jsoptions: - jsoptions = """ - var options = {{ - nodes: {{ - shape: 'dot', - size: {node_size}, - font: {{ - size: {font_size} - }} - }}, - edges: {{ - font: {{ - size: {font_size}, - align: 'middle' - }}, - color: 'gray', - arrows: {{ - to: {{enabled: true, scaleFactor: 0.5}} - }}, - - }}, - physics: {{ - enabled: {physics}, - "barnesHut": {{ - "gravitationalConstant": -18400, - "damping": 0.36, - "avoidOverlap": 0.68 - }}, - "minVelocity": 0.75 - }}, - configure: {{ - enabled: {config}, - showButton: true - }} - - }}; - """.format( - node_size=node_size, - font_size=font_size, - physics=json.dumps(physics), - config=json.dumps(config), - ) - # breakpoint() - html = html.format( - id=unique_id, - options=jsoptions, - nodes=json.dumps(nodes), - edges=json.dumps(edges), - ) - if not filename: - filename = "figure/graph-{}.html".format(unique_id) - - try: - with open(filename, "w") as file: - file.write(html) - except FileNotFoundError: - os.mkdir("figure") - with open(filename, "w") as file: - file.write(html) - - return IFrame(filename, width="100%", height=str(height)) - - -def roundness(from_node, to_node, cache) -> float: - already = cache.get((from_node, to_node), None) - if already: - precomputed = [ - 0.6, - -0.6, - 0.4, - -0.4, - 0.6, - -0.6, - 0.8, - -0.8, - 1, - -1, - 0.1, - -0.1, - 0.3, - -0.3, - 0.5, - -0.5, - 0.7, - -0.7, - 0.9, - -0.9, - ] - if len(already) < len(precomputed): - curve = precomputed[len(already)] - else: - curve = 0.15 - else: - curve = 0 - cache[(from_node, to_node)] = [] - - cache[(from_node, to_node)].append(curve) - - return curve, cache - - -def get_vis_info(node, id, options): - node_label = list(node.labels)[0] - title = "".join([f"{k}:{v} " for k, v in node.items()]).strip() - if node_label in options: - vis_label = node.get(options.get(node_label, ""), "") - else: - vis_label = title - - return {"id": id, "label": vis_label, "group": node_label, "title": title} - - -def draw( - graph, - query="", - options={}, - physics=False, - limit=100, - height=400, - node_size=25, - font_size=14, - filename=None, - jsoptions=None, - config=False, -): - """[summary] - - Arguments: - graph {[type]} -- [description] - - Keyword Arguments: - query {str[CYPHER]} -- query to run on graph to plot, default is just as many random items as defined by limit. - options {dict} -- Display configuration options (default: {{}}) - physics {bool} -- Weather to have animated physics (default: {False}) - limit {int} -- Number of items to display (default: {100}) - height {int} -- [description] (default: {400}) - node_size {int} -- [description] (default: {25}) - font_size {int} -- [description] (default: {14}) - filename {[type]} -- [description] (default: {None}) - jsoptions {[type]} -- [description] (default: {None}) - config {bool} -- [description] (default: {False}) - - Returns: - [type] -- [description] - """ - - # The options argument should be a dictionary of node labels and property keys; it determines which property - # is displayed for the node label. For example, in the movie graph, options = {"Movie": "title", "Person": "name"}. - # Omitting a node label from the options dict will leave the node unlabeled in the visualization. - # Setting physics = True makes the nodes bounce around when you touch them! - if not query: - query = f""" - MATCH (n) - WITH n, rand() AS random - ORDER BY random - LIMIT {limit} - OPTIONAL MATCH (n)-[r]->(m) - RETURN n AS source_node, - id(n) AS source_id, - r, - m AS target_node, - id(m) AS target_id - """ - - data = graph.run(query) - cache = {} - nodes = [] - edges = [] - - for row in data: - source_node = row[0] - source_id = row[1] - rel = row[2] - target_node = row[3] - target_id = row[4] - source_info = get_vis_info(source_node, source_id, options) - - if source_info not in nodes: - nodes.append(source_info) - - # smooth: {type: 'curvedCW', roundness: 0.2} - if rel is not None: - target_info = get_vis_info(target_node, target_id, options) - - if target_info not in nodes: - nodes.append(target_info) - - label = "".join([f"{name} " for name in rel.types()]).strip() - if len(rel.keys()): - # we have keys get the details - try: - title = "".join( - [f"{key}:{str(rel[key])} " for key in list(rel.keys())] - ).strip() - except: - breakpoint() - else: - # there is nothing more to it - title = "".join([f"{name} " for name in rel.types()]).strip() - # try: - # title = "".join([f"{name}:{value} " for name, value in rel.items()]).strip() - # except: - rdns, cache = roundness(source_info["id"], target_info["id"], cache) - edges.append( - { - "from": source_info["id"], - "to": target_info["id"], - "label": label, - "title": title, - "smooth": f"{{type: 'curvedCW', roundness: {rdns} }}", - } - ) - - return vis_network( - nodes, - edges, - physics=physics, - height=height, - node_size=node_size, - font_size=font_size, - filename=filename, - jsoptions=jsoptions, - config=config, - ) diff --git a/neographviz/vis_old.py b/neographviz/vis_old.py deleted file mode 100644 index f5c5b35..0000000 --- a/neographviz/vis_old.py +++ /dev/null @@ -1,137 +0,0 @@ -from IPython.display import IFrame -import json -import uuid - - -def vis_network(nodes, edges, physics=False): - html = """ - - - - - - - -
- - - - - """ - - unique_id = str(uuid.uuid4()) - html = html.format( - id=unique_id, - nodes=json.dumps(nodes), - edges=json.dumps(edges), - physics=json.dumps(physics), - ) - - filename = "figure/graph-{}.html".format(unique_id) - - file = open(filename, "w") - file.write(html) - file.close() - - return IFrame(filename, width="100%", height="400") - - -def draw(graph, options, physics=False, limit=100): - # The options argument should be a dictionary of node labels and property keys; it determines which property - # is displayed for the node label. For example, in the movie graph, options = {"Movie": "title", "Person": "name"}. - # Omitting a node label from the options dict will leave the node unlabeled in the visualization. - # Setting physics = True makes the nodes bounce around when you touch them! - query = """ - MATCH (n) - WITH n, rand() AS random - ORDER BY random - LIMIT {limit} - OPTIONAL MATCH (n)-[r]->(m) - RETURN n AS source_node, - id(n) AS source_id, - r, - m AS target_node, - id(m) AS target_id - """ - - data = graph.run(query, limit=limit) - - nodes = [] - edges = [] - - def get_vis_info(node, id): - node_label = list(node.labels())[0] - prop_key = options.get(node_label) - vis_label = node.properties.get(prop_key, "") - - return { - "id": id, - "label": vis_label, - "group": node_label, - "title": repr(node.properties), - } - - for row in data: - source_node = row[0] - source_id = row[1] - rel = row[2] - target_node = row[3] - target_id = row[4] - - source_info = get_vis_info(source_node, source_id) - - if source_info not in nodes: - nodes.append(source_info) - - if rel is not None: - target_info = get_vis_info(target_node, target_id) - - if target_info not in nodes: - nodes.append(target_info) - - edges.append( - { - "from": source_info["id"], - "to": target_info["id"], - "label": rel.type(), - } - ) - - return vis_network(nodes, edges, physics=physics) diff --git a/setup.py b/setup.py index 6fd5da1..b73f272 100644 --- a/setup.py +++ b/setup.py @@ -4,9 +4,10 @@ with open("README.md", 'r') as f: long_description = f.read() + setup( name='neographviz', - version='0.2.6', + version='0.3', description='Graph display package for py2neo graphs forked from https://github.com/nicolewhite/neo4j-jupyter', long_description = long_description, long_description_content_type="text/markdown",