evijit HF Staff commited on
Commit
8c60635
·
verified ·
1 Parent(s): 7064a74

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +130 -127
app.py CHANGED
@@ -1,34 +1,31 @@
1
- # --- START OF FINAL, POLISHED FILE app.py ---
2
 
3
  import gradio as gr
4
  import pandas as pd
5
  import plotly.express as px
6
  import time
7
  from datasets import load_dataset
8
- # Using the stable, community-built RangeSlider component
9
- from gradio_rangeslider import RangeSlider
10
 
11
  # --- Constants ---
12
- PARAM_CHOICES = ['< 1B', '1B', '5B', '12B', '32B', '64B', '128B', '256B', '> 500B']
13
- PARAM_CHOICES_DEFAULT_INDICES = (0, len(PARAM_CHOICES) - 1)
14
-
15
  TOP_K_CHOICES = list(range(5, 51, 5))
16
- HF_DATASET_ID = "evijit/modelverse_daily_data"
17
- TAG_FILTER_CHOICES = [ "Audio & Speech", "Time series", "Robotics", "Music", "Video", "Images", "Text", "Biomedical", "Sciences" ]
18
- PIPELINE_TAGS = [ 'text-generation', 'text-to-image', 'text-classification', 'text2text-generation', 'audio-to-audio', 'feature-extraction', 'image-classification', 'translation', 'reinforcement-learning', 'fill-mask', 'text-to-speech', 'automatic-speech-recognition', 'image-text-to-text', 'token-classification', 'sentence-similarity', 'question-answering', 'image-feature-extraction', 'summarization', 'zero-shot-image-classification', 'object-detection', 'image-segmentation', 'image-to-image', 'image-to-text', 'audio-classification', 'visual-question-answering', 'text-to-video', 'zero-shot-classification', 'depth-estimation', 'text-ranking', 'image-to-video', 'multiple-choice', 'unconditional-image-generation', 'video-classification', 'text-to-audio', 'time-series-forecasting', 'any-to-any', 'video-text-to-text', 'table-question-answering' ]
19
-
20
-
21
- def load_models_data():
22
- overall_start_time = time.time()
 
 
23
  print(f"Attempting to load dataset from Hugging Face Hub: {HF_DATASET_ID}")
24
  try:
 
25
  dataset_dict = load_dataset(HF_DATASET_ID)
 
26
  df = dataset_dict[list(dataset_dict.keys())[0]].to_pandas()
27
- if 'params' in df.columns:
28
- df['params'] = pd.to_numeric(df['params'], errors='coerce').fillna(0)
29
- else:
30
- df['params'] = 0
31
- msg = f"Successfully loaded dataset in {time.time() - overall_start_time:.2f}s."
32
  print(msg)
33
  return df, True, msg
34
  except Exception as e:
@@ -36,138 +33,135 @@ def load_models_data():
36
  print(err_msg)
37
  return pd.DataFrame(), False, err_msg
38
 
39
- def get_param_range_values(param_range_labels):
40
- min_label, max_label = param_range_labels
41
- min_val = 0.0 if '<' in min_label else float(min_label.replace('B', ''))
42
- max_val = float('inf') if '>' in max_label else float(max_label.replace('B', ''))
43
- return min_val, max_val
44
-
45
- def make_treemap_data(df, count_by, top_k=25, tag_filter=None, pipeline_filter=None, param_range=None, skip_orgs=None):
46
- if df is None or df.empty: return pd.DataFrame()
47
  filtered_df = df.copy()
48
- col_map = { "Audio & Speech": "is_audio_speech", "Music": "has_music", "Robotics": "has_robot", "Biomedical": "is_biomed", "Time series": "has_series", "Sciences": "has_science", "Video": "has_video", "Images": "has_image", "Text": "has_text" }
49
- if tag_filter and tag_filter in col_map and col_map[tag_filter] in filtered_df.columns:
50
- filtered_df = filtered_df[filtered_df[col_map[tag_filter]]]
51
- if pipeline_filter and "pipeline_tag" in filtered_df.columns:
52
- filtered_df = filtered_df[filtered_df["pipeline_tag"].astype(str) == pipeline_filter]
53
- if param_range:
54
- min_params, max_params = get_param_range_values(param_range)
55
- is_default_range = (param_range[0] == PARAM_CHOICES[0] and param_range[1] == PARAM_CHOICES[-1])
56
- if not is_default_range and 'params' in filtered_df.columns:
57
- if min_params is not None: filtered_df = filtered_df[filtered_df['params'] >= min_params]
58
- if max_params is not None and max_params != float('inf'): filtered_df = filtered_df[filtered_df['params'] < max_params]
 
 
 
59
  if skip_orgs and len(skip_orgs) > 0 and "organization" in filtered_df.columns:
60
  filtered_df = filtered_df[~filtered_df["organization"].isin(skip_orgs)]
61
- if filtered_df.empty: return pd.DataFrame()
62
- if count_by not in filtered_df.columns: filtered_df[count_by] = 0.0
 
 
 
 
 
63
  filtered_df[count_by] = pd.to_numeric(filtered_df[count_by], errors='coerce').fillna(0.0)
 
 
64
  org_totals = filtered_df.groupby("organization")[count_by].sum().nlargest(top_k, keep='first')
65
  top_orgs_list = org_totals.index.tolist()
 
 
66
  treemap_data = filtered_df[filtered_df["organization"].isin(top_orgs_list)][["id", "organization", count_by]].copy()
67
- treemap_data["root"] = "models"
68
  return treemap_data
69
 
70
  def create_treemap(treemap_data, count_by, title=None):
 
71
  if treemap_data.empty:
 
72
  fig = px.treemap(names=["No data matches filters"], parents=[""], values=[1])
73
  fig.update_layout(title="No data matches the selected filters", margin=dict(t=50, l=25, r=25, b=25))
74
  return fig
75
- fig = px.treemap(treemap_data, path=["root", "organization", "id"], values=count_by, title=title, color_discrete_sequence=px.colors.qualitative.Plotly)
 
 
 
76
  fig.update_layout(margin=dict(t=50, l=25, r=25, b=25))
77
- fig.update_traces(textinfo="label+value+percent root", hovertemplate="<b>%{label}</b><br>%{value:,} " + count_by + "<br>%{percentRoot:.2%} of total<extra></extra>")
 
 
 
78
  return fig
79
 
80
- # --- FINAL, CORRECTED CSS ---
81
- custom_css = """
82
- /* Hide the extra UI elements from the RangeSlider component */
83
- #param-slider-wrapper .head,
84
- #param-slider-wrapper div[data-testid="range-slider"] > span {
85
- display: none !important;
86
- }
87
-
88
- /*
89
- THIS IS THE KEY FIX:
90
- We target all the individual component containers (divs with class .block)
91
- that are *direct children* of our custom-classed group.
92
-
93
- This removes the "box-in-a-box" effect by making the inner component
94
- containers transparent. The parent gr.Group now acts as the single card,
95
- which is exactly what we want.
96
- */
97
- .model-parameters-group > .block {
98
- background: none !important;
99
- border: none !important;
100
- box-shadow: none !important;
101
- }
102
- """
103
-
104
- with gr.Blocks(title="🤗 ModelVerse Explorer", fill_width=True, css=custom_css) as demo:
105
- models_data_state = gr.State(pd.DataFrame())
106
  loading_complete_state = gr.State(False)
107
 
108
  with gr.Row():
109
- gr.Markdown("# 🤗 ModelVerse Explorer")
110
 
111
  with gr.Row():
112
  with gr.Column(scale=1):
 
 
 
 
 
 
113
 
114
- # This section remains un-grouped for a consistent flat look
115
- count_by_dropdown = gr.Dropdown(label="Metric", choices=[("Downloads (last 30 days)", "downloads"), ("Downloads (All Time)", "downloadsAllTime"), ("Likes", "likes")], value="downloads")
116
- filter_choice_radio = gr.Radio(label="Filter Type", choices=["None", "Tag Filter", "Pipeline Filter"], value="None")
 
 
117
 
118
- tag_filter_dropdown = gr.Dropdown(label="Select Tag", choices=TAG_FILTER_CHOICES, value=None, visible=False)
119
- pipeline_filter_dropdown = gr.Dropdown(label="Select Pipeline Tag", choices=PIPELINE_TAGS, value=None, visible=False)
120
-
121
- # This group's styling will be modified by the custom CSS
122
- with gr.Group(elem_classes="model-parameters-group"):
123
- gr.Markdown("<div style='font-weight: 500;'>Model Parameters</div>")
124
- param_range_slider = RangeSlider(
125
- minimum=0,
126
- maximum=len(PARAM_CHOICES) - 1,
127
- value=PARAM_CHOICES_DEFAULT_INDICES,
128
- step=1,
129
- label=None,
130
- show_label=False,
131
- elem_id="param-slider-wrapper"
132
- )
133
- param_range_display = gr.Markdown(f"Range: `{PARAM_CHOICES[0]}` to `{PARAM_CHOICES[-1]}`")
134
 
135
- # This section remains un-grouped
136
- top_k_dropdown = gr.Dropdown(label="Number of Top Organizations", choices=TOP_K_CHOICES, value=25)
137
- skip_orgs_textbox = gr.Textbox(label="Organizations to Skip (comma-separated)", value="TheBloke,MaziyarPanahi,unsloth,modularai,Gensyn,bartowski")
 
138
 
139
- generate_plot_button = gr.Button(value="Generate Plot", variant="primary", interactive=False)
 
 
 
 
140
 
141
  with gr.Column(scale=3):
 
142
  plot_output = gr.Plot()
143
  status_message_md = gr.Markdown("Initializing...")
144
  data_info_md = gr.Markdown("")
145
 
146
- def update_param_display(value: tuple):
147
- min_idx, max_idx = int(value[0]), int(value[1])
148
- return f"Range: `{PARAM_CHOICES[min_idx]}` to `{PARAM_CHOICES[max_idx]}`"
149
-
150
- param_range_slider.change(update_param_display, param_range_slider, param_range_display)
151
-
152
- def _update_button_interactivity(is_loaded_flag): return gr.update(interactive=is_loaded_flag)
153
- loading_complete_state.change(fn=_update_button_interactivity, inputs=loading_complete_state, outputs=generate_plot_button)
154
-
155
- def _toggle_filters_visibility(choice):
156
- return gr.update(visible=choice == "Tag Filter"), gr.update(visible=choice == "Pipeline Filter")
157
- filter_choice_radio.change(fn=_toggle_filters_visibility, inputs=filter_choice_radio, outputs=[tag_filter_dropdown, pipeline_filter_dropdown])
158
 
159
  def ui_load_data_controller(progress=gr.Progress()):
 
160
  progress(0, desc=f"Loading dataset '{HF_DATASET_ID}'...")
161
  try:
162
- current_df, load_success_flag, status_msg_from_load = load_models_data()
163
  if load_success_flag:
164
  progress(0.9, desc="Processing data...")
 
165
  date_display = "Pre-processed (date unavailable)"
166
  if 'data_download_timestamp' in current_df.columns and pd.notna(current_df['data_download_timestamp'].iloc[0]):
167
  ts = pd.to_datetime(current_df['data_download_timestamp'].iloc[0], utc=True)
168
  date_display = ts.strftime('%B %d, %Y, %H:%M:%S %Z')
169
- param_count = (current_df['params'] > 0).sum() if 'params' in current_df.columns else 0
170
- data_info_text = f"### Data Information\n- Source: `{HF_DATASET_ID}`\n- Status: {status_msg_from_load}\n- Total models loaded: {len(current_df):,}\n- Models with parameter counts: {param_count:,}\n- Data as of: {date_display}\n"
 
 
 
 
 
 
171
  status_msg_ui = "Data loaded. Ready to generate plot."
172
  else:
173
  data_info_text = f"### Data Load Failed\n- {status_msg_from_load}"
@@ -177,52 +171,61 @@ with gr.Blocks(title="🤗 ModelVerse Explorer", fill_width=True, css=custom_css
177
  data_info_text = f"### Critical Error\n- {status_msg_ui}"
178
  load_success_flag = False
179
  print(f"Critical error in ui_load_data_controller: {e}")
 
180
  return current_df, load_success_flag, data_info_text, status_msg_ui
181
 
182
- def ui_generate_plot_controller(metric_choice, filter_type, tag_choice, pipeline_choice,
183
- param_range_indices, k_orgs, skip_orgs_input, df_current_models, progress=gr.Progress()):
184
- if df_current_models is None or df_current_models.empty:
185
- return create_treemap(pd.DataFrame(), metric_choice, "Error: Model Data Not Loaded"), "Model data is not loaded."
 
186
 
187
  progress(0.1, desc="Preparing data...")
188
- tag_to_use = tag_choice if filter_type == "Tag Filter" else None
189
- pipeline_to_use = pipeline_choice if filter_type == "Pipeline Filter" else None
190
  orgs_to_skip = [org.strip() for org in skip_orgs_input.split(',') if org.strip()]
191
 
192
- min_label = PARAM_CHOICES[int(param_range_indices[0])]
193
- max_label = PARAM_CHOICES[int(param_range_indices[1])]
194
- param_labels_for_filtering = [min_label, max_label]
195
-
196
- treemap_df = make_treemap_data(df_current_models, metric_choice, k_orgs, tag_to_use, pipeline_to_use, param_labels_for_filtering, orgs_to_skip)
197
 
198
  progress(0.7, desc="Generating plot...")
 
199
  title_labels = {"downloads": "Downloads (last 30 days)", "downloadsAllTime": "Downloads (All Time)", "likes": "Likes"}
200
- chart_title = f"HuggingFace Models - {title_labels.get(metric_choice, metric_choice)} by Organization"
201
  plotly_fig = create_treemap(treemap_df, metric_choice, chart_title)
202
 
 
203
  if treemap_df.empty:
204
  plot_stats_md = "No data matches the selected filters. Please try different options."
205
  else:
206
  total_items_in_plot = len(treemap_df['id'].unique())
207
  total_value_in_plot = treemap_df[metric_choice].sum()
208
- plot_stats_md = f"## Plot Statistics\n- **Models shown**: {total_items_in_plot:,}\n- **Total {metric_choice}**: {int(total_value_in_plot):,}"
 
209
  return plotly_fig, plot_stats_md
210
 
 
 
 
211
  demo.load(
212
  fn=ui_load_data_controller,
213
  inputs=[],
214
- outputs=[models_data_state, loading_complete_state, data_info_md, status_message_md]
215
  )
216
 
 
 
 
 
 
 
 
 
217
  generate_plot_button.click(
218
  fn=ui_generate_plot_controller,
219
- inputs=[count_by_dropdown, filter_choice_radio, tag_filter_dropdown, pipeline_filter_dropdown,
220
- param_range_slider, top_k_dropdown, skip_orgs_textbox, models_data_state],
221
  outputs=[plot_output, status_message_md]
222
  )
223
 
224
  if __name__ == "__main__":
225
- print(f"Application starting...")
226
- demo.queue().launch()
227
-
228
- # --- END OF FINAL, POLISHED FILE app.py ---
 
1
+ # --- app.py (Dataverse Explorer) ---
2
 
3
  import gradio as gr
4
  import pandas as pd
5
  import plotly.express as px
6
  import time
7
  from datasets import load_dataset
 
 
8
 
9
  # --- Constants ---
 
 
 
10
  TOP_K_CHOICES = list(range(5, 51, 5))
11
+ HF_DATASET_ID = "evijit/dataverse_daily_data" # <-- Changed to the new dataset repo
12
+ TAG_FILTER_CHOICES = [
13
+ "None", "Audio & Speech", "Time series", "Robotics", "Music",
14
+ "Video", "Images", "Text", "Biomedical", "Sciences"
15
+ ]
16
+
17
+ def load_datasets_data():
18
+ """Load the processed datasets data from the Hugging Face Hub."""
19
+ start_time = time.time()
20
  print(f"Attempting to load dataset from Hugging Face Hub: {HF_DATASET_ID}")
21
  try:
22
+ # Load the dataset from the Hub
23
  dataset_dict = load_dataset(HF_DATASET_ID)
24
+ # Convert the first split (usually 'train') to a pandas DataFrame
25
  df = dataset_dict[list(dataset_dict.keys())[0]].to_pandas()
26
+
27
+ # No parameter processing needed for datasets
28
+ msg = f"Successfully loaded dataset in {time.time() - start_time:.2f}s."
 
 
29
  print(msg)
30
  return df, True, msg
31
  except Exception as e:
 
33
  print(err_msg)
34
  return pd.DataFrame(), False, err_msg
35
 
36
+ def make_treemap_data(df, count_by, top_k=25, tag_filter=None, skip_orgs=None):
37
+ """Filter and prepare data for the treemap visualization."""
38
+ if df is None or df.empty:
39
+ return pd.DataFrame()
40
+
 
 
 
41
  filtered_df = df.copy()
42
+
43
+ # Map UI-friendly tag names to the boolean columns in the dataframe
44
+ col_map = {
45
+ "Audio & Speech": "is_audio_speech", "Music": "has_music", "Robotics": "has_robot",
46
+ "Biomedical": "is_biomed", "Time series": "has_series", "Sciences": "has_science",
47
+ "Video": "has_video", "Images": "has_image", "Text": "has_text"
48
+ }
49
+
50
+ # Apply tag filter if a valid one is selected
51
+ if tag_filter and tag_filter != "None" and tag_filter in col_map:
52
+ if col_map[tag_filter] in filtered_df.columns:
53
+ filtered_df = filtered_df[filtered_df[col_map[tag_filter]]]
54
+
55
+ # Skip specified organizations if any are provided
56
  if skip_orgs and len(skip_orgs) > 0 and "organization" in filtered_df.columns:
57
  filtered_df = filtered_df[~filtered_df["organization"].isin(skip_orgs)]
58
+
59
+ if filtered_df.empty:
60
+ return pd.DataFrame()
61
+
62
+ # Ensure the metric column is numeric
63
+ if count_by not in filtered_df.columns:
64
+ filtered_df[count_by] = 0.0
65
  filtered_df[count_by] = pd.to_numeric(filtered_df[count_by], errors='coerce').fillna(0.0)
66
+
67
+ # Group by organization and find the top K based on the selected metric
68
  org_totals = filtered_df.groupby("organization")[count_by].sum().nlargest(top_k, keep='first')
69
  top_orgs_list = org_totals.index.tolist()
70
+
71
+ # Prepare the final data structure for the treemap
72
  treemap_data = filtered_df[filtered_df["organization"].isin(top_orgs_list)][["id", "organization", count_by]].copy()
73
+ treemap_data["root"] = "datasets" # Set the root node for the treemap
74
  return treemap_data
75
 
76
  def create_treemap(treemap_data, count_by, title=None):
77
+ """Generate the Plotly treemap figure."""
78
  if treemap_data.empty:
79
+ # Create a placeholder figure if no data matches the filters
80
  fig = px.treemap(names=["No data matches filters"], parents=[""], values=[1])
81
  fig.update_layout(title="No data matches the selected filters", margin=dict(t=50, l=25, r=25, b=25))
82
  return fig
83
+
84
+ # Create the main treemap
85
+ fig = px.treemap(treemap_data, path=["root", "organization", "id"], values=count_by,
86
+ title=title, color_discrete_sequence=px.colors.qualitative.Plotly)
87
  fig.update_layout(margin=dict(t=50, l=25, r=25, b=25))
88
+ fig.update_traces(
89
+ textinfo="label+value+percent root",
90
+ hovertemplate="<b>%{label}</b><br>%{value:,} " + count_by + "<br>%{percentRoot:.2%} of total<extra></extra>"
91
+ )
92
  return fig
93
 
94
+ # --- Gradio UI Blocks ---
95
+ with gr.Blocks(title="🤗 Dataverse Explorer", fill_width=True) as demo:
96
+ datasets_data_state = gr.State(pd.DataFrame())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  loading_complete_state = gr.State(False)
98
 
99
  with gr.Row():
100
+ gr.Markdown("# 🤗 Dataverse Explorer")
101
 
102
  with gr.Row():
103
  with gr.Column(scale=1):
104
+ # --- Control Panel ---
105
+ count_by_dropdown = gr.Dropdown(
106
+ label="Metric",
107
+ choices=[("Downloads (last 30 days)", "downloads"), ("Downloads (All Time)", "downloadsAllTime"), ("Likes", "likes")],
108
+ value="downloads"
109
+ )
110
 
111
+ tag_filter_dropdown = gr.Dropdown(
112
+ label="Filter by Tag",
113
+ choices=TAG_FILTER_CHOICES,
114
+ value="None"
115
+ )
116
 
117
+ top_k_dropdown = gr.Dropdown(
118
+ label="Number of Top Organizations",
119
+ choices=TOP_K_CHOICES,
120
+ value=25
121
+ )
 
 
 
 
 
 
 
 
 
 
 
122
 
123
+ skip_orgs_textbox = gr.Textbox(
124
+ label="Organizations to Skip (comma-separated)",
125
+ value="huggingface,google,facebook,microsoft,amazon"
126
+ )
127
 
128
+ generate_plot_button = gr.Button(
129
+ value="Generate Plot",
130
+ variant="primary",
131
+ interactive=False
132
+ )
133
 
134
  with gr.Column(scale=3):
135
+ # --- Output Area ---
136
  plot_output = gr.Plot()
137
  status_message_md = gr.Markdown("Initializing...")
138
  data_info_md = gr.Markdown("")
139
 
140
+ # --- Controller Functions ---
141
+ def _update_button_interactivity(is_loaded_flag):
142
+ """Enable the generate button once data is loaded."""
143
+ return gr.update(interactive=is_loaded_flag)
 
 
 
 
 
 
 
 
144
 
145
  def ui_load_data_controller(progress=gr.Progress()):
146
+ """Handles the initial data loading and updates the UI with status."""
147
  progress(0, desc=f"Loading dataset '{HF_DATASET_ID}'...")
148
  try:
149
+ current_df, load_success_flag, status_msg_from_load = load_datasets_data()
150
  if load_success_flag:
151
  progress(0.9, desc="Processing data...")
152
+ # Format the timestamp for display
153
  date_display = "Pre-processed (date unavailable)"
154
  if 'data_download_timestamp' in current_df.columns and pd.notna(current_df['data_download_timestamp'].iloc[0]):
155
  ts = pd.to_datetime(current_df['data_download_timestamp'].iloc[0], utc=True)
156
  date_display = ts.strftime('%B %d, %Y, %H:%M:%S %Z')
157
+
158
+ # Create the data information summary
159
+ data_info_text = (
160
+ f"### Data Information\n- Source: `{HF_DATASET_ID}`\n"
161
+ f"- Status: {status_msg_from_load}\n"
162
+ f"- Total datasets loaded: {len(current_df):,}\n"
163
+ f"- Data as of: {date_display}\n"
164
+ )
165
  status_msg_ui = "Data loaded. Ready to generate plot."
166
  else:
167
  data_info_text = f"### Data Load Failed\n- {status_msg_from_load}"
 
171
  data_info_text = f"### Critical Error\n- {status_msg_ui}"
172
  load_success_flag = False
173
  print(f"Critical error in ui_load_data_controller: {e}")
174
+
175
  return current_df, load_success_flag, data_info_text, status_msg_ui
176
 
177
+ def ui_generate_plot_controller(metric_choice, tag_choice, k_orgs,
178
+ skip_orgs_input, df_current_datasets, progress=gr.Progress()):
179
+ """Handles the plot generation based on user inputs."""
180
+ if df_current_datasets is None or df_current_datasets.empty:
181
+ return create_treemap(pd.DataFrame(), metric_choice), "Dataset data is not loaded."
182
 
183
  progress(0.1, desc="Preparing data...")
 
 
184
  orgs_to_skip = [org.strip() for org in skip_orgs_input.split(',') if org.strip()]
185
 
186
+ # Prepare data for the treemap
187
+ treemap_df = make_treemap_data(df_current_datasets, metric_choice, k_orgs, tag_choice, orgs_to_skip)
 
 
 
188
 
189
  progress(0.7, desc="Generating plot...")
190
+ # Create a user-friendly title for the chart
191
  title_labels = {"downloads": "Downloads (last 30 days)", "downloadsAllTime": "Downloads (All Time)", "likes": "Likes"}
192
+ chart_title = f"HuggingFace Datasets - {title_labels.get(metric_choice, metric_choice)} by Organization"
193
  plotly_fig = create_treemap(treemap_df, metric_choice, chart_title)
194
 
195
+ # Generate summary statistics for the plot
196
  if treemap_df.empty:
197
  plot_stats_md = "No data matches the selected filters. Please try different options."
198
  else:
199
  total_items_in_plot = len(treemap_df['id'].unique())
200
  total_value_in_plot = treemap_df[metric_choice].sum()
201
+ plot_stats_md = f"## Plot Statistics\n- **Datasets shown**: {total_items_in_plot:,}\n- **Total {metric_choice}**: {int(total_value_in_plot):,}"
202
+
203
  return plotly_fig, plot_stats_md
204
 
205
+ # --- Event Wiring ---
206
+
207
+ # When the app loads, trigger the data fetching process
208
  demo.load(
209
  fn=ui_load_data_controller,
210
  inputs=[],
211
+ outputs=[datasets_data_state, loading_complete_state, data_info_md, status_message_md]
212
  )
213
 
214
+ # When the loading is complete, enable the "Generate Plot" button
215
+ loading_complete_state.change(
216
+ fn=_update_button_interactivity,
217
+ inputs=loading_complete_state,
218
+ outputs=generate_plot_button
219
+ )
220
+
221
+ # When the "Generate Plot" button is clicked, trigger the plot generation
222
  generate_plot_button.click(
223
  fn=ui_generate_plot_controller,
224
+ inputs=[count_by_dropdown, tag_filter_dropdown, top_k_dropdown,
225
+ skip_orgs_textbox, datasets_data_state],
226
  outputs=[plot_output, status_message_md]
227
  )
228
 
229
  if __name__ == "__main__":
230
+ print("Application starting...")
231
+ demo.queue().launch()