diff --git a/dash_app/callbacks.py b/dash_app/callbacks.py index 96cc25a812f2163cc033139a0cb0e5f55500d7ae..cc990a7ef9f37135d9f0b3e03938396f3574c908 100644 --- a/dash_app/callbacks.py +++ b/dash_app/callbacks.py @@ -11,13 +11,33 @@ from dash_app.words import AssociatedWords word2vec_model = AssociatedWords() +@app.callback( + Output(component_id='msx-graph', component_property='autoRefreshLayout'), + Input(component_id='submit-word-button', component_property='n_clicks'), + Input(component_id='add-word-button', component_property='n_clicks'), + Input(component_id='extend-graph-button', component_property='n_clicks'), + Input(component_id='remove-word-button', component_property='n_clicks'), + State(component_id='msx-graph-div', component_property='children'), +) +def set_auto_refresh_layout(submit_word_button, add_word_button, extend_graph_button, remove_word_button, msx_graph): + callback_context = dash.callback_context + button_id = callback_context.triggered[0]['prop_id'].split('.')[0] + if button_id == 'submit-word-button': + return True + else: + return False + + @app.callback( Output(component_id='msx-graph-div', component_property='children'), Input(component_id='submit-word-button', component_property='n_clicks'), + State(component_id='base-word-input', component_property='value'), ) -def update_graph_div(submit_word_button): +def update_graph_div(submit_word_button, base_word_input): graph = Graph() - graph.set_nodes_and_edges({'nodes': [], 'edges': []}) + base_word_lower = base_word_input.lower() + graph.add_node(base_word_lower, is_base_node=1) + # graph.set_nodes_and_edges({'nodes': [], 'edges': []}) return graph.get_cytoscape_graph('msx-graph') @@ -39,26 +59,25 @@ def update_base_word(submit_word_button, base_word_input): @app.callback( Output(component_id='graph-elements-div', component_property='children'), - Input(component_id='submit-word-button', component_property='n_clicks'), + Input(component_id='base-word-div', component_property='children'), Input(component_id='add-word-button', component_property='n_clicks'), Input(component_id='extend-graph-button', component_property='n_clicks'), Input(component_id='remove-word-button', component_property='n_clicks'), State(component_id='base-word-input', component_property='value'), State(component_id='add-word-input', component_property='value'), State(component_id='graph-elements-div', component_property='children'), + State(component_id='msx-graph-div', component_property='children'), State(component_id='msx-graph', component_property='selectedNodeData'), - State(component_id='base-word-div', component_property='children'), - prevent_initial_call=True ) -def update_graph_elements(submit_word_button, add_word_button, extend_graph_button, remove_word_button, base_word_input, - add_word_input, nodes_and_edges, selected_nodes, base_word_state): +def update_graph_elements(base_word_state, add_word_button, extend_graph_button, remove_word_button, base_word_input, + add_word_input, nodes_and_edges, msx_graph, selected_nodes): callback_context = dash.callback_context button_id = callback_context.triggered[0]['prop_id'].split('.')[0] graph = Graph() - if button_id == 'submit-word-button': + if button_id == 'base-word-div': if base_word_input is not None: if base_word_input != '': graph.fill_with_associations(word2vec_model, base_word_input) @@ -107,25 +126,8 @@ def update_graph_elements(submit_word_button, add_word_button, extend_graph_butt @app.callback( Output(component_id='msx-graph', component_property='elements'), Input(component_id='graph-elements-div', component_property='children'), - prevent_initial_call=True ) def update_graph(nodes_and_edges): graph = Graph() graph.set_nodes_and_edges(json.loads(nodes_and_edges)) return graph.get_elements() - - -@app.callback( - Output(component_id='msx-graph', component_property='autoRefreshLayout'), - Input(component_id='add-word-button', component_property='n_clicks'), - Input(component_id='extend-graph-button', component_property='n_clicks'), - Input(component_id='remove-word-button', component_property='n_clicks'), - prevent_initial_call=True -) -def set_auto_refresh_layout(add_word_button, extend_graph_button, remove_word_button): - callback_context = dash.callback_context - button_id = callback_context.triggered[0]['prop_id'].split('.')[0] - if button_id == 'add-word-button' or button_id == 'remove-word-button': - return False - if button_id == 'extend-graph-button': - return True diff --git a/dash_app/graph.py b/dash_app/graph.py index 85a3c78fb43dd3b48c894dde17820968b316ea64..c3bfaac61c3fad8c8d49945a258490091b0932d8 100644 --- a/dash_app/graph.py +++ b/dash_app/graph.py @@ -1,5 +1,6 @@ import dash_cytoscape as cyto import pandas as pd +import numpy as np from math import factorial @@ -8,6 +9,7 @@ class Graph: def __init__(self): self.nodes = [] self.edges = [] + self.positions = {} self.COUNT_THRESHOLD = 2 self.MAX_NUM_WORDS = 10 @@ -34,13 +36,32 @@ class Graph: self.add_node(node) def add_node(self, node, is_base_node=0): - node_dict = {'data': {'id': node, 'label': node, 'is_base_node': is_base_node}} + node_dict = {'data': {'id': node, 'label': node, 'is_base_node': is_base_node}, 'position': self.get_node_position(node, is_base_node)} if is_base_node: node_dict['selectable'] = False node_dict['grabbable'] = False if node_dict not in self.nodes: self.nodes.append(node_dict) + def get_node_position(self, node_id, is_base_node): + if is_base_node: + return {'x': 0, 'y': 0} + else: + position_id = 0 + while str(position_id) in self.positions: + position_id += 1 + + length = position_id * 4 + 80 + angle = ((90.0 - 37.5 * position_id) / 180.0) * np.pi + if angle < -np.pi: + angle += (2 * np.pi) + x = length * np.cos(angle) + y = length * np.sin(angle) + position = {'x': x, 'y': -y} + + self.positions[str(position_id)] = node_id + return position + def add_edges(self, source, targets): for target in targets: self.add_edge(source, target) @@ -57,6 +78,7 @@ class Graph: if node_idx_to_remove >= 0: self.nodes.pop(node_idx_to_remove) + self.positions = {position_id: node_id for position_id, node_id in self.positions.items() if node_id != node} self.remove_edges(node) def remove_edges(self, node): @@ -69,11 +91,12 @@ class Graph: self.edges.pop(edge_idx_to_remove) def get_nodes_and_edges(self): - return {'nodes': self.nodes, 'edges': self.edges} + return {'nodes': self.nodes, 'edges': self.edges, 'positions': self.positions} def set_nodes_and_edges(self, nodes_and_edges): self.nodes = nodes_and_edges['nodes'] self.edges = nodes_and_edges['edges'] + self.positions = nodes_and_edges['positions'] def get_elements(self): elements = [] @@ -84,8 +107,8 @@ class Graph: def get_cytoscape_graph(self, component_id): elements = self.get_elements() return cyto.Cytoscape(id=component_id, - autoRefreshLayout=True, - layout={'name': 'cose', 'animate': True}, + autoRefreshLayout=False, + layout={'name': 'preset'}, style={'width': '100%', 'height': '100%'}, elements=elements, userZoomingEnabled=False,