Ashkan Taghipour (The University of Western Australia) Claude Opus 4.6 commited on
Commit
16c6db2
·
1 Parent(s): f01b448

Fix Protein World performance: replace 55k-item dropdown with textbox

Browse files

The gene dropdown with 55,512 choices caused the browser to hang.
Replaced with:
- A textbox where users type a gene ID and press Enter or click Load
- A small "pick from backpack" dropdown showing only pinned genes
- A hidden dropdown for Gene Card "Show Protein" button integration
All pin callbacks now also sync the backpack picker choices.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +58 -14
  2. ui/quest4.py +23 -5
app.py CHANGED
@@ -227,12 +227,14 @@ with demo:
227
  def on_q2_pin(gene_id, state):
228
  bp_text, state = on_pin_gene(gene_id, state)
229
  q4_bp = _backpack_text(state)
230
- return bp_text, q4_bp, state
 
231
 
232
  C["q2_pin_btn"].click(
233
  fn=on_q2_pin,
234
  inputs=[C["q2_selected_gene_text"], C["state"]],
235
- outputs=[C["q2_backpack_display"], C["q4_backpack_display"], C["state"]],
 
236
  )
237
 
238
  # Table row click → select gene
@@ -260,41 +262,81 @@ with demo:
260
  )
261
 
262
  # -- Quest 4 --
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  C["q4_gene_dropdown"].change(
264
- fn=lambda gene_id: get_protein_stats_html(gene_id, DATA),
265
  inputs=[C["q4_gene_dropdown"]],
266
- outputs=[C["q4_protein_stats_html"]],
267
  )
268
 
269
  def on_q4_pin(gene_id, state):
270
- """Pin a gene from Quest 4 dropdown, update both backpack displays and charts."""
271
- bp_text, state = on_pin_gene(gene_id if gene_id else "", state)
 
272
  radar = build_backpack_comparison(state, DATA)
273
  heatmap = build_composition_heatmap(state, DATA)
274
  q4_bp = _backpack_text(state)
275
- return bp_text, q4_bp, radar, heatmap, state
 
 
276
 
277
  C["q4_pin_btn"].click(
278
  fn=on_q4_pin,
279
- inputs=[C["q4_gene_dropdown"], C["state"]],
280
  outputs=[
281
  C["q2_backpack_display"],
282
  C["q4_backpack_display"],
283
  C["q4_comparison_bar_plot"],
284
  C["q4_composition_heatmap"],
 
285
  C["state"],
286
  ],
287
  )
288
 
289
- C["q4_tab"].select(
290
- fn=lambda state: (
 
291
  build_backpack_comparison(state, DATA),
292
  build_composition_heatmap(state, DATA),
293
  _backpack_text(state),
294
- ),
 
 
 
 
295
  inputs=[C["state"]],
296
  outputs=[C["q4_comparison_bar_plot"], C["q4_composition_heatmap"],
297
- C["q4_backpack_display"]],
298
  )
299
 
300
  # -- Gene Card --
@@ -319,12 +361,14 @@ with demo:
319
  def on_gc_pin(state):
320
  bp_text, state = on_pin_gene(state.selected_gene if state else "", state)
321
  q4_bp = _backpack_text(state)
322
- return bp_text, q4_bp, state
 
323
 
324
  C["gc_pin_card_btn"].click(
325
  fn=on_gc_pin,
326
  inputs=[C["state"]],
327
- outputs=[C["q2_backpack_display"], C["q4_backpack_display"], C["state"]],
 
328
  )
329
 
330
  C["gc_download_gene_btn"].click(
 
227
  def on_q2_pin(gene_id, state):
228
  bp_text, state = on_pin_gene(gene_id, state)
229
  q4_bp = _backpack_text(state)
230
+ picker = gr.Dropdown(choices=state.backpack_genes if state else [])
231
+ return bp_text, q4_bp, picker, state
232
 
233
  C["q2_pin_btn"].click(
234
  fn=on_q2_pin,
235
  inputs=[C["q2_selected_gene_text"], C["state"]],
236
+ outputs=[C["q2_backpack_display"], C["q4_backpack_display"],
237
+ C["q4_backpack_picker"], C["state"]],
238
  )
239
 
240
  # Table row click → select gene
 
262
  )
263
 
264
  # -- Quest 4 --
265
+
266
+ # Track the currently loaded gene in Quest 4 via state
267
+ def _load_gene(gene_id):
268
+ """Load protein stats for a gene ID."""
269
+ if not gene_id or not gene_id.strip():
270
+ return get_protein_stats_html("", DATA), ""
271
+ gid = gene_id.strip()
272
+ return get_protein_stats_html(gid, DATA), gid
273
+
274
+ # Load button: type a gene ID and click Load
275
+ C["q4_load_btn"].click(
276
+ fn=_load_gene,
277
+ inputs=[C["q4_gene_input"]],
278
+ outputs=[C["q4_protein_stats_html"], C["q4_gene_input"]],
279
+ )
280
+
281
+ # Also trigger on Enter key in the textbox
282
+ C["q4_gene_input"].submit(
283
+ fn=_load_gene,
284
+ inputs=[C["q4_gene_input"]],
285
+ outputs=[C["q4_protein_stats_html"], C["q4_gene_input"]],
286
+ )
287
+
288
+ # Backpack quick-pick dropdown
289
+ C["q4_backpack_picker"].change(
290
+ fn=lambda gene_id: (get_protein_stats_html(gene_id, DATA), gene_id or ""),
291
+ inputs=[C["q4_backpack_picker"]],
292
+ outputs=[C["q4_protein_stats_html"], C["q4_gene_input"]],
293
+ )
294
+
295
+ # Hidden dropdown receives value from Gene Card "Show Protein" button
296
  C["q4_gene_dropdown"].change(
297
+ fn=lambda gene_id: (get_protein_stats_html(gene_id, DATA), gene_id or ""),
298
  inputs=[C["q4_gene_dropdown"]],
299
+ outputs=[C["q4_protein_stats_html"], C["q4_gene_input"]],
300
  )
301
 
302
  def on_q4_pin(gene_id, state):
303
+ """Pin a gene from Quest 4, update both backpack displays and charts."""
304
+ gid = gene_id.strip() if gene_id else ""
305
+ bp_text, state = on_pin_gene(gid, state)
306
  radar = build_backpack_comparison(state, DATA)
307
  heatmap = build_composition_heatmap(state, DATA)
308
  q4_bp = _backpack_text(state)
309
+ # Update the backpack picker choices
310
+ picker_update = gr.Dropdown(choices=state.backpack_genes if state else [])
311
+ return bp_text, q4_bp, radar, heatmap, picker_update, state
312
 
313
  C["q4_pin_btn"].click(
314
  fn=on_q4_pin,
315
+ inputs=[C["q4_gene_input"], C["state"]],
316
  outputs=[
317
  C["q2_backpack_display"],
318
  C["q4_backpack_display"],
319
  C["q4_comparison_bar_plot"],
320
  C["q4_composition_heatmap"],
321
+ C["q4_backpack_picker"],
322
  C["state"],
323
  ],
324
  )
325
 
326
+ def _on_q4_tab_select(state):
327
+ bp_genes = state.backpack_genes if state else []
328
+ return (
329
  build_backpack_comparison(state, DATA),
330
  build_composition_heatmap(state, DATA),
331
  _backpack_text(state),
332
+ gr.Dropdown(choices=bp_genes),
333
+ )
334
+
335
+ C["q4_tab"].select(
336
+ fn=_on_q4_tab_select,
337
  inputs=[C["state"]],
338
  outputs=[C["q4_comparison_bar_plot"], C["q4_composition_heatmap"],
339
+ C["q4_backpack_display"], C["q4_backpack_picker"]],
340
  )
341
 
342
  # -- Gene Card --
 
361
  def on_gc_pin(state):
362
  bp_text, state = on_pin_gene(state.selected_gene if state else "", state)
363
  q4_bp = _backpack_text(state)
364
+ picker = gr.Dropdown(choices=state.backpack_genes if state else [])
365
+ return bp_text, q4_bp, picker, state
366
 
367
  C["gc_pin_card_btn"].click(
368
  fn=on_gc_pin,
369
  inputs=[C["state"]],
370
+ outputs=[C["q2_backpack_display"], C["q4_backpack_display"],
371
+ C["q4_backpack_picker"], C["state"]],
372
  )
373
 
374
  C["gc_download_gene_btn"].click(
ui/quest4.py CHANGED
@@ -167,7 +167,8 @@ def build_quest4(gene_choices: list[str]):
167
  """Build Quest 4 tab components. Returns dict of components.
168
 
169
  Returned keys (prefixed with ``q4_`` by layout.py):
170
- tab, gene_dropdown, protein_stats_html, pin_btn, backpack_display,
 
171
  comparison_bar_plot, composition_heatmap
172
  """
173
  with gr.Tab("Protein World", id="quest4") as tab:
@@ -186,12 +187,26 @@ def build_quest4(gene_choices: list[str]):
186
  # -- Single-gene selector --
187
  with gr.Row():
188
  with gr.Column(scale=2):
 
189
  gene_dropdown = gr.Dropdown(
190
- choices=gene_choices,
191
- label="Select a gene to inspect",
192
- interactive=True,
193
  allow_custom_value=True,
194
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  pin_btn = gr.Button(
196
  "Pin this gene to Backpack",
197
  variant="secondary",
@@ -201,7 +216,7 @@ def build_quest4(gene_choices: list[str]):
201
  protein_stats_html = gr.HTML(
202
  value=(
203
  '<div style="padding:16px;color:#757575;text-align:center;">'
204
- "Select a gene above to see its protein length and amino acid breakdown."
205
  "</div>"
206
  ),
207
  label="Protein Statistics",
@@ -234,6 +249,9 @@ def build_quest4(gene_choices: list[str]):
234
  return {
235
  "tab": tab,
236
  "gene_dropdown": gene_dropdown,
 
 
 
237
  "protein_stats_html": protein_stats_html,
238
  "pin_btn": pin_btn,
239
  "backpack_display": backpack_display,
 
167
  """Build Quest 4 tab components. Returns dict of components.
168
 
169
  Returned keys (prefixed with ``q4_`` by layout.py):
170
+ tab, gene_dropdown, gene_input, load_btn, backpack_picker,
171
+ protein_stats_html, pin_btn, backpack_display,
172
  comparison_bar_plot, composition_heatmap
173
  """
174
  with gr.Tab("Protein World", id="quest4") as tab:
 
187
  # -- Single-gene selector --
188
  with gr.Row():
189
  with gr.Column(scale=2):
190
+ # Hidden dropdown to receive values from Gene Card "Show Protein"
191
  gene_dropdown = gr.Dropdown(
192
+ choices=[],
193
+ visible=False,
 
194
  allow_custom_value=True,
195
  )
196
+ with gr.Row():
197
+ gene_input = gr.Textbox(
198
+ label="Type a gene ID (e.g. g05743)",
199
+ placeholder="g05743",
200
+ interactive=True,
201
+ scale=3,
202
+ )
203
+ load_btn = gr.Button("Load", variant="primary", size="sm", scale=1)
204
+ backpack_picker = gr.Dropdown(
205
+ choices=[],
206
+ label="Or pick from your backpack",
207
+ interactive=True,
208
+ allow_custom_value=False,
209
+ )
210
  pin_btn = gr.Button(
211
  "Pin this gene to Backpack",
212
  variant="secondary",
 
216
  protein_stats_html = gr.HTML(
217
  value=(
218
  '<div style="padding:16px;color:#757575;text-align:center;">'
219
+ "Type a gene ID and click <b>Load</b>, or pick one from your backpack."
220
  "</div>"
221
  ),
222
  label="Protein Statistics",
 
249
  return {
250
  "tab": tab,
251
  "gene_dropdown": gene_dropdown,
252
+ "gene_input": gene_input,
253
+ "load_btn": load_btn,
254
+ "backpack_picker": backpack_picker,
255
  "protein_stats_html": protein_stats_html,
256
  "pin_btn": pin_btn,
257
  "backpack_display": backpack_display,