Spaces:
Running
Running
# ui_generators.py | |
""" | |
Generates HTML content and Matplotlib plots for the Gradio UI tabs, | |
and UI components for the Analytics tab. | |
""" | |
import pandas as pd | |
import logging | |
import matplotlib.pyplot as plt | |
import matplotlib # To ensure backend is switched before any plt import from other modules if app structure changes | |
import gradio as gr # Added for UI components | |
# Switch backend for Matplotlib to Agg for Gradio compatibility | |
matplotlib.use('Agg') | |
# Assuming config.py contains all necessary constants | |
from config import ( | |
BUBBLE_POST_DATE_COLUMN_NAME, BUBBLE_MENTIONS_DATE_COLUMN_NAME, BUBBLE_MENTIONS_ID_COLUMN_NAME, | |
FOLLOWER_STATS_TYPE_COLUMN, FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, | |
FOLLOWER_STATS_PAID_COLUMN, FOLLOWER_STATS_CATEGORY_COLUMN_DT, UI_DATE_FORMAT, UI_MONTH_FORMAT | |
) | |
# Configure logging for this module if not already configured at app level | |
# logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(message)s') | |
# --- Constants for Button Icons/Text --- | |
# These are also defined/imported in app.py, ensure consistency | |
BOMB_ICON = "💣" | |
EXPLORE_ICON = "🧭" | |
FORMULA_ICON = "ƒ" | |
ACTIVE_ICON = "❌ Close" # Ensure this matches app.py | |
def display_main_dashboard(token_state): | |
"""Generates HTML for the main dashboard display using data from token_state.""" | |
if not token_state or not token_state.get("token"): | |
logging.warning("Dashboard display: Access denied. No token available.") | |
return "❌ Access denied. No token available for dashboard." | |
html_parts = ["<div style='padding:10px;'><h3>Dashboard Overview</h3>"] | |
# Display Recent Posts | |
posts_df = token_state.get("bubble_posts_df", pd.DataFrame()) | |
html_parts.append(f"<h4>Recent Posts ({len(posts_df)} in Bubble):</h4>") | |
if not posts_df.empty: | |
cols_to_show_posts = [col for col in [BUBBLE_POST_DATE_COLUMN_NAME, 'text', 'sentiment', 'summary_text', 'li_eb_label'] if col in posts_df.columns] | |
if not cols_to_show_posts: | |
html_parts.append("<p>No relevant post columns found to display.</p>") | |
else: | |
display_df_posts = posts_df.copy() | |
if BUBBLE_POST_DATE_COLUMN_NAME in display_df_posts.columns: | |
try: | |
# Ensure the date column is datetime before formatting | |
display_df_posts[BUBBLE_POST_DATE_COLUMN_NAME] = pd.to_datetime(display_df_posts[BUBBLE_POST_DATE_COLUMN_NAME], errors='coerce') | |
display_df_posts = display_df_posts.sort_values(by=BUBBLE_POST_DATE_COLUMN_NAME, ascending=False) | |
# Format for display after sorting | |
display_df_posts[BUBBLE_POST_DATE_COLUMN_NAME] = display_df_posts[BUBBLE_POST_DATE_COLUMN_NAME].dt.strftime(UI_DATE_FORMAT) | |
except Exception as e: | |
logging.error(f"Error formatting post dates for display: {e}") | |
html_parts.append("<p>Error formatting post dates.</p>") | |
html_parts.append(display_df_posts[cols_to_show_posts].head().to_html(escape=False, index=False, classes="table table-striped table-sm")) | |
else: | |
html_parts.append("<p>No posts loaded from Bubble.</p>") | |
html_parts.append("<hr/>") | |
# Display Recent Mentions | |
mentions_df = token_state.get("bubble_mentions_df", pd.DataFrame()) | |
html_parts.append(f"<h4>Recent Mentions ({len(mentions_df)} in Bubble):</h4>") | |
if not mentions_df.empty: | |
cols_to_show_mentions = [col for col in [BUBBLE_MENTIONS_DATE_COLUMN_NAME, "mention_text", "sentiment_label"] if col in mentions_df.columns] | |
if not cols_to_show_mentions: | |
html_parts.append("<p>No relevant mention columns found to display.</p>") | |
else: | |
display_df_mentions = mentions_df.copy() | |
if BUBBLE_MENTIONS_DATE_COLUMN_NAME in display_df_mentions.columns: | |
try: | |
display_df_mentions[BUBBLE_MENTIONS_DATE_COLUMN_NAME] = pd.to_datetime(display_df_mentions[BUBBLE_MENTIONS_DATE_COLUMN_NAME], errors='coerce') | |
display_df_mentions = display_df_mentions.sort_values(by=BUBBLE_MENTIONS_DATE_COLUMN_NAME, ascending=False) | |
display_df_mentions[BUBBLE_MENTIONS_DATE_COLUMN_NAME] = display_df_mentions[BUBBLE_MENTIONS_DATE_COLUMN_NAME].dt.strftime(UI_DATE_FORMAT) | |
except Exception as e: | |
logging.error(f"Error formatting mention dates for display: {e}") | |
html_parts.append("<p>Error formatting mention dates.</p>") | |
html_parts.append(display_df_mentions[cols_to_show_mentions].head().to_html(escape=False, index=False, classes="table table-striped table-sm")) | |
else: | |
html_parts.append("<p>No mentions loaded from Bubble.</p>") | |
html_parts.append("<hr/>") | |
# Display Follower Statistics Summary | |
follower_stats_df = token_state.get("bubble_follower_stats_df", pd.DataFrame()) | |
html_parts.append(f"<h4>Follower Statistics ({len(follower_stats_df)} entries in Bubble):</h4>") | |
if not follower_stats_df.empty: | |
monthly_gains = follower_stats_df[follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_gains_monthly'].copy() | |
if not monthly_gains.empty and FOLLOWER_STATS_CATEGORY_COLUMN in monthly_gains.columns and \ | |
FOLLOWER_STATS_ORGANIC_COLUMN in monthly_gains.columns and FOLLOWER_STATS_PAID_COLUMN in monthly_gains.columns: | |
try: | |
monthly_gains.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN_DT] = pd.to_datetime(monthly_gains[FOLLOWER_STATS_CATEGORY_COLUMN], errors='coerce') | |
monthly_gains_display = monthly_gains.sort_values(by=FOLLOWER_STATS_CATEGORY_COLUMN_DT, ascending=False) | |
latest_gain = monthly_gains_display.head(1).copy() | |
if not latest_gain.empty: | |
latest_gain.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN] = latest_gain[FOLLOWER_STATS_CATEGORY_COLUMN_DT].dt.strftime(UI_DATE_FORMAT) | |
html_parts.append("<h5>Latest Monthly Follower Gain:</h5>") | |
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")) | |
else: | |
html_parts.append("<p>No valid monthly follower gain data to display after processing.</p>") | |
except Exception as e: | |
logging.error(f"Error formatting follower gain dates for display: {e}", exc_info=True) | |
html_parts.append("<p>Error displaying monthly follower gain data.</p>") | |
else: | |
html_parts.append("<p>No monthly follower gain data or required columns are missing.</p>") | |
demographics_count = len(follower_stats_df[follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] != 'follower_gains_monthly']) | |
html_parts.append(f"<p>Total demographic entries (seniority, industry, etc.): {demographics_count}</p>") | |
else: | |
html_parts.append("<p>No follower statistics loaded from Bubble.</p>") | |
html_parts.append("</div>") | |
return "".join(html_parts) | |
def run_mentions_tab_display(token_state): | |
"""Generates HTML and a plot for the Mentions tab.""" | |
logging.info("Updating Mentions Tab display.") | |
if not token_state or not token_state.get("token"): | |
logging.warning("Mentions tab: Access denied. No token.") | |
return "❌ Access denied. No token available for mentions.", None | |
mentions_df = token_state.get("bubble_mentions_df", pd.DataFrame()) | |
if mentions_df.empty: | |
logging.info("Mentions tab: No mentions data in Bubble.") | |
return "<p style='text-align:center;'>No mentions data in Bubble. Try syncing.</p>", None | |
html_parts = ["<h3 style='text-align:center;'>Recent Mentions</h3>"] | |
display_columns = [col for col in [BUBBLE_MENTIONS_DATE_COLUMN_NAME, "mention_text", "sentiment_label", BUBBLE_MENTIONS_ID_COLUMN_NAME] if col in mentions_df.columns] | |
mentions_df_display = mentions_df.copy() | |
if BUBBLE_MENTIONS_DATE_COLUMN_NAME in mentions_df_display.columns: | |
try: | |
mentions_df_display[BUBBLE_MENTIONS_DATE_COLUMN_NAME] = pd.to_datetime(mentions_df_display[BUBBLE_MENTIONS_DATE_COLUMN_NAME], errors='coerce') | |
mentions_df_display = mentions_df_display.sort_values(by=BUBBLE_MENTIONS_DATE_COLUMN_NAME, ascending=False) | |
mentions_df_display[BUBBLE_MENTIONS_DATE_COLUMN_NAME] = mentions_df_display[BUBBLE_MENTIONS_DATE_COLUMN_NAME].dt.strftime(UI_DATE_FORMAT) | |
except Exception as e: | |
logging.error(f"Error formatting mention dates for tab display: {e}") | |
html_parts.append("<p>Error formatting mention dates.</p>") | |
if not display_columns or mentions_df_display[display_columns].empty: | |
html_parts.append("<p>Required columns for mentions display are missing or no data after processing.</p>") | |
else: | |
html_parts.append(mentions_df_display[display_columns].head(20).to_html(escape=False, index=False, classes="table table-sm")) | |
mentions_html_output = "\n".join(html_parts) | |
fig = None | |
fig_plot_local = None | |
if not mentions_df.empty and "sentiment_label" in mentions_df.columns: | |
try: | |
fig_plot_local, ax = plt.subplots(figsize=(6,4)) # Keep figsize for aspect ratio | |
sentiment_counts = mentions_df["sentiment_label"].value_counts() | |
sentiment_counts.plot(kind='bar', ax=ax, color=['#4CAF50', '#FFC107', '#F44336', '#9E9E9E', '#2196F3']) | |
ax.set_title("Mention Sentiment Distribution", y=1.03) | |
ax.set_ylabel("Count") | |
plt.xticks(rotation=45, ha='right') | |
plt.tight_layout() | |
fig_plot_local.subplots_adjust(top=0.90) | |
fig = fig_plot_local | |
logging.info("Mentions tab: Sentiment distribution plot generated.") | |
except Exception as e: | |
logging.error(f"Error generating mentions plot: {e}", exc_info=True) | |
fig = None | |
finally: | |
# Ensure plt.close is called on the figure object, not plt itself if it's not the same | |
if fig_plot_local and fig_plot_local is not plt: # Check if fig_plot_local is a Figure object | |
plt.close(fig_plot_local) | |
return mentions_html_output, fig | |
def run_follower_stats_tab_display(token_state): | |
"""Generates HTML and plots for the Follower Stats tab.""" | |
logging.info("Updating Follower Stats Tab display.") | |
if not token_state or not token_state.get("token"): | |
logging.warning("Follower stats tab: Access denied. No token.") | |
return "❌ Access denied. No token available for follower stats.", None, None, None | |
follower_stats_df_orig = token_state.get("bubble_follower_stats_df", pd.DataFrame()) | |
if follower_stats_df_orig.empty: | |
logging.info("Follower stats tab: No follower stats data in Bubble.") | |
return "<p style='text-align:center;'>No follower stats data in Bubble. Try syncing.</p>", None, None, None | |
follower_stats_df = follower_stats_df_orig.copy() | |
html_parts = ["<div style='padding:10px;'><h3 style='text-align:center;'>Follower Statistics Overview</h3>"] | |
plot_monthly_gains = None | |
plot_seniority_dist = None | |
plot_industry_dist = None | |
# Monthly Gains Plot | |
fig_gains_local = None | |
try: | |
monthly_gains_df = follower_stats_df[ | |
(follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_gains_monthly') & | |
(follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) & | |
(follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna()) & | |
(follower_stats_df[FOLLOWER_STATS_PAID_COLUMN].notna()) | |
].copy() | |
if not monthly_gains_df.empty: | |
monthly_gains_df.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN_DT] = pd.to_datetime(monthly_gains_df[FOLLOWER_STATS_CATEGORY_COLUMN], errors='coerce') | |
monthly_gains_df_sorted_table = monthly_gains_df.sort_values(by=FOLLOWER_STATS_CATEGORY_COLUMN_DT, ascending=False) | |
html_parts.append("<h4>Monthly Follower Gains (Last 13 Months):</h4>") | |
table_display_df = monthly_gains_df_sorted_table.copy() | |
table_display_df.loc[:,FOLLOWER_STATS_CATEGORY_COLUMN] = table_display_df[FOLLOWER_STATS_CATEGORY_COLUMN_DT].dt.strftime(UI_MONTH_FORMAT) | |
html_parts.append(table_display_df[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].head(13).to_html(escape=True, index=False, classes="table table-sm")) | |
monthly_gains_df_sorted_plot = monthly_gains_df.sort_values(by=FOLLOWER_STATS_CATEGORY_COLUMN_DT, ascending=True).copy() | |
monthly_gains_df_sorted_plot.loc[:, '_plot_month'] = monthly_gains_df_sorted_plot[FOLLOWER_STATS_CATEGORY_COLUMN_DT].dt.strftime(UI_MONTH_FORMAT) | |
plot_data = monthly_gains_df_sorted_plot.groupby('_plot_month').agg( | |
organic=(FOLLOWER_STATS_ORGANIC_COLUMN, 'sum'), | |
paid=(FOLLOWER_STATS_PAID_COLUMN, 'sum') | |
).reset_index() | |
plot_data['_plot_month_dt'] = pd.to_datetime(plot_data['_plot_month'], format=UI_MONTH_FORMAT) # Ensure correct month format | |
plot_data = plot_data.sort_values(by='_plot_month_dt') | |
fig_gains_local, ax_gains = plt.subplots(figsize=(10,5)) # Keep figsize for aspect ratio | |
ax_gains.plot(plot_data['_plot_month'], plot_data['organic'], marker='o', linestyle='-', label='Organic Gain') | |
ax_gains.plot(plot_data['_plot_month'], plot_data['paid'], marker='x', linestyle='--', label='Paid Gain') | |
ax_gains.set_title("Monthly Follower Gains Over Time", y=1.03) | |
ax_gains.set_ylabel("Follower Count") | |
ax_gains.set_xlabel("Month (YYYY-MM)") | |
plt.xticks(rotation=45, ha='right') | |
ax_gains.legend() | |
plt.grid(True, linestyle='--', alpha=0.7) | |
plt.tight_layout() | |
fig_gains_local.subplots_adjust(top=0.90) | |
plot_monthly_gains = fig_gains_local | |
logging.info("Follower stats tab: Monthly gains plot generated.") | |
else: | |
html_parts.append("<p>No monthly follower gain data available or required columns missing.</p>") | |
except Exception as e: | |
logging.error(f"Error processing or plotting monthly gains: {e}", exc_info=True) | |
html_parts.append("<p>Error displaying monthly follower gain data.</p>") | |
plot_monthly_gains = None | |
finally: | |
if fig_gains_local and fig_gains_local is not plt: | |
plt.close(fig_gains_local) | |
html_parts.append("<hr/>") | |
# Seniority Plot | |
fig_seniority_local = None | |
try: | |
seniority_df = follower_stats_df[ | |
(follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_seniority') & | |
(follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) & | |
(follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna()) | |
].copy() | |
if not seniority_df.empty: | |
seniority_df_sorted = seniority_df.sort_values(by=FOLLOWER_STATS_ORGANIC_COLUMN, ascending=False) | |
html_parts.append("<h4>Followers by Seniority (Top 10 Organic):</h4>") | |
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")) | |
fig_seniority_local, ax_seniority = plt.subplots(figsize=(8,5)) # Keep figsize for aspect ratio | |
top_n_seniority = seniority_df_sorted.nlargest(10, FOLLOWER_STATS_ORGANIC_COLUMN) | |
ax_seniority.bar(top_n_seniority[FOLLOWER_STATS_CATEGORY_COLUMN], top_n_seniority[FOLLOWER_STATS_ORGANIC_COLUMN], color='skyblue') | |
ax_seniority.set_title("Follower Distribution by Seniority (Top 10 Organic)", y=1.03) | |
ax_seniority.set_ylabel("Organic Follower Count") | |
plt.xticks(rotation=45, ha='right') | |
plt.grid(axis='y', linestyle='--', alpha=0.7) | |
plt.tight_layout() | |
fig_seniority_local.subplots_adjust(top=0.88) | |
plot_seniority_dist = fig_seniority_local | |
logging.info("Follower stats tab: Seniority distribution plot generated.") | |
else: | |
html_parts.append("<p>No follower seniority data available or required columns missing.</p>") | |
except Exception as e: | |
logging.error(f"Error processing or plotting seniority data: {e}", exc_info=True) | |
html_parts.append("<p>Error displaying follower seniority data.</p>") | |
plot_seniority_dist = None | |
finally: | |
if fig_seniority_local and fig_seniority_local is not plt: | |
plt.close(fig_seniority_local) | |
html_parts.append("<hr/>") | |
# Industry Plot | |
fig_industry_local = None | |
try: | |
industry_df = follower_stats_df[ | |
(follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_industry') & | |
(follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) & | |
(follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna()) | |
].copy() | |
if not industry_df.empty: | |
industry_df_sorted = industry_df.sort_values(by=FOLLOWER_STATS_ORGANIC_COLUMN, ascending=False) | |
html_parts.append("<h4>Followers by Industry (Top 10 Organic):</h4>") | |
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")) | |
fig_industry_local, ax_industry = plt.subplots(figsize=(8,5)) # Keep figsize for aspect ratio | |
top_n_industry = industry_df_sorted.nlargest(10, FOLLOWER_STATS_ORGANIC_COLUMN) | |
ax_industry.bar(top_n_industry[FOLLOWER_STATS_CATEGORY_COLUMN], top_n_industry[FOLLOWER_STATS_ORGANIC_COLUMN], color='lightcoral') | |
ax_industry.set_title("Follower Distribution by Industry (Top 10 Organic)", y=1.03) | |
ax_industry.set_ylabel("Organic Follower Count") | |
plt.xticks(rotation=45, ha='right') | |
plt.grid(axis='y', linestyle='--', alpha=0.7) | |
plt.tight_layout() | |
fig_industry_local.subplots_adjust(top=0.88) | |
plot_industry_dist = fig_industry_local | |
logging.info("Follower stats tab: Industry distribution plot generated.") | |
else: | |
html_parts.append("<p>No follower industry data available or required columns missing.</p>") | |
except Exception as e: | |
logging.error(f"Error processing or plotting industry data: {e}", exc_info=True) | |
html_parts.append("<p>Error displaying follower industry data.</p>") | |
plot_industry_dist = None | |
finally: | |
if fig_industry_local and fig_industry_local is not plt: | |
plt.close(fig_industry_local) | |
html_parts.append("</div>") | |
follower_html_output = "\n".join(html_parts) | |
return follower_html_output, plot_monthly_gains, plot_seniority_dist, plot_industry_dist | |
def create_analytics_plot_panel(plot_label_str, plot_id_str): | |
""" | |
Creates an individual plot panel with its plot component and action buttons. | |
Plot title and action buttons are on the same row. | |
Returns the panel (Column), plot component, and button components. | |
""" | |
# Icons are defined globally or imported. For this function, ensure they are accessible. | |
# If not using from config directly here, you might need to pass them or use fixed strings. | |
# Using fixed strings as a fallback if import fails, though they should be available via app.py's import. | |
local_bomb_icon, local_explore_icon, local_formula_icon = BOMB_ICON, EXPLORE_ICON, FORMULA_ICON | |
with gr.Column(visible=True) as panel_component: # Main container for this plot | |
with gr.Row(variant="compact"): | |
gr.Markdown(f"#### {plot_label_str}") # Plot title (scale might help balance) | |
with gr.Row(elem_classes="plot-actions", scale=1): # Action buttons container, give it some min_width | |
bomb_button = gr.Button(value=local_bomb_icon, variant="secondary", size="sm", min_width=30, elem_id=f"bomb_btn_{plot_id_str}") | |
formula_button = gr.Button(value=local_formula_icon, variant="secondary", size="sm", min_width=30, elem_id=f"formula_btn_{plot_id_str}") | |
explore_button = gr.Button(value=local_explore_icon, variant="secondary", size="sm", min_width=30, elem_id=f"explore_btn_{plot_id_str}") | |
# MODIFIED: Added height to gr.Plot for consistent sizing | |
plot_component = gr.Plot(label=plot_label_str, show_label=False) # Adjust height as needed | |
logging.debug(f"Created analytics panel for: {plot_label_str} (ID: {plot_id_str}) with fixed plot height.") | |
return panel_component, plot_component, bomb_button, explore_button, formula_button | |
def build_analytics_tab_plot_area(plot_configs): | |
""" | |
Builds the main plot area for the Analytics tab, arranging plot panels into rows of two, | |
with section titles appearing before their respective plots. | |
Returns a tuple: | |
- plot_ui_objects (dict): Dictionary of plot UI objects. | |
- section_titles_map (dict): Dictionary mapping section names to their gr.Markdown title components. | |
""" | |
logging.info(f"Building plot area for {len(plot_configs)} analytics plots with interleaved section titles.") | |
plot_ui_objects = {} | |
section_titles_map = {} | |
last_rendered_section = None | |
idx = 0 | |
while idx < len(plot_configs): | |
current_plot_config = plot_configs[idx] | |
current_section_name = current_plot_config["section"] | |
# Render section title if it's new for this block of plots | |
if current_section_name != last_rendered_section: | |
if current_section_name not in section_titles_map: | |
# Create the Markdown component for the section title | |
section_md_component = gr.Markdown(f"## {current_section_name}", visible=True) | |
section_titles_map[current_section_name] = section_md_component | |
logging.debug(f"Rendered and stored Markdown for section: {current_section_name}") | |
# No 'else' needed here for visibility, as it's handled by click handlers if sections are hidden/shown. | |
# The component is created once and its visibility is controlled elsewhere. | |
last_rendered_section = current_section_name | |
with gr.Row(equal_height=True): # Row for one or two plots. equal_height=False allows plots to define their height. | |
# --- Process the first plot in the row (config1) --- | |
config1 = plot_configs[idx] | |
# Safety check for section consistency (should always pass if configs are ordered by section) | |
if config1["section"] != current_section_name: | |
logging.warning(f"Plot {config1['id']} section mismatch. Expected {current_section_name}, got {config1['section']}. This might affect layout if a new section title was expected.") | |
# If a new section starts unexpectedly, ensure its title is created if missing | |
if config1["section"] not in section_titles_map: | |
sec_md = gr.Markdown(f"### {config1['section']}", visible=True) # Create and make visible | |
section_titles_map[config1['section']] = sec_md | |
last_rendered_section = config1["section"] # Update the current section context | |
panel_col1, plot_comp1, bomb_btn1, explore_btn1, formula_btn1 = \ | |
create_analytics_plot_panel(config1["label"], config1["id"]) | |
plot_ui_objects[config1["id"]] = { | |
"plot_component": plot_comp1, "bomb_button": bomb_btn1, | |
"explore_button": explore_btn1, "formula_button": formula_btn1, | |
"label": config1["label"], "panel_component": panel_col1, # This is the gr.Column containing the plot and its actions | |
"section": config1["section"] | |
} | |
logging.debug(f"Created UI panel for plot_id: {config1['id']} in section {config1['section']}") | |
idx += 1 | |
# --- Process the second plot in the row (config2), if applicable --- | |
if idx < len(plot_configs): | |
config2 = plot_configs[idx] | |
# Only add to the same row if it's part of the same section | |
if config2["section"] == current_section_name: | |
panel_col2, plot_comp2, bomb_btn2, explore_btn2, formula_btn2 = \ | |
create_analytics_plot_panel(config2["label"], config2["id"]) | |
plot_ui_objects[config2["id"]] = { | |
"plot_component": plot_comp2, "bomb_button": bomb_btn2, | |
"explore_button": explore_btn2, "formula_button": formula_btn2, | |
"label": config2["label"], "panel_component": panel_col2, | |
"section": config2["section"] | |
} | |
logging.debug(f"Created UI panel for plot_id: {config2['id']} in same row, section {config2['section']}") | |
idx += 1 | |
# If the next plot is in a new section, it will be handled in the next iteration of the while loop, | |
# starting with a new section title and a new gr.Row. | |
logging.info(f"Finished building plot area. Total plot objects: {len(plot_ui_objects)}. Section titles created: {len(section_titles_map)}") | |
if len(plot_ui_objects) != len(plot_configs): | |
logging.error(f"MISMATCH: Expected {len(plot_configs)} plot objects, but created {len(plot_ui_objects)}.") | |
return plot_ui_objects, section_titles_map | |