Spaces:
Building
Building
Update ui_generators.py
Browse files- 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
|
166 |
-
|
167 |
-
|
168 |
-
|
|
|
|
|
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 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
|
|
|
|
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()
|
|
|
|
|
|
|
214 |
|
215 |
-
|
|
|
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 =
|
226 |
logging.info("Follower stats tab: Monthly gains plot generated.")
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
html_parts.append("<p>No monthly follower gain data available or required columns missing.</p>")
|
237 |
html_parts.append("<hr/>")
|
238 |
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
|
|
|
|
|
|
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 |
-
|
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 =
|
259 |
logging.info("Follower stats tab: Seniority distribution plot generated.")
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
html_parts.append("<p>No follower seniority data available or required columns missing.</p>")
|
270 |
html_parts.append("<hr/>")
|
271 |
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
|
|
|
|
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 |
-
|
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 =
|
292 |
logging.info("Follower stats tab: Industry distribution plot generated.")
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
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
|
349 |
-
|
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
|
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
|
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 |
-
#
|
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)}.")
|