2025-03-20 07:31:38 +01:00

338 lines
9.2 KiB
Python

import copy
import time
import webbrowser
from pathlib import Path
from threading import Thread
from typing import cast
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 = graphs.filter_graph_by_edge_weight(tk_graph, 150, None)
tk_graph_filtered = graphs.filter_graph_by_node_degree(tk_graph_filtered, 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': 500,
'refresh': 20,
'fit': True,
'padding': 20,
'randomize': False,
'componentSpacing': 1.2,
'nodeRepulsion': 1000,
'edgeElasticity': 1000,
'idealEdgeLength': 100,
'nestingFactor': 1.2,
'gravity': 50,
'numIter': 3000,
'initialTemp': 2000,
'coolingFactor': 0.7,
'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'}},
]
layout = html.Div(
[
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.Button('Re-Layout', id='trigger_relayout'),
html.Div(
[
cyto.Cytoscape(
id='cytoscape-graph',
style={'width': '100%', 'height': '600px'},
layout=cose_layout,
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.layout = layout
@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 = graphs.filter_graph_by_edge_weight(
tk_graph,
weight_min,
weight_max,
)
tk_graph_filtered = graphs.filter_graph_by_node_degree(
tk_graph_filtered,
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 * 0.05;
};
layout.idealEdgeLength = function(edge) {
return edge.data().weight * 0.4;
};
cy.layout(layout).run();
return layout;
}
""",
Output('cytoscape-graph', 'layout', allow_duplicate=True),
Input('trigger_relayout', '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()