1414from pandas .util ._decorators import Appender
1515import re
1616
17+ import matplotlib .pyplot as plt
18+ from math import ceil
19+ import numpy as np
20+
1721from numpy import ceil , log1p , log2 , nan , mean , repeat , concatenate
1822from ._config import (
1923 LegendConfig ,
@@ -539,7 +543,6 @@ def _create_tooltips(self, entries: dict, index: bool = True):
539543
540544
541545class ChromatogramPlot (BaseMSPlot , ABC ):
542-
543546 _config : ChromatogramConfig = None
544547
545548 @property
@@ -560,9 +563,7 @@ def load_config(self, **kwargs):
560563
561564 def __init__ (self , data , config : ChromatogramConfig = None , ** kwargs ) -> None :
562565 super ().__init__ (data , config , ** kwargs )
563-
564566 self .label_suffix = self .x # set label suffix for bounding box
565-
566567 self ._check_and_aggregate_duplicates ()
567568
568569 # sort data by x so in order
@@ -579,45 +580,89 @@ def __init__(self, data, config: ChromatogramConfig = None, **kwargs) -> None:
579580
580581 def plot (self ):
581582 """
582- Create the plot
583+ Create the plot. If the configuration includes a valid tile_by column,
584+ the data will be split into subplots based on unique values in that column.
583585 """
584- tooltip_entries = {"retention time" : self .x , "intensity" : self .y }
585- if "Annotation" in self .data .columns :
586- tooltip_entries ["annotation" ] = "Annotation"
587- if "product_mz" in self .data .columns :
588- tooltip_entries ["product m/z" ] = "product_mz"
589- tooltips , custom_hover_data = self ._create_tooltips (
590- tooltip_entries , index = False
591- )
592-
593- linePlot = self .get_line_renderer (data = self .data , config = self ._config )
594-
595- self .canvas = linePlot .generate (tooltips , custom_hover_data )
596- self ._modify_y_range ((0 , self .data [self .y ].max ()), (0 , 0.1 ))
597-
598- if self ._interactive :
599- self .manual_boundary_renderer = self ._add_bounding_vertical_drawer ()
600-
601- if self .annotation_data is not None :
602- self ._add_peak_boundaries (self .annotation_data )
586+ # Check for tiling functionality
587+ tile_by = self ._config .tile_by if hasattr (self ._config , "tile_by" ) else None
588+
589+ if tile_by and tile_by in self .data .columns :
590+ # Group the data by the tile_by column
591+ grouped = self .data .groupby (tile_by )
592+ num_groups = len (grouped )
593+
594+ # Get tiling options from config
595+ tile_columns = self ._config .tile_columns if hasattr (self ._config , "tile_columns" ) else 1
596+ tile_rows = int (ceil (num_groups / tile_columns ))
597+ figsize = self ._config .tile_figsize if hasattr (self ._config , "tile_figsize" ) else (10 , 15 )
598+
599+ # Create a figure with a grid of subplots
600+ fig , axes = plt .subplots (tile_rows , tile_columns , figsize = figsize , squeeze = False )
601+ axes = axes .flatten () # Easier indexing for a 1D list
602+
603+ # Loop through each group and plot on its own axis
604+ for i , (group_val , group_df ) in enumerate (grouped ):
605+ ax = axes [i ]
606+
607+ # Prepare tooltips for this group (if applicable)
608+ tooltip_entries = {"retention time" : self .x , "intensity" : self .y }
609+ if "Annotation" in group_df .columns :
610+ tooltip_entries ["annotation" ] = "Annotation"
611+ if "product_mz" in group_df .columns :
612+ tooltip_entries ["product m/z" ] = "product_mz"
613+ tooltips , custom_hover_data = self ._create_tooltips (tooltip_entries , index = False )
614+
615+ # Get a line renderer instance and generate the plot for the current group,
616+ # passing the current axis (canvas) using a parameter like `canvas` or `ax`.
617+ linePlot = self .get_line_renderer (data = group_df , config = self ._config )
618+ # Here, we assume that your renderer can accept the axis to plot on:
619+ linePlot .canvas = ax
620+ linePlot .generate (tooltips , custom_hover_data )
621+
622+
623+ # Set the title of this subplot based on the group value
624+ ax .set_title (f"{ tile_by } : { group_val } " , fontsize = 14 )
625+ # Optionally adjust the y-axis limits for the subplot
626+ ax .set_ylim (0 , group_df [self .y ].max ())
627+
628+ # If you have annotations that should be split, filter them too
629+ if self .annotation_data is not None and tile_by in self .annotation_data .columns :
630+ group_annotations = self .annotation_data [self .annotation_data [tile_by ] == group_val ]
631+ self ._add_peak_boundaries (group_annotations )
632+
633+ # Remove any extra axes if the grid size is larger than the number of groups
634+ for j in range (i + 1 , len (axes )):
635+ fig .delaxes (axes [j ])
636+
637+ fig .tight_layout ()
638+ self .canvas = fig
639+ else :
640+ # Fallback: plot on a single canvas if no valid tiling is specified
641+ tooltip_entries = {"retention time" : self .x , "intensity" : self .y }
642+ if "Annotation" in self .data .columns :
643+ tooltip_entries ["annotation" ] = "Annotation"
644+ if "product_mz" in self .data .columns :
645+ tooltip_entries ["product m/z" ] = "product_mz"
646+ tooltips , custom_hover_data = self ._create_tooltips (tooltip_entries , index = False )
647+ linePlot = self .get_line_renderer (data = self .data , config = self ._config )
648+ self .canvas = linePlot .generate (tooltips , custom_hover_data )
649+ self ._modify_y_range ((0 , self .data [self .y ].max ()), (0 , 0.1 ))
650+
651+ if self ._interactive :
652+ self .manual_boundary_renderer = self ._add_bounding_vertical_drawer ()
653+ if self .annotation_data is not None :
654+ self ._add_peak_boundaries (self .annotation_data )
603655
604656 def _add_peak_boundaries (self , annotation_data ):
605657 """
606658 Prepare data for adding peak boundaries to the plot.
607- This is not a complete method should be overridden by subclasses.
608-
609- Args:
610- annotation_data (DataFrame): The feature data containing the peak boundaries.
611-
612- Returns:
613- None
659+ (Override this method if needed.)
614660 """
615- # compute the apex intensity
616661 self .compute_apex_intensity (annotation_data )
617662
618663 def compute_apex_intensity (self , annotation_data ):
619664 """
620- Compute the apex intensity of the peak group based on the peak boundaries
665+ Compute the apex intensity of the peak group based on the peak boundaries.
621666 """
622667 for idx , feature in annotation_data .iterrows ():
623668 annotation_data .loc [idx , "apexIntensity" ] = self .data .loc [
0 commit comments