Skip to content

Commit eed552f

Browse files
author
Joseph Hirsch
committed
💄 optimize graph styling
1 parent e0e52d9 commit eed552f

File tree

1 file changed

+150
-132
lines changed

1 file changed

+150
-132
lines changed

demonstrator/views/graph.py

Lines changed: 150 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,20 @@
1-
import functools
2-
import json
3-
import math
4-
import random
5-
from dash import dcc, Input, Output, State, html
6-
import dash_cytoscape as cyto
7-
import py2neo
8-
from py2neo import NodeMatcher, Graph, RelationshipMatcher
9-
10-
import networkx as nx
11-
12-
finding = ['defect', 'corrosion', 'marine_growth', 'paint_peel', 'anode', 'over_board_valve', 'propeller', 'sea_chest_grating', 'bilge_keel']
13-
14-
empty_elements = {
15-
'nodes': [
16-
{'data':{'label': 'Nothing to show', 'id': 3}, 'classes' : 'finding'},
17-
],
18-
'edges': [
19-
]
20-
}
21-
22-
"""
23-
elmts have the structure {'nodes': [], 'edges': []}
24-
nodes: List of nodes:
25-
{
26-
'data': {arbitrary data, no meaning for graph unless used in layout, eg. size},
27-
'classes': "classes, comma seperated i guess",
28-
'position': {'x': ..., 'y': ...}
29-
}
30-
31-
edges: List of edges:
32-
{
33-
'data': {
34-
'source' : <index of node in nodes List>
35-
'target' : <index of node in nodes List>
36-
+ optional arbitrary data used in layout
37-
}
38-
}
39-
"""
40-
41-
def redact_json_data(json_data, positions):
42-
for i, n in enumerate(json_data['elements']['nodes']):
43-
n['data']['id'] = n['data']['value']
44-
if 'classes' in n['data']: n['classes'] = n['data']['classes']
45-
elif n['data']['id'].startswith('s_'):
46-
n['classes'] = 'ship'
47-
n['data']['label'] = n['data']['name']
48-
49-
elif n['data']['id'].startswith('p_s_'):
50-
n['classes'] = 'ship_part'
51-
n['data']['label'] = n['data']['name']
52-
elif n['data']['id'].startswith('in_'):
53-
n['classes'] = 'inspection'
54-
n['data']['label'] = f"Inspection on {n['data']['date']}"
55-
elif n['data']['id'].startswith('c'):
56-
n['classes'] = 'cluster'
57-
n['data']['label'] = f"Cluster {n['data']['id'].split('.')[-1]}"
58-
elif n['data']['id'].startswith('m_'):
59-
n['classes'] = 'mosaic'
60-
n['data']['image_path'] = 'assets/imgs/mosaics/' + n['data']['seg_image_file']
61-
elif n['data']['id'].startswith('im_'):
62-
n['classes'] = 'frame'
63-
n['data']['image_path'] = 'assets/imgs/frames/' + n['data']['thumbnail']
64-
n['data']['label'] = f"{n['data']['frame_index']}"
65-
66-
if 'style' in n['data']: n['style'] = n['data']['style']
67-
try:
68-
#pos = n['data']['pca'].split(',')
69-
#pos = [float(p) for p in pos]
70-
pos = positions[n['data']['id']]
71-
n['position'] = {'x': 1000 * pos[0], 'y': 1000 * pos[1]}
72-
except:
73-
n['position'] = {'x': 0, 'y': 0}
74-
75-
for i, e in enumerate(json_data['elements']['edges']):
76-
if 'classes' in e['data']: e['classes'] = e['data']['classes']
77-
return json_data
78-
79-
80-
legend_colors_segmenter = {
81-
'anode': (0, 255, 255),
82-
'bilge_keel': (255, 165, 0),
83-
'corrosion': (255, 255, 0),
84-
'defect': (255, 192, 203),
85-
'marine_growth': (0, 128, 0),
86-
'over_board_valves': (64, 224, 208),
87-
'paint_peel': (255, 0, 0),
88-
'propeller': (128, 0, 128),
89-
'sea_chest_grating': (255, 255, 255),
90-
'ship_hull': (0, 0, 255),
91-
}
1+
graph_stylesheet = [
2+
{
3+
'selector': '.msg',
4+
'style': {
5+
"border-width": 0,
6+
"border-color": "black",
7+
"border-opacity": 1,
8+
"opacity": 1,
9+
"width": 0,
10+
"height": 0,
9211

93-
def layout():
94-
return html.Div(className="two_columns", children=[
95-
html.Div(className="graphContainer", children=[
96-
html.H1("Graph View"),
97-
cyto.Cytoscape(
98-
id='gr',
99-
layout={
100-
'name': 'preset',
101-
'animate': True,
102-
#'idealEdgeLength': 400,
103-
#'nodeOverlap': 20,
104-
#'refresh': 20,
105-
'fit': True,
106-
#'padding': 30,
107-
#'randomize': False,
108-
'componentSpacing': 1,
109-
'nodeRepulsion': 8000,
110-
#'edgeElasticity': 10,
111-
#'nestingFactor': 5,
112-
#'gravity': 80,
113-
#'numIter': 1000,
114-
#'initialTemp': 200,
115-
#'coolingFactor': 0.95,
116-
#'minTemp': 1.0
117-
},
118-
style={'width': '100%', 'height': '1000px'},
119-
elements={},
120-
stylesheet = [
12+
"label": "data(label)",
13+
"color": "#000000",
14+
"text-opacity": 1,
15+
"font-size": 12,
16+
}
17+
},
12118
{
12219
'selector': '.finding',
12320
'style': {
@@ -282,7 +179,124 @@ def layout():
282179
}
283180
},
284181
]
285-
)
182+
import functools
183+
import json
184+
import math
185+
import random
186+
from dash import dcc, Input, Output, State, html
187+
import dash_cytoscape as cyto
188+
import py2neo
189+
from py2neo import NodeMatcher, Graph, RelationshipMatcher
190+
191+
import networkx as nx
192+
193+
finding = ['defect', 'corrosion', 'marine_growth', 'paint_peel', 'anode', 'over_board_valve', 'propeller', 'sea_chest_grating', 'bilge_keel']
194+
195+
empty_elements = {
196+
'nodes': [
197+
{'data':{'label': 'Nothing to show', 'id': 3}, 'classes' : 'msg'},
198+
],
199+
'edges': [
200+
]
201+
}
202+
loading_elements = {
203+
'nodes': [
204+
{'data':{'label': 'Loading...', 'id': 3}, 'classes' : 'msg'},
205+
],
206+
'edges': [
207+
]
208+
}
209+
210+
"""
211+
elmts have the structure {'nodes': [], 'edges': []}
212+
nodes: List of nodes:
213+
{
214+
'data': {arbitrary data, no meaning for graph unless used in layout, eg. size},
215+
'classes': "classes, comma seperated i guess",
216+
'position': {'x': ..., 'y': ...}
217+
}
218+
219+
edges: List of edges:
220+
{
221+
'data': {
222+
'source' : <index of node in nodes List>
223+
'target' : <index of node in nodes List>
224+
+ optional arbitrary data used in layout
225+
}
226+
}
227+
"""
228+
229+
def redact_json_data(json_data, positions):
230+
for i, n in enumerate(json_data['elements']['nodes']):
231+
n['data']['id'] = n['data']['value']
232+
if 'classes' in n['data']: n['classes'] = n['data']['classes']
233+
elif n['data']['id'].startswith('s_'):
234+
n['classes'] = 'ship'
235+
n['data']['label'] = n['data']['name']
236+
237+
elif n['data']['id'].startswith('p_s_'):
238+
n['classes'] = 'ship_part'
239+
n['data']['label'] = n['data']['name']
240+
elif n['data']['id'].startswith('in_'):
241+
n['classes'] = 'inspection'
242+
n['data']['label'] = f"Inspection on {n['data']['date']}"
243+
elif n['data']['id'].startswith('c'):
244+
n['classes'] = 'cluster'
245+
n['data']['label'] = f"Cluster {n['data']['id'].split('.')[-1]}"
246+
elif n['data']['id'].startswith('m_'):
247+
n['classes'] = 'mosaic'
248+
n['data']['image_path'] = 'assets/imgs/mosaics/' + n['data']['seg_image_file']
249+
elif n['data']['id'].startswith('im_'):
250+
n['classes'] = 'frame'
251+
n['data']['image_path'] = 'assets/imgs/frames/' + n['data']['thumbnail']
252+
n['data']['label'] = f"{n['data']['frame_index']}"
253+
254+
if 'style' in n['data']: n['style'] = n['data']['style']
255+
try:
256+
#pos = n['data']['pca'].split(',')
257+
#pos = [float(p) for p in pos]
258+
pos = positions[n['data']['id']]
259+
n['position'] = {'x': 1 * pos[0], 'y': 1 * pos[1]}
260+
except:
261+
n['position'] = {'x': 0, 'y': 0}
262+
263+
for i, e in enumerate(json_data['elements']['edges']):
264+
if 'classes' in e['data']: e['classes'] = e['data']['classes']
265+
return json_data
266+
267+
268+
legend_colors_segmenter = {
269+
'anode': (0, 255, 255),
270+
'bilge_keel': (255, 165, 0),
271+
'corrosion': (255, 255, 0),
272+
'defect': (255, 192, 203),
273+
'marine_growth': (0, 128, 0),
274+
'over_board_valves': (64, 224, 208),
275+
'paint_peel': (255, 0, 0),
276+
'propeller': (128, 0, 128),
277+
'sea_chest_grating': (255, 255, 255),
278+
'ship_hull': (0, 0, 255),
279+
}
280+
281+
def layout():
282+
return html.Div(className="two_columns", children=[
283+
html.Div(className="graphContainer", children=[
284+
html.H1("Graph View"),
285+
dcc.Loading(
286+
id="loading_gr",
287+
type="default",
288+
children=[
289+
cyto.Cytoscape(
290+
id='gr',
291+
layout={
292+
'name': 'preset',
293+
'animate': False,
294+
'fit': True,
295+
},
296+
style={'width': '100%', 'height': '1000px'},
297+
elements={},
298+
stylesheet = graph_stylesheet)
299+
])
286300
]),
287301
html.Div(className="nodeInfo", children=[
288302
html.H1("Node Info"),
@@ -318,19 +332,25 @@ def update_graph(data, filter_options):
318332
relcount = 0
319333

320334
replace_with = set()
335+
336+
SIMILAR_EDGE_WEIGHT = 0.6
337+
CLUSTER_EDGE_WEIGHT = 0.6
338+
ININSPE_EDGE_WEIGHT = 0.1
339+
PARTOFS_EDGE_WEIGHT = 0.1
340+
SHOWSPR_EDGE_WEIGHT = 0.1
321341

322342
for n1, n2 in data['in_inspection']:
323343
if not n1 in node_keys or not n2 in node_keys: continue
324344
relcount += 1
325-
graph.add_edge(n1, n2, classes="inspection", weight=0)
345+
graph.add_edge(n1, n2, classes="inspection", weight=ININSPE_EDGE_WEIGHT)
326346
for n1, n2 in data['similar_to']:
327347
if not n1 in node_keys or not n2 in node_keys: continue
328348
relcount += 1
329-
graph.add_edge(n1, n2, classes="dashed", weight=0.08)
349+
graph.add_edge(n1, n2, classes="dashed", weight=SIMILAR_EDGE_WEIGHT)
330350
for n1, n2 in data['visually_similar_to']:
331351
if not n1 in node_keys or not n2 in node_keys: continue
332352
relcount += 1
333-
graph.add_edge(n1, n2, classes="dotted", weight=0.08)
353+
graph.add_edge(n1, n2, classes="dotted", weight=SIMILAR_EDGE_WEIGHT)
334354
for n1, n2 in data['in_mosaic']:
335355
if not n1 in node_keys or not n2 in node_keys: continue
336356
relcount += 1
@@ -340,16 +360,16 @@ def update_graph(data, filter_options):
340360
for n1, n2 in data['in_cluster']:
341361
if not n1 in node_keys or not n2 in node_keys: continue
342362
relcount += 1
343-
graph.add_edge(n1, n2, weight=0.1)
363+
graph.add_edge(n1, n2, weight=CLUSTER_EDGE_WEIGHT)
344364

345365
for n1, n2 in data['part_of_ship']:
346366
if not n1 in node_keys or not n2 in node_keys: continue
347367
relcount += 1
348-
graph.add_edge(n1, n2, weight=0.01)
368+
graph.add_edge(n1, n2, weight=PARTOFS_EDGE_WEIGHT)
349369
for n1, n2 in data['shows_part']:
350370
if not n1 in node_keys or not n2 in node_keys: continue
351371
relcount += 1
352-
graph.add_edge(n1, n2, weight=0.02, classes="part")
372+
graph.add_edge(n1, n2, weight=SHOWSPR_EDGE_WEIGHT, classes="part")
353373

354374
for image, mosaic in replace_with:
355375
for n1, n2, data in graph.edges(image, data=True):
@@ -359,20 +379,18 @@ def update_graph(data, filter_options):
359379
graph.remove_node(image)
360380

361381

362-
363-
364-
365-
print("pre layout jobs...")
366-
367382
positions_by_pca = {
368383
key: (float(tup['tsne'].split(',')[0]),float(tup['tsne'].split(',')[1]))
369384

370385
for key, tup in node_tuples if 'tsne' in tup}
371386

372387
fixed_positions = [k for k in positions_by_pca.keys()]
373388
print(f"loaded result, got {len(node_tuples)} nodes and {relcount} relationships, layouting...")
389+
374390
#pos = nx.spring_layout(graph, fixed = fixed_positions, pos = positions_by_pca, k=1/math.sqrt(graph.order() + 1), iterations=100)
375-
pos = nx.spring_layout(graph, k=4/math.sqrt(graph.order() + 1), iterations=200)
391+
392+
pos = nx.spring_layout(graph, scale=1000, k=10/math.sqrt(graph.order() + 1), iterations=300, seed=84365)
393+
376394
elmts = redact_json_data(nx.cytoscape_data(graph), pos)['elements']
377395
print(f"done")
378396

0 commit comments

Comments
 (0)