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:

  1. PerDatasetPlottingStrategy: Generates separate plots for each dataset

  2. OverlayDatasetsPlottingStrategy: Overlays all datasets on a single plot

  3. 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(...)