flixopt 2.1.6__py3-none-any.whl → 2.1.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flixopt might be problematic. Click here for more details.

flixopt/network_app.py CHANGED
@@ -23,6 +23,7 @@ from .flow_system import FlowSystem
23
23
 
24
24
  logger = logging.getLogger('flixopt')
25
25
 
26
+
26
27
  # Configuration class for better organization
27
28
  class VisualizationConfig:
28
29
  """Configuration constants for the visualization"""
@@ -39,16 +40,28 @@ class VisualizationConfig:
39
40
  COLOR_PRESETS = {
40
41
  'Default': DEFAULT_COLORS,
41
42
  'Vibrant': {
42
- 'Bus': '#FF6B6B', 'Source': '#4ECDC4', 'Sink': '#45B7D1',
43
- 'Storage': '#96CEB4', 'Converter': '#FFEAA7', 'Other': '#DDA0DD',
43
+ 'Bus': '#FF6B6B',
44
+ 'Source': '#4ECDC4',
45
+ 'Sink': '#45B7D1',
46
+ 'Storage': '#96CEB4',
47
+ 'Converter': '#FFEAA7',
48
+ 'Other': '#DDA0DD',
44
49
  },
45
50
  'Dark': {
46
- 'Bus': '#2C3E50', 'Source': '#34495E', 'Sink': '#7F8C8D',
47
- 'Storage': '#95A5A6', 'Converter': '#BDC3C7', 'Other': '#ECF0F1',
51
+ 'Bus': '#2C3E50',
52
+ 'Source': '#34495E',
53
+ 'Sink': '#7F8C8D',
54
+ 'Storage': '#95A5A6',
55
+ 'Converter': '#BDC3C7',
56
+ 'Other': '#ECF0F1',
48
57
  },
49
58
  'Pastel': {
50
- 'Bus': '#FFB3BA', 'Source': '#BAFFC9', 'Sink': '#BAE1FF',
51
- 'Storage': '#FFFFBA', 'Converter': '#FFDFBA', 'Other': '#E0BBE4',
59
+ 'Bus': '#FFB3BA',
60
+ 'Source': '#BAFFC9',
61
+ 'Sink': '#BAE1FF',
62
+ 'Storage': '#FFFFBA',
63
+ 'Converter': '#FFDFBA',
64
+ 'Other': '#E0BBE4',
52
65
  },
53
66
  }
54
67
 
@@ -96,6 +109,7 @@ class VisualizationConfig:
96
109
  },
97
110
  ]
98
111
 
112
+
99
113
  def flow_graph(flow_system: FlowSystem) -> networkx.DiGraph:
100
114
  """Convert FlowSystem to NetworkX graph - simplified and more robust"""
101
115
  nodes = list(flow_system.components.values()) + list(flow_system.buses.values())
@@ -149,10 +163,11 @@ def flow_graph(flow_system: FlowSystem) -> networkx.DiGraph:
149
163
  parameters=edge.__str__().replace(')', '\n)'),
150
164
  )
151
165
  except Exception as e:
152
- logger.error(f"Failed to add edge {edge}: {e}")
166
+ logger.error(f'Failed to add edge {edge}: {e}')
153
167
 
154
168
  return graph
155
169
 
170
+
156
171
  def make_cytoscape_elements(graph: networkx.DiGraph) -> List[Dict[str, Any]]:
157
172
  """Convert NetworkX graph to Cytoscape elements"""
158
173
  elements = []
@@ -160,146 +175,213 @@ def make_cytoscape_elements(graph: networkx.DiGraph) -> List[Dict[str, Any]]:
160
175
  # Add nodes
161
176
  for node_id in graph.nodes():
162
177
  node_data = graph.nodes[node_id]
163
- elements.append({
164
- 'data': {
165
- 'id': node_id,
166
- 'label': node_id,
167
- 'color': node_data.get('color', '#7F8C8D'),
168
- 'shape': node_data.get('shape', 'rectangle'),
169
- 'element_type': node_data.get('element_type', 'Other'),
170
- 'parameters': node_data.get('parameters', ''),
178
+ elements.append(
179
+ {
180
+ 'data': {
181
+ 'id': node_id,
182
+ 'label': node_id,
183
+ 'color': node_data.get('color', '#7F8C8D'),
184
+ 'shape': node_data.get('shape', 'rectangle'),
185
+ 'element_type': node_data.get('element_type', 'Other'),
186
+ 'parameters': node_data.get('parameters', ''),
187
+ }
171
188
  }
172
- })
189
+ )
173
190
 
174
191
  # Add edges
175
192
  for u, v in graph.edges():
176
193
  edge_data = graph.edges[u, v]
177
- elements.append({
178
- 'data': {
179
- 'source': u,
180
- 'target': v,
181
- 'id': f'{u}-{v}',
182
- 'label': edge_data.get('label', ''),
183
- 'parameters': edge_data.get('parameters', ''),
194
+ elements.append(
195
+ {
196
+ 'data': {
197
+ 'source': u,
198
+ 'target': v,
199
+ 'id': f'{u}-{v}',
200
+ 'label': edge_data.get('label', ''),
201
+ 'parameters': edge_data.get('parameters', ''),
202
+ }
184
203
  }
185
- })
204
+ )
186
205
 
187
206
  return elements
188
207
 
208
+
189
209
  def create_color_picker_input(label: str, input_id: str, default_color: str):
190
210
  """Create a compact color picker with DAQ ColorPicker"""
191
- return html.Div([
192
- html.Label(label, style={
193
- 'color': 'white', 'font-size': '12px', 'margin-bottom': '5px',
194
- 'display': 'block'
195
- }),
196
- daq.ColorPicker(
197
- id=input_id,
198
- label="",
199
- value={'hex': default_color},
200
- size=200,
201
- theme={'dark': True},
202
- style={'margin-bottom': '10px'}
203
- ),
204
- ])
211
+ return html.Div(
212
+ [
213
+ html.Label(
214
+ label, style={'color': 'white', 'font-size': '12px', 'margin-bottom': '5px', 'display': 'block'}
215
+ ),
216
+ daq.ColorPicker(
217
+ id=input_id,
218
+ label='',
219
+ value={'hex': default_color},
220
+ size=200,
221
+ theme={'dark': True},
222
+ style={'margin-bottom': '10px'},
223
+ ),
224
+ ]
225
+ )
226
+
205
227
 
206
228
  def create_style_section(title: str, children: List):
207
229
  """Create a collapsible section for organizing controls"""
208
- return html.Div([
209
- html.H4(title, style={
210
- 'color': 'white', 'margin-bottom': '10px',
211
- 'border-bottom': '2px solid #3498DB', 'padding-bottom': '5px',
212
- }),
213
- html.Div(children, style={'margin-bottom': '20px'}),
214
- ])
230
+ return html.Div(
231
+ [
232
+ html.H4(
233
+ title,
234
+ style={
235
+ 'color': 'white',
236
+ 'margin-bottom': '10px',
237
+ 'border-bottom': '2px solid #3498DB',
238
+ 'padding-bottom': '5px',
239
+ },
240
+ ),
241
+ html.Div(children, style={'margin-bottom': '20px'}),
242
+ ]
243
+ )
244
+
215
245
 
216
246
  def create_sidebar():
217
247
  """Create the main sidebar with improved organization"""
218
- return html.Div([
219
- html.Div([
220
- html.H3('Style Controls', style={
221
- 'color': 'white', 'margin-bottom': '20px', 'text-align': 'center',
222
- 'border-bottom': '3px solid #9B59B6', 'padding-bottom': '10px',
223
- }),
224
-
225
- # Layout Section
226
- create_style_section('Layout', [
227
- dcc.Dropdown(
228
- id='layout-dropdown',
229
- options=[
230
- {'label': 'Klay (horizontal)', 'value': 'klay'},
231
- {'label': 'Dagre (vertical)', 'value': 'dagre'},
232
- {'label': 'Breadthfirst', 'value': 'breadthfirst'},
233
- {'label': 'Cose (force-directed)', 'value': 'cose'},
234
- {'label': 'Grid', 'value': 'grid'},
235
- {'label': 'Circle', 'value': 'circle'},
236
- ],
237
- value='klay',
238
- clearable=False,
239
- style={'width': '100%'},
240
- ),
241
- ]),
242
-
243
- # Color Scheme Section
244
- create_style_section('Color Scheme', [
245
- dcc.Dropdown(
246
- id='color-scheme-dropdown',
247
- options=[{'label': k, 'value': k} for k in VisualizationConfig.COLOR_PRESETS.keys()],
248
- value='Default',
249
- style={'width': '100%', 'margin-bottom': '10px'},
250
- ),
251
- ]),
252
-
253
- # Color Pickers Section
254
- create_style_section('Custom Colors', [
255
- create_color_picker_input('Bus', 'bus-color-picker', '#7F8C8D'),
256
- create_color_picker_input('Source', 'source-color-picker', '#F1C40F'),
257
- create_color_picker_input('Sink', 'sink-color-picker', '#F1C40F'),
258
- create_color_picker_input('Storage', 'storage-color-picker', '#2980B9'),
259
- create_color_picker_input('Converter', 'converter-color-picker', '#D35400'),
260
- create_color_picker_input('Edge', 'edge-color-picker', '#808080'),
261
- ]),
262
-
263
- # Node Settings
264
- create_style_section('Node Settings', [
265
- html.Label('Size', style={'color': 'white', 'font-size': '12px'}),
266
- dcc.Slider(
267
- id='node-size-slider', min=50, max=150, step=10, value=90,
268
- marks={i: {'label': str(i), 'style': {'color': 'white', 'font-size': '10px'}}
269
- for i in range(50, 151, 25)},
270
- tooltip={'placement': 'bottom', 'always_visible': True},
271
- ),
272
- html.Br(),
273
- html.Label('Font Size', style={'color': 'white', 'font-size': '12px'}),
274
- dcc.Slider(
275
- id='font-size-slider', min=8, max=20, step=1, value=10,
276
- marks={i: {'label': str(i), 'style': {'color': 'white', 'font-size': '10px'}}
277
- for i in range(8, 21, 2)},
278
- tooltip={'placement': 'bottom', 'always_visible': True},
279
- ),
280
- ]),
281
-
282
- # Reset Button
283
- html.Div([
284
- html.Button('Reset to Defaults', id='reset-btn', n_clicks=0, style={
285
- 'width': '100%', 'background-color': '#E74C3C', 'color': 'white',
286
- 'border': 'none', 'padding': '10px', 'border-radius': '5px',
287
- 'cursor': 'pointer', 'margin-top': '20px',
288
- }),
289
- ]),
290
- ], id='sidebar-content', style={
291
- 'width': '280px', 'height': '100vh', 'background-color': '#2C3E50',
292
- 'padding': '20px', 'position': 'fixed', 'left': '0', 'top': '0',
293
- 'overflow-y': 'auto', 'border-right': '3px solid #34495E',
294
- 'box-shadow': '2px 0 5px rgba(0,0,0,0.1)', 'z-index': '999',
295
- 'transform': 'translateX(-100%)', 'transition': 'transform 0.3s ease',
296
- })
297
- ])
248
+ return html.Div(
249
+ [
250
+ html.Div(
251
+ [
252
+ html.H3(
253
+ 'Style Controls',
254
+ style={
255
+ 'color': 'white',
256
+ 'margin-bottom': '20px',
257
+ 'text-align': 'center',
258
+ 'border-bottom': '3px solid #9B59B6',
259
+ 'padding-bottom': '10px',
260
+ },
261
+ ),
262
+ # Layout Section
263
+ create_style_section(
264
+ 'Layout',
265
+ [
266
+ dcc.Dropdown(
267
+ id='layout-dropdown',
268
+ options=[
269
+ {'label': 'Klay (horizontal)', 'value': 'klay'},
270
+ {'label': 'Dagre (vertical)', 'value': 'dagre'},
271
+ {'label': 'Breadthfirst', 'value': 'breadthfirst'},
272
+ {'label': 'Cose (force-directed)', 'value': 'cose'},
273
+ {'label': 'Grid', 'value': 'grid'},
274
+ {'label': 'Circle', 'value': 'circle'},
275
+ ],
276
+ value='klay',
277
+ clearable=False,
278
+ style={'width': '100%'},
279
+ ),
280
+ ],
281
+ ),
282
+ # Color Scheme Section
283
+ create_style_section(
284
+ 'Color Scheme',
285
+ [
286
+ dcc.Dropdown(
287
+ id='color-scheme-dropdown',
288
+ options=[{'label': k, 'value': k} for k in VisualizationConfig.COLOR_PRESETS.keys()],
289
+ value='Default',
290
+ style={'width': '100%', 'margin-bottom': '10px'},
291
+ ),
292
+ ],
293
+ ),
294
+ # Color Pickers Section
295
+ create_style_section(
296
+ 'Custom Colors',
297
+ [
298
+ create_color_picker_input('Bus', 'bus-color-picker', '#7F8C8D'),
299
+ create_color_picker_input('Source', 'source-color-picker', '#F1C40F'),
300
+ create_color_picker_input('Sink', 'sink-color-picker', '#F1C40F'),
301
+ create_color_picker_input('Storage', 'storage-color-picker', '#2980B9'),
302
+ create_color_picker_input('Converter', 'converter-color-picker', '#D35400'),
303
+ create_color_picker_input('Edge', 'edge-color-picker', '#808080'),
304
+ ],
305
+ ),
306
+ # Node Settings
307
+ create_style_section(
308
+ 'Node Settings',
309
+ [
310
+ html.Label('Size', style={'color': 'white', 'font-size': '12px'}),
311
+ dcc.Slider(
312
+ id='node-size-slider',
313
+ min=50,
314
+ max=150,
315
+ step=10,
316
+ value=90,
317
+ marks={
318
+ i: {'label': str(i), 'style': {'color': 'white', 'font-size': '10px'}}
319
+ for i in range(50, 151, 25)
320
+ },
321
+ tooltip={'placement': 'bottom', 'always_visible': True},
322
+ ),
323
+ html.Br(),
324
+ html.Label('Font Size', style={'color': 'white', 'font-size': '12px'}),
325
+ dcc.Slider(
326
+ id='font-size-slider',
327
+ min=8,
328
+ max=20,
329
+ step=1,
330
+ value=10,
331
+ marks={
332
+ i: {'label': str(i), 'style': {'color': 'white', 'font-size': '10px'}}
333
+ for i in range(8, 21, 2)
334
+ },
335
+ tooltip={'placement': 'bottom', 'always_visible': True},
336
+ ),
337
+ ],
338
+ ),
339
+ # Reset Button
340
+ html.Div(
341
+ [
342
+ html.Button(
343
+ 'Reset to Defaults',
344
+ id='reset-btn',
345
+ n_clicks=0,
346
+ style={
347
+ 'width': '100%',
348
+ 'background-color': '#E74C3C',
349
+ 'color': 'white',
350
+ 'border': 'none',
351
+ 'padding': '10px',
352
+ 'border-radius': '5px',
353
+ 'cursor': 'pointer',
354
+ 'margin-top': '20px',
355
+ },
356
+ ),
357
+ ]
358
+ ),
359
+ ],
360
+ id='sidebar-content',
361
+ style={
362
+ 'width': '280px',
363
+ 'height': '100vh',
364
+ 'background-color': '#2C3E50',
365
+ 'padding': '20px',
366
+ 'position': 'fixed',
367
+ 'left': '0',
368
+ 'top': '0',
369
+ 'overflow-y': 'auto',
370
+ 'border-right': '3px solid #34495E',
371
+ 'box-shadow': '2px 0 5px rgba(0,0,0,0.1)',
372
+ 'z-index': '999',
373
+ 'transform': 'translateX(-100%)',
374
+ 'transition': 'transform 0.3s ease',
375
+ },
376
+ )
377
+ ]
378
+ )
379
+
298
380
 
299
381
  def shownetwork(graph: networkx.DiGraph):
300
382
  """Main function to create and run the network visualization"""
301
383
  if not DASH_CYTOSCAPE_AVAILABLE:
302
- raise ImportError(f"Required packages not available: {VISUALIZATION_ERROR}")
384
+ raise ImportError(f'Required packages not available: {VISUALIZATION_ERROR}')
303
385
 
304
386
  app = Dash(__name__, suppress_callback_exceptions=True)
305
387
 
@@ -310,72 +392,118 @@ def shownetwork(graph: networkx.DiGraph):
310
392
  elements = make_cytoscape_elements(graph)
311
393
 
312
394
  # App Layout
313
- app.layout = html.Div([
314
- # Toggle button
315
- html.Button('☰', id='toggle-sidebar', n_clicks=0, style={
316
- 'position': 'fixed', 'top': '20px', 'left': '20px', 'z-index': '1000',
317
- 'background-color': '#3498DB', 'color': 'white', 'border': 'none',
318
- 'padding': '10px 15px', 'border-radius': '5px', 'cursor': 'pointer',
319
- 'font-size': '18px', 'box-shadow': '0 2px 5px rgba(0,0,0,0.3)',
320
- }),
321
-
322
- # Data storage
323
- dcc.Store(id='elements-store', data=elements),
324
-
325
- # Sidebar
326
- create_sidebar(),
327
-
328
- # Main content
329
- html.Div([
330
- # Header
331
- html.Div([
332
- html.H2('Network Visualization', style={
333
- 'color': 'white', 'margin': '0', 'text-align': 'center'
334
- }),
335
- html.Button('Export PNG', id='export-btn', n_clicks=0, style={
336
- 'position': 'absolute', 'right': '20px', 'top': '15px',
337
- 'background-color': '#27AE60', 'color': 'white', 'border': 'none',
338
- 'padding': '10px 20px', 'border-radius': '5px', 'cursor': 'pointer',
339
- }),
340
- ], style={
341
- 'background-color': '#34495E', 'padding': '15px 20px',
342
- 'position': 'relative', 'border-bottom': '2px solid #3498DB',
343
- }),
344
-
345
- # Cytoscape graph
346
- cyto.Cytoscape(
347
- id='cytoscape',
348
- layout={'name': 'klay'},
349
- style={'width': '100%', 'height': '70vh'},
350
- elements=elements,
351
- stylesheet=VisualizationConfig.DEFAULT_STYLESHEET,
395
+ app.layout = html.Div(
396
+ [
397
+ # Toggle button
398
+ html.Button(
399
+ '',
400
+ id='toggle-sidebar',
401
+ n_clicks=0,
402
+ style={
403
+ 'position': 'fixed',
404
+ 'top': '20px',
405
+ 'left': '20px',
406
+ 'z-index': '1000',
407
+ 'background-color': '#3498DB',
408
+ 'color': 'white',
409
+ 'border': 'none',
410
+ 'padding': '10px 15px',
411
+ 'border-radius': '5px',
412
+ 'cursor': 'pointer',
413
+ 'font-size': '18px',
414
+ 'box-shadow': '0 2px 5px rgba(0,0,0,0.3)',
415
+ },
352
416
  ),
353
-
354
- # Info panel
355
- html.Div([
356
- html.H4('Element Information', style={
357
- 'color': 'white', 'margin': '0 0 10px 0',
358
- 'border-bottom': '2px solid #3498DB', 'padding-bottom': '5px',
359
- }),
360
- html.Div(id='info-panel', children=[
361
- html.P('Click on a node or edge to see details.',
362
- style={'color': '#95A5A6', 'font-style': 'italic'})
363
- ]),
364
- ], style={
365
- 'background-color': '#2C3E50', 'padding': '15px',
366
- 'height': '25vh', 'overflow-y': 'auto',
367
- 'border-top': '2px solid #34495E',
368
- }),
369
- ], id='main-content', style={
370
- 'margin-left': '0', 'background-color': '#1A252F',
371
- 'min-height': '100vh', 'transition': 'margin-left 0.3s ease',
372
- }),
373
- ])
417
+ # Data storage
418
+ dcc.Store(id='elements-store', data=elements),
419
+ # Sidebar
420
+ create_sidebar(),
421
+ # Main content
422
+ html.Div(
423
+ [
424
+ # Header
425
+ html.Div(
426
+ [
427
+ html.H2(
428
+ 'Network Visualization', style={'color': 'white', 'margin': '0', 'text-align': 'center'}
429
+ ),
430
+ html.Button(
431
+ 'Export PNG',
432
+ id='export-btn',
433
+ n_clicks=0,
434
+ style={
435
+ 'position': 'absolute',
436
+ 'right': '20px',
437
+ 'top': '15px',
438
+ 'background-color': '#27AE60',
439
+ 'color': 'white',
440
+ 'border': 'none',
441
+ 'padding': '10px 20px',
442
+ 'border-radius': '5px',
443
+ 'cursor': 'pointer',
444
+ },
445
+ ),
446
+ ],
447
+ style={
448
+ 'background-color': '#34495E',
449
+ 'padding': '15px 20px',
450
+ 'position': 'relative',
451
+ 'border-bottom': '2px solid #3498DB',
452
+ },
453
+ ),
454
+ # Cytoscape graph
455
+ cyto.Cytoscape(
456
+ id='cytoscape',
457
+ layout={'name': 'klay'},
458
+ style={'width': '100%', 'height': '70vh'},
459
+ elements=elements,
460
+ stylesheet=VisualizationConfig.DEFAULT_STYLESHEET,
461
+ ),
462
+ # Info panel
463
+ html.Div(
464
+ [
465
+ html.H4(
466
+ 'Element Information',
467
+ style={
468
+ 'color': 'white',
469
+ 'margin': '0 0 10px 0',
470
+ 'border-bottom': '2px solid #3498DB',
471
+ 'padding-bottom': '5px',
472
+ },
473
+ ),
474
+ html.Div(
475
+ id='info-panel',
476
+ children=[
477
+ html.P(
478
+ 'Click on a node or edge to see details.',
479
+ style={'color': '#95A5A6', 'font-style': 'italic'},
480
+ )
481
+ ],
482
+ ),
483
+ ],
484
+ style={
485
+ 'background-color': '#2C3E50',
486
+ 'padding': '15px',
487
+ 'height': '25vh',
488
+ 'overflow-y': 'auto',
489
+ 'border-top': '2px solid #34495E',
490
+ },
491
+ ),
492
+ ],
493
+ id='main-content',
494
+ style={
495
+ 'margin-left': '0',
496
+ 'background-color': '#1A252F',
497
+ 'min-height': '100vh',
498
+ 'transition': 'margin-left 0.3s ease',
499
+ },
500
+ ),
501
+ ]
502
+ )
374
503
 
375
504
  # Callbacks
376
505
  @app.callback(
377
- [Output('sidebar-content', 'style'), Output('main-content', 'style')],
378
- [Input('toggle-sidebar', 'n_clicks')]
506
+ [Output('sidebar-content', 'style'), Output('main-content', 'style')], [Input('toggle-sidebar', 'n_clicks')]
379
507
  )
380
508
  def toggle_sidebar(n_clicks):
381
509
  is_open = (n_clicks or 0) % 2 == 1
@@ -383,16 +511,26 @@ def shownetwork(graph: networkx.DiGraph):
383
511
  main_margin = '280px' if is_open else '0'
384
512
 
385
513
  sidebar_style = {
386
- 'width': '280px', 'height': '100vh', 'background-color': '#2C3E50',
387
- 'padding': '20px', 'position': 'fixed', 'left': '0', 'top': '0',
388
- 'overflow-y': 'auto', 'border-right': '3px solid #34495E',
389
- 'box-shadow': '2px 0 5px rgba(0,0,0,0.1)', 'z-index': '999',
390
- 'transform': sidebar_transform, 'transition': 'transform 0.3s ease',
514
+ 'width': '280px',
515
+ 'height': '100vh',
516
+ 'background-color': '#2C3E50',
517
+ 'padding': '20px',
518
+ 'position': 'fixed',
519
+ 'left': '0',
520
+ 'top': '0',
521
+ 'overflow-y': 'auto',
522
+ 'border-right': '3px solid #34495E',
523
+ 'box-shadow': '2px 0 5px rgba(0,0,0,0.1)',
524
+ 'z-index': '999',
525
+ 'transform': sidebar_transform,
526
+ 'transition': 'transform 0.3s ease',
391
527
  }
392
528
 
393
529
  main_style = {
394
- 'margin-left': main_margin, 'background-color': '#1A252F',
395
- 'min-height': '100vh', 'transition': 'margin-left 0.3s ease',
530
+ 'margin-left': main_margin,
531
+ 'background-color': '#1A252F',
532
+ 'min-height': '100vh',
533
+ 'transition': 'margin-left 0.3s ease',
396
534
  }
397
535
 
398
536
  return sidebar_style, main_style
@@ -410,17 +548,25 @@ def shownetwork(graph: networkx.DiGraph):
410
548
  Input('node-size-slider', 'value'),
411
549
  Input('font-size-slider', 'value'),
412
550
  ],
413
- [State('elements-store', 'data')]
551
+ [State('elements-store', 'data')],
414
552
  )
415
- def update_visualization(color_scheme, bus_color, source_color, sink_color,
416
- storage_color, converter_color, edge_color,
417
- node_size, font_size, stored_elements):
553
+ def update_visualization(
554
+ color_scheme,
555
+ bus_color,
556
+ source_color,
557
+ sink_color,
558
+ storage_color,
559
+ converter_color,
560
+ edge_color,
561
+ node_size,
562
+ font_size,
563
+ stored_elements,
564
+ ):
418
565
  if not stored_elements:
419
566
  return no_update, no_update
420
567
 
421
568
  # Determine colors to use
422
- if any(picker for picker in [bus_color, source_color, sink_color,
423
- storage_color, converter_color, edge_color]):
569
+ if any(picker for picker in [bus_color, source_color, sink_color, storage_color, converter_color, edge_color]):
424
570
  # Use custom colors from pickers
425
571
  colors = {
426
572
  'Bus': bus_color.get('hex') if bus_color else '#7F8C8D',
@@ -432,8 +578,7 @@ def shownetwork(graph: networkx.DiGraph):
432
578
  }
433
579
  else:
434
580
  # Use preset scheme
435
- colors = VisualizationConfig.COLOR_PRESETS.get(color_scheme,
436
- VisualizationConfig.DEFAULT_COLORS)
581
+ colors = VisualizationConfig.COLOR_PRESETS.get(color_scheme, VisualizationConfig.DEFAULT_COLORS)
437
582
 
438
583
  # Update element colors
439
584
  updated_elements = []
@@ -497,44 +642,42 @@ def shownetwork(graph: networkx.DiGraph):
497
642
  return updated_elements, stylesheet
498
643
 
499
644
  @app.callback(
500
- Output('info-panel', 'children'),
501
- [Input('cytoscape', 'tapNodeData'), Input('cytoscape', 'tapEdgeData')]
645
+ Output('info-panel', 'children'), [Input('cytoscape', 'tapNodeData'), Input('cytoscape', 'tapEdgeData')]
502
646
  )
503
647
  def display_element_info(node_data, edge_data):
504
648
  ctx = callback_context
505
649
  if not ctx.triggered:
506
- return [html.P('Click on a node or edge to see details.',
507
- style={'color': '#95A5A6', 'font-style': 'italic'})]
650
+ return [
651
+ html.P('Click on a node or edge to see details.', style={'color': '#95A5A6', 'font-style': 'italic'})
652
+ ]
508
653
 
509
654
  # Determine what was clicked
510
655
  if ctx.triggered[0]['prop_id'] == 'cytoscape.tapNodeData' and node_data:
511
656
  return [
512
- html.H5(f"Node: {node_data.get('label', 'Unknown')}",
513
- style={'color': 'white', 'margin-bottom': '10px'}),
514
- html.P(f"Type: {node_data.get('element_type', 'Unknown')}",
515
- style={'color': '#BDC3C7'}),
516
- html.Pre(node_data.get('parameters', 'No parameters'),
517
- style={'color': '#BDC3C7', 'font-size': '11px',
518
- 'white-space': 'pre-wrap'})
657
+ html.H5(
658
+ f'Node: {node_data.get("label", "Unknown")}', style={'color': 'white', 'margin-bottom': '10px'}
659
+ ),
660
+ html.P(f'Type: {node_data.get("element_type", "Unknown")}', style={'color': '#BDC3C7'}),
661
+ html.Pre(
662
+ node_data.get('parameters', 'No parameters'),
663
+ style={'color': '#BDC3C7', 'font-size': '11px', 'white-space': 'pre-wrap'},
664
+ ),
519
665
  ]
520
666
  elif ctx.triggered[0]['prop_id'] == 'cytoscape.tapEdgeData' and edge_data:
521
667
  return [
522
- html.H5(f"Edge: {edge_data.get('label', 'Unknown')}",
523
- style={'color': 'white', 'margin-bottom': '10px'}),
524
- html.P(f"{edge_data.get('source', '')} → {edge_data.get('target', '')}",
525
- style={'color': '#E67E22'}),
526
- html.Pre(edge_data.get('parameters', 'No parameters'),
527
- style={'color': '#BDC3C7', 'font-size': '11px',
528
- 'white-space': 'pre-wrap'})
668
+ html.H5(
669
+ f'Edge: {edge_data.get("label", "Unknown")}', style={'color': 'white', 'margin-bottom': '10px'}
670
+ ),
671
+ html.P(f'{edge_data.get("source", "")} → {edge_data.get("target", "")}', style={'color': '#E67E22'}),
672
+ html.Pre(
673
+ edge_data.get('parameters', 'No parameters'),
674
+ style={'color': '#BDC3C7', 'font-size': '11px', 'white-space': 'pre-wrap'},
675
+ ),
529
676
  ]
530
677
 
531
- return [html.P('Click on a node or edge to see details.',
532
- style={'color': '#95A5A6', 'font-style': 'italic'})]
678
+ return [html.P('Click on a node or edge to see details.', style={'color': '#95A5A6', 'font-style': 'italic'})]
533
679
 
534
- @app.callback(
535
- Output('cytoscape', 'layout'),
536
- Input('layout-dropdown', 'value')
537
- )
680
+ @app.callback(Output('cytoscape', 'layout'), Input('layout-dropdown', 'value'))
538
681
  def update_layout(selected_layout):
539
682
  return {'name': selected_layout}
540
683
 
@@ -552,7 +695,7 @@ def shownetwork(graph: networkx.DiGraph):
552
695
  Output('font-size-slider', 'value'),
553
696
  Output('layout-dropdown', 'value'),
554
697
  ],
555
- [Input('reset-btn', 'n_clicks')]
698
+ [Input('reset-btn', 'n_clicks')],
556
699
  )
557
700
  def reset_controls(n_clicks):
558
701
  if n_clicks and n_clicks > 0: