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.
- docs/examples/00-Minimal Example.md +1 -1
- docs/examples/01-Basic Example.md +1 -1
- docs/examples/02-Complex Example.md +1 -1
- docs/examples/index.md +1 -1
- docs/faq/contribute.md +26 -14
- docs/faq/index.md +1 -1
- docs/javascripts/mathjax.js +1 -1
- docs/user-guide/Mathematical Notation/Bus.md +1 -1
- docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +13 -13
- docs/user-guide/Mathematical Notation/Flow.md +1 -1
- docs/user-guide/Mathematical Notation/LinearConverter.md +2 -2
- docs/user-guide/Mathematical Notation/Piecewise.md +1 -1
- docs/user-guide/Mathematical Notation/Storage.md +1 -1
- docs/user-guide/Mathematical Notation/index.md +1 -1
- docs/user-guide/Mathematical Notation/others.md +1 -1
- docs/user-guide/index.md +2 -2
- flixopt/__init__.py +4 -0
- flixopt/calculation.py +3 -7
- flixopt/components.py +14 -4
- flixopt/core.py +10 -6
- flixopt/effects.py +2 -1
- flixopt/elements.py +5 -3
- flixopt/features.py +17 -13
- flixopt/flow_system.py +4 -3
- flixopt/network_app.py +371 -228
- flixopt/structure.py +1 -3
- {flixopt-2.1.6.dist-info → flixopt-2.1.7.dist-info}/METADATA +22 -15
- flixopt-2.1.7.dist-info/RECORD +54 -0
- scripts/extract_release_notes.py +5 -5
- flixopt-2.1.6.dist-info/RECORD +0 -54
- {flixopt-2.1.6.dist-info → flixopt-2.1.7.dist-info}/WHEEL +0 -0
- {flixopt-2.1.6.dist-info → flixopt-2.1.7.dist-info}/licenses/LICENSE +0 -0
- {flixopt-2.1.6.dist-info → flixopt-2.1.7.dist-info}/top_level.txt +0 -0
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',
|
|
43
|
-
'
|
|
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',
|
|
47
|
-
'
|
|
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',
|
|
51
|
-
'
|
|
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
|
|
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
|
-
|
|
165
|
-
'
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
179
|
-
'
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
220
|
-
html.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
html.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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',
|
|
387
|
-
'
|
|
388
|
-
'
|
|
389
|
-
'
|
|
390
|
-
'
|
|
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,
|
|
395
|
-
'
|
|
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(
|
|
416
|
-
|
|
417
|
-
|
|
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 [
|
|
507
|
-
|
|
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(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
html.Pre(
|
|
517
|
-
|
|
518
|
-
|
|
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(
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
html.Pre(
|
|
527
|
-
|
|
528
|
-
|
|
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:
|