GuglielmoTor commited on
Commit
9c20604
·
verified ·
1 Parent(s): 8673558

Update ui_generators.py

Browse files
Files changed (1) hide show
  1. ui_generators.py +100 -90
ui_generators.py CHANGED
@@ -94,9 +94,9 @@ def display_main_dashboard(token_state):
94
  try:
95
  monthly_gains.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN_DT] = pd.to_datetime(monthly_gains[FOLLOWER_STATS_CATEGORY_COLUMN], errors='coerce')
96
  monthly_gains_display = monthly_gains.sort_values(by=FOLLOWER_STATS_CATEGORY_COLUMN_DT, ascending=False)
97
- latest_gain = monthly_gains_display.head(1).copy()
98
  if not latest_gain.empty:
99
- latest_gain.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN] = latest_gain[FOLLOWER_STATS_CATEGORY_COLUMN_DT].dt.strftime(UI_DATE_FORMAT)
100
  html_parts.append("<h5>Latest Monthly Follower Gain:</h5>")
101
  html_parts.append(latest_gain[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].to_html(escape=True, index=False, classes="table table-sm"))
102
  else:
@@ -147,25 +147,28 @@ def run_mentions_tab_display(token_state):
147
  html_parts.append(mentions_df_display[display_columns].head(20).to_html(escape=False, index=False, classes="table table-sm"))
148
 
149
  mentions_html_output = "\n".join(html_parts)
150
- fig = None
151
  if not mentions_df.empty and "sentiment_label" in mentions_df.columns:
152
  try:
153
  fig_plot, ax = plt.subplots(figsize=(6,4))
154
  sentiment_counts = mentions_df["sentiment_label"].value_counts()
155
  sentiment_counts.plot(kind='bar', ax=ax, color=['#4CAF50', '#FFC107', '#F44336', '#9E9E9E', '#2196F3'])
156
- ax.set_title("Mention Sentiment Distribution")
157
  ax.set_ylabel("Count")
158
  plt.xticks(rotation=45, ha='right')
159
- plt.tight_layout()
160
- fig = fig_plot
161
  logging.info("Mentions tab: Sentiment distribution plot generated.")
162
  except Exception as e:
163
  logging.error(f"Error generating mentions plot: {e}", exc_info=True)
 
164
  finally:
165
- if fig is not None and fig is not plt:
166
- plt.close(fig)
167
- elif fig is None and plt.get_fignums():
168
- plt.close('all')
 
 
169
  return mentions_html_output, fig
170
 
171
 
@@ -188,15 +191,17 @@ def run_follower_stats_tab_display(token_state):
188
  plot_seniority_dist = None
189
  plot_industry_dist = None
190
 
191
- monthly_gains_df = follower_stats_df[
192
- (follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_gains_monthly') &
193
- (follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) &
194
- (follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna()) &
195
- (follower_stats_df[FOLLOWER_STATS_PAID_COLUMN].notna())
196
- ].copy()
197
-
198
- if not monthly_gains_df.empty:
199
- try:
 
 
200
  monthly_gains_df.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN_DT] = pd.to_datetime(monthly_gains_df[FOLLOWER_STATS_CATEGORY_COLUMN], errors='coerce')
201
  monthly_gains_df_sorted_table = monthly_gains_df.sort_values(by=FOLLOWER_STATS_CATEGORY_COLUMN_DT, ascending=False)
202
 
@@ -210,96 +215,103 @@ def run_follower_stats_tab_display(token_state):
210
  plot_data = monthly_gains_df_sorted_plot.groupby('_plot_month').agg(
211
  organic=(FOLLOWER_STATS_ORGANIC_COLUMN, 'sum'),
212
  paid=(FOLLOWER_STATS_PAID_COLUMN, 'sum')
213
- ).reset_index().sort_values(by='_plot_month')
 
 
 
214
 
215
- fig_gains, ax_gains = plt.subplots(figsize=(10,5))
 
216
  ax_gains.plot(plot_data['_plot_month'], plot_data['organic'], marker='o', linestyle='-', label='Organic Gain')
217
  ax_gains.plot(plot_data['_plot_month'], plot_data['paid'], marker='x', linestyle='--', label='Paid Gain')
218
- ax_gains.set_title("Monthly Follower Gains Over Time")
219
  ax_gains.set_ylabel("Follower Count")
220
  ax_gains.set_xlabel("Month (YYYY-MM)")
221
  plt.xticks(rotation=45, ha='right')
222
  ax_gains.legend()
223
  plt.grid(True, linestyle='--', alpha=0.7)
224
  plt.tight_layout()
225
- plot_monthly_gains = fig_gains
226
  logging.info("Follower stats tab: Monthly gains plot generated.")
227
- except Exception as e:
228
- logging.error(f"Error processing or plotting monthly gains: {e}", exc_info=True)
229
- html_parts.append("<p>Error displaying monthly follower gain data.</p>")
230
- finally:
231
- if plot_monthly_gains is not None and plot_monthly_gains is not plt:
232
- plt.close(plot_monthly_gains)
233
- elif plot_monthly_gains is None and plt.get_fignums():
234
- plt.close('all')
235
- else:
236
- html_parts.append("<p>No monthly follower gain data available or required columns missing.</p>")
237
  html_parts.append("<hr/>")
238
 
239
- seniority_df = follower_stats_df[
240
- (follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_seniority') &
241
- (follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) &
242
- (follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna())
243
- ].copy()
244
- if not seniority_df.empty:
245
- try:
 
 
 
246
  seniority_df_sorted = seniority_df.sort_values(by=FOLLOWER_STATS_ORGANIC_COLUMN, ascending=False)
247
  html_parts.append("<h4>Followers by Seniority (Top 10 Organic):</h4>")
248
  html_parts.append(seniority_df_sorted[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].head(10).to_html(escape=True, index=False, classes="table table-sm"))
249
 
250
- fig_seniority, ax_seniority = plt.subplots(figsize=(8,5))
251
  top_n_seniority = seniority_df_sorted.nlargest(10, FOLLOWER_STATS_ORGANIC_COLUMN)
252
  ax_seniority.bar(top_n_seniority[FOLLOWER_STATS_CATEGORY_COLUMN], top_n_seniority[FOLLOWER_STATS_ORGANIC_COLUMN], color='skyblue')
253
- ax_seniority.set_title("Follower Distribution by Seniority (Top 10 Organic)")
254
  ax_seniority.set_ylabel("Organic Follower Count")
255
  plt.xticks(rotation=45, ha='right')
256
  plt.grid(axis='y', linestyle='--', alpha=0.7)
257
  plt.tight_layout()
258
- plot_seniority_dist = fig_seniority
259
  logging.info("Follower stats tab: Seniority distribution plot generated.")
260
- except Exception as e:
261
- logging.error(f"Error processing or plotting seniority data: {e}", exc_info=True)
262
- html_parts.append("<p>Error displaying follower seniority data.</p>")
263
- finally:
264
- if plot_seniority_dist is not None and plot_seniority_dist is not plt:
265
- plt.close(plot_seniority_dist)
266
- elif plot_seniority_dist is None and plt.get_fignums():
267
- plt.close('all')
268
- else:
269
- html_parts.append("<p>No follower seniority data available or required columns missing.</p>")
270
  html_parts.append("<hr/>")
271
 
272
- industry_df = follower_stats_df[
273
- (follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_industry') &
274
- (follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) &
275
- (follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna())
276
- ].copy()
277
- if not industry_df.empty:
278
- try:
 
 
279
  industry_df_sorted = industry_df.sort_values(by=FOLLOWER_STATS_ORGANIC_COLUMN, ascending=False)
280
  html_parts.append("<h4>Followers by Industry (Top 10 Organic):</h4>")
281
  html_parts.append(industry_df_sorted[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].head(10).to_html(escape=True, index=False, classes="table table-sm"))
282
 
283
- fig_industry, ax_industry = plt.subplots(figsize=(8,5))
284
  top_n_industry = industry_df_sorted.nlargest(10, FOLLOWER_STATS_ORGANIC_COLUMN)
285
  ax_industry.bar(top_n_industry[FOLLOWER_STATS_CATEGORY_COLUMN], top_n_industry[FOLLOWER_STATS_ORGANIC_COLUMN], color='lightcoral')
286
- ax_industry.set_title("Follower Distribution by Industry (Top 10 Organic)")
287
  ax_industry.set_ylabel("Organic Follower Count")
288
  plt.xticks(rotation=45, ha='right')
289
  plt.grid(axis='y', linestyle='--', alpha=0.7)
290
  plt.tight_layout()
291
- plot_industry_dist = fig_industry
292
  logging.info("Follower stats tab: Industry distribution plot generated.")
293
- except Exception as e:
294
- logging.error(f"Error processing or plotting industry data: {e}", exc_info=True)
295
- html_parts.append("<p>Error displaying follower industry data.</p>")
296
- finally:
297
- if plot_industry_dist is not None and plot_industry_dist is not plt:
298
- plt.close(plot_industry_dist)
299
- elif plot_industry_dist is None and plt.get_fignums():
300
- plt.close('all')
301
- else:
302
- html_parts.append("<p>No follower industry data available or required columns missing.</p>")
303
 
304
  html_parts.append("</div>")
305
  follower_html_output = "\n".join(html_parts)
@@ -318,10 +330,10 @@ def create_analytics_plot_panel(label, plot_id_str):
318
  explore_button (gr.Button): Explore/Zoom button.
319
  formula_button (gr.Button): Formula button.
320
  """
321
- with gr.Column() as panel_col:
322
- with gr.Row(equal_height=False, variant="panel"):
323
- plot_component = gr.Plot(label=label, scale=8)
324
- with gr.Column(scale=2, min_width=100):
325
  bomb_button = gr.Button(BOMB_ICON, variant="secondary", size="sm", elem_id=f"bomb_{plot_id_str}")
326
  explore_button = gr.Button(EXPLORE_ICON, variant="secondary", size="sm", elem_id=f"explore_{plot_id_str}")
327
  formula_button = gr.Button(FORMULA_ICON, variant="secondary", size="sm", elem_id=f"formula_{plot_id_str}")
@@ -333,8 +345,8 @@ def build_analytics_tab_plot_area(plot_configs):
333
  Returns a dictionary of plot UI objects.
334
  """
335
  logging.info(f"Building plot area for {len(plot_configs)} analytics plots.")
336
- plot_ui_objects = {}
337
-
338
  current_section_title = ""
339
  i = 0
340
  while i < len(plot_configs):
@@ -343,26 +355,24 @@ def build_analytics_tab_plot_area(plot_configs):
343
  # Start a new section if necessary
344
  if config1["section"] != current_section_title:
345
  current_section_title = config1["section"]
346
- gr.Markdown(f"### {current_section_title}")
347
 
348
- # Create a new row for each pair of plots or a single plot if it's the last one
349
- # or if the next plot is in a different section.
350
- with gr.Row(equal_height=False):
351
  # Process the first plot of the potential pair
352
  panel_col1, plot_comp1, bomb_btn1, explore_btn1, formula_btn1 = \
353
  create_analytics_plot_panel(config1["label"], config1["id"])
354
  plot_ui_objects[config1["id"]] = {
355
  "plot_component": plot_comp1, "bomb_button": bomb_btn1,
356
  "explore_button": explore_btn1, "formula_button": formula_btn1,
357
- "label": config1["label"], "panel_component": panel_col1
358
  }
359
  logging.debug(f"Created UI panel for plot_id: {config1['id']}")
360
- i += 1 # Move to the next plot configuration
361
 
362
- # Check if there's a second plot for this row
363
  if i < len(plot_configs):
364
  config2 = plot_configs[i]
365
- # Add to the same row ONLY if it's in the same section
366
  if config2["section"] == current_section_title:
367
  panel_col2, plot_comp2, bomb_btn2, explore_btn2, formula_btn2 = \
368
  create_analytics_plot_panel(config2["label"], config2["id"])
@@ -372,11 +382,11 @@ def build_analytics_tab_plot_area(plot_configs):
372
  "label": config2["label"], "panel_component": panel_col2
373
  }
374
  logging.debug(f"Created UI panel for plot_id: {config2['id']} in same row")
375
- i += 1 # Move to the next plot configuration
376
  # If config2 is in a new section, the row ends with config1.
377
  # config2 will be processed as the first plot in a new row in the next iteration.
378
- # If no more plots or next is in new section, row is complete.
379
-
380
  logging.info(f"Finished building plot area. Total plot objects: {len(plot_ui_objects)}")
381
  if len(plot_ui_objects) != len(plot_configs):
382
  logging.error(f"MISMATCH: Expected {len(plot_configs)} plot objects, but created {len(plot_ui_objects)}.")
 
94
  try:
95
  monthly_gains.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN_DT] = pd.to_datetime(monthly_gains[FOLLOWER_STATS_CATEGORY_COLUMN], errors='coerce')
96
  monthly_gains_display = monthly_gains.sort_values(by=FOLLOWER_STATS_CATEGORY_COLUMN_DT, ascending=False)
97
+ latest_gain = monthly_gains_display.head(1).copy()
98
  if not latest_gain.empty:
99
+ latest_gain.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN] = latest_gain[FOLLOWER_STATS_CATEGORY_COLUMN_DT].dt.strftime(UI_DATE_FORMAT)
100
  html_parts.append("<h5>Latest Monthly Follower Gain:</h5>")
101
  html_parts.append(latest_gain[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].to_html(escape=True, index=False, classes="table table-sm"))
102
  else:
 
147
  html_parts.append(mentions_df_display[display_columns].head(20).to_html(escape=False, index=False, classes="table table-sm"))
148
 
149
  mentions_html_output = "\n".join(html_parts)
150
+ fig = None
151
  if not mentions_df.empty and "sentiment_label" in mentions_df.columns:
152
  try:
153
  fig_plot, ax = plt.subplots(figsize=(6,4))
154
  sentiment_counts = mentions_df["sentiment_label"].value_counts()
155
  sentiment_counts.plot(kind='bar', ax=ax, color=['#4CAF50', '#FFC107', '#F44336', '#9E9E9E', '#2196F3'])
156
+ ax.set_title("Mention Sentiment Distribution", y=1.03) # MODIFIED: Added y parameter
157
  ax.set_ylabel("Count")
158
  plt.xticks(rotation=45, ha='right')
159
+ plt.tight_layout() # Ensure tight_layout is called
160
+ fig = fig_plot
161
  logging.info("Mentions tab: Sentiment distribution plot generated.")
162
  except Exception as e:
163
  logging.error(f"Error generating mentions plot: {e}", exc_info=True)
164
+ fig = None # Ensure fig is None on error
165
  finally:
166
+ # Only close if fig_plot was successfully created and is not the global plt context
167
+ if fig_plot and fig_plot is not plt: # fig_plot is the figure object from subplots
168
+ plt.close(fig_plot)
169
+ # Fallback if somehow a global figure was left open without being assigned to fig_plot
170
+ # elif fig is None and plt.get_fignums():
171
+ # plt.close('all') # This might be too aggressive if other plots are intended to be open
172
  return mentions_html_output, fig
173
 
174
 
 
191
  plot_seniority_dist = None
192
  plot_industry_dist = None
193
 
194
+ # Monthly Gains Plot
195
+ fig_gains_local = None # Use local var for figure to manage closing
196
+ try:
197
+ monthly_gains_df = follower_stats_df[
198
+ (follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_gains_monthly') &
199
+ (follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) &
200
+ (follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna()) &
201
+ (follower_stats_df[FOLLOWER_STATS_PAID_COLUMN].notna())
202
+ ].copy()
203
+
204
+ if not monthly_gains_df.empty:
205
  monthly_gains_df.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN_DT] = pd.to_datetime(monthly_gains_df[FOLLOWER_STATS_CATEGORY_COLUMN], errors='coerce')
206
  monthly_gains_df_sorted_table = monthly_gains_df.sort_values(by=FOLLOWER_STATS_CATEGORY_COLUMN_DT, ascending=False)
207
 
 
215
  plot_data = monthly_gains_df_sorted_plot.groupby('_plot_month').agg(
216
  organic=(FOLLOWER_STATS_ORGANIC_COLUMN, 'sum'),
217
  paid=(FOLLOWER_STATS_PAID_COLUMN, 'sum')
218
+ ).reset_index()
219
+ # Ensure months are sorted correctly for plotting after groupby
220
+ plot_data['_plot_month_dt'] = pd.to_datetime(plot_data['_plot_month'], format=UI_MONTH_FORMAT)
221
+ plot_data = plot_data.sort_values(by='_plot_month_dt')
222
 
223
+
224
+ fig_gains_local, ax_gains = plt.subplots(figsize=(10,5))
225
  ax_gains.plot(plot_data['_plot_month'], plot_data['organic'], marker='o', linestyle='-', label='Organic Gain')
226
  ax_gains.plot(plot_data['_plot_month'], plot_data['paid'], marker='x', linestyle='--', label='Paid Gain')
227
+ ax_gains.set_title("Monthly Follower Gains Over Time", y=1.03) # MODIFIED
228
  ax_gains.set_ylabel("Follower Count")
229
  ax_gains.set_xlabel("Month (YYYY-MM)")
230
  plt.xticks(rotation=45, ha='right')
231
  ax_gains.legend()
232
  plt.grid(True, linestyle='--', alpha=0.7)
233
  plt.tight_layout()
234
+ plot_monthly_gains = fig_gains_local # Assign to the return variable
235
  logging.info("Follower stats tab: Monthly gains plot generated.")
236
+ else:
237
+ html_parts.append("<p>No monthly follower gain data available or required columns missing.</p>")
238
+ except Exception as e:
239
+ logging.error(f"Error processing or plotting monthly gains: {e}", exc_info=True)
240
+ html_parts.append("<p>Error displaying monthly follower gain data.</p>")
241
+ plot_monthly_gains = None # Ensure it's None on error
242
+ finally:
243
+ if fig_gains_local and fig_gains_local is not plt: # Check local var
244
+ plt.close(fig_gains_local)
 
245
  html_parts.append("<hr/>")
246
 
247
+
248
+ # Seniority Plot
249
+ fig_seniority_local = None
250
+ try:
251
+ seniority_df = follower_stats_df[
252
+ (follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_seniority') &
253
+ (follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) &
254
+ (follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna())
255
+ ].copy()
256
+ if not seniority_df.empty:
257
  seniority_df_sorted = seniority_df.sort_values(by=FOLLOWER_STATS_ORGANIC_COLUMN, ascending=False)
258
  html_parts.append("<h4>Followers by Seniority (Top 10 Organic):</h4>")
259
  html_parts.append(seniority_df_sorted[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].head(10).to_html(escape=True, index=False, classes="table table-sm"))
260
 
261
+ fig_seniority_local, ax_seniority = plt.subplots(figsize=(8,5))
262
  top_n_seniority = seniority_df_sorted.nlargest(10, FOLLOWER_STATS_ORGANIC_COLUMN)
263
  ax_seniority.bar(top_n_seniority[FOLLOWER_STATS_CATEGORY_COLUMN], top_n_seniority[FOLLOWER_STATS_ORGANIC_COLUMN], color='skyblue')
264
+ ax_seniority.set_title("Follower Distribution by Seniority (Top 10 Organic)", y=1.03) # MODIFIED
265
  ax_seniority.set_ylabel("Organic Follower Count")
266
  plt.xticks(rotation=45, ha='right')
267
  plt.grid(axis='y', linestyle='--', alpha=0.7)
268
  plt.tight_layout()
269
+ plot_seniority_dist = fig_seniority_local
270
  logging.info("Follower stats tab: Seniority distribution plot generated.")
271
+ else:
272
+ html_parts.append("<p>No follower seniority data available or required columns missing.</p>")
273
+ except Exception as e:
274
+ logging.error(f"Error processing or plotting seniority data: {e}", exc_info=True)
275
+ html_parts.append("<p>Error displaying follower seniority data.</p>")
276
+ plot_seniority_dist = None
277
+ finally:
278
+ if fig_seniority_local and fig_seniority_local is not plt:
279
+ plt.close(fig_seniority_local)
 
280
  html_parts.append("<hr/>")
281
 
282
+ # Industry Plot
283
+ fig_industry_local = None
284
+ try:
285
+ industry_df = follower_stats_df[
286
+ (follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_industry') &
287
+ (follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) &
288
+ (follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna())
289
+ ].copy()
290
+ if not industry_df.empty:
291
  industry_df_sorted = industry_df.sort_values(by=FOLLOWER_STATS_ORGANIC_COLUMN, ascending=False)
292
  html_parts.append("<h4>Followers by Industry (Top 10 Organic):</h4>")
293
  html_parts.append(industry_df_sorted[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].head(10).to_html(escape=True, index=False, classes="table table-sm"))
294
 
295
+ fig_industry_local, ax_industry = plt.subplots(figsize=(8,5))
296
  top_n_industry = industry_df_sorted.nlargest(10, FOLLOWER_STATS_ORGANIC_COLUMN)
297
  ax_industry.bar(top_n_industry[FOLLOWER_STATS_CATEGORY_COLUMN], top_n_industry[FOLLOWER_STATS_ORGANIC_COLUMN], color='lightcoral')
298
+ ax_industry.set_title("Follower Distribution by Industry (Top 10 Organic)", y=1.03) # MODIFIED
299
  ax_industry.set_ylabel("Organic Follower Count")
300
  plt.xticks(rotation=45, ha='right')
301
  plt.grid(axis='y', linestyle='--', alpha=0.7)
302
  plt.tight_layout()
303
+ plot_industry_dist = fig_industry_local
304
  logging.info("Follower stats tab: Industry distribution plot generated.")
305
+ else:
306
+ html_parts.append("<p>No follower industry data available or required columns missing.</p>")
307
+ except Exception as e:
308
+ logging.error(f"Error processing or plotting industry data: {e}", exc_info=True)
309
+ html_parts.append("<p>Error displaying follower industry data.</p>")
310
+ plot_industry_dist = None
311
+ finally:
312
+ if fig_industry_local and fig_industry_local is not plt:
313
+ plt.close(fig_industry_local)
314
+
315
 
316
  html_parts.append("</div>")
317
  follower_html_output = "\n".join(html_parts)
 
330
  explore_button (gr.Button): Explore/Zoom button.
331
  formula_button (gr.Button): Formula button.
332
  """
333
+ with gr.Column() as panel_col: # This will be the component that gets hidden/shown by explore
334
+ with gr.Row(equal_height=False, variant="panel"): # Removed variant="compact" as it might affect spacing
335
+ plot_component = gr.Plot(label=label, scale=8) # The label here is for Gradio, not the Matplotlib title
336
+ with gr.Column(scale=2, min_width=100): # min_width for button column
337
  bomb_button = gr.Button(BOMB_ICON, variant="secondary", size="sm", elem_id=f"bomb_{plot_id_str}")
338
  explore_button = gr.Button(EXPLORE_ICON, variant="secondary", size="sm", elem_id=f"explore_{plot_id_str}")
339
  formula_button = gr.Button(FORMULA_ICON, variant="secondary", size="sm", elem_id=f"formula_{plot_id_str}")
 
345
  Returns a dictionary of plot UI objects.
346
  """
347
  logging.info(f"Building plot area for {len(plot_configs)} analytics plots.")
348
+ plot_ui_objects = {}
349
+
350
  current_section_title = ""
351
  i = 0
352
  while i < len(plot_configs):
 
355
  # Start a new section if necessary
356
  if config1["section"] != current_section_title:
357
  current_section_title = config1["section"]
358
+ gr.Markdown(f"### {current_section_title}") # Section title
359
 
360
+ # Create a new row for each pair of plots or a single plot
361
+ with gr.Row(equal_height=False): # A new row for one or two plots
 
362
  # Process the first plot of the potential pair
363
  panel_col1, plot_comp1, bomb_btn1, explore_btn1, formula_btn1 = \
364
  create_analytics_plot_panel(config1["label"], config1["id"])
365
  plot_ui_objects[config1["id"]] = {
366
  "plot_component": plot_comp1, "bomb_button": bomb_btn1,
367
  "explore_button": explore_btn1, "formula_button": formula_btn1,
368
+ "label": config1["label"], "panel_component": panel_col1 # panel_col1 is the gr.Column from create_analytics_plot_panel
369
  }
370
  logging.debug(f"Created UI panel for plot_id: {config1['id']}")
371
+ i += 1
372
 
373
+ # Check if there's a second plot for this row and if it's in the same section
374
  if i < len(plot_configs):
375
  config2 = plot_configs[i]
 
376
  if config2["section"] == current_section_title:
377
  panel_col2, plot_comp2, bomb_btn2, explore_btn2, formula_btn2 = \
378
  create_analytics_plot_panel(config2["label"], config2["id"])
 
382
  "label": config2["label"], "panel_component": panel_col2
383
  }
384
  logging.debug(f"Created UI panel for plot_id: {config2['id']} in same row")
385
+ i += 1
386
  # If config2 is in a new section, the row ends with config1.
387
  # config2 will be processed as the first plot in a new row in the next iteration.
388
+ # Row ends here. Next iteration will start a new gr.Row if needed.
389
+
390
  logging.info(f"Finished building plot area. Total plot objects: {len(plot_ui_objects)}")
391
  if len(plot_ui_objects) != len(plot_configs):
392
  logging.error(f"MISMATCH: Expected {len(plot_configs)} plot objects, but created {len(plot_ui_objects)}.")