Metadata-Version: 2.4
Name: fullplot
Version: 0.1.1
Summary: A documented HDF5 plotting, trace-processing, and map-generation package for engineering simulation and test data.
Keywords: hdf5,plotting,engineering,simulation,test-data,map-generation,scientific-computing
Author: Saaketh Ramoju
Author-email: Saaketh Ramoju <skramoju@gmail.com>
License-Expression: GPL-3.0-only
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Visualization
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Dist: numpy>=1.26.0
Requires-Dist: scipy>=1.11.0
Requires-Dist: h5py>=3.10.0
Requires-Dist: matplotlib>=3.8.0
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/saakethramoju/FullPlot
Project-URL: Repository, https://github.com/saakethramoju/FullPlot
Project-URL: Issues, https://github.com/saakethramoju/FullPlot/issues
Project-URL: Changelog, https://github.com/saakethramoju/FullPlot/blob/main/CHANGELOG.md
Project-URL: Documentation, https://github.com/saakethramoju/FullPlot#readme
Project-URL: PyPI, https://pypi.org/project/fullplot/
Description-Content-Type: text/markdown

# FullPlot

[![PyPI version](https://img.shields.io/pypi/v/fullplot)](https://pypi.org/project/fullplot/)
[![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://pypi.org/project/fullplot/)
[![License](https://img.shields.io/pypi/l/fullplot)](https://github.com/saakethramoju/FullPlot)

FullPlot is a lightweight HDF5 plotting, trace-processing, and map-generation package for engineering simulation and test data.

It is designed for workflows where the data is already in HDF5 and the user wants a simple Python interface for inspecting, plotting, filtering, aligning, and saving engineering traces without building a large application around the file format.

FullPlot is especially useful for:

* simulation outputs stored in HDF5,
* rocket-engine and test-stand time histories,
* generic sensor data,
* controller commands and sequence traces,
* redline, blueline, yellowline, and greenline overlays,
* quick HDF5 inspection,
* 1D trace overlays,
* dual-axis plots,
* 2D heat maps,
* multidimensional dataset slicing,
* simple rectangular-grid map generation for downstream tools such as FullFlow.

FullPlot does **not** require a special FullFlow file format. If your HDF5 file contains normal numeric datasets, FullPlot can inspect and plot them.

---

## Installation

```bash
pip3 install fullplot
```

FullPlot requires Python 3.11 or newer.

---

## Quick start

Create or open an HDF5 file that contains a `time` dataset and one or more numeric channels:

```text
hotfire.h5
├── time
├── PCMC_1
├── PCMC_2
├── PCMC_3
├── OIPT
├── FIPT
├── PBTC_1
└── MOV_CMD
```

Plot a single trace:

```python
import fullplot as fplt

run = fplt.open("hotfire.h5")

run.plot(
    x="time",
    y="PCMC_1",
    xlabel="Time [s]",
    ylabel="Pressure [psia]",
    title="Chamber Pressure",
)
```

Plot several traces:

```python
run.plot(
    x="time",
    y=["PCMC_1", "PCMC_2", "PCMC_3"],
    xlabel="Time [s]",
    ylabel="Pressure [psia]",
    title="Chamber Pressure Sensors",
)
```

Make a dual-axis plot:

```python
run.plot(
    x="time",
    y=["PCMC_1", "OIPT", "FIPT"],
    y2="MOV_CMD",
    xlabel="Time [s]",
    ylabel="Pressure [psia]",
    y2label="Command [-]",
    title="Pressure and Main Ox Valve Command",
)
```

Save a figure without showing a GUI window:

```python
run.plot(
    x="time",
    y="PCMC_1",
    save="pcmc_1.png",
    show=False,
)
```

---

## HDF5 inspection

FullPlot starts with inspection. Use `tree()` when you want to see the file layout and `list()` when you want the datasets grouped by dimensionality.

```python
import fullplot as fplt

run = fplt.open("hotfire.h5")

run.tree()
run.list()
```

The same operations are available at module level:

```python
fplt.tree("hotfire.h5")
fplt.list("hotfire.h5")
```

You can scope an `H5File` to a group:

```python
run = fplt.open("engine_sim.h5")
transient = run.at("/Engine/transient/runs/startup")

transient.tree()
transient.plot(x="time", y="Chamber_Pressure")
```

Dataset selectors can be:

* absolute HDF5 paths, such as `"/Engine/transient/time"`,
* paths relative to the current root, such as `"tracks/PCMC_1"`,
* unique short names, such as `"PCMC_1"`.

If a short name matches more than one dataset, FullPlot raises `AmbiguousDatasetError` instead of guessing.

Read a raw dataset:

```python
pressure = run.read("PCMC_1")
```

Read scalar datasets under a group:

```python
settings = run.values("metadata")
```

---

## Core public API

Most users only need these names:

```python
import fullplot as fplt

fplt.open
fplt.plot
fplt.map
fplt.tree
fplt.list
fplt.read
fplt.time
fplt.trace
fplt.write_traces
fplt.Trace
fplt.TimeAxis
fplt.Axis
fplt.generate_map
```

The main classes are:

| Name | Purpose |
| --- | --- |
| `H5File` | Lightweight handle for one HDF5 file and root group. Created with `fplt.open(...)`. |
| `Trace` | Reusable one-dimensional x/y data object. Used for raw data, filtered data, generated limits, commands, and derived traces. |
| `TimeAxis` | Shared shiftable and windowable time basis. Several traces can share one axis and follow the same alignment/window. |
| `Axis` | Independent variable definition for rectangular-grid map generation. |

Important exceptions are:

| Name | Meaning |
| --- | --- |
| `FullPlotError` | Base package exception. |
| `DatasetNotFoundError` | A requested HDF5 dataset or group could not be found. |
| `AmbiguousDatasetError` | A short name matched more than one HDF5 object. |
| `PlotDataError` | Data shape, type, dimensionality, or scale is invalid for plotting or trace creation. |
| `FullPlotMapError` | Base map-generation exception. |
| `MapGenerationError` | Map generation failed during evaluation, resume, or file layout validation. |
| `MapOutputError` | The map `evaluate(...)` function returned invalid outputs. |

---

## Trace objects

A `Trace` is a one-dimensional line of data:

```python
import fullplot as fplt

run = fplt.open("hotfire.h5")

time = run.time("time")
pc = run.trace(y="PCMC_1", x=time, name="Chamber Pressure")
```

A trace stores:

* `x`: current x-values,
* `y`: y-values,
* `name`: legend/display name,
* `role`: plotting role,
* `attrs`: metadata dictionary.

Useful trace attributes:

```python
pc.x
pc.y
pc.value
pc.time
pc.finite
pc.tmin
pc.tmax
pc.time_range
```

Create traces directly from arrays:

```python
import numpy as np
import fullplot as fplt

x = np.linspace(0.0, 10.0, 1001)
y = 300.0 + 10.0 * np.sin(x)

trace = fplt.Trace.from_arrays("Synthetic Pressure", x=x, y=y)
```

Create a constant reference trace:

```python
redline = fplt.Trace.constant("PCMC Redline", x=pc.x, y=400.0, role="redline")
```

Create a command or sequence trace from points:

```python
mov_command = fplt.Trace.from_points(
    "MOV Command",
    points=[
        (0.0, 0.0),
        (0.5, 0.0),
        (0.6, 1.0),
        (10.0, 1.0),
        (10.1, 0.0),
    ],
    x=pc.x,
    mode="previous",
    role="command",
)
```

Create an analytic trace from a function:

```python
reference = fplt.Trace.from_function(
    "Reference",
    x=pc.x,
    function=lambda t: 300.0 + 5.0 * np.sin(2.0 * np.pi * 0.2 * t),
)
```

Plot trace objects directly:

```python
fplt.plot([pc, redline, mov_command])
```

---

## Trace roles

Trace roles are plotting hints. They do not perform limit checking, abort checking, controller execution, or sequence execution.

Valid roles are:

| Role | Intended meaning | Default line style |
| --- | --- | --- |
| `"data"` | Normal measured or simulated data | solid |
| `"redline"` | Abort or hard limit reference | dashed red-tinted line |
| `"blueline"` | Lower/secondary reference | dashed blue-tinted line |
| `"yellowline"` | Warning or caution reference | dashed yellow-tinted line |
| `"greenline"` | Nominal target or expected value | dashed green-tinted line |
| `"command"` | Command, schedule, or sequence state | dash-dot step line |

Example:

```python
redline = fplt.Trace.constant("Abort", x=pc.x, y=400.0, role="redline")
yellowline = fplt.Trace.constant("Warning", x=pc.x, y=350.0, role="yellowline")
greenline = fplt.Trace.constant("Target", x=pc.x, y=300.0, role="greenline")

fplt.plot([pc, redline, yellowline, greenline])
```

---

## Shared time axes and test-data alignment

`TimeAxis` is useful when several traces should move together in time.

```python
time = run.time("time")

pc = run.trace(y="PCMC_1", x=time)
oipt = run.trace(y="OIPT", x=time)
fipt = run.trace(y="FIPT", x=time)
```

Shift the shared time axis so raw test time 95 seconds becomes model time zero:

```python
time.zero_at(95.0)
```

All three traces shift because they share the same `TimeAxis` object.

Use `align(...)` when matching a raw data time to a model time:

```python
time.align(data_time=95.0, model_time=0.0)
```

Window the shared time axis when every attached trace should use the same section of the test:

```python
time.window(start=2.0, stop=6.5)

pc.x      # only 2.0 ... 6.5 s
pc.y      # matching PCMC samples
oipt.y    # matching OIPT samples
fipt.y    # matching FIPT samples
```

The original arrays are preserved. Restore the full range with:

```python
time.clear_window()
```

Or create an independently windowed copy:

```python
mainstage_time = time.windowed(2.0, 6.5)
```

Useful `TimeAxis` attributes:

```python
time.raw          # original unshifted samples
time.full_values  # all shifted samples before windowing
time.values       # shifted samples in the active window
time.zero         # current zero offset
time.count        # active sample count
time.raw_count    # original sample count
time.duration     # active time span
time.raw_duration # original time span
time.window_range # (start, stop)
time.has_window   # whether a shared window is active
```

For a trace that uses a shared `TimeAxis`, `trace.raw_y` gives the original y-array while `trace.y` follows the active time window.

---

## Missing values and windows

FullPlot preserves non-finite y-values such as `NaN`. This is deliberate. Missing test-data samples should appear as gaps in plots instead of being connected by a misleading line.

Use `TimeAxis.window(...)` when several traces should be cropped together. Use `Trace.window(...)` when only one trace should be valid over part of the otherwise unchanged time axis:

```python
startup = pc.window(start=0.0, stop=3.0, name="Startup Window")
```

`Trace.window(...)` keeps its current x-axis and replaces y-values outside the interval with `NaN`. This is useful when a solver or comparison routine should treat one signal as unavailable outside that interval.

Remove missing samples when you explicitly need compact finite data:

```python
pc_compact = pc.omit_missing()
```

`drop_missing` is an alias:

```python
pc_compact = pc.drop_missing()
```

---

## Filtering and trace math

Filter a trace:

```python
pc_filtered = pc.filter("moving_average", window=0.05, name="PCMC Filtered")
```

Supported filters:

```python
pc.filter("moving_average", window=0.05)
pc.filter("median", window=0.05)
pc.filter("savgol", window=0.05, order=2)
pc.filter("lowpass", cutoff=50.0)
```

For moving-average, median, and Savitzky-Golay filters, `window` can be either:

* an integer sample count, or
* a positive x-width, such as seconds when x is time.

Scale or offset a trace:

```python
pressure_pa = pc.scale(6894.757, name="Pressure [Pa]")
pressure_gauge = pc.offset(-14.7, name="Gauge Pressure")
```

Compute a numerical derivative:

```python
pc_rate = pc.derivative(name="dPc/dt")
```

Resample a trace:

```python
new_time = np.linspace(0.0, 10.0, 1001)
pc_resampled = pc.resample(new_time)
```

Do trace math. When combining two traces, FullPlot automatically resamples the right-hand trace onto the left-hand trace x-axis:

```python
error = sim_pc - test_pc
ratio = sim_pc / test_pc
```

Sample a trace at one or more x-values:

```python
value = pc.value_at(1.25)
values = pc.value_at([1.0, 1.5, 2.0], method="linear")
value = pc(1.25, method="nearest")
```

Supported sample methods are `"previous"`, `"linear"`, and `"nearest"`.

Supported bounds modes are:

* `"nan"`: return `NaN` outside the trace range,
* `"clamp"`: use the first or last sample outside the trace range,
* `"raise"`: raise an error outside the trace range.

---

## Writing processed traces

Write one or more `Trace` objects to a simple HDF5 layout:

```python
fplt.write_traces(
    "processed_traces.h5",
    [pc, pc_filtered, redline],
    group="traces",
    overwrite=True,
)
```

The file layout is:

```text
processed_traces.h5
└── traces
    ├── PCMC_1
    │   ├── x
    │   └── y
    ├── PCMC_Filtered
    │   ├── x
    │   └── y
    └── PCMC_Redline
        ├── x
        └── y
```

Trace metadata is stored as HDF5 attributes when possible.

---

## Line plotting options

`H5File.plot(...)` and `fplt.plot(...)` support:

* single or multiple left-axis traces,
* optional right-axis traces with `y2`,
* HDF5 datasets and `Trace` objects in the same plot,
* custom labels,
* log x/y axes,
* display-only `xlim`, `ylim`, and `y2lim` limits,
* dark and light themes,
* saving to PNG, SVG, PDF, or any Matplotlib-supported output,
* returning Matplotlib objects for custom edits.

Example with labels and a right axis:

```python
fig, axes = run.plot(
    x="time",
    y=["PCMC_1", "PCMC_2"],
    y2="MOV_CMD",
    labels=["PC 1", "PC 2"],
    y2labels=["MOV"],
    xlabel="Time [s]",
    ylabel="Pressure [psia]",
    y2label="Command [-]",
    title="Pressure and Valve Command",
    xlim=(2.0, 8.0),
    ylim=(0.0, 500.0),
    y2lim=(-0.1, 1.1),
    theme="light",
    save="pressure_command.svg",
    show=False,
)
```

For module-level plotting:

```python
fplt.plot(
    "hotfire.h5",
    x="time",
    y="PCMC_1",
)
```

For trace-only plotting:

```python
fplt.plot([pc, pc_filtered, redline])
```

Plot limits only change what is displayed; they do not window or modify the underlying trace data:

```python
fplt.plot([pc, oipt], xlim=(2.0, 6.5))
```

---

## Multidimensional datasets, axes, and slices

FullPlot can plot and trace 2D or higher-dimensional datasets by selecting one dimension to vary and holding the other dimensions fixed.

The two key arguments are:

| Argument | Meaning |
| --- | --- |
| `axis` | The dataset dimension that remains as the plotted line direction. |
| `slice` | A dictionary of `{dimension_index: fixed_index}` values used to hold the other dimensions fixed. |

For a dataset with shape:

```text
(chamber_pressure, mixture_ratio, expansion_ratio, ambient_pressure, nfz)
```

the dimension numbers are:

```text
axis 0 -> chamber_pressure
axis 1 -> mixture_ratio
axis 2 -> expansion_ratio
axis 3 -> ambient_pressure
axis 4 -> nfz
```

So this call plots a line that varies along chamber pressure:

```python
run.plot(
    x="axes/chamber_pressure",
    y="outputs/thrust_coefficient",
    axis=0,
    slice={
        1: 12,  # fixed mixture_ratio index
        2: 10,  # fixed expansion_ratio index
        3: 3,   # fixed ambient_pressure index
        4: 0,   # fixed nfz index
    },
    xlabel="Chamber Pressure [Pa]",
    ylabel="Thrust Coefficient [-]",
)
```

The dimension selected by `axis` is the dimension that becomes the line. The dimensions listed in `slice` are reduced to one fixed index.

### Creating traces from multidimensional data

`trace(...)` accepts the same `axis` and `slice` arguments as `plot(...)`. This makes it possible to create reusable traces from multidimensional map data and then plot them together.

```python
import fullplot as fplt

psia_to_pa = 6894.75728

file = fplt.open("sizer_lookups.h5")
result = file.at("/engine_lookup")

pc_psia = result.read("axes/chamber_pressure") / psia_to_pa

cf_eps_4 = result.trace(
    y="outputs/thrust_coefficient",
    x=pc_psia,
    axis=0,
    slice={
        1: 12,  # mixture_ratio index
        2: 8,   # expansion_ratio index
        3: 3,   # ambient_pressure index
        4: 0,   # nfz index
    },
    name="eps = 4",
)

cf_eps_6 = result.trace(
    y="outputs/thrust_coefficient",
    x=pc_psia,
    axis=0,
    slice={
        1: 12,
        2: 14,
        3: 3,
        4: 0,
    },
    name="eps = 6",
)

result.plot(
    y=[cf_eps_4, cf_eps_6],
    xlabel="Chamber Pressure [psia]",
    ylabel="Thrust Coefficient [-]",
    title="Thrust Coefficient vs Chamber Pressure",
)
```

This is equivalent to manually extracting:

```python
cf[:, 12, 8, 3, 0]   # eps = 4 trace
cf[:, 12, 14, 3, 0]  # eps = 6 trace
```

but keeps the plotting workflow trace-based.

### Selecting nearest physical values

`slice` uses integer indices, not physical axis values. If you want to select the nearest value on an axis, use a small helper:

```python
import numpy as np


def nearest_index(values, target):
    values = np.asarray(values, dtype=float)
    return int(np.argmin(np.abs(values - target)))
```

Example:

```python
mr = result.read("axes/mixture_ratio")
eps = result.read("axes/expansion_ratio")
pamb_psia = result.read("axes/ambient_pressure") / psia_to_pa
nfz = result.read("axes/nfz")

mr_i = nearest_index(mr, 2.5)
pamb_i = nearest_index(pamb_psia, 14.7)
nfz_i = nearest_index(nfz, 0)

traces = []

for target_eps in [2, 4, 6, 8, 10]:
    eps_i = nearest_index(eps, target_eps)

    traces.append(
        result.trace(
            y="outputs/thrust_coefficient",
            x=pc_psia,
            axis=0,
            slice={
                1: mr_i,
                2: eps_i,
                3: pamb_i,
                4: nfz_i,
            },
            name=f"eps = {eps[eps_i]:.1f}",
        )
    )

result.plot(
    y=traces,
    xlabel="Chamber Pressure [psia]",
    ylabel="Thrust Coefficient [-]",
    title="Thrust Coefficient vs Chamber Pressure",
)
```

### Common slicing patterns

For a dataset ordered as:

```text
(chamber_pressure, mixture_ratio, expansion_ratio, ambient_pressure, nfz)
```

these are common one-dimensional traces:

```python
cf[:, mr_i, eps_i, pamb_i, nfz_i]      # vary chamber pressure
cf[pc_i, :, eps_i, pamb_i, nfz_i]      # vary mixture ratio
cf[pc_i, mr_i, :, pamb_i, nfz_i]       # vary expansion ratio
cf[pc_i, mr_i, eps_i, :, nfz_i]        # vary ambient pressure
cf[pc_i, mr_i, eps_i, pamb_i, :]       # vary nfz
```

The matching FullPlot arguments are:

```python
# Vary chamber pressure.
axis=0
slice={1: mr_i, 2: eps_i, 3: pamb_i, 4: nfz_i}

# Vary mixture ratio.
axis=1
slice={0: pc_i, 2: eps_i, 3: pamb_i, 4: nfz_i}

# Vary expansion ratio.
axis=2
slice={0: pc_i, 1: mr_i, 3: pamb_i, 4: nfz_i}
```

### Slicing for heat maps

For heat maps, leave two dimensions unsliced. For example, to plot thrust coefficient as a function of mixture ratio and chamber pressure, hold expansion ratio, ambient pressure, and `nfz` fixed:

```python
result.map(
    z="outputs/thrust_coefficient",
    x="axes/mixture_ratio",
    y="axes/chamber_pressure",
    slice={
        2: eps_i,
        3: pamb_i,
        4: nfz_i,
    },
    xlabel="Mixture Ratio [-]",
    ylabel="Chamber Pressure [Pa]",
    zlabel="Thrust Coefficient [-]",
)
```

Use `xscale="log"`, `yscale="log"`, or `zscale="log"` only when the displayed values on that axis are positive.

---

## Heat maps

Plot a 2D dataset:

```python
run.map(
    z="pressure_map",
    x="mixture_ratio",
    y="chamber_pressure",
    xlabel="Mixture Ratio [-]",
    ylabel="Chamber Pressure [Pa]",
    zlabel="Temperature [K]",
)
```

Stack several 1D datasets into a heat map:

```python
run.map(
    z=["TC_1", "TC_2", "TC_3", "TC_4"],
    x="time",
    ylabel="Station Index",
    zlabel="Temperature [K]",
)
```

Use log scales when all displayed values are positive:

```python
run.map(
    z="residual_map",
    x="iteration",
    y="case",
    zscale="log",
)
```

---

## Map generation

FullPlot can generate simple rectangular-grid HDF5 maps.

The generated layout is intentionally generic:

```text
demo_map.h5
└── properties
    ├── axes
    │   ├── pressure
    │   └── temperature
    ├── outputs
    │   ├── density
    │   └── enthalpy
    └── status
        ├── success
        └── message
```

Generate a map:

```python
import fullplot as fplt

fplt.generate_map(
    "demo_map.h5",
    group="properties",
    axes=[
        fplt.Axis.linear("pressure", 1.0e5, 5.0e5, 5, units="Pa"),
        fplt.Axis.linear("temperature", 250.0, 500.0, 6, units="K"),
    ],
    constants={"gas_constant": 287.0},
    evaluate=lambda pressure, temperature, gas_constant: {
        "density": pressure / (gas_constant * temperature),
        "enthalpy": 1005.0 * temperature,
    },
    overwrite=True,
)
```

Axis helpers:

```python
fplt.Axis.linear("temperature", start=250.0, stop=600.0, count=8, units="K")
fplt.Axis.log("pressure", start=1.0e5, stop=1.0e7, count=9, units="Pa")
fplt.Axis.values("mixture_ratio", values=[1.5, 2.0, 2.5, 3.0])
```

Use `constants={...}` for values that should be passed to every evaluation but should not become interpolation axes.

The `evaluate(...)` function must return a flat dictionary of scalar numeric outputs. Each key becomes one dataset in `/outputs`.

Example:

```python
def evaluate(pressure, temperature, gas_constant):
    density = pressure / (gas_constant * temperature)
    return {"density": density}
```

`generate_map(...)` supports:

* explicit output names with `outputs=[...]`,
* overwrite protection with `overwrite=True`,
* interrupted-map continuation with `resume=True`,
* configurable failure behavior with `raise_errors=False`,
* optional compression,
* metadata storage.

A robust long-running map call might look like this:

```python
fplt.generate_map(
    "engine_map.h5",
    group="chamber",
    axes=[
        fplt.Axis.log("pressure", 1.0e5, 1.0e7, 25, units="Pa"),
        fplt.Axis.values("mixture_ratio", [1.5, 2.0, 2.5, 3.0]),
    ],
    constants={"area": 0.0039},
    outputs=["temperature", "gamma", "molecular_weight"],
    evaluate=evaluate_chamber,
    metadata={"description": "Chamber property map"},
    resume=True,
    raise_errors=False,
    fill_value=float("nan"),
)
```

---

## Command-line inspection

FullPlot installs a small `fullplot` command for HDF5 inspection.

Print a tree:

```bash
fullplot hotfire.h5
fullplot hotfire.h5 --tree
```

List datasets:

```bash
fullplot hotfire.h5 --list
```

Inspect a root group:

```bash
fullplot engine_sim.h5 --root /Engine/transient/runs/startup --list
```

Limit tree depth:

```bash
fullplot engine_sim.h5 --max-depth 2
```

The CLI does not create plots. Use the Python API for plotting so scripts can control Matplotlib backends, figure editing, saving, and showing.

---

## Examples

The repository includes three example folders:

```text
examples/
├── hdf5_plotting/
├── maps/
└── traces/
```

`examples/hdf5_plotting/` demonstrates:

* generating a synthetic HDF5 plotting file,
* inspecting the tree and dataset list,
* single-trace plots,
* multiple-trace plots,
* dual-axis plots,
* 2D heat maps,
* stacked 1D heat maps,
* log axes and log color scales,
* multidimensional line traces,
* multidimensional slices,
* light theme plots,
* module-level API calls,
* saving figures.

`examples/traces/` demonstrates:

* generating synthetic hotfire-style sensor data,
* extracting reusable traces,
* filtering,
* redlines/bluelines/yellowlines/greenlines,
* command and sequence traces,
* trace math and automatic resampling,
* windowing,
* scaling and offsetting,
* derivatives,
* saving processed traces,
* missing-value handling,
* shared `TimeAxis` alignment.

`examples/maps/` demonstrates:

* simple map generation,
* linear/log/explicit axes,
* constants,
* metadata,
* output discovery,
* HDF5 layout inspection.

---

## Themes

FullPlot includes two themes:

```python
run.plot(x="time", y="PCMC_1", theme="dark")
run.plot(x="time", y="PCMC_1", theme="light")
```

The dark theme is useful for quick interactive engineering plots. The light theme is better for reports, documents, and slides.

---

## Units

FullPlot does not perform unit conversion.

It reads numeric arrays and uses labels provided by the user or stored in HDF5 attributes. This keeps FullPlot generic and avoids guessing how engineering units should be converted.

Recommended practice:

```python
run.plot(
    x="time",
    y="PCMC_1",
    xlabel="Time [s]",
    ylabel="Pressure [psia]",
)
```

If you need converted data, convert explicitly with trace math:

```python
pc_pa = pc.scale(6894.757, name="PCMC_1 [Pa]")
```

---

## Limitations and design choices

FullPlot is intentionally small.

Current limitations:

* HDF5 is the only supported file format.
* The package focuses on numeric datasets.
* There is no built-in unit conversion.
* There is no built-in DAQ metadata standard.
* Trace roles are plotting hints only.
* Redlines and commands are not safety logic.
* Map generation is for rectangular grids only.
* Map outputs must be scalar numeric values.
* Interpolation and filtering are intentionally simple and NumPy/SciPy based.
* FullPlot does not attempt to replace specialized dashboards, data historians, or test-stand control software.

This is deliberate. The package is meant to be a simple bridge between HDF5 engineering data and Python plotting/processing workflows.

---

# Documentation

Full documentation:

https://saakethramoju.github.io/softwares/fullplot/

Source code:

https://github.com/saakethramoju/FullPlot

PyPI:

https://pypi.org/project/fullplot/

---

## License

FullPlot is released under the GNU General Public License v3.0 only (`GPL-3.0-only`). See `LICENSE` for the full text.

See `THIRD_PARTY_LICENSES.md` for dependency license notes.
