Victarry commited on
Commit
7c49b8c
·
1 Parent(s): 6b14326

Update UI.

Browse files
Files changed (3) hide show
  1. app.py +575 -250
  2. assets/custom.css +977 -0
  3. src/visualizer.py +5 -6
app.py CHANGED
@@ -23,7 +23,49 @@ STRATEGIES = {
23
  "dualpipe": generate_dualpipe_schedule,
24
  }
25
 
26
- app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.BOOTSTRAP], suppress_callback_exceptions=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  app.title = "Pipeline Parallelism Schedule Visualizer"
28
 
29
  # Initial default values
@@ -42,214 +84,345 @@ default_values = {
42
  }
43
 
44
  # Define input groups using dbc components
45
- card_style = {"marginBottom": "24px"}
46
-
47
- basic_params_card = dbc.Card(
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  dbc.CardBody([
49
- html.H5("Basic Parameters", className="card-title mb-4"),
50
- html.Div([
51
- dbc.Label("Number of Devices (GPUs)", html_for='num_devices', className="form-label"),
52
- dbc.Input(id='num_devices', type='number', value=default_values["num_devices"], min=1, step=1, required=True),
53
- dbc.FormFeedback("Please provide a positive integer for the number of devices.", type="invalid", id="feedback-num_devices"),
54
- ], className="mb-3"),
55
- html.Div([
56
- dbc.Label("Number of Stages (Model Chunks)", html_for='num_stages', className="form-label"),
57
- dbc.Input(id='num_stages', type='number', value=default_values["num_stages"], min=1, step=1, required=True),
58
- dbc.FormFeedback("Please provide a positive integer for the number of stages.", type="invalid", id="feedback-num_stages"),
59
- ], className="mb-3"),
60
- html.Div([
61
- dbc.Label("Number of Microbatches", html_for='num_batches', className="form-label"),
62
- dbc.Input(id='num_batches', type='number', value=default_values["num_batches"], min=1, step=1, required=True),
63
- dbc.FormFeedback("Please provide a positive integer for the number of microbatches.", type="invalid", id="feedback-num_batches"),
 
 
 
 
 
 
 
 
 
 
64
  ], className="mb-3"),
65
- ]),
66
- style=card_style
67
- )
68
-
69
- scheduling_params_card = dbc.Card(
 
 
 
 
 
 
 
 
 
 
 
 
70
  dbc.CardBody([
71
- html.H5("Scheduling Strategy", className="card-title mb-4"),
72
- dbc.ButtonGroup(
73
- [
74
- dbc.Button(
75
- strategy,
76
- id={"type": "strategy-button", "index": strategy},
77
- color="secondary",
78
- outline=True,
79
- active=strategy in default_values["strategy"],
80
- className="me-1"
81
- )
82
- for strategy in STRATEGIES.keys()
83
- ],
84
- className="d-flex flex-wrap"
85
- ),
 
 
 
 
 
 
 
86
  dcc.Store(id='selected-strategies-store', data=default_values["strategy"]),
87
  html.Div(id='strategy-selection-feedback', className='invalid-feedback d-block mt-2')
88
- ]),
89
- style=card_style
90
- )
91
 
92
- timing_params_card = dbc.Card(
 
93
  dbc.CardBody([
94
- html.H5("Operation Timing (ms)", className="card-title mb-4"),
95
- html.Div([
96
- html.Div([
97
- dbc.Label("P2P Latency", html_for='p2p_latency', className="form-label d-inline-block me-1"),
98
- html.I(className="bi bi-info-circle", id="tooltip-target-p2p", style={"cursor": "pointer"})
99
- ]),
100
- dbc.Input(id='p2p_latency', type='number', value=default_values["p2p_latency"], min=0, step=0.01, required=True),
101
- dbc.FormFeedback("P2P latency must be a number >= 0.", type="invalid", id="feedback-p2p_latency"),
102
- dbc.Tooltip(
103
- "Time (ms) for point-to-point communication between adjacent devices.",
104
- target="tooltip-target-p2p",
105
- placement="right"
106
- )
107
- ], className="mb-3"),
108
- html.Div([
109
- html.Div([
110
- dbc.Label("Forward Operation Time", html_for='op_time_forward', className="form-label d-inline-block me-1"),
111
- html.I(className="bi bi-info-circle", id="tooltip-target-fwd", style={"cursor": "pointer"})
112
- ]),
113
- dbc.Input(id='op_time_forward', type='number', value=default_values["op_time_forward"], min=0.01, step=0.01, required=True),
114
- dbc.FormFeedback("Forward time must be a number > 0.", type="invalid", id="feedback-op_time_forward"),
115
- dbc.Tooltip(
116
- "Time (ms) for a single forward pass of one microbatch through one stage.",
117
- target="tooltip-target-fwd",
118
- placement="right"
119
- )
120
- ], className="mb-3"),
121
- html.Div([
122
- html.Div([
123
- dbc.Label("Backward (Combined)", html_for='op_time_backward', className="form-label d-inline-block me-1"),
124
- html.I(className="bi bi-info-circle", id="tooltip-target-bwd", style={"cursor": "pointer"})
125
- ]),
126
- dbc.Input(id='op_time_backward', type='number', value=default_values["op_time_backward"], min=0.01, step=0.01),
127
- dbc.FormText("Used when strategy does NOT require split backward."),
128
- dbc.FormFeedback("Backward time must be > 0 if specified.", type="invalid", id="feedback-op_time_backward"),
129
- dbc.Tooltip(
130
- "Time (ms) for a combined backward pass (data gradient + weight gradient) of one microbatch through one stage.",
131
- target="tooltip-target-bwd",
132
- placement="right"
133
- )
134
- ], className="mb-3"),
135
-
136
- # --- Collapsible Advanced Options (Item 3) ---
137
- html.Hr(className="my-3"),
138
- dbc.Switch(
139
- id="advanced-timing-switch",
140
- label="Show Advanced Timing Options",
141
- value=False,
142
- className="mb-3"
143
- ),
144
- dbc.Collapse(
145
- id="advanced-timing-collapse",
146
- is_open=False,
147
- children=[
148
- html.Div([
149
- html.Div([
150
- dbc.Label("Backward D (Data Grad)", html_for='op_time_backward_d', className="form-label d-inline-block me-1"),
151
- html.I(className="bi bi-info-circle", id="tooltip-target-bwd-d", style={"cursor": "pointer"})
152
- ]),
153
- dbc.Input(id='op_time_backward_d', type='number', value=default_values["op_time_backward_d"], min=0.01, step=0.01),
154
- dbc.FormText("Used when strategy requires split backward (e.g., ZB-1P, DualPipe)."),
155
- dbc.FormFeedback("Backward D time must be > 0 if specified.", type="invalid", id="feedback-op_time_backward_d"),
156
- dbc.Tooltip(
157
- "Time (ms) for the data gradient part of the backward pass.",
158
- target="tooltip-target-bwd-d",
159
- placement="right"
160
- )
161
- ], className="mb-3"),
162
- html.Div([
163
- html.Div([
164
- dbc.Label("Backward W (Weight Grad)", html_for='op_time_backward_w', className="form-label d-inline-block me-1"),
165
- html.I(className="bi bi-info-circle", id="tooltip-target-bwd-w", style={"cursor": "pointer"})
166
- ]),
167
- dbc.Input(id='op_time_backward_w', type='number', value=default_values["op_time_backward_w"], min=0.01, step=0.01),
168
- dbc.FormText("Used when strategy requires split backward (e.g., ZB-1P, DualPipe)."),
169
- dbc.FormFeedback("Backward W time must be > 0 if specified.", type="invalid", id="feedback-op_time_backward_w"),
170
- dbc.Tooltip(
171
- "Time (ms) for the weight gradient part of the backward pass.",
172
- target="tooltip-target-bwd-w",
173
- placement="right"
174
- )
175
- ], className="mb-3"),
176
  html.Div([
177
- html.Div([
178
- dbc.Label("Overlapped Forward+Backward", html_for='op_time_overlapped_fwd_bwd', className="form-label d-inline-block me-1"),
179
- html.I(className="bi bi-info-circle", id="tooltip-target-overlap", style={"cursor": "pointer"})
180
- ]),
181
- dbc.Input(id='op_time_overlapped_fwd_bwd', type='number', placeholder="Defaults to Fwd + Bwd", min=0.01, step=0.01, value=default_values["op_time_overlapped_fwd_bwd"]),
182
- dbc.FormText("Specify if Forward and Backward ops overlap completely."),
183
- dbc.FormFeedback("Overlapped time must be > 0 if specified.", type="invalid", id="feedback-op_time_overlapped_fwd_bwd"),
 
184
  dbc.Tooltip(
185
- "Optional: Specify a single time (ms) if the forward and backward passes for a microbatch can be fully overlapped within the same stage execution slot.",
186
- target="tooltip-target-overlap",
187
  placement="right"
188
  )
189
- ], className="mb-3"),
 
 
 
190
  html.Div([
191
- html.Div([
192
- dbc.Label("Microbatch Group Size per VP Stage", html_for='microbatch_group_size_per_vp_stage', className="form-label d-inline-block me-1"),
193
- html.I(className="bi bi-info-circle", id="tooltip-target-microbatch-group", style={"cursor": "pointer"})
194
- ]),
195
- dbc.Input(id='microbatch_group_size_per_vp_stage', type='number', placeholder=f"Defaults to num_devices", min=1, step=1, value=default_values["microbatch_group_size_per_vp_stage"]),
196
- dbc.FormText("Used for interleave strategies (1f1b_interleave, 1f1b_interleave_overlap)."),
197
- dbc.FormFeedback("Microbatch group size must be a positive integer if specified.", type="invalid", id="feedback-microbatch_group_size_per_vp_stage"),
 
198
  dbc.Tooltip(
199
- "Number of microbatches to process per virtual pipeline stage before switching to the next stage. Used primarily with interleave scheduling strategies. Defaults to the number of devices.",
200
- target="tooltip-target-microbatch-group",
201
  placement="right"
202
  )
203
- ], className="mb-3"),
204
- ]
205
- )
206
- ]),
207
- style=card_style
208
- )
209
-
210
- # Updated app layout using dbc components and structure
211
- app.layout = dbc.Container([
212
- html.H1("Pipeline Parallelism Schedule Visualizer", className="my-4 text-center"),
213
-
214
- # Main Row with Left (Graphs) and Right (Controls) Columns
215
- dbc.Row([
216
- # --- Left Column (Graphs Area) ---
217
- dbc.Col([
218
- # Output Area for Graphs
219
- dcc.Loading(
220
- id="loading-graph-area",
221
- type="circle",
222
- children=html.Div(id='graph-output-container', style={"minHeight": "600px"})
223
  )
224
- ], lg=10, md=9, sm=12, className="mb-4 mb-lg-0"),
225
-
226
- # --- Right Column (Controls Area) ---
227
- dbc.Col([
228
- # Parameter Cards Stacked Vertically
229
- basic_params_card,
230
- scheduling_params_card,
231
- timing_params_card,
232
 
233
- # Generate Button below the cards in the right column
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  dbc.Row([
235
- dbc.Col(
236
- dbc.Button(
237
- 'Generate Schedule',
238
- id='generate-button',
239
- n_clicks=0,
240
- color="primary",
241
- className="w-100",
242
- disabled=False
243
- ),
244
- )
245
- ], className="mt-3")
246
- ], lg=2, md=3, sm=12)
247
- ]),
248
-
249
- # --- Toast Container (Positioned Fixed) ---
250
- html.Div(id="toast-container", style={"position": "fixed", "top": 20, "right": 20, "zIndex": 1050})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
- ], fluid=True, className="py-4")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
  # --- Callback for Input Validation and Generate Button State ---
255
  @app.callback(
@@ -343,24 +516,49 @@ def validate_inputs(num_devices, num_stages, num_batches, p2p_latency,
343
  # --- Callback to toggle Advanced Options Collapse ---
344
  @app.callback(
345
  Output("advanced-timing-collapse", "is_open"),
346
- Input("advanced-timing-switch", "value"),
 
347
  prevent_initial_call=True,
348
  )
349
- def toggle_advanced_options(switch_value):
350
- return switch_value
 
 
351
 
352
- # --- Client-side Callback for Strategy ButtonGroup ---
353
  app.clientside_callback(
354
- ClientsideFunction(
355
- namespace='clientside',
356
- function_name='update_strategy_selection'
357
- ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  Output('selected-strategies-store', 'data'),
359
- Output({'type': 'strategy-button', 'index': ALL}, 'active'),
360
- Output({'type': 'strategy-button', 'index': ALL}, 'color'),
361
- Output({'type': 'strategy-button', 'index': ALL}, 'outline'),
362
- Output('strategy-selection-feedback', 'children'),
363
- Input({'type': 'strategy-button', 'index': ALL}, 'n_clicks'),
364
  State('selected-strategies-store', 'data'),
365
  prevent_initial_call=True
366
  )
@@ -549,75 +747,202 @@ def update_graph(n_clicks, num_devices, num_stages, num_batches, p2p_latency,
549
  )
550
  )
551
 
552
- # --- Generate Graphs ---
553
  if valid_results:
554
  max_execution_time = max(schedule.get_total_execution_time() for _, schedule, _ in valid_results)
555
  sorted_valid_results = sorted(valid_results, key=lambda x: strategy_display_order.index(x[0]) if x[0] in strategy_display_order else float('inf'))
556
 
557
- # Prepare graphs for single-column layout
558
- graph_components = [] # Use graph_components again
559
- for strategy, _, vis_data in sorted_valid_results:
 
 
 
 
 
560
  fig = create_pipeline_figure(vis_data, max_time=max_execution_time, show_progress=False)
561
  margin = max_execution_time * 0.05
562
  fig.update_layout(
563
- xaxis=dict(range=[0, max_execution_time + margin])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
  )
565
- # Append the Div directly for vertical stacking
566
- graph_components.append(
 
567
  html.Div([
568
- html.H4(f"Schedule: {strategy}", className="text-center mt-3 mb-2"),
569
- dcc.Graph(figure=fig)
570
- ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
  )
572
-
573
- # No grid arrangement needed for single column
574
- # rows = [] ... removed ...
 
 
 
 
 
 
 
 
575
 
576
  # If there are graphs, use the component list, otherwise show a message
577
  output_content = []
578
- if graph_components: # Check if graph_components list is populated
579
- output_content = graph_components # Assign the list of components
580
- elif not toast_components: # Only show 'no results' if no errors/adjustments either
581
- output_content = dbc.Alert("Click 'Generate Schedule' to see results.", color="info", className="mt-3")
582
-
583
- # Add the execution time table if there are results
 
 
 
584
  if execution_times:
585
- # Sort times based on execution time (ascending)
586
  sorted_times = sorted(execution_times, key=lambda x: x[1])
587
  min_time = sorted_times[0][1] if sorted_times else None
588
-
589
- table_header = [html.Thead(html.Tr([html.Th("Strategy"), html.Th("Total Execution Time (ms)")]))]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  table_rows = []
591
  for strategy, time in sorted_times:
592
- row_class = "table-success" if time == min_time else ""
593
- table_rows.append(html.Tr([html.Td(strategy), html.Td(f"{time:.2f}")], className=row_class))
594
-
595
- table_body = [html.Tbody(table_rows)]
596
- summary_table = dbc.Table(
597
- table_header + table_body,
598
- bordered=True,
599
- striped=True,
600
- hover=True,
601
- responsive=True,
602
- color="light", # Apply a light theme color
603
- className="mt-5" # Add margin top
604
- )
605
- # Prepend title to the table
606
- table_component = html.Div([
607
- html.H4("Execution Time Summary", className="text-center mt-4 mb-3"),
608
- summary_table
609
- ])
610
-
611
- # Append the table component to the output content
612
- # If output_content is just the alert, replace it. Otherwise, append.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613
  if isinstance(output_content, list):
614
- output_content.append(table_component)
615
- else: # It must be the Alert
616
- output_content = [output_content, table_component] # Replace Alert with list
617
 
618
- # Return graph components (single column list or message) and toast components
619
  return output_content, toast_components
620
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
  # For Hugging Face Spaces deployment
622
  server = app.server
623
 
 
23
  "dualpipe": generate_dualpipe_schedule,
24
  }
25
 
26
+ # Strategy descriptions for better UX
27
+ STRATEGY_INFO = {
28
+ "1f1b": {
29
+ "name": "1F1B",
30
+ "description": "One Forward One Backward - Standard pipeline parallelism",
31
+ "icon": "bi-arrow-left-right"
32
+ },
33
+ "zb1p": {
34
+ "name": "ZB-1P",
35
+ "description": "Zero Bubble 1-stage Pipeline - Minimizes pipeline bubbles",
36
+ "icon": "bi-circle"
37
+ },
38
+ "1f1b_overlap": {
39
+ "name": "1F1B Overlap",
40
+ "description": "1F1B with overlapped forward and backward passes",
41
+ "icon": "bi-layers"
42
+ },
43
+ "1f1b_interleave": {
44
+ "name": "1F1B Interleave",
45
+ "description": "Interleaved pipeline stages for better efficiency",
46
+ "icon": "bi-shuffle"
47
+ },
48
+ "1f1b_interleave_overlap": {
49
+ "name": "1F1B Interleave + Overlap",
50
+ "description": "Combines interleaving with overlapped execution",
51
+ "icon": "bi-layers-fill"
52
+ },
53
+ "dualpipe": {
54
+ "name": "DualPipe",
55
+ "description": "Dual pipeline execution for enhanced parallelism",
56
+ "icon": "bi-diagram-2"
57
+ }
58
+ }
59
+
60
+ app = dash.Dash(
61
+ __name__,
62
+ external_stylesheets=[
63
+ dbc.themes.BOOTSTRAP,
64
+ dbc.icons.BOOTSTRAP,
65
+ "https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
66
+ ],
67
+ suppress_callback_exceptions=True
68
+ )
69
  app.title = "Pipeline Parallelism Schedule Visualizer"
70
 
71
  # Initial default values
 
84
  }
85
 
86
  # Define input groups using dbc components
87
+ card_style = {"marginBottom": "20px"}
88
+
89
+ # Header section
90
+ header = html.Div([
91
+ html.Div([
92
+ html.H1([
93
+ html.I(className="bi bi-diagram-3 me-3"),
94
+ "Pipeline Parallelism Schedule Visualizer"
95
+ ], className="text-center mb-0"),
96
+ html.P("Visualize and compare different pipeline parallelism scheduling strategies",
97
+ className="text-center mt-2 mb-0 lead")
98
+ ], className="container")
99
+ ], className="main-header")
100
+
101
+ # Basic parameters card with improved styling
102
+ basic_params_card = dbc.Card([
103
  dbc.CardBody([
104
+ html.H5([
105
+ html.I(className="bi bi-sliders section-icon"),
106
+ "Basic Configuration"
107
+ ], className="section-title"),
108
+
109
+ dbc.Row([
110
+ dbc.Col([
111
+ dbc.Label("Number of Devices (GPUs)", html_for='num_devices', className="form-label"),
112
+ dbc.InputGroup([
113
+ dbc.InputGroupText(html.I(className="bi bi-gpu-card")),
114
+ dbc.Input(id='num_devices', type='number', value=default_values["num_devices"],
115
+ min=1, step=1, required=True),
116
+ ]),
117
+ dbc.FormFeedback("Please provide a positive integer.", type="invalid"),
118
+ ], md=6),
119
+
120
+ dbc.Col([
121
+ dbc.Label("Number of Stages", html_for='num_stages', className="form-label"),
122
+ dbc.InputGroup([
123
+ dbc.InputGroupText(html.I(className="bi bi-stack")),
124
+ dbc.Input(id='num_stages', type='number', value=default_values["num_stages"],
125
+ min=1, step=1, required=True),
126
+ ]),
127
+ dbc.FormFeedback("Please provide a positive integer.", type="invalid"),
128
+ ], md=6),
129
  ], className="mb-3"),
130
+
131
+ dbc.Row([
132
+ dbc.Col([
133
+ dbc.Label("Number of Microbatches", html_for='num_batches', className="form-label"),
134
+ dbc.InputGroup([
135
+ dbc.InputGroupText(html.I(className="bi bi-collection")),
136
+ dbc.Input(id='num_batches', type='number', value=default_values["num_batches"],
137
+ min=1, step=1, required=True),
138
+ ]),
139
+ dbc.FormFeedback("Please provide a positive integer.", type="invalid"),
140
+ ], md=12),
141
+ ]),
142
+ ])
143
+ ], style=card_style)
144
+
145
+ # Improved scheduling strategy selection
146
+ scheduling_params_card = dbc.Card([
147
  dbc.CardBody([
148
+ html.H5([
149
+ html.I(className="bi bi-diagram-2 section-icon"),
150
+ "Scheduling Strategy"
151
+ ], className="section-title"),
152
+
153
+ html.P("Select one or more strategies to compare:", className="text-muted mb-3"),
154
+
155
+ dbc.Row([
156
+ dbc.Col([
157
+ html.Div([
158
+ html.Div([
159
+ html.I(className=f"{STRATEGY_INFO[strategy]['icon']} mb-2"),
160
+ html.H6(STRATEGY_INFO[strategy]['name'], className="mb-1"),
161
+ html.Small(STRATEGY_INFO[strategy]['description'], className="text-muted")
162
+ ],
163
+ id={"type": "strategy-card", "index": strategy},
164
+ className=f"strategy-card p-3 text-center {'selected' if strategy in default_values['strategy'] else ''}",
165
+ )
166
+ ], className="mb-3")
167
+ ], lg=4, md=6) for strategy in STRATEGIES.keys()
168
+ ], className="g-3"),
169
+
170
  dcc.Store(id='selected-strategies-store', data=default_values["strategy"]),
171
  html.Div(id='strategy-selection-feedback', className='invalid-feedback d-block mt-2')
172
+ ])
173
+ ], style=card_style)
 
174
 
175
+ # Timing parameters with better organization
176
+ timing_params_card = dbc.Card([
177
  dbc.CardBody([
178
+ html.H5([
179
+ html.I(className="bi bi-clock section-icon"),
180
+ "Operation Timing Configuration"
181
+ ], className="section-title"),
182
+
183
+ # Basic timing parameters
184
+ dbc.Row([
185
+ dbc.Col([
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  html.Div([
187
+ dbc.Label([
188
+ "P2P Latency (ms)",
189
+ dbc.Badge("?", pill=True, color="secondary", className="ms-1", id="tooltip-p2p",
190
+ style={"cursor": "pointer", "fontSize": "0.75rem"})
191
+ ], html_for='p2p_latency', className="form-label"),
192
+ dbc.Input(id='p2p_latency', type='number', value=default_values["p2p_latency"],
193
+ min=0, step=0.01, required=True),
194
+ dbc.FormFeedback("Must be ≥ 0", type="invalid"),
195
  dbc.Tooltip(
196
+ "Time for point-to-point communication between adjacent devices.",
197
+ target="tooltip-p2p",
198
  placement="right"
199
  )
200
+ ])
201
+ ], md=6),
202
+
203
+ dbc.Col([
204
  html.Div([
205
+ dbc.Label([
206
+ "Forward Pass Time (ms)",
207
+ dbc.Badge("?", pill=True, color="secondary", className="ms-1", id="tooltip-fwd",
208
+ style={"cursor": "pointer", "fontSize": "0.75rem"})
209
+ ], html_for='op_time_forward', className="form-label"),
210
+ dbc.Input(id='op_time_forward', type='number', value=default_values["op_time_forward"],
211
+ min=0.01, step=0.01, required=True),
212
+ dbc.FormFeedback("Must be > 0", type="invalid"),
213
  dbc.Tooltip(
214
+ "Time for a forward pass of one microbatch through one stage.",
215
+ target="tooltip-fwd",
216
  placement="right"
217
  )
218
+ ])
219
+ ], md=6),
220
+ ], className="mb-3"),
221
+
222
+ # Backward timing
223
+ html.Div([
224
+ dbc.Label([
225
+ "Backward Pass Time (ms)",
226
+ dbc.Badge("?", pill=True, color="secondary", className="ms-1", id="tooltip-bwd",
227
+ style={"cursor": "pointer", "fontSize": "0.75rem"})
228
+ ], html_for='op_time_backward', className="form-label"),
229
+ dbc.Input(id='op_time_backward', type='number', value=default_values["op_time_backward"],
230
+ min=0.01, step=0.01),
231
+ dbc.FormText("Combined backward pass time (data + weight gradients)", className="mt-1"),
232
+ dbc.FormFeedback("Must be > 0", type="invalid"),
233
+ dbc.Tooltip(
234
+ "Time for combined backward pass (data + weight gradients).",
235
+ target="tooltip-bwd",
236
+ placement="right"
 
237
  )
238
+ ], className="mb-3"),
 
 
 
 
 
 
 
239
 
240
+ # Advanced options with better styling
241
+ html.Hr(className="my-4"),
242
+ dbc.Button([
243
+ html.I(className="bi bi-gear-fill me-2"),
244
+ "Advanced Timing Options"
245
+ ],
246
+ id="advanced-timing-toggle",
247
+ color="light",
248
+ className="mb-3",
249
+ size="sm"
250
+ ),
251
+
252
+ dbc.Collapse([
253
+ dbc.Alert([
254
+ html.I(className="bi bi-info-circle-fill me-2"),
255
+ "These options are for advanced users and specific scheduling strategies."
256
+ ], color="info", className="mb-3"),
257
+
258
  dbc.Row([
259
+ dbc.Col([
260
+ dbc.Label("Backward D (Data Gradient)", html_for='op_time_backward_d'),
261
+ dbc.Input(id='op_time_backward_d', type='number',
262
+ value=default_values["op_time_backward_d"], min=0.01, step=0.01),
263
+ dbc.FormText("For strategies with split backward"),
264
+ dbc.FormFeedback("Must be > 0", type="invalid"),
265
+ ], md=6),
266
+
267
+ dbc.Col([
268
+ dbc.Label("Backward W (Weight Gradient)", html_for='op_time_backward_w'),
269
+ dbc.Input(id='op_time_backward_w', type='number',
270
+ value=default_values["op_time_backward_w"], min=0.01, step=0.01),
271
+ dbc.FormText("For strategies with split backward"),
272
+ dbc.FormFeedback("Must be > 0", type="invalid"),
273
+ ], md=6),
274
+ ], className="mb-3"),
275
+
276
+ html.Div([
277
+ dbc.Label("Overlapped Forward+Backward Time", html_for='op_time_overlapped_fwd_bwd'),
278
+ dbc.Input(id='op_time_overlapped_fwd_bwd', type='number',
279
+ placeholder="Auto-calculated if not specified", min=0.01, step=0.01),
280
+ dbc.FormText("Time when forward and backward can be fully overlapped"),
281
+ dbc.FormFeedback("Must be > 0", type="invalid"),
282
+ ], className="mb-3"),
283
+
284
+ html.Div([
285
+ dbc.Label("Microbatch Group Size per VP Stage", html_for='microbatch_group_size_per_vp_stage'),
286
+ dbc.Input(id='microbatch_group_size_per_vp_stage', type='number',
287
+ placeholder=f"Defaults to number of devices", min=1, step=1),
288
+ dbc.FormText("For interleave strategies only"),
289
+ dbc.FormFeedback("Must be a positive integer", type="invalid"),
290
+ ]),
291
+ ], id="advanced-timing-collapse", is_open=False)
292
+ ])
293
+ ], style=card_style)
294
 
295
+ # Updated app layout with improved structure
296
+ app.layout = html.Div([
297
+ header,
298
+
299
+ dbc.Container([
300
+ dbc.Row([
301
+ # Left Column - Visualization Area
302
+ dbc.Col([
303
+ dbc.Card([
304
+ dbc.CardBody([
305
+ html.H5([
306
+ html.I(className="bi bi-graph-up section-icon"),
307
+ "Visualization Results"
308
+ ], className="section-title"),
309
+
310
+ dcc.Loading(
311
+ id="loading-graph-area",
312
+ type="circle",
313
+ children=html.Div([
314
+ # Welcome message for initial state
315
+ dbc.Alert([
316
+ html.H4([
317
+ html.I(className="bi bi-lightbulb me-2"),
318
+ "Welcome to Pipeline Parallelism Schedule Visualizer"
319
+ ], className="alert-heading"),
320
+ html.Hr(),
321
+ html.P([
322
+ "This tool helps you visualize and compare different pipeline parallelism scheduling strategies. ",
323
+ "To get started:"
324
+ ], className="mb-3"),
325
+ html.Ol([
326
+ html.Li("Configure your basic parameters (devices, stages, microbatches)"),
327
+ html.Li("Select one or more scheduling strategies to compare"),
328
+ html.Li("Set the operation timing parameters"),
329
+ html.Li("Click 'Generate Schedules' to visualize the results")
330
+ ], className="mb-3"),
331
+ html.P([
332
+ html.Strong("Tip: "),
333
+ "Hover over the ",
334
+ html.I(className="bi bi-question-circle"),
335
+ " icons for detailed explanations of each parameter."
336
+ ], className="mb-0")
337
+ ], color="info", className="text-start", id="welcome-message"),
338
+ ], id='graph-output-container', style={"minHeight": "400px"})
339
+ )
340
+ ])
341
+ ])
342
+ ], lg=8, md=7, className="mb-4"),
343
+
344
+ # Right Column - Controls
345
+ dbc.Col([
346
+ basic_params_card,
347
+ scheduling_params_card,
348
+ timing_params_card,
349
+
350
+ # Generate button with better styling
351
+ dbc.Button([
352
+ html.I(className="bi bi-play-fill me-2"),
353
+ "Generate Schedules"
354
+ ],
355
+ id='generate-button',
356
+ color="primary",
357
+ size="lg",
358
+ className="w-100 mt-3",
359
+ disabled=False
360
+ )
361
+ ], lg=4, md=5)
362
+ ])
363
+ ], fluid=True),
364
+
365
+ # Toast container
366
+ html.Div(id="toast-container", style={
367
+ "position": "fixed",
368
+ "top": 20,
369
+ "right": 20,
370
+ "zIndex": 1050,
371
+ "maxWidth": "400px"
372
+ }),
373
+
374
+ # Footer
375
+ html.Footer([
376
+ dbc.Container([
377
+ html.Hr(className="mt-5"),
378
+ dbc.Row([
379
+ dbc.Col([
380
+ html.H6("About Pipeline Parallelism", className="text-muted mb-3"),
381
+ html.P([
382
+ "Pipeline parallelism is a distributed training technique that splits a model across multiple devices. ",
383
+ "This visualizer helps you understand different scheduling strategies and their performance characteristics."
384
+ ], className="small text-muted")
385
+ ], md=4),
386
+ dbc.Col([
387
+ html.H6("Scheduling Strategies", className="text-muted mb-3"),
388
+ html.Ul([
389
+ html.Li("1F1B: Standard pipeline with one forward, one backward", className="small text-muted"),
390
+ html.Li("ZB-1P: Zero bubble optimization", className="small text-muted"),
391
+ html.Li("Interleave: Virtual pipeline stages", className="small text-muted"),
392
+ html.Li("Overlap: Concurrent forward/backward", className="small text-muted")
393
+ ], className="list-unstyled")
394
+ ], md=4),
395
+ dbc.Col([
396
+ html.H6("Resources", className="text-muted mb-3"),
397
+ html.Div([
398
+ html.A([
399
+ html.I(className="bi bi-github me-2"),
400
+ "View on GitHub"
401
+ ], href="#", className="small text-muted d-block mb-2"),
402
+ html.A([
403
+ html.I(className="bi bi-book me-2"),
404
+ "Documentation"
405
+ ], href="#", className="small text-muted d-block mb-2"),
406
+ html.A([
407
+ html.I(className="bi bi-question-circle me-2"),
408
+ "Report an Issue"
409
+ ], href="#", className="small text-muted d-block")
410
+ ])
411
+ ], md=4)
412
+ ]),
413
+ html.Hr(),
414
+ html.P([
415
+ "© 2024 Pipeline Parallelism Schedule Visualizer. ",
416
+ "Built with ",
417
+ html.I(className="bi bi-heart-fill text-danger"),
418
+ " using Dash and Plotly."
419
+ ], className="text-center text-muted small mb-3")
420
+ ], fluid=True)
421
+ ], className="mt-5 bg-light py-4")
422
+ ])
423
+
424
+ # Keep the existing store for backward compatibility
425
+ app.layout.children.append(dcc.Store(id='advanced-timing-switch', data=False))
426
 
427
  # --- Callback for Input Validation and Generate Button State ---
428
  @app.callback(
 
516
  # --- Callback to toggle Advanced Options Collapse ---
517
  @app.callback(
518
  Output("advanced-timing-collapse", "is_open"),
519
+ Input("advanced-timing-toggle", "n_clicks"),
520
+ State("advanced-timing-collapse", "is_open"),
521
  prevent_initial_call=True,
522
  )
523
+ def toggle_advanced_options(n_clicks, is_open):
524
+ if n_clicks:
525
+ return not is_open
526
+ return is_open
527
 
528
+ # --- Client-side Callback for Strategy Card Selection ---
529
  app.clientside_callback(
530
+ """
531
+ function(n_clicks_list, current_strategies) {
532
+ const ctx = dash_clientside.callback_context;
533
+ if (!ctx.triggered || ctx.triggered.length === 0) {
534
+ return [dash_clientside.no_update, dash_clientside.no_update];
535
+ }
536
+
537
+ const triggered = ctx.triggered[0];
538
+ const clickedIndex = JSON.parse(triggered.prop_id.split('.')[0]).index;
539
+
540
+ let newStrategies = current_strategies ? [...current_strategies] : [];
541
+
542
+ if (newStrategies.includes(clickedIndex)) {
543
+ newStrategies = newStrategies.filter(s => s !== clickedIndex);
544
+ } else {
545
+ newStrategies.push(clickedIndex);
546
+ }
547
+
548
+ // Update card classes
549
+ const allStrategies = ['1f1b', 'zb1p', '1f1b_overlap', '1f1b_interleave', '1f1b_interleave_overlap', 'dualpipe'];
550
+ const cardClasses = allStrategies.map(strategy =>
551
+ newStrategies.includes(strategy)
552
+ ? 'strategy-card p-3 text-center selected'
553
+ : 'strategy-card p-3 text-center'
554
+ );
555
+
556
+ return [newStrategies, cardClasses];
557
+ }
558
+ """,
559
  Output('selected-strategies-store', 'data'),
560
+ Output({'type': 'strategy-card', 'index': ALL}, 'className'),
561
+ Input({'type': 'strategy-card', 'index': ALL}, 'n_clicks'),
 
 
 
562
  State('selected-strategies-store', 'data'),
563
  prevent_initial_call=True
564
  )
 
747
  )
748
  )
749
 
750
+ # --- Generate Graphs with improved layout ---
751
  if valid_results:
752
  max_execution_time = max(schedule.get_total_execution_time() for _, schedule, _ in valid_results)
753
  sorted_valid_results = sorted(valid_results, key=lambda x: strategy_display_order.index(x[0]) if x[0] in strategy_display_order else float('inf'))
754
 
755
+ # Create tabs for multiple strategies
756
+ tabs = []
757
+ tab_panels = []
758
+
759
+ for idx, (strategy, _, vis_data) in enumerate(sorted_valid_results):
760
+ strategy_info = STRATEGY_INFO[strategy]
761
+
762
+ # Create figure
763
  fig = create_pipeline_figure(vis_data, max_time=max_execution_time, show_progress=False)
764
  margin = max_execution_time * 0.05
765
  fig.update_layout(
766
+ xaxis=dict(range=[0, max_execution_time + margin]),
767
+ paper_bgcolor='rgba(0,0,0,0)',
768
+ plot_bgcolor='rgba(0,0,0,0)',
769
+ font=dict(family="Inter, sans-serif"),
770
+ height=400, # Set explicit height
771
+ autosize=True, # Enable autosize
772
+ margin=dict(l=60, r=20, t=40, b=60), # Set proper margins
773
+ )
774
+
775
+ # Create tab
776
+ tab_label = strategy_info['name']
777
+ tab_value = f"tab-{strategy}"
778
+
779
+ tabs.append(
780
+ dbc.Tab(
781
+ label=tab_label,
782
+ tab_id=tab_value,
783
+ activeTabClassName="fw-bold"
784
+ )
785
  )
786
+
787
+ # Create tab panel content
788
+ tab_panels.append(
789
  html.Div([
790
+ dbc.Alert([
791
+ html.I(className=f"{strategy_info['icon']} me-2"),
792
+ strategy_info['description']
793
+ ], color="light", className="mb-3"),
794
+ html.Div([
795
+ dcc.Graph(
796
+ figure=fig,
797
+ config={
798
+ 'displayModeBar': False,
799
+ 'responsive': True # Make graph responsive
800
+ },
801
+ className="dash-graph"
802
+ )
803
+ ], className="graph-container")
804
+ ],
805
+ id=f"content-{strategy}",
806
+ style={"display": "block" if idx == 0 else "none"} # Show first tab by default
807
+ )
808
  )
809
+
810
+ # Create tabbed interface with callback to switch content
811
+ graph_components = [
812
+ dbc.Tabs(
813
+ tabs,
814
+ id="strategy-tabs",
815
+ active_tab=f"tab-{sorted_valid_results[0][0]}",
816
+ className="mb-3"
817
+ ),
818
+ html.Div(tab_panels, id="tab-content-container")
819
+ ]
820
 
821
  # If there are graphs, use the component list, otherwise show a message
822
  output_content = []
823
+ if graph_components:
824
+ output_content = graph_components
825
+ elif not toast_components:
826
+ output_content = dbc.Alert([
827
+ html.I(className="bi bi-info-circle-fill me-2"),
828
+ "Click 'Generate Schedules' to visualize pipeline parallelism strategies."
829
+ ], color="info", className="text-center")
830
+
831
+ # Add execution time summary with improved design
832
  if execution_times:
 
833
  sorted_times = sorted(execution_times, key=lambda x: x[1])
834
  min_time = sorted_times[0][1] if sorted_times else None
835
+
836
+ # Create metric cards for top strategies
837
+ metric_cards = []
838
+ for i, (strategy, time) in enumerate(sorted_times[:3]): # Show top 3
839
+ strategy_info = STRATEGY_INFO[strategy]
840
+ badge_color = "success" if i == 0 else "primary" if i == 1 else "secondary"
841
+
842
+ metric_cards.append(
843
+ dbc.Col([
844
+ html.Div([
845
+ html.Div([
846
+ html.I(className=f"{strategy_info['icon']} mb-2",
847
+ style={"fontSize": "2rem", "color": "#667eea"}),
848
+ html.H3(f"{time:.2f} ms", className="metric-value"),
849
+ html.P(strategy_info['name'], className="metric-label mb-0"),
850
+ dbc.Badge(f"#{i+1}", color=badge_color, className="mt-2")
851
+ ], className="metric-card")
852
+ ])
853
+ ], lg=4, md=6, className="mb-3")
854
+ )
855
+
856
+ # Create detailed comparison table
857
  table_rows = []
858
  for strategy, time in sorted_times:
859
+ strategy_info = STRATEGY_INFO[strategy]
860
+ efficiency = (min_time / time * 100) if min_time else 100
861
+
862
+ table_rows.append(
863
+ html.Tr([
864
+ html.Td([
865
+ html.I(className=f"{strategy_info['icon']} me-2"),
866
+ strategy_info['name']
867
+ ]),
868
+ html.Td(f"{time:.2f} ms"),
869
+ html.Td([
870
+ dbc.Progress(
871
+ value=efficiency,
872
+ color="success" if efficiency >= 95 else "warning" if efficiency >= 80 else "danger",
873
+ className="mb-0",
874
+ style={"height": "10px"}
875
+ ),
876
+ html.Small(f"{efficiency:.1f}%", className="ms-2 text-muted")
877
+ ])
878
+ ], className="align-middle")
879
+ )
880
+
881
+ execution_summary = html.Div([
882
+ html.H4([
883
+ html.I(className="bi bi-speedometer2 section-icon"),
884
+ "Performance Summary"
885
+ ], className="section-title mt-5"),
886
+
887
+ # Metric cards
888
+ dbc.Row(metric_cards, className="mb-4"),
889
+
890
+ # Detailed table
891
+ dbc.Card([
892
+ dbc.CardBody([
893
+ html.H5("Detailed Comparison", className="mb-3"),
894
+ dbc.Table([
895
+ html.Thead([
896
+ html.Tr([
897
+ html.Th("Strategy"),
898
+ html.Th("Execution Time"),
899
+ html.Th("Relative Efficiency", style={"width": "40%"})
900
+ ])
901
+ ]),
902
+ html.Tbody(table_rows)
903
+ ], hover=True, responsive=True, className="mb-0")
904
+ ])
905
+ ])
906
+ ], className="execution-summary")
907
+
908
+ # Append the execution summary
909
  if isinstance(output_content, list):
910
+ output_content.append(execution_summary)
911
+ else:
912
+ output_content = [output_content, execution_summary]
913
 
914
+ # Return graph components and toast components
915
  return output_content, toast_components
916
 
917
+ # --- Client-side Callback for Tab Switching ---
918
+ app.clientside_callback(
919
+ """
920
+ function(activeTab) {
921
+ if (!activeTab) return window.dash_clientside.no_update;
922
+
923
+ // Extract strategy from tab id (format: "tab-strategy")
924
+ const activeStrategy = activeTab.replace("tab-", "");
925
+
926
+ // Get all tab content divs
927
+ const contentDivs = document.querySelectorAll('[id^="content-"]');
928
+
929
+ contentDivs.forEach(div => {
930
+ const strategy = div.id.replace("content-", "");
931
+ if (strategy === activeStrategy) {
932
+ div.style.display = "block";
933
+ } else {
934
+ div.style.display = "none";
935
+ }
936
+ });
937
+
938
+ return window.dash_clientside.no_update;
939
+ }
940
+ """,
941
+ Output("tab-content-container", "children"), # Dummy output
942
+ Input("strategy-tabs", "active_tab"),
943
+ prevent_initial_call=True
944
+ )
945
+
946
  # For Hugging Face Spaces deployment
947
  server = app.server
948
 
assets/custom.css CHANGED
@@ -126,4 +126,981 @@ body {
126
  /* Chart Container - Add basic styles, will be refined (Item 9) */
127
  #graph-output-container .plotly.graph-div {
128
  /* Add styles for the chart itself if needed */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  }
 
126
  /* Chart Container - Add basic styles, will be refined (Item 9) */
127
  #graph-output-container .plotly.graph-div {
128
  /* Add styles for the chart itself if needed */
129
+ }
130
+
131
+ /* Custom CSS for Pipeline Parallelism Schedule Visualizer */
132
+
133
+ /* Smooth transitions for all interactive elements */
134
+ * {
135
+ transition: all 0.3s ease;
136
+ }
137
+
138
+ /* Enhanced card hover effects */
139
+ .card {
140
+ border-radius: 12px !important;
141
+ overflow: hidden;
142
+ }
143
+
144
+ .card:hover {
145
+ transform: translateY(-4px);
146
+ box-shadow: 0 8px 24px rgba(0,0,0,0.12) !important;
147
+ }
148
+
149
+ /* Strategy card animations */
150
+ .strategy-card {
151
+ border-radius: 10px;
152
+ position: relative;
153
+ overflow: hidden;
154
+ }
155
+
156
+ .strategy-card::before {
157
+ content: '';
158
+ position: absolute;
159
+ top: 0;
160
+ left: -100%;
161
+ width: 100%;
162
+ height: 100%;
163
+ background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.1), transparent);
164
+ transition: left 0.5s ease;
165
+ }
166
+
167
+ .strategy-card:hover::before {
168
+ left: 100%;
169
+ }
170
+
171
+ .strategy-card.selected {
172
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
173
+ }
174
+
175
+ /* Enhanced button animations */
176
+ .btn-primary {
177
+ position: relative;
178
+ overflow: hidden;
179
+ }
180
+
181
+ .btn-primary::before {
182
+ content: '';
183
+ position: absolute;
184
+ top: 50%;
185
+ left: 50%;
186
+ width: 0;
187
+ height: 0;
188
+ border-radius: 50%;
189
+ background: rgba(255, 255, 255, 0.2);
190
+ transform: translate(-50%, -50%);
191
+ transition: width 0.6s, height 0.6s;
192
+ }
193
+
194
+ .btn-primary:active::before {
195
+ width: 300px;
196
+ height: 300px;
197
+ }
198
+
199
+ /* Loading animation enhancement */
200
+ ._dash-loading {
201
+ position: relative;
202
+ }
203
+
204
+ ._dash-loading::after {
205
+ content: '';
206
+ position: absolute;
207
+ top: 50%;
208
+ left: 50%;
209
+ width: 40px;
210
+ height: 40px;
211
+ margin: -20px 0 0 -20px;
212
+ border: 3px solid #f3f3f3;
213
+ border-top: 3px solid #667eea;
214
+ border-radius: 50%;
215
+ animation: spin 1s linear infinite;
216
+ }
217
+
218
+ @keyframes spin {
219
+ 0% { transform: rotate(0deg); }
220
+ 100% { transform: rotate(360deg); }
221
+ }
222
+
223
+ /* Tab animations */
224
+ .nav-tabs .nav-link {
225
+ position: relative;
226
+ transition: all 0.3s ease;
227
+ }
228
+
229
+ .nav-tabs .nav-link::after {
230
+ content: '';
231
+ position: absolute;
232
+ bottom: 0;
233
+ left: 50%;
234
+ width: 0;
235
+ height: 3px;
236
+ background: #667eea;
237
+ transform: translateX(-50%);
238
+ transition: width 0.3s ease;
239
+ }
240
+
241
+ .nav-tabs .nav-link:hover::after {
242
+ width: 100%;
243
+ }
244
+
245
+ .nav-tabs .nav-link.active::after {
246
+ width: 100%;
247
+ }
248
+
249
+ /* Metric card animations */
250
+ .metric-card {
251
+ transition: all 0.3s ease;
252
+ }
253
+
254
+ .metric-card:hover {
255
+ transform: translateY(-5px);
256
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.15) !important;
257
+ }
258
+
259
+ /* Progress bar animations */
260
+ .progress {
261
+ overflow: visible;
262
+ }
263
+
264
+ .progress-bar {
265
+ position: relative;
266
+ animation: progressAnimation 1s ease-out;
267
+ }
268
+
269
+ @keyframes progressAnimation {
270
+ from {
271
+ width: 0;
272
+ }
273
+ }
274
+
275
+ /* Toast animations */
276
+ .toast {
277
+ animation: slideIn 0.3s ease-out;
278
+ }
279
+
280
+ @keyframes slideIn {
281
+ from {
282
+ transform: translateX(100%);
283
+ opacity: 0;
284
+ }
285
+ to {
286
+ transform: translateX(0);
287
+ opacity: 1;
288
+ }
289
+ }
290
+
291
+ /* Input focus effects */
292
+ .form-control:focus {
293
+ transform: translateY(-2px);
294
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15) !important;
295
+ }
296
+
297
+ /* Badge pulse animation */
298
+ .badge {
299
+ animation: pulse 2s infinite;
300
+ }
301
+
302
+ @keyframes pulse {
303
+ 0% {
304
+ transform: scale(1);
305
+ }
306
+ 50% {
307
+ transform: scale(1.05);
308
+ }
309
+ 100% {
310
+ transform: scale(1);
311
+ }
312
+ }
313
+
314
+ /* Smooth scrolling */
315
+ html {
316
+ scroll-behavior: smooth;
317
+ }
318
+
319
+ /* Enhanced tooltip styling */
320
+ .tooltip {
321
+ font-size: 0.875rem;
322
+ }
323
+
324
+ .tooltip-inner {
325
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
326
+ padding: 8px 12px;
327
+ border-radius: 6px;
328
+ }
329
+
330
+ /* Graph container enhancements */
331
+ .graph-container {
332
+ position: relative;
333
+ }
334
+
335
+ .graph-container::before {
336
+ content: '';
337
+ position: absolute;
338
+ top: -2px;
339
+ left: -2px;
340
+ right: -2px;
341
+ bottom: -2px;
342
+ background: linear-gradient(45deg, #667eea, #764ba2, #667eea);
343
+ border-radius: 10px;
344
+ opacity: 0;
345
+ z-index: -1;
346
+ transition: opacity 0.3s ease;
347
+ }
348
+
349
+ .graph-container:hover::before {
350
+ opacity: 0.1;
351
+ }
352
+
353
+ /* Responsive improvements */
354
+ @media (max-width: 768px) {
355
+ .main-header {
356
+ padding: 1.5rem 0;
357
+ }
358
+
359
+ .main-header h1 {
360
+ font-size: 1.75rem;
361
+ }
362
+
363
+ .strategy-card {
364
+ margin-bottom: 0.75rem;
365
+ }
366
+
367
+ .metric-card {
368
+ margin-bottom: 1rem;
369
+ }
370
+ }
371
+
372
+ /* Dark mode support */
373
+ @media (prefers-color-scheme: dark) {
374
+ body {
375
+ background-color: #1a1a1a;
376
+ color: #e0e0e0;
377
+ }
378
+
379
+ .card {
380
+ background-color: #2a2a2a;
381
+ color: #e0e0e0;
382
+ }
383
+
384
+ .form-control {
385
+ background-color: #3a3a3a;
386
+ border-color: #4a4a4a;
387
+ color: #e0e0e0;
388
+ }
389
+
390
+ .form-control:focus {
391
+ background-color: #3a3a3a;
392
+ border-color: #667eea;
393
+ color: #e0e0e0;
394
+ }
395
+ }
396
+
397
+ /* Print styles */
398
+ @media print {
399
+ .main-header {
400
+ background: none !important;
401
+ color: #000 !important;
402
+ }
403
+
404
+ .card {
405
+ box-shadow: none !important;
406
+ border: 1px solid #ddd !important;
407
+ }
408
+
409
+ .btn {
410
+ display: none !important;
411
+ }
412
+ }
413
+
414
+ /* Pipeline Parallelism Schedule Visualizer - Custom CSS */
415
+
416
+ /* --- Base Typography & Colors --- */
417
+ body {
418
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
419
+ background-color: #f8f9fa;
420
+ color: #212B36;
421
+ font-size: 14px;
422
+ }
423
+
424
+ /* --- Header Section --- */
425
+ .main-header {
426
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
427
+ color: white;
428
+ padding: 2rem 0;
429
+ margin-bottom: 2rem;
430
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
431
+ }
432
+
433
+ .main-header h1 {
434
+ font-size: 2rem;
435
+ font-weight: 600;
436
+ margin-bottom: 0;
437
+ }
438
+
439
+ .main-header .lead {
440
+ font-size: 1.1rem;
441
+ opacity: 0.9;
442
+ }
443
+
444
+ /* --- Cards & Containers --- */
445
+ .card {
446
+ border: none;
447
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
448
+ transition: all 0.3s ease;
449
+ border-radius: 12px;
450
+ overflow: hidden;
451
+ margin-bottom: 24px;
452
+ }
453
+
454
+ .card:hover {
455
+ transform: translateY(-4px);
456
+ box-shadow: 0 8px 24px rgba(0,0,0,0.12);
457
+ }
458
+
459
+ .card-body {
460
+ padding: 1.5rem;
461
+ }
462
+
463
+ /* --- Strategy Cards --- */
464
+ .strategy-card {
465
+ cursor: pointer;
466
+ border: 2px solid #e9ecef;
467
+ transition: all 0.3s ease;
468
+ height: 100%;
469
+ border-radius: 10px;
470
+ background: white;
471
+ position: relative;
472
+ overflow: hidden;
473
+ }
474
+
475
+ .strategy-card::before {
476
+ content: '';
477
+ position: absolute;
478
+ top: 0;
479
+ left: -100%;
480
+ width: 100%;
481
+ height: 100%;
482
+ background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.1), transparent);
483
+ transition: left 0.5s ease;
484
+ }
485
+
486
+ .strategy-card:hover {
487
+ border-color: #667eea;
488
+ transform: translateY(-2px);
489
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
490
+ }
491
+
492
+ .strategy-card:hover::before {
493
+ left: 100%;
494
+ }
495
+
496
+ .strategy-card.selected {
497
+ border-color: #667eea;
498
+ background: linear-gradient(135deg, #f8f9ff 0%, #f0f2ff 100%);
499
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
500
+ }
501
+
502
+ .strategy-card i {
503
+ font-size: 1.5rem;
504
+ color: #667eea;
505
+ margin-bottom: 0.5rem;
506
+ display: block;
507
+ }
508
+
509
+ /* --- Section Titles --- */
510
+ .section-title {
511
+ font-size: 1.1rem;
512
+ font-weight: 600;
513
+ color: #495057;
514
+ margin-bottom: 1rem;
515
+ display: flex;
516
+ align-items: center;
517
+ gap: 0.5rem;
518
+ }
519
+
520
+ .section-icon {
521
+ color: #667eea;
522
+ }
523
+
524
+ /* --- Forms & Inputs --- */
525
+ .form-label {
526
+ font-weight: 500;
527
+ color: #495057;
528
+ margin-bottom: 0.5rem;
529
+ display: block;
530
+ }
531
+
532
+ .form-control, .form-select {
533
+ font-size: 14px;
534
+ padding: 0.5rem 0.75rem;
535
+ border-radius: 8px;
536
+ border: 1px solid #dee2e6;
537
+ transition: all 0.3s ease;
538
+ }
539
+
540
+ .form-control:focus {
541
+ border-color: #667eea;
542
+ box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
543
+ transform: translateY(-2px);
544
+ }
545
+
546
+ .input-group-text {
547
+ background-color: #f8f9fa;
548
+ border: 1px solid #dee2e6;
549
+ border-radius: 8px 0 0 8px;
550
+ }
551
+
552
+ .form-text {
553
+ font-size: 12px;
554
+ color: #6c757d;
555
+ margin-top: 0.25rem;
556
+ }
557
+
558
+ .invalid-feedback {
559
+ font-size: 0.875rem;
560
+ margin-top: 0.25rem;
561
+ }
562
+
563
+ /* --- Buttons --- */
564
+ .btn-primary {
565
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
566
+ border: none;
567
+ font-weight: 500;
568
+ padding: 0.75rem 1.5rem;
569
+ transition: all 0.3s ease;
570
+ position: relative;
571
+ overflow: hidden;
572
+ }
573
+
574
+ .btn-primary::before {
575
+ content: '';
576
+ position: absolute;
577
+ top: 50%;
578
+ left: 50%;
579
+ width: 0;
580
+ height: 0;
581
+ border-radius: 50%;
582
+ background: rgba(255, 255, 255, 0.2);
583
+ transform: translate(-50%, -50%);
584
+ transition: width 0.6s, height 0.6s;
585
+ }
586
+
587
+ .btn-primary:hover:not(:disabled) {
588
+ transform: translateY(-2px);
589
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
590
+ }
591
+
592
+ .btn-primary:active::before {
593
+ width: 300px;
594
+ height: 300px;
595
+ }
596
+
597
+ .btn-primary:disabled {
598
+ opacity: 0.6;
599
+ cursor: not-allowed;
600
+ }
601
+
602
+ .btn-light {
603
+ background-color: #f8f9fa;
604
+ border-color: #dee2e6;
605
+ color: #495057;
606
+ }
607
+
608
+ .btn-light:hover {
609
+ background-color: #e9ecef;
610
+ border-color: #dee2e6;
611
+ color: #495057;
612
+ }
613
+
614
+ /* --- Tabs --- */
615
+ .nav-tabs {
616
+ border-bottom: 2px solid #e9ecef;
617
+ }
618
+
619
+ .nav-tabs .nav-link {
620
+ color: #6c757d;
621
+ border: none;
622
+ padding: 0.75rem 1.5rem;
623
+ font-weight: 500;
624
+ position: relative;
625
+ transition: all 0.3s ease;
626
+ }
627
+
628
+ .nav-tabs .nav-link::after {
629
+ content: '';
630
+ position: absolute;
631
+ bottom: 0;
632
+ left: 50%;
633
+ width: 0;
634
+ height: 3px;
635
+ background: #667eea;
636
+ transform: translateX(-50%);
637
+ transition: width 0.3s ease;
638
+ }
639
+
640
+ .nav-tabs .nav-link:hover {
641
+ color: #667eea;
642
+ background-color: #f8f9fa;
643
+ }
644
+
645
+ .nav-tabs .nav-link:hover::after {
646
+ width: 100%;
647
+ }
648
+
649
+ .nav-tabs .nav-link.active {
650
+ color: #667eea;
651
+ background-color: transparent;
652
+ border-bottom: 3px solid #667eea;
653
+ }
654
+
655
+ .nav-tabs .nav-link.active::after {
656
+ width: 100%;
657
+ }
658
+
659
+ /* --- Graphs & Visualizations --- */
660
+ .graph-container {
661
+ background: white;
662
+ border-radius: 8px;
663
+ padding: 1rem;
664
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
665
+ margin-bottom: 1.5rem;
666
+ position: relative;
667
+ min-height: 400px; /* Ensure minimum height */
668
+ width: 100%; /* Full width */
669
+ }
670
+
671
+ /* Ensure Plotly graphs are fully visible */
672
+ .graph-container .js-plotly-plot,
673
+ .graph-container .plot-container,
674
+ .graph-container .plotly {
675
+ width: 100% !important;
676
+ height: auto !important;
677
+ min-height: 350px;
678
+ }
679
+
680
+ /* Fix for the main modebar groups */
681
+ .graph-container .modebar {
682
+ position: absolute !important;
683
+ top: 10px;
684
+ right: 10px;
685
+ }
686
+
687
+ /* Ensure the SVG container is properly sized */
688
+ .graph-container .main-svg {
689
+ overflow: visible !important;
690
+ }
691
+
692
+ /* Tab content container should have proper height */
693
+ #tab-content-container {
694
+ min-height: 500px;
695
+ width: 100%;
696
+ }
697
+
698
+ /* Individual tab content */
699
+ [id^="content-"] {
700
+ width: 100%;
701
+ min-height: 450px;
702
+ }
703
+
704
+ /* Ensure the graph output container has proper dimensions */
705
+ #graph-output-container {
706
+ width: 100%;
707
+ overflow-x: auto; /* Allow horizontal scroll if needed */
708
+ overflow-y: visible;
709
+ }
710
+
711
+ /* Visualization Results card should expand to content */
712
+ .card:has(#graph-output-container) {
713
+ overflow: visible;
714
+ }
715
+
716
+ .card-body:has(#graph-output-container) {
717
+ overflow: visible;
718
+ padding-bottom: 2rem; /* Extra padding at bottom */
719
+ }
720
+
721
+ /* --- Execution Summary --- */
722
+ .execution-summary {
723
+ background: linear-gradient(135deg, #f8f9ff 0%, #f0f2ff 100%);
724
+ border-radius: 8px;
725
+ padding: 1.5rem;
726
+ margin-top: 2rem;
727
+ }
728
+
729
+ .metric-card {
730
+ background: white;
731
+ border-radius: 8px;
732
+ padding: 1.5rem;
733
+ text-align: center;
734
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
735
+ transition: all 0.3s ease;
736
+ }
737
+
738
+ .metric-card:hover {
739
+ transform: translateY(-5px);
740
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.15);
741
+ }
742
+
743
+ .metric-value {
744
+ font-size: 2rem;
745
+ font-weight: 700;
746
+ color: #667eea;
747
+ }
748
+
749
+ .metric-label {
750
+ font-size: 0.875rem;
751
+ color: #6c757d;
752
+ margin-top: 0.25rem;
753
+ }
754
+
755
+ /* --- Alerts & Toasts --- */
756
+ .alert {
757
+ border-radius: 8px;
758
+ border: none;
759
+ }
760
+
761
+ .alert-info {
762
+ background-color: #e7f3ff;
763
+ color: #0c5460;
764
+ }
765
+
766
+ .alert-light {
767
+ background-color: #f8f9fa;
768
+ color: #495057;
769
+ }
770
+
771
+ .toast {
772
+ min-width: 300px;
773
+ border-radius: 8px;
774
+ border: none;
775
+ animation: slideIn 0.3s ease-out;
776
+ }
777
+
778
+ .toast-header {
779
+ background-color: transparent;
780
+ border-bottom: 1px solid rgba(0,0,0,0.05);
781
+ }
782
+
783
+ @keyframes slideIn {
784
+ from {
785
+ transform: translateX(100%);
786
+ opacity: 0;
787
+ }
788
+ to {
789
+ transform: translateX(0);
790
+ opacity: 1;
791
+ }
792
+ }
793
+
794
+ /* --- Badges --- */
795
+ .badge {
796
+ padding: 0.35em 0.65em;
797
+ font-weight: 500;
798
+ animation: pulse 2s infinite;
799
+ }
800
+
801
+ @keyframes pulse {
802
+ 0% { transform: scale(1); }
803
+ 50% { transform: scale(1.05); }
804
+ 100% { transform: scale(1); }
805
+ }
806
+
807
+ /* --- Progress Bars --- */
808
+ .progress {
809
+ height: 10px;
810
+ border-radius: 5px;
811
+ background-color: #e9ecef;
812
+ overflow: visible;
813
+ }
814
+
815
+ .progress-bar {
816
+ border-radius: 5px;
817
+ position: relative;
818
+ animation: progressAnimation 1s ease-out;
819
+ }
820
+
821
+ @keyframes progressAnimation {
822
+ from { width: 0; }
823
+ }
824
+
825
+ /* --- Loading Animation --- */
826
+ ._dash-loading {
827
+ position: relative;
828
+ }
829
+
830
+ ._dash-loading::after {
831
+ content: '';
832
+ position: absolute;
833
+ top: 50%;
834
+ left: 50%;
835
+ width: 40px;
836
+ height: 40px;
837
+ margin: -20px 0 0 -20px;
838
+ border: 3px solid #f3f3f3;
839
+ border-top: 3px solid #667eea;
840
+ border-radius: 50%;
841
+ animation: spin 1s linear infinite;
842
+ }
843
+
844
+ @keyframes spin {
845
+ 0% { transform: rotate(0deg); }
846
+ 100% { transform: rotate(360deg); }
847
+ }
848
+
849
+ /* --- Tooltips --- */
850
+ .tooltip {
851
+ font-size: 0.875rem;
852
+ }
853
+
854
+ .tooltip-inner {
855
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
856
+ padding: 8px 12px;
857
+ border-radius: 6px;
858
+ }
859
+
860
+ /* --- Utilities --- */
861
+ html {
862
+ scroll-behavior: smooth;
863
+ }
864
+
865
+ .collapse {
866
+ transition: height 0.35s ease;
867
+ }
868
+
869
+ /* --- Footer --- */
870
+ footer {
871
+ background-color: #f8f9fa;
872
+ margin-top: 3rem;
873
+ }
874
+
875
+ /* --- Responsive Design --- */
876
+ @media (max-width: 992px) {
877
+ .main-header {
878
+ padding: 1.5rem 0;
879
+ }
880
+
881
+ .main-header h1 {
882
+ font-size: 1.75rem;
883
+ }
884
+
885
+ .section-title {
886
+ font-size: 1rem;
887
+ }
888
+
889
+ .metric-value {
890
+ font-size: 1.5rem;
891
+ }
892
+ }
893
+
894
+ @media (max-width: 768px) {
895
+ .main-header h1 {
896
+ font-size: 1.5rem;
897
+ }
898
+
899
+ .strategy-card {
900
+ margin-bottom: 0.75rem;
901
+ }
902
+
903
+ .metric-card {
904
+ margin-bottom: 1rem;
905
+ }
906
+
907
+ .nav-tabs .nav-link {
908
+ padding: 0.5rem 1rem;
909
+ font-size: 0.875rem;
910
+ }
911
+
912
+ .card-body {
913
+ padding: 1rem;
914
+ }
915
+ }
916
+
917
+ /* --- Dark Mode Support --- */
918
+ @media (prefers-color-scheme: dark) {
919
+ body {
920
+ background-color: #1a1a1a;
921
+ color: #e0e0e0;
922
+ }
923
+
924
+ .card {
925
+ background-color: #2a2a2a;
926
+ color: #e0e0e0;
927
+ }
928
+
929
+ .strategy-card {
930
+ background-color: #2a2a2a;
931
+ border-color: #3a3a3a;
932
+ }
933
+
934
+ .form-control, .form-select {
935
+ background-color: #3a3a3a;
936
+ border-color: #4a4a4a;
937
+ color: #e0e0e0;
938
+ }
939
+
940
+ .form-control:focus {
941
+ background-color: #3a3a3a;
942
+ border-color: #667eea;
943
+ color: #e0e0e0;
944
+ }
945
+
946
+ .input-group-text {
947
+ background-color: #3a3a3a;
948
+ border-color: #4a4a4a;
949
+ color: #e0e0e0;
950
+ }
951
+
952
+ .nav-tabs .nav-link {
953
+ color: #a0a0a0;
954
+ }
955
+
956
+ .nav-tabs .nav-link:hover {
957
+ background-color: #2a2a2a;
958
+ }
959
+
960
+ .alert-info {
961
+ background-color: #1e3a5f;
962
+ color: #a0c4ff;
963
+ }
964
+
965
+ .alert-light {
966
+ background-color: #2a2a2a;
967
+ color: #e0e0e0;
968
+ }
969
+
970
+ footer {
971
+ background-color: #2a2a2a;
972
+ }
973
+ }
974
+
975
+ /* --- Print Styles --- */
976
+ @media print {
977
+ .main-header {
978
+ background: none !important;
979
+ color: #000 !important;
980
+ }
981
+
982
+ .card {
983
+ box-shadow: none !important;
984
+ border: 1px solid #ddd !important;
985
+ }
986
+
987
+ .btn, .toast-container {
988
+ display: none !important;
989
+ }
990
+
991
+ .strategy-card {
992
+ border: 1px solid #ddd !important;
993
+ }
994
+ }
995
+
996
+ /* --- Layout Fixes for Graph Display --- */
997
+ /* Ensure the visualization column takes proper space */
998
+ .row > .col-lg-8 {
999
+ flex: 0 0 auto;
1000
+ width: 66.66667%;
1001
+ max-width: none; /* Remove max-width constraint */
1002
+ }
1003
+
1004
+ /* Ensure cards don't clip content */
1005
+ .card {
1006
+ overflow: visible !important;
1007
+ }
1008
+
1009
+ .card-body {
1010
+ overflow: visible !important;
1011
+ }
1012
+
1013
+ /* Fix for tab container */
1014
+ .tab-content {
1015
+ overflow: visible !important;
1016
+ }
1017
+
1018
+ /* Ensure proper spacing for graph container */
1019
+ .graph-container {
1020
+ margin: 0 -0.5rem; /* Negative margin to use full width */
1021
+ padding: 1rem 1.5rem; /* Compensate with padding */
1022
+ }
1023
+
1024
+ /* Override any Bootstrap constraints */
1025
+ .container-fluid {
1026
+ max-width: none !important;
1027
+ }
1028
+
1029
+ /* Responsive adjustments for larger screens */
1030
+ @media (min-width: 1200px) {
1031
+ .row > .col-lg-8 {
1032
+ width: 70%; /* Give more space to visualization on large screens */
1033
+ }
1034
+
1035
+ .row > .col-lg-4 {
1036
+ width: 30%;
1037
+ }
1038
+ }
1039
+
1040
+ /* Fix plotly's own responsive behavior */
1041
+ .js-plotly-plot .plotly .main-svg {
1042
+ overflow: visible !important;
1043
+ }
1044
+
1045
+ .js-plotly-plot .plotly .modebar-container {
1046
+ position: absolute;
1047
+ top: 0;
1048
+ right: 0;
1049
+ }
1050
+
1051
+ /* Ensure the alert message doesn't interfere */
1052
+ #welcome-message {
1053
+ margin-bottom: 0;
1054
+ }
1055
+
1056
+ /* Final fixes for Plotly graph display */
1057
+ .dash-graph {
1058
+ width: 100% !important;
1059
+ height: auto !important;
1060
+ }
1061
+
1062
+ .js-plotly-plot {
1063
+ width: 100% !important;
1064
+ height: auto !important;
1065
+ }
1066
+
1067
+ .plotly-graph-div {
1068
+ width: 100% !important;
1069
+ overflow: visible !important;
1070
+ }
1071
+
1072
+ /* Ensure the graph container div inside dcc.Graph has proper dimensions */
1073
+ ._dash-graph-container {
1074
+ width: 100% !important;
1075
+ overflow: visible !important;
1076
+ }
1077
+
1078
+ /* Fix for SVG container */
1079
+ .svg-container {
1080
+ width: 100% !important;
1081
+ height: auto !important;
1082
+ }
1083
+
1084
+ /* Ensure modebar doesn't interfere with layout */
1085
+ .modebar-container {
1086
+ position: absolute !important;
1087
+ z-index: 1000;
1088
+ }
1089
+
1090
+ /* Override any inline styles that might be constraining the graph */
1091
+ div[style*="height: 400px"] {
1092
+ height: auto !important;
1093
+ min-height: 400px !important;
1094
+ }
1095
+
1096
+ /* Ensure responsive behavior on window resize */
1097
+ @media screen and (max-width: 1400px) {
1098
+ .row > .col-lg-8 {
1099
+ width: 100%;
1100
+ margin-bottom: 2rem;
1101
+ }
1102
+
1103
+ .row > .col-lg-4 {
1104
+ width: 100%;
1105
+ }
1106
  }
src/visualizer.py CHANGED
@@ -454,15 +454,14 @@ def create_pipeline_figure(
454
  legend=dict(
455
  orientation="v", # Changed from horizontal to vertical
456
  yanchor="top",
457
- y=1.02, # Position at the top
458
- xanchor="right",
459
- x=1.20, # Position further to the right to accommodate more items
460
- title=dict(text="<b>Operation Types:</b>"),
461
  itemsizing="constant",
462
  tracegroupgap=0,
 
463
  ),
464
- width=2000, # Increase width to accommodate the expanded legend
465
- height=400, # Maintain current height
466
  bargap=0,
467
  bargroupgap=0,
468
  )
 
454
  legend=dict(
455
  orientation="v", # Changed from horizontal to vertical
456
  yanchor="top",
457
+ y=0.98, # Position near the top
458
+ xanchor="left",
459
+ x=1.02, # Position just outside the plot area
460
+ title=dict(text="<b>Operation Types:</b>", font=dict(size=12)),
461
  itemsizing="constant",
462
  tracegroupgap=0,
463
+ font=dict(size=10), # Smaller font for legend items
464
  ),
 
 
465
  bargap=0,
466
  bargroupgap=0,
467
  )