import time import webbrowser from pathlib import Path from threading import Thread from typing import cast import copy import dash_cytoscape as cyto from dash import Dash, Input, Output, State, dcc, html from dash.exceptions import PreventUpdate import lang_main.io from lang_main.analysis import graphs target = '../results/test_20240529/Pipe-Token_Analysis_Step-1_build_token_graph.pkl' p = Path(target).resolve() ret = lang_main.io.load_pickle(p) tk_graph = cast(graphs.TokenGraph, ret[0]) tk_graph_filtered = tk_graph.filter_by_edge_weight(150, None) tk_graph_filtered = tk_graph_filtered.filter_by_node_degree(1, None) cyto_data_base, weight_data = graphs.convert_graph_to_cytoscape(tk_graph_filtered) MIN_WEIGHT = weight_data['min'] MAX_WEIGHT = weight_data['max'] cyto.load_extra_layouts() external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] app = Dash(__name__, external_stylesheets=external_stylesheets) cose_layout = { 'name': 'cose', 'nodeOverlap': 20, 'refresh': 20, 'fit': True, 'padding': 30, 'randomize': True, 'componentSpacing': 40, 'nodeRepulsion': 2000, 'edgeElasticity': 1000, 'idealEdgeLength': 100, 'nestingFactor': 1.2, 'gravity': 50, 'numIter': 2000, 'initialTemp': 1000, 'coolingFactor': 0.95, 'minTemp': 1.0, 'nodeDimensionsIncludeLabels': True, } cose_bilkent_layout = { 'name': 'cose-bilkent', 'nodeDimensionsIncludeLabels': True, 'idealEdgeLength': 100, 'edgeElasticity': 0.45, 'nodeRepulsion': 10000, 'nestingFactor': 0.1, 'gravity': 0.25, 'numIter': 2500, 'initialTemp': 1000, 'coolingFactor': 0.95, 'minTemp': 1.0, } cola_layout = { 'name': 'cola', 'nodeDimensionsIncludeLabels': True, 'nodeSpacing': 30, 'edgeLength': 45, 'animate': True, 'centerGraph': True, 'randomize': False, } my_stylesheet = [ # Group selectors { 'selector': 'node', 'style': { 'shape': 'circle', 'content': 'data(label)', 'background-color': '#B10DC9', 'border-width': 2, 'border-color': 'black', 'border-opacity': 1, 'opacity': 1, 'color': 'black', 'text-opacity': 1, 'font-size': 12, 'z-index': 9999, }, }, { 'selector': 'edge', 'style': { #'width': f'mapData(weight, {MIN_WEIGHT}, {MAX_WEIGHT}, 1, 10)', # 'width': """function(ele) { # return ele.data('weight'); # """, 'curve-style': 'bezier', 'line-color': 'grey', 'line-style': 'solid', 'line-opacity': 1, }, }, # Class selectors # {'selector': '.red', 'style': {'background-color': 'red', 'line-color': 'red'}}, # {'selector': '.triangle', 'style': {'shape': 'triangle'}}, ] app.layout = html.Div( [ html.Button('Trigger JS Layout', id='test_js'), html.Button('Trigger JS Weight', id='test_js_weight'), html.Div(id='output'), html.Div( [ html.H2('Token Graph', style={'margin': 0}), html.Button( 'Reset Default', id='bt-reset', style={ 'marginLeft': 'auto', 'width': '300px', }, ), ], style={ 'display': 'flex', 'marginBottom': '1em', }, ), html.H3('Layout'), dcc.Dropdown( id='layout_choice', options=[ 'cose', 'cola', 'euler', 'random', ], value='cose', clearable=False, ), html.Div( [ html.H3('Graph Filter'), dcc.Input( id='weight_min', type='number', min=MIN_WEIGHT, max=MAX_WEIGHT, step=1, placeholder=f'Minimum edge weight: {MIN_WEIGHT} - {MAX_WEIGHT}', debounce=True, style={'width': '40%'}, ), dcc.Input( id='weight_max', type='number', min=MIN_WEIGHT, max=MAX_WEIGHT, step=1, placeholder=f'Maximum edge weight: {MIN_WEIGHT} - {MAX_WEIGHT}', debounce=True, style={'width': '40%'}, ), html.H3('Graph'), html.Div( [ cyto.Cytoscape( id='cytoscape-graph', style={'width': '100%', 'height': '600px'}, stylesheet=my_stylesheet, elements=cyto_data_base, zoom=1, ), ], style={ 'border': '3px solid black', 'borderRadius': '25px', 'marginTop': '1em', 'marginBottom': '2em', 'padding': '7px', }, ), ], style={'marginTop': '1em'}, ), ], style={'margin': '2em'}, ) @app.callback( Output('cytoscape-graph', 'layout', allow_duplicate=True), Input('layout_choice', 'value'), prevent_initial_call=True, ) def update_layout_internal(layout_choice): # return {'name': layout_choice} return cose_layout # return cose_bilkent_layout # return cola_layout @app.callback( Output('cytoscape-graph', 'zoom'), Output('cytoscape-graph', 'elements', allow_duplicate=True), Output('weight_min', 'value'), Output('weight_max', 'value'), Input('bt-reset', 'n_clicks'), prevent_initial_call=True, ) def reset_layout(n_clicks): return (1, cyto_data_base, None, None) # update edge weight @app.callback( Output('cytoscape-graph', 'elements', allow_duplicate=True), Input('weight_min', 'value'), Input('weight_max', 'value'), prevent_initial_call=True, ) def update_edge_weight(weight_min, weight_max): if not any([weight_min, weight_max]): return cyto_data_base if weight_min is None: weight_min = MIN_WEIGHT if weight_max is None: weight_max = MAX_WEIGHT tk_graph_filtered = tk_graph.filter_by_edge_weight(weight_min, weight_max) tk_graph_filtered = tk_graph_filtered.filter_by_node_degree(1, None) cyto_data, _ = graphs.convert_graph_to_cytoscape(tk_graph_filtered) return cyto_data # app.clientside_callback( # """ # function(n_clicks, layout) { # let threshold = 1000; # layout.edgeLength = function(edge) { # let weight = edge.data().weight; # let length; # if (weight > threshold) { # length = 10; # } else { # length = 1000 / edge.data().weight; # length = Math.max(20, length); # } # return length; # }; # cy.layout(layout).run(); # return layout; # } # """, # Output('cytoscape-graph', 'layout', allow_duplicate=True), # Input('test_js', 'n_clicks'), # State('cytoscape-graph', 'layout'), # prevent_initial_call=True, # ) app.clientside_callback( """ function(n_clicks, layout) { layout.edgeElasticity = function(edge) { return edge.data().weight * 4; }; layout.idealEdgeLength = function(edge) { return edge.data().weight * 0.8; }; cy.layout(layout).run(); return layout; } """, Output('cytoscape-graph', 'layout', allow_duplicate=True), Input('test_js', 'n_clicks'), State('cytoscape-graph', 'layout'), prevent_initial_call=True, ) app.clientside_callback( """ function(n_clicks, stylesheet) { function edge_weight(ele) { let threshold = 1000; let weight = ele.data('weight'); if (weight > threshold) { weight = 12; } else { weight = weight / threshold * 10; weight = Math.max(1, weight); } return weight; } stylesheet[1].style.width = edge_weight; cy.style(stylesheet).update(); return stylesheet; } """, Output('cytoscape-graph', 'stylesheet'), Input('test_js_weight', 'n_clicks'), State('cytoscape-graph', 'stylesheet'), prevent_initial_call=False, ) def _start_webbrowser(): host = '127.0.0.1' port = '8050' adress = f'http://{host}:{port}/' time.sleep(2) webbrowser.open_new(adress) def main(): webbrowser_thread = Thread(target=_start_webbrowser, daemon=True) webbrowser_thread.start() app.run(debug=True) if __name__ == '__main__': main()