Plotting#
VIBE uses Strategies for generating plots from validation data. This allows you to control:
- How datasets are combined in plots (overlaid, separate, per-component) 
- Which rendering backend to use (matplotlib, ROOT) 
- How plots are styled and formatted 
Overview of Strategies#
A plotting strategy defines how plots are generated from your parquet data files. VIBE provides three built-in strategies:
- PerDatasetPlottingStrategy: Generates separate plots for each dataset 
- OverlayDatasetsPlottingStrategy: Overlays all datasets on a single plot 
- OverlayDatasetsPerComponentPlottingStrategy: Creates overlay plots separately for each histogram component 
The plotting design separates the plotting logic (strategy) from the rendering backend (renderer), allowing you to mix and match different combinations.
Built-in Plotting Strategies#
Per-Dataset Strategy#
This strategy generates one plot per dataset. Each histogram defined in your validation mode will produce multiple output files - one for each dataset in your configuration.
Use case: When you want to analyze each dataset individually and compare them side-by-side.
Output: For 3 datasets and 5 histograms, you get 15 separate plots.
Overlay Datasets Strategy#
This strategy overlays all datasets on the same plot. All histogram components from all datasets are shown together with distinct colors/styles.
Use case: When you want to directly compare all datasets on a single plot.
Output: For 3 datasets and 5 histograms, you get 5 plots, each containing all datasets.
Overlay Datasets Per-Component Strategy#
This strategy creates separate overlay plots for each histogram component. All datasets are overlaid, but only for a single component at a time.
Use case: When your histogram has multiple components (e.g., signal/background) and you want to compare how each component varies across datasets.
Output: For 3 datasets, 5 histograms, and 2 components per histogram, you get 10 plots.
Configuring Strategies in Your Mode#
Default Strategies#
By default, all three built-in strategies are available for any validation mode. You can control which strategies to use by overriding the plotting_strategies property:
from vibe.core.validation_mode import ValidationModeBaseClass
class MyValidationMode(ValidationModeBaseClass):
    name = "MyMode"
    @property
    def plotting_strategies(self):
        # Only use per-dataset and overlay strategies
        return ["per_dataset", "overlay_datasets"]
    # ... rest of your mode implementation
Available strategy names:
- "per_dataset"
- "overlay_datasets"
- "overlay_datasets_per_component"
Using Custom Strategies#
If you’ve registered a custom plotting strategy (see below), you can include it in the list:
@property
def plotting_strategies(self):
    return ["per_dataset", "my_custom_strategy"]
Renderers and Output Formats#
VIBE supports multiple renderers for different output formats:
- Matplotlib Renderer (MatplotlibHistogramRenderer)
- Generates matplotlib-style plots 
- Default renderer for all built-in strategies 
 
- ROOT Renderer (ROOTHistogramRenderer)
- Generates ROOT-compatible plots 
- Uses PyROOT for rendering 
 
Creating a Custom Plotting Strategy#
Step 1: Inherit from PlottingStrategy#
Create a new Python file in vibe/core/plotting/strategies/custom/ and define your strategy class:
# vibe/core/plotting/strategies/custom/my_custom_strategy.py
from vibe.core.plotting.strategies.plotting_strategy import PlottingStrategy
from vibe.core.plotting.renderers.renderer_config import (
    RendererConfigFactory,
    ConfigurableRenderer
)
from vibe.core.helper.root_helper import makeROOTCompatible
from vibe.core.helper.settings import get_setting
from vibe.core.helper.parquet_helper import read_parquet
import os
import numpy as np
class MyCustomPlottingStrategy(PlottingStrategy):
    """Custom plotting strategy with specialized behavior."""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
Step 2: Implement the generate_plots Method#
The generate_plots method is where all the plotting logic goes. It receives:
- input_file_paths: Dictionary mapping dataset keys to parquet file paths
- mode: The validation mode instance containing configuration
- dry_run: Boolean flag for testing without actually generating plots
def generate_plots(self, input_file_paths, mode, dry_run=False):
    """Generate plots according to custom strategy.
    Args:
        input_file_paths (dict): Mapping of keys to parquet file paths
        mode: Validation mode configuration
        dry_run (bool): If True, only notify observers without rendering
    """
    if self._histogram_renderer is None:
        raise ValueError("Histogram renderer is not set.")
    # Iterate through histograms
    histograms_by_particle_list = mode.validation_class(
        base_path=get_setting(key="base_path")
    ).get_analysis_validation_histograms_by_particle_list()
    for particle_list, histograms in histograms_by_particle_list.items():
        for histogram in histograms:
            self._generate_single_plot(
                histogram=histogram,
                particle_list=particle_list,
                input_file_paths=input_file_paths,
                mode=mode,
                dry_run=dry_run
            )
Here you would implement your custom plotting logic inside the _generate_single_plot. In principle, this can be any logic you want, for example generating ratio plots, normalized overlays, etc.
Step 4: Register Your Strategy#
Register your custom strategy with the PlottingStrategiesProvider so it can be used in validation modes:
# vibe/core/plotting/strategies/plotting_strategies_provider.py
from vibe.core.plotting.strategies.custom.my_custom_strategy import (
    MyCustomPlottingStrategy
)
from vibe.core.plotting.renderers.matplotlib_histogram_renderer import (
    MatplotlibHistogramRenderer
)
from vibe.core.plotting.renderers.root_histogram_renderer import (
    ROOTHistogramRenderer
)
# Register with matplotlib renderer
PlottingStrategiesProvider.register(
    "my_custom_strategy",
    MyCustomPlottingStrategy(
        histogram_renderer=MatplotlibHistogramRenderer()
    )
)
# Or register with ROOT renderer
PlottingStrategiesProvider.register(
    "my_custom_strategy_root",
    MyCustomPlottingStrategy(
        histogram_renderer=ROOTHistogramRenderer()
    )
)
Step 5: Use in Your Validation Mode#
Now you can use your custom strategy in any validation mode:
from vibe.core.validation_mode import ValidationModeBaseClass
class MyMode(ValidationModeBaseClass):
    name = "MyMode"
    @property
    def plotting_strategies(self):
        return ["my_custom_strategy", "per_dataset"]
    # ... rest of mode implementation
Notifying Observers#
You need to remember to notify observers before generating each plot, as it is a way to tell VIBE what plots are being created. When a plot is about to be generated, the strategy notifies observers with the output key and path:
def generate_plots(self, input_file_paths, mode, dry_run=False):
    # ... setup code ...
    for histogram in histograms:
        output_key = "MyMode_dataset1_histogram1"
        output_path = "/path/to/output.pdf"
        # Notify observers before generating
        self.notify_observers(output_key, output_path)
        # Generate the actual plot
        if not dry_run:
            self._histogram_renderer.render(...)
