Hello,
I am trying to plot a kind of “whisker” plot in ROOT using TLine for the vertical and horizontal whiskers, and TMarker for the median. The plot is meant to display several “whiskers” iteratively for different datasets. However, I am facing an issue where the final canvas only shows the result of the last iteration. I have tried using canvas.Modified() and canvas.Update() after each drawing step, but the issue persists.
I have attached a screenshot showing the final plot where only the last whisker appears. I would appreciate any guidance on what might be going wrong and how to properly draw multiple whiskers on the same canvas.
Here’s a simplified version of the code I am using:
import ROOT
import numpy as np
import re
def create_stats_plot(data, title="Statistical Boxplot", output_file="boxplot_stats.png", y_scale_factor=1.0):
    """
    Create a boxplot-like visualization with vertical lines and whiskers for two datasets using ROOT.
    Parameters:
    data (dict): A dictionary where keys are folder names, and values are dictionaries containing statistics (min, q1, median, q3, max, etc.).
    title (str): Title of the boxplot.
    output_file (str): The file path to save the output plot.
    y_scale_factor (float): Factor to scale the y-axis.
    """
    # Extract x-axis values (e.g., time or categories) and statistics for both DatasetA and DatasetB
    x_axisA = []
    datasetA_stats = []
    datasetB_stats = []
    #==============================================================================
    #            EXTRACT STATISTICAL DATA FROM THE DICTIONARY
    for folder, stats in data.items():
        match = re.search(r'Exp(\d+)', folder)
        if match:
            x_value = int(match.group(1))
            x_axisA.append(x_value)
            # DatasetA statistics
            datasetA_stats.append([
                stats['datasetA']['min'] / y_scale_factor,    # Min
                stats['datasetA']['q1'] / y_scale_factor,     # Q1
                stats['datasetA']['median'] / y_scale_factor, # Median
                stats['datasetA']['q3'] / y_scale_factor,     # Q3
                stats['datasetA']['max'] / y_scale_factor     # Max
            ])
            # DatasetB statistics
            datasetB_stats.append([
                stats['datasetB']['min'] / y_scale_factor,    # Min
                stats['datasetB']['q1'] / y_scale_factor,     # Q1
                stats['datasetB']['median'] / y_scale_factor, # Median
                stats['datasetB']['q3'] / y_scale_factor,     # Q3
                stats['datasetB']['max'] / y_scale_factor     # Max
            ])
    # Sort the data by x-axis value (e.g., exposure time)
    sorted_indices = np.argsort(x_axisA)
    x_axisA = np.array(x_axisA)[sorted_indices]
    datasetA_stats = np.array(datasetA_stats)[sorted_indices]
    datasetB_stats = np.array(datasetB_stats)[sorted_indices]
    #==============================================================================
    # Create a ROOT TCanvas for drawing
    canvas = ROOT.TCanvas("canvas", title, 800, 600)
    # Set axis labels, title, and grid
    frame = canvas.DrawFrame(np.min(x_axisA) - 1, 
                             np.min([datasetA_stats.min(), datasetB_stats.min()]) - 10, 
                             np.max(x_axisA) + 1, 
                             np.max([datasetA_stats.max(), datasetB_stats.max()]) + 10)
    
    frame.SetTitle(title)
    frame.GetXaxis().SetTitle("X-Axis Variable")
    frame.GetYaxis().SetTitle("Y-Axis Variable")
    # Draw DatasetA lines and whiskers
    whisker_length = 0.5  # Adjusted for better visual spacing
    datasetA_ref_line = None
    for i, x_value in enumerate(x_axisA):
        min_val, q1_val, median_val, q3_val, max_val = datasetA_stats[i]
        # Draw vertical line for DatasetA
        line = ROOT.TLine(x_value, min_val, x_value, max_val)
        line.SetLineColor(ROOT.kBlue)
        line.SetLineWidth(2)
        line.Draw("SAME")
        
        if i == 0:
            datasetA_ref_line = line
        # Draw horizontal whiskers for min and max
        min_whisker = ROOT.TLine(x_value - whisker_length, min_val, x_value + whisker_length, min_val)
        max_whisker = ROOT.TLine(x_value - whisker_length, max_val, x_value + whisker_length, max_val)
        min_whisker.SetLineColor(ROOT.kBlue)
        max_whisker.SetLineColor(ROOT.kBlue)
        min_whisker.Draw("SAME")
        max_whisker.Draw("SAME")
        # Draw median marker for DatasetA
        marker = ROOT.TMarker(x_value, median_val, 20)
        marker.SetMarkerColor(ROOT.kBlue)
        marker.Draw("SAME")
    # Draw DatasetB lines and whiskers
    datasetB_ref_line = None
    for i, x_value in enumerate(x_axisA):
        min_val, q1_val, median_val, q3_val, max_val = datasetB_stats[i]
        # Draw vertical line for DatasetB
        line = ROOT.TLine(x_value, min_val, x_value, max_val)
        line.SetLineColor(ROOT.kGreen)
        line.SetLineWidth(2)
        line.Draw("SAME")
        
        if i == 0:
            datasetB_ref_line = line
        # Draw horizontal whiskers for min and max
        min_whisker = ROOT.TLine(x_value - whisker_length, min_val, x_value + whisker_length, min_val)
        max_whisker = ROOT.TLine(x_value - whisker_length, max_val, x_value + whisker_length, max_val)
        min_whisker.SetLineColor(ROOT.kGreen)
        max_whisker.SetLineColor(ROOT.kGreen)
        min_whisker.Draw("SAME")
        max_whisker.Draw("SAME")
        # Draw median marker for DatasetB
        marker = ROOT.TMarker(x_value, median_val, 20)
        marker.SetMarkerColor(ROOT.kGreen)
        marker.Draw("SAME")
    # Add legend using the first line for each dataset
    legend = ROOT.TLegend(0.15, 0.65, 0.35, 0.85)
    legend.AddEntry(datasetA_ref_line, "DatasetA", "l")
    legend.AddEntry(datasetB_ref_line, "DatasetB", "l")
    legend.Draw()
    canvas.Modified()
    canvas.Update()
    # Save the canvas as an image file
    canvas.SaveAs(output_file)
    # Clean up
    canvas.Close()
    del canvas
    print(f"Boxplot saved to {output_file}")
# Sample data dictionary for testing purposes
sample_data = {
    'Exp10': {'datasetA': {'min': 1, 'q1': 2, 'median': 3, 'q3': 4, 'max': 5},
              'datasetB': {'min': 2, 'q1': 3, 'median': 4, 'q3': 5, 'max': 6}},
    'Exp20': {'datasetA': {'min': 1.5, 'q1': 2.5, 'median': 3.5, 'q3': 4.5, 'max': 5.5},
              'datasetB': {'min': 2.5, 'q1': 3.5, 'median': 4.5, 'q3': 5.5, 'max': 6.5}},
    'Exp30': {'datasetA': {'min': 2, 'q1': 3, 'median': 4, 'q3': 5, 'max': 6},
              'datasetB': {'min': 3, 'q1': 4, 'median': 5, 'q3': 6, 'max': 7}},
}
# Running the function with sample data
create_stats_plot(sample_data)
ROOT Version: 6.32.00
Platform: linuxx8664gcc
Compiler: GCC
