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