Metadata-Version: 2.4
Name: expops-matplotlib
Version: 0.1.2.dev0
Summary: Matplotlib chart component plugin for expops
License: MIT License
        
        Copyright (c) 2026
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: expops==0.1.25.dev0
Requires-Dist: matplotlib
Requires-Dist: numpy
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Dynamic: license-file

# expops_matplotlib

Matplotlib chart component plugin for `expops`.

## Install

```bash
pip install expops-matplotlib
```

This installs `expops` and registers the matplotlib component runner via entry points.

## Usage

Matplotlib component processes **must** declare `probe_paths` (same model as static reporting charts). Series data is read from the run KV store via those paths; you **cannot** supply `x` / `y` from pipeline edges or `input_transform`.

Use shorthand probe params for chart definitions (step-keyed dicts: `"1": value`, …):
- Line/Histogram/BoxPlot/HeatMap: `parameters.probe_metric` (+ optional `parameters.probe_key`). This can be omitted if `probe_paths.<probe_key>.metric` is provided.
- ScatterPlot: `parameters.probe_x_metric` + `parameters.probe_y_metric` (+ optional `parameters.probe_key`). This can be omitted if `probe_paths.<probe_key>.x_metric` / `probe_paths.<probe_key>.y_metric` are provided.

For bar charts, prefer `parameters.probe_bar_metric` and/or per-probe `probe_paths.<key>.metric`.

Supported curated charts:

- `matplotlib.BarChart.render` — `parameters.probe_bar_metric` and/or per-probe `probe_paths.<key>.metric`
- `matplotlib.LineChart.render` — `parameters.probe_metric` (+ optional `parameters.probe_key`)
- `matplotlib.ScatterPlot.render` — `parameters.probe_x_metric` + `parameters.probe_y_metric` (+ optional `parameters.probe_key`)
- `matplotlib.Histogram.render` — `parameters.probe_metric` (+ optional `parameters.probe_key`)
- `matplotlib.BoxPlot.render` — `parameters.probe_metric` (+ optional `parameters.probe_key`, one box)
- `matplotlib.HeatMap.render` — `parameters.probe_metric` (+ optional `parameters.probe_key`)

`output_mapping` may still remap canonical outputs (`chart_path`, `chart_name`).

## Probe Paths and Metric Formats

### `probe_paths`

`probe_paths` is a required mapping from a short probe key (for example `train`, `eval`, `mlp_train`) to a selector string that identifies a process run (the same kind of selector used by static reporting charts).

For some charts, `probe_paths.<key>` can also be an **object** instead of a plain string. In that case, the runner still reads the KV metrics from the `path` field, but it can also use the extra override fields described below.

Example shape:

```yaml
probe_paths:
  train:
    path: "//*[@name='train_model']"
    metric: loss            # chart-specific override (see below)
    x_metric: step_time    # scatter-specific override (see below)
    y_metric: loss         # scatter-specific override (see below)
    label: Train Loss      # BarChart-specific label override
```

### Step-keyed series

Most series-based charts (Line/Scatter/Histogram/BoxPlot) read metric values from the run KV store using the selected metric name.

For these charts, each metric is expected to be a **step-keyed dict** in the form:

```json
{
  "1": 0.42,
  "2": 0.41,
  "3": 0.40
}
```

The runner converts step keys to `int` and values to `float`. Missing steps simply do not appear in the chart data.

### HeatMap matrix

For `HeatMap`, the selected metric must be a **2D nested list** (matrix) of numbers (passed to `matplotlib.axes.Axes.imshow`).

```json
[[1, 2], [3, 4]]
```

### Precedence summary (shorthand + probe-path overrides)

All curated charts use shorthand params. For non-bar charts:
- `probe_metric` (+ optional `probe_key`)
- `probe_x_metric` / `probe_y_metric` (+ optional `probe_key`)

Per-probe overrides in `probe_paths.<key>` take precedence over the shorthand values.

### Defaults (all matplotlib chart processes)

If omitted, the runner sets:

- **`filename`** — derived from the process `name` (seed/part suffixes stripped), sanitized to a safe basename, with a `.png` extension (e.g. `plot_metrics` → `plot_metrics.png`).
- **`title`** — the same base process name with underscores replaced by spaces (e.g. `plot_metrics` → `plot metrics`).
- **`ylabel` (BarChart only)** — if `parameters.ylabel` is empty, uses `probe_bar_metric` (or a single shared per-probe metric when metrics differ).

Override any of these by setting the corresponding `parameters` key explicitly.

### Bar chart (categorical bars, last value per probe)

The runner creates one bar per `probe_paths` entry, in YAML/map order.

Simple form (same metric for all probes):

```yaml
processes:
  - name: "accuracy_bars"
    component: "matplotlib.BarChart.render"
    probe_paths:
      train: "//*[@name='train_model']"
      eval: "//*[@name='predict_model']"
    parameters:
      grid: true
      ylim: [0, 1]
      probe_bar_metric: accuracy
```

Optional per-probe metrics (metric can live next to the path):

```yaml
processes:
  - name: "mixed_metrics_bars"
    component: "matplotlib.BarChart.render"
    probe_paths:
      train:
        path: "//*[@name='train_model']"
        metric: accuracy
      eval:
        path: "//*[@name='predict_model']"
        metric: loss
```

For each bar entry, the runner takes the **last** value of the selected `metric` over logged steps from each probe key in `probe_paths` (metric comes from `probe_bar_metric` or per-probe `probe_paths.<key>.metric`).

More details:

1. **Bar order** follows the YAML/map order of `probe_paths`.
2. **Bar labels** come from the `probe_paths` key by default, or `probe_paths.<key>.label` when set.
3. **Metric selection** comes from:
   - `parameters.probe_bar_metric` (shared metric for all probes),
   - `probe_paths.<key>.metric` (per-probe metric).

Matplotlib parameters forwarded by the adapter for BarChart: `color`, `alpha`, `width`. Axis labels come from `xlabel`/`ylabel` (or defaults set by the chart spec), and `grid`/`ylim` are handled by the runner.

### Line chart (one metric vs step index)

```yaml
processes:
  - name: "loss_line"
    component: "matplotlib.LineChart.render"
    probe_paths:
      train: "//*[@name='train_model']"
    parameters:
      probe_key: train
      probe_metric: loss   # can be omitted if probe_paths.<probe_key>.metric is provided
```

Optional per-probe override: set `probe_paths.<key>.metric`; it takes precedence over `parameters.probe_metric`.

Data contract:

1. `parameters.probe_metric` selects a KV metric name inside the probed block named `parameters.probe_key`.
2. The metric must be a step-keyed dict. The chart x-values are the integer step keys (sorted), and the y-values are the corresponding floats.

Matplotlib parameters forwarded by the adapter for LineChart: `color`, `alpha`, `linewidth`, `linestyle`, `marker`.

Example using shorthand with per-probe `metric` overrides:

```yaml
processes:
  - name: "loss_line_shorthand_overrides"
    component: "matplotlib.LineChart.render"
    probe_paths:
      train:
        path: "//*[@name='train_model']"
        metric: train_loss
      val:
        path: "//*[@name='predict_model']"
        metric: val_loss
    parameters:
      probe_key: val
      probe_metric: val_loss   # optional (override already set on probe_paths.val.metric)
      grid: true
      color: "steelblue"
```

### Scatter (two metrics on the same probe, aligned by step keys)

```yaml
parameters:
  probe_key: train   # optional; defaults to the first key in `probe_paths`
  probe_x_metric: step_time   # can be omitted if probe_paths.<probe_key>.x_metric is provided
  probe_y_metric: loss        # can be omitted if probe_paths.<probe_key>.y_metric is provided
```

Optional per-probe override: set `probe_paths.<key>.x_metric` / `probe_paths.<key>.y_metric`; it takes precedence over `parameters.probe_x_metric` / `parameters.probe_y_metric`.

Data contract:

1. `probe_x_metric` / `probe_y_metric` select two step-keyed KV metrics from the same probe block.
2. The runner aligns the two series by taking the intersection of step keys present in both x and y dicts.
3. The chart x-values are floats from the x-metric at the shared steps; y-values come from the y-metric at those same steps.

Matplotlib parameters forwarded by the adapter for ScatterPlot: `color`, `alpha`, `marker`, `s`.

Example using shorthand with per-probe `x_metric` / `y_metric` overrides:

```yaml
processes:
  - name: "pred_vs_time_scatter"
    component: "matplotlib.ScatterPlot.render"
    probe_paths:
      train:
        path: "//*[@name='train_model']"
        x_metric: step_time
        y_metric: loss
    parameters:
      probe_key: train
      probe_x_metric: step_time   # optional (override already set)
      probe_y_metric: loss         # optional (override already set)
      marker: "o"
      s: 10
```

### Histogram (values = y-series of one step-keyed metric)

```yaml
parameters:
  probe_key: train   # optional; defaults to the first key in `probe_paths`
  probe_metric: score   # can be omitted if probe_paths.<probe_key>.metric is provided
```

Optional per-probe override: set `probe_paths.<key>.metric`; it takes precedence over `parameters.probe_metric`.

Data contract:

1. `probe_metric` selects a step-keyed KV metric.
2. The runner converts that step-keyed metric to a list of y-values; step keys themselves are not used for binning.
3. `bins` (if provided) is forwarded to `ax.hist(...)` via the adapter.

Matplotlib parameters forwarded by the adapter for Histogram: `bins`, `color`, `alpha`.

Example:

```yaml
processes:
  - name: "loss_hist"
    component: "matplotlib.Histogram.render"
    probe_paths:
      train: "//*[@name='train_model']"
    parameters:
      probe_metric: train_loss
      probe_key: train
      bins: 20
      color: "steelblue"
      alpha: 0.7
      grid: true
```

### Box plot (one list of values per probe key)

```yaml
parameters:
  probe_key: train   # optional; defaults to the first key in `probe_paths`
  probe_metric: loss   # can be omitted if probe_paths.<probe_key>.metric is provided
```

Data contract:

1. `probe_metric` selects a step-keyed KV metric from the selected probe key.
2. The runner extracts y-values from that metric’s step-keyed dict and passes a single data series to `ax.boxplot(...)`.

Matplotlib parameters forwarded by the adapter for BoxPlot: `vert`, `patch_artist`.

### Heat map (metric value is a nested list matrix)

```yaml
parameters:
  probe_key: train   # optional; defaults to the first key in `probe_paths`
  probe_metric: confusion_matrix   # can be omitted if probe_paths.<probe_key>.metric is provided
```

Optional per-probe override: set `probe_paths.<key>.metric`; it takes precedence over `parameters.probe_metric`.

Data contract:

1. `probe_metric` selects a matrix-valued KV metric.
2. The matrix must be a 2D nested list (rows x columns). Values are converted to `float`.
3. The runner passes the matrix to `ax.imshow(...)`; `cmap`/`aspect`/`interpolation` are forwarded to `imshow`.

Matplotlib parameters forwarded by the adapter for HeatMap: `cmap`, `aspect`, `interpolation`.

Example:

```yaml
processes:
  - name: "confusion_heatmap"
    component: "matplotlib.HeatMap.render"
    probe_paths:
      val: "//*[@name='predict_model']"
    parameters:
      probe_key: val
      probe_metric: confusion_matrix
      cmap: "Blues"
      aspect: "auto"
      interpolation: "nearest"
      grid: true
```
