openms-insight 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  """Line plot component using Plotly.js."""
2
2
 
3
- from typing import Any, Dict, Optional
3
+ from typing import TYPE_CHECKING, Any, Dict, Optional
4
4
 
5
5
  import polars as pl
6
6
 
@@ -8,6 +8,9 @@ from ..core.base import BaseComponent
8
8
  from ..core.registry import register_component
9
9
  from ..preprocessing.filtering import filter_and_collect_cached
10
10
 
11
+ if TYPE_CHECKING:
12
+ from .sequenceview import SequenceView
13
+
11
14
 
12
15
  @register_component("lineplot")
13
16
  class LinePlot(BaseComponent):
@@ -51,8 +54,8 @@ class LinePlot(BaseComponent):
51
54
  interactivity: Optional[Dict[str, str]] = None,
52
55
  cache_path: str = ".",
53
56
  regenerate_cache: bool = False,
54
- x_column: str = 'x',
55
- y_column: str = 'y',
57
+ x_column: str = "x",
58
+ y_column: str = "y",
56
59
  title: Optional[str] = None,
57
60
  x_label: Optional[str] = None,
58
61
  y_label: Optional[str] = None,
@@ -60,7 +63,7 @@ class LinePlot(BaseComponent):
60
63
  annotation_column: Optional[str] = None,
61
64
  styling: Optional[Dict[str, Any]] = None,
62
65
  config: Optional[Dict[str, Any]] = None,
63
- **kwargs
66
+ **kwargs,
64
67
  ):
65
68
  """
66
69
  Initialize the LinePlot component.
@@ -134,7 +137,7 @@ class LinePlot(BaseComponent):
134
137
  annotation_column=annotation_column,
135
138
  styling=styling,
136
139
  config=config,
137
- **kwargs
140
+ **kwargs,
138
141
  )
139
142
 
140
143
  def _get_cache_config(self) -> Dict[str, Any]:
@@ -145,12 +148,32 @@ class LinePlot(BaseComponent):
145
148
  Dict of config values that affect preprocessing
146
149
  """
147
150
  return {
148
- 'x_column': self._x_column,
149
- 'y_column': self._y_column,
150
- 'highlight_column': self._highlight_column,
151
- 'annotation_column': self._annotation_column,
151
+ "x_column": self._x_column,
152
+ "y_column": self._y_column,
153
+ "highlight_column": self._highlight_column,
154
+ "annotation_column": self._annotation_column,
155
+ "title": self._title,
156
+ "x_label": self._x_label,
157
+ "y_label": self._y_label,
158
+ "styling": self._styling,
159
+ "plot_config": self._plot_config,
152
160
  }
153
161
 
162
+ def _restore_cache_config(self, config: Dict[str, Any]) -> None:
163
+ """Restore component-specific configuration from cached config."""
164
+ self._x_column = config.get("x_column", "x")
165
+ self._y_column = config.get("y_column", "y")
166
+ self._highlight_column = config.get("highlight_column")
167
+ self._annotation_column = config.get("annotation_column")
168
+ self._title = config.get("title")
169
+ self._x_label = config.get("x_label", self._x_column)
170
+ self._y_label = config.get("y_label", self._y_column)
171
+ self._styling = config.get("styling", {})
172
+ self._plot_config = config.get("plot_config", {})
173
+ # Initialize dynamic annotations (not cached)
174
+ self._dynamic_annotations = None
175
+ self._dynamic_title = None
176
+
154
177
  def _get_row_group_size(self) -> int:
155
178
  """
156
179
  Get optimal row group size for parquet writing.
@@ -175,8 +198,10 @@ class LinePlot(BaseComponent):
175
198
  column_names = schema.names()
176
199
 
177
200
  # Validate x and y columns exist
178
- for col_name, col_label in [(self._x_column, 'x_column'),
179
- (self._y_column, 'y_column')]:
201
+ for col_name, col_label in [
202
+ (self._x_column, "x_column"),
203
+ (self._y_column, "y_column"),
204
+ ]:
180
205
  if col_name not in column_names:
181
206
  raise ValueError(
182
207
  f"{col_label} '{col_name}' not found in data. "
@@ -214,24 +239,24 @@ class LinePlot(BaseComponent):
214
239
  data = data.sort(sort_columns)
215
240
 
216
241
  # Store configuration in preprocessed data for serialization
217
- self._preprocessed_data['plot_config'] = {
218
- 'x_column': self._x_column,
219
- 'y_column': self._y_column,
220
- 'highlight_column': self._highlight_column,
221
- 'annotation_column': self._annotation_column,
242
+ self._preprocessed_data["plot_config"] = {
243
+ "x_column": self._x_column,
244
+ "y_column": self._y_column,
245
+ "highlight_column": self._highlight_column,
246
+ "annotation_column": self._annotation_column,
222
247
  }
223
248
 
224
249
  # Store LazyFrame for streaming to disk (filter happens at render time)
225
250
  # Base class will use sink_parquet() to stream without full materialization
226
- self._preprocessed_data['data'] = data # Keep lazy
251
+ self._preprocessed_data["data"] = data # Keep lazy
227
252
 
228
253
  def _get_vue_component_name(self) -> str:
229
254
  """Return the Vue component name."""
230
- return 'PlotlyLineplotUnified'
255
+ return "PlotlyLineplotUnified"
231
256
 
232
257
  def _get_data_key(self) -> str:
233
258
  """Return the key used to send primary data to Vue."""
234
- return 'plotData'
259
+ return "plotData"
235
260
 
236
261
  def _prepare_vue_data(self, state: Dict[str, Any]) -> Dict[str, Any]:
237
262
  """
@@ -266,7 +291,7 @@ class LinePlot(BaseComponent):
266
291
  columns_to_select.append(col)
267
292
 
268
293
  # Get cached data (DataFrame or LazyFrame)
269
- data = self._preprocessed_data.get('data')
294
+ data = self._preprocessed_data.get("data")
270
295
  if data is None:
271
296
  # Fallback to raw data if available
272
297
  data = self._raw_data
@@ -293,7 +318,7 @@ class LinePlot(BaseComponent):
293
318
  if self._dynamic_annotations and len(df_pandas) > 0:
294
319
  num_rows = len(df_pandas)
295
320
  highlights = [False] * num_rows
296
- annotations = [''] * num_rows
321
+ annotations = [""] * num_rows
297
322
 
298
323
  # Get the interactivity column to use for lookup (e.g., 'peak_id')
299
324
  # Use the first interactivity column as the ID column for annotation lookup
@@ -307,44 +332,38 @@ class LinePlot(BaseComponent):
307
332
  for row_idx, peak_id in enumerate(peak_ids):
308
333
  if peak_id in self._dynamic_annotations:
309
334
  ann_data = self._dynamic_annotations[peak_id]
310
- highlights[row_idx] = ann_data.get('highlight', False)
311
- annotations[row_idx] = ann_data.get('annotation', '')
335
+ highlights[row_idx] = ann_data.get("highlight", False)
336
+ annotations[row_idx] = ann_data.get("annotation", "")
312
337
  else:
313
338
  # Fallback: use row index as key (legacy behavior)
314
339
  for idx, ann_data in self._dynamic_annotations.items():
315
340
  if isinstance(idx, int) and 0 <= idx < num_rows:
316
- highlights[idx] = ann_data.get('highlight', False)
317
- annotations[idx] = ann_data.get('annotation', '')
341
+ highlights[idx] = ann_data.get("highlight", False)
342
+ annotations[idx] = ann_data.get("annotation", "")
318
343
 
319
344
  # Add dynamic columns to dataframe
320
345
  df_pandas = df_pandas.copy()
321
- df_pandas['_dynamic_highlight'] = highlights
322
- df_pandas['_dynamic_annotation'] = annotations
346
+ df_pandas["_dynamic_highlight"] = highlights
347
+ df_pandas["_dynamic_annotation"] = annotations
323
348
 
324
349
  # Update column names to use dynamic columns
325
- highlight_col = '_dynamic_highlight'
326
- annotation_col = '_dynamic_annotation'
350
+ highlight_col = "_dynamic_highlight"
351
+ annotation_col = "_dynamic_annotation"
327
352
 
328
353
  # Update hash to include dynamic annotation state
329
354
  import hashlib
330
- ann_hash = hashlib.md5(str(sorted(self._dynamic_annotations.keys())).encode()).hexdigest()[:8]
355
+
356
+ ann_hash = hashlib.md5(
357
+ str(sorted(self._dynamic_annotations.keys())).encode()
358
+ ).hexdigest()[:8]
331
359
  data_hash = f"{data_hash}_{ann_hash}"
332
360
 
333
361
  # Send as DataFrame for Arrow serialization (efficient binary transfer)
334
362
  # Vue will parse and extract columns using the config
335
363
  return {
336
- 'plotData': df_pandas,
337
- '_hash': data_hash,
338
- # Config tells Vue which columns map to x, y, etc.
339
- '_plotConfig': {
340
- 'xColumn': self._x_column,
341
- 'yColumn': self._y_column,
342
- 'highlightColumn': highlight_col,
343
- 'annotationColumn': annotation_col,
344
- 'interactivityColumns': {
345
- col: col for col in (self._interactivity.values() if self._interactivity else [])
346
- },
347
- }
364
+ "plotData": df_pandas,
365
+ "_hash": data_hash,
366
+ "_plotConfig": self._build_plot_config(highlight_col, annotation_col),
348
367
  }
349
368
 
350
369
  def _get_component_args(self) -> Dict[str, Any]:
@@ -356,45 +375,45 @@ class LinePlot(BaseComponent):
356
375
  """
357
376
  # Default styling
358
377
  default_styling = {
359
- 'highlightColor': '#E4572E',
360
- 'selectedColor': '#F3A712',
361
- 'unhighlightedColor': 'lightblue',
362
- 'highlightHiddenColor': '#1f77b4',
363
- 'annotationColors': {
364
- 'massButton': '#E4572E',
365
- 'selectedMassButton': '#F3A712',
366
- 'sequenceArrow': '#E4572E',
367
- 'selectedSequenceArrow': '#F3A712',
368
- 'background': '#f0f0f0',
369
- 'buttonHover': '#e0e0e0',
370
- }
378
+ "highlightColor": "#E4572E",
379
+ "selectedColor": "#F3A712",
380
+ "unhighlightedColor": "lightblue",
381
+ "highlightHiddenColor": "#1f77b4",
382
+ "annotationColors": {
383
+ "massButton": "#E4572E",
384
+ "selectedMassButton": "#F3A712",
385
+ "sequenceArrow": "#E4572E",
386
+ "selectedSequenceArrow": "#F3A712",
387
+ "background": "#f0f0f0",
388
+ "buttonHover": "#e0e0e0",
389
+ },
371
390
  }
372
391
 
373
392
  # Merge user styling with defaults
374
393
  styling = {**default_styling, **self._styling}
375
- if 'annotationColors' in self._styling:
376
- styling['annotationColors'] = {
377
- **default_styling['annotationColors'],
378
- **self._styling['annotationColors']
394
+ if "annotationColors" in self._styling:
395
+ styling["annotationColors"] = {
396
+ **default_styling["annotationColors"],
397
+ **self._styling["annotationColors"],
379
398
  }
380
399
 
381
400
  # Use dynamic title if set, otherwise static title
382
- title = self._dynamic_title if self._dynamic_title else (self._title or '')
401
+ title = self._dynamic_title if self._dynamic_title else (self._title or "")
383
402
 
384
403
  args: Dict[str, Any] = {
385
- 'componentType': self._get_vue_component_name(),
386
- 'title': title,
387
- 'xLabel': self._x_label,
388
- 'yLabel': self._y_label,
389
- 'styling': styling,
390
- 'config': self._plot_config,
404
+ "componentType": self._get_vue_component_name(),
405
+ "title": title,
406
+ "xLabel": self._x_label,
407
+ "yLabel": self._y_label,
408
+ "styling": styling,
409
+ "config": self._plot_config,
391
410
  # Pass interactivity for click handling (sets selection on peak click)
392
- 'interactivity': self._interactivity,
411
+ "interactivity": self._interactivity,
393
412
  # Column mappings for Arrow data parsing in Vue
394
- 'xColumn': self._x_column,
395
- 'yColumn': self._y_column,
396
- 'highlightColumn': self._highlight_column,
397
- 'annotationColumn': self._annotation_column,
413
+ "xColumn": self._x_column,
414
+ "yColumn": self._y_column,
415
+ "highlightColumn": self._highlight_column,
416
+ "annotationColumn": self._annotation_column,
398
417
  }
399
418
 
400
419
  # Add any extra config options
@@ -407,7 +426,7 @@ class LinePlot(BaseComponent):
407
426
  highlight_color: Optional[str] = None,
408
427
  selected_color: Optional[str] = None,
409
428
  unhighlighted_color: Optional[str] = None,
410
- ) -> 'LinePlot':
429
+ ) -> "LinePlot":
411
430
  """
412
431
  Update plot styling.
413
432
 
@@ -420,11 +439,11 @@ class LinePlot(BaseComponent):
420
439
  Self for method chaining
421
440
  """
422
441
  if highlight_color:
423
- self._styling['highlightColor'] = highlight_color
442
+ self._styling["highlightColor"] = highlight_color
424
443
  if selected_color:
425
- self._styling['selectedColor'] = selected_color
444
+ self._styling["selectedColor"] = selected_color
426
445
  if unhighlighted_color:
427
- self._styling['unhighlightedColor'] = unhighlighted_color
446
+ self._styling["unhighlightedColor"] = unhighlighted_color
428
447
  return self
429
448
 
430
449
  def with_annotations(
@@ -432,7 +451,7 @@ class LinePlot(BaseComponent):
432
451
  background_color: Optional[str] = None,
433
452
  button_color: Optional[str] = None,
434
453
  selected_button_color: Optional[str] = None,
435
- ) -> 'LinePlot':
454
+ ) -> "LinePlot":
436
455
  """
437
456
  Configure annotation styling.
438
457
 
@@ -444,15 +463,17 @@ class LinePlot(BaseComponent):
444
463
  Returns:
445
464
  Self for method chaining
446
465
  """
447
- if 'annotationColors' not in self._styling:
448
- self._styling['annotationColors'] = {}
466
+ if "annotationColors" not in self._styling:
467
+ self._styling["annotationColors"] = {}
449
468
 
450
469
  if background_color:
451
- self._styling['annotationColors']['background'] = background_color
470
+ self._styling["annotationColors"]["background"] = background_color
452
471
  if button_color:
453
- self._styling['annotationColors']['massButton'] = button_color
472
+ self._styling["annotationColors"]["massButton"] = button_color
454
473
  if selected_button_color:
455
- self._styling['annotationColors']['selectedMassButton'] = selected_button_color
474
+ self._styling["annotationColors"]["selectedMassButton"] = (
475
+ selected_button_color
476
+ )
456
477
 
457
478
  return self
458
479
 
@@ -460,7 +481,7 @@ class LinePlot(BaseComponent):
460
481
  self,
461
482
  annotations: Optional[Dict[int, Dict[str, Any]]] = None,
462
483
  title: Optional[str] = None,
463
- ) -> 'LinePlot':
484
+ ) -> "LinePlot":
464
485
  """
465
486
  Set dynamic annotations to be applied at render time.
466
487
 
@@ -493,7 +514,7 @@ class LinePlot(BaseComponent):
493
514
  self._dynamic_title = title
494
515
  return self
495
516
 
496
- def clear_dynamic_annotations(self) -> 'LinePlot':
517
+ def clear_dynamic_annotations(self) -> "LinePlot":
497
518
  """
498
519
  Clear any dynamic annotations.
499
520
 
@@ -503,3 +524,277 @@ class LinePlot(BaseComponent):
503
524
  self._dynamic_annotations = None
504
525
  self._dynamic_title = None
505
526
  return self
527
+
528
+ def _build_plot_config(
529
+ self,
530
+ highlight_col: Optional[str],
531
+ annotation_col: Optional[str],
532
+ ) -> Dict[str, Any]:
533
+ """
534
+ Build _plotConfig dict for Vue component.
535
+
536
+ Args:
537
+ highlight_col: Column name for highlight values
538
+ annotation_col: Column name for annotation text
539
+
540
+ Returns:
541
+ Config dict with column mappings for Vue
542
+ """
543
+ return {
544
+ "xColumn": self._x_column,
545
+ "yColumn": self._y_column,
546
+ "highlightColumn": highlight_col,
547
+ "annotationColumn": annotation_col,
548
+ "interactivityColumns": {
549
+ col: col
550
+ for col in (self._interactivity.values() if self._interactivity else [])
551
+ },
552
+ }
553
+
554
+ def _strip_dynamic_columns(self, vue_data: Dict[str, Any]) -> Dict[str, Any]:
555
+ """
556
+ Strip dynamic annotation columns from vue_data for caching.
557
+
558
+ Returns a copy with dynamic columns removed so the cached version
559
+ doesn't contain stale annotation data.
560
+
561
+ Args:
562
+ vue_data: The vue data dict to strip
563
+
564
+ Returns:
565
+ Copy of vue_data without dynamic columns and _plotConfig
566
+ """
567
+ import pandas as pd
568
+
569
+ vue_data = dict(vue_data)
570
+ df = vue_data.get("plotData")
571
+
572
+ if df is not None and isinstance(df, pd.DataFrame):
573
+ dynamic_cols = ["_dynamic_highlight", "_dynamic_annotation"]
574
+ cols_to_drop = [c for c in dynamic_cols if c in df.columns]
575
+ if cols_to_drop:
576
+ vue_data["plotData"] = df.drop(columns=cols_to_drop)
577
+
578
+ # Remove _plotConfig since it may reference dynamic columns
579
+ vue_data.pop("_plotConfig", None)
580
+ return vue_data
581
+
582
+ def _apply_fresh_annotations(self, vue_data: Dict[str, Any]) -> Dict[str, Any]:
583
+ """
584
+ Apply current dynamic annotations to cached base vue_data.
585
+
586
+ This is called by bridge.py when there's a cache hit for a component
587
+ with dynamic annotations. Re-applies the current annotation state.
588
+
589
+ Args:
590
+ vue_data: Cached base vue_data (without annotation columns)
591
+
592
+ Returns:
593
+ vue_data with current annotations applied
594
+ """
595
+ import pandas as pd
596
+
597
+ df_pandas = vue_data.get("plotData")
598
+ if df_pandas is None:
599
+ return vue_data
600
+
601
+ # Ensure we have a DataFrame
602
+ if not isinstance(df_pandas, pd.DataFrame):
603
+ return vue_data
604
+
605
+ # Determine highlight/annotation columns
606
+ highlight_col = self._highlight_column
607
+ annotation_col = self._annotation_column
608
+
609
+ if self._dynamic_annotations and len(df_pandas) > 0:
610
+ # Apply dynamic annotations
611
+ df_pandas = df_pandas.copy()
612
+ num_rows = len(df_pandas)
613
+ highlights = [False] * num_rows
614
+ annotations = [""] * num_rows
615
+
616
+ # Get the interactivity column for lookup
617
+ id_column = None
618
+ if self._interactivity:
619
+ id_column = list(self._interactivity.values())[0]
620
+
621
+ # Apply annotations by peak_id lookup
622
+ if id_column and id_column in df_pandas.columns:
623
+ peak_ids = df_pandas[id_column].tolist()
624
+ for row_idx, peak_id in enumerate(peak_ids):
625
+ if peak_id in self._dynamic_annotations:
626
+ ann_data = self._dynamic_annotations[peak_id]
627
+ highlights[row_idx] = ann_data.get("highlight", False)
628
+ annotations[row_idx] = ann_data.get("annotation", "")
629
+ else:
630
+ # Fallback: use row index as key
631
+ for idx, ann_data in self._dynamic_annotations.items():
632
+ if isinstance(idx, int) and 0 <= idx < num_rows:
633
+ highlights[idx] = ann_data.get("highlight", False)
634
+ annotations[idx] = ann_data.get("annotation", "")
635
+
636
+ df_pandas["_dynamic_highlight"] = highlights
637
+ df_pandas["_dynamic_annotation"] = annotations
638
+ highlight_col = "_dynamic_highlight"
639
+ annotation_col = "_dynamic_annotation"
640
+
641
+ # Build result
642
+ vue_data = dict(vue_data)
643
+ vue_data["plotData"] = df_pandas
644
+ vue_data["_plotConfig"] = self._build_plot_config(highlight_col, annotation_col)
645
+ return vue_data
646
+
647
+ @classmethod
648
+ def from_sequence_view(
649
+ cls,
650
+ sequence_view: "SequenceView",
651
+ cache_id: str,
652
+ cache_path: str = ".",
653
+ title: Optional[str] = None,
654
+ x_label: str = "m/z",
655
+ y_label: str = "Intensity",
656
+ styling: Optional[Dict[str, Any]] = None,
657
+ **kwargs,
658
+ ) -> "LinePlot":
659
+ """
660
+ Create a LinePlot linked to a SequenceView for annotated spectrum display.
661
+
662
+ The created LinePlot will:
663
+ - Use the same peaks data as the SequenceView
664
+ - Use the same filters (spectrum selection)
665
+ - Use the same interactivity (peak selection)
666
+ - Automatically apply annotations from SequenceView when rendered
667
+
668
+ Args:
669
+ sequence_view: The SequenceView to link to
670
+ cache_id: Unique identifier for this component's cache
671
+ cache_path: Base path for cache storage
672
+ title: Plot title (optional)
673
+ x_label: X-axis label (default: "m/z")
674
+ y_label: Y-axis label (default: "Intensity")
675
+ styling: Style configuration dict
676
+ **kwargs: Additional LinePlot configuration
677
+
678
+ Returns:
679
+ A new LinePlot instance linked to the SequenceView
680
+
681
+ Example:
682
+ sequence_view = SequenceView(
683
+ cache_id="seq",
684
+ sequence_data=sequences_df,
685
+ peaks_data=peaks_df,
686
+ filters={"spectrum": "scan_id"},
687
+ interactivity={"peak": "peak_id"},
688
+ )
689
+
690
+ # Create linked LinePlot
691
+ spectrum_plot = LinePlot.from_sequence_view(
692
+ sequence_view,
693
+ cache_id="spectrum",
694
+ title="Annotated Spectrum",
695
+ )
696
+
697
+ # Render both - annotations flow automatically
698
+ sv_result = sequence_view(key="sv", state_manager=state_manager)
699
+ spectrum_plot(key="plot", state_manager=state_manager, sequence_view_key="sv")
700
+ """
701
+ # Get peaks data from SequenceView (uses cached data)
702
+ peaks_data = sequence_view.peaks_data
703
+
704
+ if peaks_data is None:
705
+ raise ValueError(
706
+ "SequenceView has no peaks_data. Cannot create linked LinePlot."
707
+ )
708
+
709
+ # Only include filters whose columns exist in peaks_data
710
+ # SequenceView may have filters for both sequence_data and peaks_data,
711
+ # but LinePlot only uses peaks_data
712
+ peaks_columns = peaks_data.collect_schema().names()
713
+ valid_filters = (
714
+ {
715
+ identifier: column
716
+ for identifier, column in sequence_view._filters.items()
717
+ if column in peaks_columns
718
+ }
719
+ if sequence_view._filters
720
+ else None
721
+ )
722
+
723
+ # Create the LinePlot with filtered filters and interactivity
724
+ plot = cls(
725
+ cache_id=cache_id,
726
+ data=peaks_data,
727
+ filters=valid_filters,
728
+ interactivity=sequence_view._interactivity.copy()
729
+ if sequence_view._interactivity
730
+ else None,
731
+ cache_path=cache_path,
732
+ x_column="mass",
733
+ y_column="intensity",
734
+ title=title,
735
+ x_label=x_label,
736
+ y_label=y_label,
737
+ styling=styling,
738
+ **kwargs,
739
+ )
740
+
741
+ # Store reference to sequence view key for annotation lookup
742
+ plot._linked_sequence_view_key: Optional[str] = None
743
+
744
+ return plot
745
+
746
+ def __call__(
747
+ self,
748
+ key: Optional[str] = None,
749
+ state_manager: Optional["StateManager"] = None,
750
+ height: Optional[int] = None,
751
+ sequence_view_key: Optional[str] = None,
752
+ ) -> Any:
753
+ """
754
+ Render the component in Streamlit.
755
+
756
+ Args:
757
+ key: Optional unique key for the Streamlit component
758
+ state_manager: Optional StateManager for cross-component state.
759
+ If not provided, uses a default shared StateManager.
760
+ height: Optional height in pixels for the component
761
+ sequence_view_key: Optional key of a SequenceView component to get
762
+ annotations from. When provided, automatically applies fragment
763
+ annotations from that SequenceView.
764
+
765
+ Returns:
766
+ The value returned by the Vue component (usually selection state)
767
+ """
768
+ from ..core.state import get_default_state_manager
769
+ from ..rendering.bridge import get_component_annotations, render_component
770
+
771
+ if state_manager is None:
772
+ state_manager = get_default_state_manager()
773
+
774
+ # Apply annotations from linked SequenceView if specified
775
+ if sequence_view_key:
776
+ annotations_df = get_component_annotations(sequence_view_key)
777
+ if annotations_df is not None and annotations_df.height > 0:
778
+ # Convert annotation DataFrame to dynamic annotations dict
779
+ # keyed by peak_id for stable lookup
780
+ dynamic_annotations = {}
781
+ for row in annotations_df.iter_rows(named=True):
782
+ peak_id = row.get("peak_id")
783
+ if peak_id is not None:
784
+ dynamic_annotations[peak_id] = {
785
+ "highlight": True,
786
+ "annotation": row.get("annotation", ""),
787
+ "color": row.get("highlight_color", "#E4572E"),
788
+ }
789
+ self.set_dynamic_annotations(dynamic_annotations)
790
+ else:
791
+ self.clear_dynamic_annotations()
792
+
793
+ return render_component(
794
+ component=self, state_manager=state_manager, key=key, height=height
795
+ )
796
+
797
+
798
+ # Type hint import
799
+ if TYPE_CHECKING:
800
+ from ..core.state import StateManager