Created
April 5, 2019 19:29
-
-
Save xhluca/7f59d3042a9c5074543d736cc1cb9f87 to your computer and use it in GitHub Desktop.
Experiments of collapsing nodes - not implemented yet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import json | |
import dash | |
from dash.dependencies import Input, Output, State | |
import dash_core_components as dcc | |
import dash_html_components as html | |
import dash_cytoscape as cyto | |
from demos import dash_reusable_components as drc | |
app = dash.Dash(__name__) | |
server = app.server | |
# ###################### DATA PREPROCESSING ###################### | |
# Load data | |
with open('demos/data/sample_network.txt', 'r') as f: | |
network_data = f.read().split('\n') | |
# We select the first 750 edges and associated nodes for an easier visualization | |
edges = network_data[:750] | |
nodes = set() | |
following_node_di = {} # user id -> list of users they are following | |
following_edges_di = {} # user id -> list of cy edges starting from user id | |
followers_node_di = {} # user id -> list of followers (cy_node format) | |
followers_edges_di = {} # user id -> list of cy edges ending at user id | |
cy_edges = [] | |
cy_nodes = [] | |
for edge in edges: | |
if " " not in edge: | |
continue | |
source, target = edge.split(" ") | |
cy_edge = {'data': {'id': source + target, 'source': source, 'target': target}} | |
cy_target = {"data": {"id": target, "label": "User #" + str(target[-5:])}} | |
cy_source = {"data": {"id": source, "label": "User #" + str(source[-5:])}} | |
if source not in nodes: | |
nodes.add(source) | |
cy_nodes.append(cy_source) | |
if target not in nodes: | |
nodes.add(target) | |
cy_nodes.append(cy_target) | |
# Process dictionary of following | |
if not following_node_di.get(source): | |
following_node_di[source] = [] | |
if not following_edges_di.get(source): | |
following_edges_di[source] = [] | |
following_node_di[source].append(cy_target) | |
following_edges_di[source].append(cy_edge) | |
# Process dictionary of followers | |
if not followers_node_di.get(target): | |
followers_node_di[target] = [] | |
if not followers_edges_di.get(target): | |
followers_edges_di[target] = [] | |
followers_node_di[target].append(cy_source) | |
followers_edges_di[target].append(cy_edge) | |
genesis_node = cy_nodes[0] | |
genesis_node['classes'] = "genesis" | |
default_elements = [genesis_node] | |
default_stylesheet = [ | |
{ | |
"selector": 'node', | |
'style': { | |
"opacity": 0.65, | |
'z-index': 9999 | |
} | |
}, | |
{ | |
"selector": 'edge', | |
'style': { | |
"curve-style": "bezier", | |
"opacity": 0.45, | |
'z-index': 5000 | |
} | |
}, | |
{ | |
'selector': '.followersNode', | |
'style': { | |
'background-color': '#0074D9' | |
} | |
}, | |
{ | |
'selector': '.followersEdge', | |
"style": { | |
"mid-target-arrow-color": "blue", | |
"mid-target-arrow-shape": "vee", | |
"line-color": "#0074D9" | |
} | |
}, | |
{ | |
'selector': '.followingNode', | |
'style': { | |
'background-color': '#FF4136' | |
} | |
}, | |
{ | |
'selector': '.followingEdge', | |
"style": { | |
"mid-target-arrow-color": "red", | |
"mid-target-arrow-shape": "vee", | |
"line-color": "#FF4136", | |
} | |
}, | |
{ | |
"selector": '.genesis', | |
"style": { | |
'background-color': '#B10DC9', | |
"border-width": 2, | |
"border-color": "purple", | |
"border-opacity": 1, | |
"opacity": 1, | |
"label": "data(label)", | |
"color": "#B10DC9", | |
"text-opacity": 1, | |
"font-size": 12, | |
'z-index': 9999 | |
} | |
}, | |
{ | |
'selector': ':selected', | |
"style": { | |
"border-width": 2, | |
"border-color": "black", | |
"border-opacity": 1, | |
"opacity": 1, | |
"label": "data(label)", | |
"color": "black", | |
"font-size": 12, | |
'z-index': 9999 | |
} | |
} | |
] | |
# ################################# APP LAYOUT ################################ | |
styles = { | |
'json-output': { | |
'overflow-y': 'scroll', | |
'height': 'calc(50% - 25px)', | |
'border': 'thin lightgrey solid' | |
}, | |
'tab': {'height': 'calc(98vh - 80px)'} | |
} | |
app.layout = html.Div([ | |
html.Div(className='eight columns', children=[ | |
cyto.Cytoscape( | |
id='cytoscape', | |
elements=default_elements, | |
stylesheet=default_stylesheet, | |
style={ | |
'height': '95vh', | |
'width': '100%' | |
} | |
) | |
]), | |
html.Div(className='four columns', children=[ | |
dcc.Tabs(id='tabs', children=[ | |
dcc.Tab(label='Control Panel', children=[ | |
drc.NamedDropdown( | |
name='Layout', | |
id='dropdown-layout', | |
options=drc.DropdownOptionsList( | |
'random', | |
'grid', | |
'circle', | |
'concentric', | |
'breadthfirst', | |
'cose' | |
), | |
value='grid', | |
clearable=False | |
), | |
dcc.RadioItems( | |
id='radio-followers-following', | |
options=drc.DropdownOptionsList( | |
'followers', | |
'following' | |
), | |
value='followers' | |
), | |
dcc.RadioItems( | |
id='radio-expansion-collapse', | |
options=drc.DropdownOptionsList( | |
'expansion', | |
'collapse' | |
), | |
value='expansion' | |
), | |
]), | |
dcc.Tab(label='JSON', children=[ | |
html.Div(style=styles['tab'], children=[ | |
html.P('Node Object JSON:'), | |
html.Pre( | |
id='tap-node-json-output', | |
style=styles['json-output'] | |
), | |
html.P('Edge Object JSON:'), | |
html.Pre( | |
id='tap-edge-json-output', | |
style=styles['json-output'] | |
) | |
]) | |
]) | |
]), | |
]) | |
]) | |
# ############################## CALLBACKS #################################### | |
@app.callback(Output('tap-node-json-output', 'children'), | |
[Input('cytoscape', 'tapNode')]) | |
def display_tap_node(data): | |
return json.dumps(data, indent=2) | |
@app.callback(Output('tap-edge-json-output', 'children'), | |
[Input('cytoscape', 'tapEdge')]) | |
def display_tap_edge(data): | |
return json.dumps(data, indent=2) | |
@app.callback(Output('cytoscape', 'layout'), | |
[Input('dropdown-layout', 'value')]) | |
def update_cytoscape_layout(layout): | |
return {'name': layout} | |
@app.callback(Output('cytoscape', 'elements'), | |
[Input('cytoscape', 'tapNodeData')], | |
[State('cytoscape', 'elements'), | |
State('radio-followers-following', 'value'), | |
State('radio-expansion-collapse', 'value')]) | |
def generate_elements(nodeData, elements, followers_following_mode, expansion_collapse_mode): | |
if not nodeData: | |
return default_elements | |
# If the node has already been expanded, we don't expand it again | |
if nodeData.get('expanded'): | |
return elements | |
# This retrieves the currently selected element, and tag it as expanded | |
for element in elements: | |
if nodeData['id'] == element.get('data').get('id'): | |
element['data']['expanded'] = True | |
break | |
if followers_following_mode == 'followers': | |
node_di = followers_node_di | |
edges_di = followers_edges_di | |
else: | |
node_di = following_node_di | |
edges_di = following_edges_di | |
new_nodes = node_di.get(nodeData['id']) | |
new_edges = edges_di.get(nodeData['id']) | |
if expansion_collapse_mode == 'expansion': | |
if new_nodes: | |
for node in new_nodes: | |
if node['data']['id'] != genesis_node['data']['id']: | |
node['classes'] = followers_following_mode + 'Node' | |
elements.append(node) | |
if new_edges: | |
for edge in new_edges: | |
edge['classes'] = followers_following_mode + 'Edge' | |
elements.extend(new_edges) | |
else: # Expansion collapse_mode == 'collapse' | |
if new_nodes or new_edges: | |
ids_to_remove = set([elem['data']['id'] for elem in new_nodes + new_edges]) | |
new_elements = [ | |
elem for elem in elements | |
if elem['data']['id'] == genesis_node['data']['id'] or | |
elem['data']['id'] not in ids_to_remove | |
] | |
return new_elements | |
return elements | |
if __name__ == '__main__': | |
app.run_server(debug=True) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment