Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add node size and edge width scaling based on property values #192

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions src/graph_notebook/magics/graph_magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
from graph_notebook.configuration.get_config import get_config, get_config_from_dict
from graph_notebook.seed.load_query import get_data_sets, get_queries, normalize_model_name
from graph_notebook.widgets import Force
from graph_notebook.options import OPTIONS_DEFAULT_DIRECTED, vis_options_merge
from graph_notebook.options import OPTIONS_DEFAULT_DIRECTED, OPTIONS_DEFAULT_SCALING_NODES, \
OPTIONS_DEFAULT_SCALING_EDGES_ONLY, vis_options_merge
from graph_notebook.magics.metadata import build_sparql_metadata_from_query, build_gremlin_metadata_from_query, \
build_opencypher_metadata_from_query

Expand Down Expand Up @@ -224,6 +225,10 @@ def sparql(self, line='', cell='', local_ns: dict = None):
choices=['dynamic', 'static', 'details'])
parser.add_argument('--explain-format', default='text/html', help='response format for explain query mode',
choices=['text/csv', 'text/html'])
parser.add_argument('-sn', '--node-scaling-property', type=str, default=None,
help='Optional property to specify what node property to use for node size scaling.')
parser.add_argument('-se', '--edge-scaling-property', type=str, default=None,
help='Optional property to specify what edge property to use for edge width scaling.')
parser.add_argument('--store-to', type=str, default='', help='store query result to this variable')
parser.add_argument('-sp', '--stop-physics', action='store_true', default=False,
help="Disable visualization physics after the initial simulation stabilizes.")
Expand Down Expand Up @@ -275,7 +280,9 @@ def sparql(self, line='', cell='', local_ns: dict = None):
sparql_metadata = build_sparql_metadata_from_query(query_type='query', res=query_res, results=results,
scd_query=True)

sn = SPARQLNetwork(expand_all=args.expand_all)
sn = SPARQLNetwork(expand_all=args.expand_all,
node_scaling_property=args.node_scaling_property,
edge_scaling_property=args.edge_scaling_property)
sn.extract_prefix_declarations_from_query(cell)
try:
sn.add_results(results)
Expand All @@ -284,6 +291,12 @@ def sparql(self, line='', cell='', local_ns: dict = None):

logger.debug(f'number of nodes is {len(sn.graph.nodes)}')
if len(sn.graph.nodes) > 0:
if args.node_scaling_property:
self.graph_notebook_vis_options = OPTIONS_DEFAULT_SCALING_NODES
elif args.edge_scaling_property:
self.graph_notebook_vis_options = OPTIONS_DEFAULT_SCALING_EDGES_ONLY
else:
self.graph_notebook_vis_options = OPTIONS_DEFAULT_DIRECTED
self.graph_notebook_vis_options['physics']['disablePhysicsAfterInitialSimulation'] \
= args.stop_physics
self.graph_notebook_vis_options['physics']['simulationDuration'] = args.simulation_duration
Expand Down Expand Up @@ -387,6 +400,10 @@ def gremlin(self, line, cell, local_ns: dict = None):
help='Property to display the value of on each node, default is T.label')
parser.add_argument('-de', '--edge-display-property', type=str, default='T.label',
help='Property to display the value of on each edge, default is T.label')
parser.add_argument('-sn', '--node-scaling-property', type=str, default=None,
help='Optional property to specify what node property to use for node size scaling.')
parser.add_argument('-se', '--edge-scaling-property', type=str, default=None,
help='Optional property to specify what edge property to use for edge width scaling.')
parser.add_argument('-l', '--label-max-length', type=int, default=10,
help='Specifies max length of vertex label, in characters. Default is 10')
parser.add_argument('--store-to', type=str, default='', help='store query result to this variable')
Expand Down Expand Up @@ -464,6 +481,8 @@ def gremlin(self, line, cell, local_ns: dict = None):
logger.debug(f'ignore_groups: {args.ignore_groups}')
gn = GremlinNetwork(group_by_property=args.group_by, display_property=args.display_property,
edge_display_property=args.edge_display_property,
node_scaling_property=args.node_scaling_property,
edge_scaling_property=args.edge_scaling_property,
label_max_length=args.label_max_length, ignore_groups=args.ignore_groups)

if args.path_pattern == '':
Expand All @@ -473,6 +492,12 @@ def gremlin(self, line, cell, local_ns: dict = None):
gn.add_results_with_pattern(query_res, pattern)
logger.debug(f'number of nodes is {len(gn.graph.nodes)}')
if len(gn.graph.nodes) > 0:
if args.node_scaling_property:
self.graph_notebook_vis_options = OPTIONS_DEFAULT_SCALING_NODES
elif args.edge_scaling_property:
self.graph_notebook_vis_options = OPTIONS_DEFAULT_SCALING_EDGES_ONLY
else:
self.graph_notebook_vis_options = OPTIONS_DEFAULT_DIRECTED
self.graph_notebook_vis_options['physics']['disablePhysicsAfterInitialSimulation'] \
= args.stop_physics
self.graph_notebook_vis_options['physics']['simulationDuration'] = args.simulation_duration
Expand Down Expand Up @@ -1373,6 +1398,10 @@ def handle_opencypher_query(self, line, cell, local_ns):
help='Property to display the value of on each node, default is ~labels')
parser.add_argument('-de', '--edge-display-property', type=str, default='~labels',
help='Property to display the value of on each edge, default is ~type')
parser.add_argument('-sn', '--node-scaling-property', type=str, default=None,
help='Optional property to specify what node property to use for node size scaling.')
parser.add_argument('-se', '--edge-scaling-property', type=str, default=None,
help='Optional property to specify what edge property to use for edge width scaling.')
parser.add_argument('-l', '--label-max-length', type=int, default=10,
help='Specifies max length of vertex label, in characters. Default is 10')
parser.add_argument('--store-to', type=str, default='', help='store query result to this variable')
Expand All @@ -1386,7 +1415,7 @@ def handle_opencypher_query(self, line, cell, local_ns):
logger.debug(args)
titles = []
children = []
force_graph_output=None
force_graph_output = None
res = None
if args.mode == 'query':
query_start = time.time() * 1000 # time.time() returns time in seconds w/high precision; x1000 to get in ms
Expand All @@ -1398,11 +1427,19 @@ def handle_opencypher_query(self, line, cell, local_ns):
query_time=query_time)
try:
gn = OCNetwork(group_by_property=args.group_by, display_property=args.display_property,
edge_display_property=args.edge_display_property,
label_max_length=args.label_max_length, ignore_groups=args.ignore_groups)
edge_display_property=args.edge_display_property, label_max_length=args.label_max_length,
node_scaling_property=args.node_scaling_property,
edge_scaling_property=args.edge_scaling_property,
ignore_groups=args.ignore_groups)
gn.add_results(res)
logger.debug(f'number of nodes is {len(gn.graph.nodes)}')
if len(gn.graph.nodes) > 0:
if args.node_scaling_property:
self.graph_notebook_vis_options = OPTIONS_DEFAULT_SCALING_NODES
elif args.edge_scaling_property:
self.graph_notebook_vis_options = OPTIONS_DEFAULT_SCALING_EDGES_ONLY
else:
self.graph_notebook_vis_options = OPTIONS_DEFAULT_DIRECTED
self.graph_notebook_vis_options['physics']['disablePhysicsAfterInitialSimulation'] \
= args.stop_physics
self.graph_notebook_vis_options['physics']['simulationDuration'] = args.simulation_duration
Expand Down
6 changes: 4 additions & 2 deletions src/graph_notebook/network/EventfulNetwork.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,18 @@ def add_node_property(self, node_id: str, key: str, value: str):
}
self.dispatch_callbacks(EVENT_ADD_NODE_PROPERTY, data)

def add_node(self, node_id: str, data: dict = None):
def add_node(self, node_id: str, value: float = None, data: dict = None):
if data is None:
data = {}
super().add_node(node_id, data)
payload = {
'node_id': node_id,
'value': value,
'data': data
}
self.dispatch_callbacks(EVENT_ADD_NODE, payload)

def add_edge(self, from_id: str, to_id: str, edge_id: str, label: str, data: dict = None):
def add_edge(self, from_id: str, to_id: str, edge_id: str, label: str, value: float = None, data: dict = None):
if data is None:
data = {}
super().add_edge(from_id, to_id, edge_id, label, data)
Expand All @@ -149,6 +150,7 @@ def add_edge(self, from_id: str, to_id: str, edge_id: str, label: str, data: dic
'to_id': to_id,
'edge_id': edge_id,
'label': label,
'value': value,
'data': data
}
self.dispatch_callbacks(EVENT_ADD_EDGE, payload)
Expand Down
52 changes: 46 additions & 6 deletions src/graph_notebook/network/gremlin/GremlinNetwork.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class GremlinNetwork(EventfulNetwork):

def __init__(self, graph: MultiDiGraph = None, callbacks=None, label_max_length=DEFAULT_LABEL_MAX_LENGTH,
group_by_property=T_LABEL, display_property=T_LABEL, edge_display_property=T_LABEL,
ignore_groups=False):
node_scaling_property=None, edge_scaling_property=None, ignore_groups=False):
if graph is None:
graph = MultiDiGraph()
if label_max_length < 3:
Expand All @@ -119,6 +119,8 @@ def __init__(self, graph: MultiDiGraph = None, callbacks=None, label_max_length=
self.edge_display_property = self.convert_multiproperties_to_tuples(json.loads(edge_display_property))
except ValueError:
self.edge_display_property = self.convert_multiproperties_to_tuples(edge_display_property)
self.node_scaling_property = self.convert_multiproperties_to_tuples(node_scaling_property)
self.edge_scaling_property = self.convert_multiproperties_to_tuples(edge_scaling_property)
self.ignore_groups = ignore_groups
super().__init__(graph, callbacks)

Expand Down Expand Up @@ -285,6 +287,8 @@ def add_vertex(self, v):
:param v: The vertex taken from a path traversal object.
"""
node_id = ''
has_value = False
scaling_value = None
if type(v) is Vertex:
node_id = v.id
title = v.label
Expand Down Expand Up @@ -401,7 +405,28 @@ def add_vertex(self, v):
if label == '':
label = title if len(title) <= self.label_max_length else title[:self.label_max_length - 3] + '...'

if self.node_scaling_property:
if isinstance(self.node_scaling_property, tuple):
if self.node_scaling_property[0] in v and isinstance(v[self.node_scaling_property[0]], list):
try:
node_scaling_value = v[self.node_scaling_property[0]][self.node_scaling_property[1]]
if isinstance(node_scaling_value, (int, float)):
has_value = True
scaling_value = float(node_scaling_value)
except IndexError:
pass
elif self.node_scaling_property in v:
if isinstance(v[self.node_scaling_property], list):
node_scaling_value = v[self.node_scaling_property][0]
else:
node_scaling_value = v[self.node_scaling_property]
if isinstance(node_scaling_value, (int, float)):
has_value = True
scaling_value = float(node_scaling_value)

data = {'properties': properties, 'label': label, 'title': title, 'group': group}
if has_value:
data['value'] = scaling_value
else:
node_id = str(v)
title = str(v)
Expand All @@ -410,7 +435,7 @@ def add_vertex(self, v):

if self.ignore_groups:
data['group'] = DEFAULT_GRP
self.add_node(node_id, data)
self.add_node(node_id=node_id, value=scaling_value, data=data)

def add_path_edge(self, edge, from_id='', to_id='', data=None):
if data is None:
Expand All @@ -433,7 +458,7 @@ def add_path_edge(self, edge, from_id='', to_id='', data=None):
display_label = data['properties'][self.edge_display_property]
except KeyError:
display_label = edge.label
self.add_edge(from_id, to_id, edge.id, display_label, data)
self.add_edge(from_id=from_id, to_id=to_id, edge_id=edge.id, label=display_label, data=data)
elif type(edge) is dict:
properties = {}
edge_id = ''
Expand Down Expand Up @@ -478,10 +503,25 @@ def add_path_edge(self, edge, from_id='', to_id='', data=None):
edge_label = str(edge[k])
display_is_set = True

if self.edge_scaling_property:
if isinstance(self.edge_scaling_property, tuple):
if self.edge_scaling_property[0] in properties and \
isinstance(properties[self.edge_scaling_property[0]], list):
try:
node_scaling_value = properties[self.edge_scaling_property[0]][self.edge_scaling_property[1]]
if isinstance(node_scaling_value, (int, float)):
data['value'] = float(node_scaling_value)
except IndexError:
pass
elif self.edge_scaling_property in properties:
if isinstance(properties[self.edge_scaling_property], (int, float)):
value = properties[self.edge_scaling_property]
data['value'] = float(value)

data['properties'] = properties
self.add_edge(from_id, to_id, edge_id, edge_label, data)
self.add_edge(from_id=from_id, to_id=to_id, edge_id=edge_id, label=edge_label, data=data)
else:
self.add_edge(from_id, to_id, edge, str(edge), data)
self.add_edge(from_id=from_id, to_id=to_id, edge_id=edge, label=str(edge), data=data)

def add_blank_edge(self, from_id, to_id, edge_id=None, undirected=True, label=''):
"""
Expand All @@ -497,7 +537,7 @@ def add_blank_edge(self, from_id, to_id, edge_id=None, undirected=True, label=''
if edge_id is None:
edge_id = str(uuid.uuid4())
edge_data = UNDIRECTED_EDGE if undirected else {}
self.add_edge(from_id, to_id, edge_id, label, edge_data)
self.add_edge(from_id=from_id, to_id=to_id, edge_id=edge_id, label=label, data=edge_data)

def insert_path_element(self, path, i):
if i == 0:
Expand Down
Loading