# driedfigs

**Preserve your matplotlib figures with their data for reproducible research.**

`driedfigs` makes it simple to save the data behind your matplotlib plots alongside your figures, improving reproducibility in academic publications.


### Key Features

- **Zero effort reproducibility**: Automatically capture plot data with minimal code changes
- **Non-intrusive**: Works with existing matplotlib code - just add `name` parameters
- **Flexible**: Supports most matplotlib plotting functions (plot, scatter, contour, etc.)
- **Simple API**: Just two main functions - `start_figure()` and `save_figuredata()`

## Installation

```bash
pip install driedfigs
```

For xarray support (automatic conversion of DataArrays to numpy):
```bash
pip install driedfigs[xarray]
```

## Quick Start

```python
import driedfigs as df
import matplotlib.pyplot as plt
import numpy as np

# 1. Start capturing data for a figure
fig_data = df.start_figure("example_plot")

# 2. Create plots with the 'name' parameter to identify each artist
x = np.linspace(0, 10, 100)
plt.plot(x, np.sin(x), name="sin_wave")
plt.scatter([1, 2, 3], [0.5, 0.8, 1.2], name="data_points")

# 3. Save the data
df.save_figuredata("./data/example_plot.pkl")

# 4. Save the figure (as you would normally)
plt.savefig("./figures/example_plot.pdf")
```

This creates:
- `./figures/example_plot.pdf` (the figure, saved normally)
- `./data/example_plot.pkl` (the captured data)

The way `driedfigs` works is simple:
- When `driedfigs` is imported, it overwrites commonly used matplotlib plotting functions and methods with "hooked" versions of the function/method that first store the data before calling the function/method normally
- When you call `start_figure`, a FigureData object for storing that figure's data is created
- When you call any of the "hooked" functions/methods with a `name` keyword argument, the data is stored in the current FigureData object
- When you call `save_figuredata`, all of the data in the FigureData object is saved to disk using python's `pickle` module


## Detailed Usage

### Basic Workflow

```python
import driedfigs as df
import matplotlib.pyplot as plt

# Start a new figure
fig_data = df.start_figure("my_analysis", verbose=True)

# Make plots - use 'name' to capture data
plt.plot(x_data, y_data, name="raw_data")
plt.plot(x_model, y_model, '--', name="model_fit")

# Save the data
df.save_figuredata("./data/my_analysis.pkl")

# Save the figure (standard matplotlib)
plt.savefig("./figures/my_analysis.pdf")
plt.savefig("./figures/my_analysis.png", dpi=300)
```

### Loading Saved Data

```python
import pickle

# Load the saved data
with open("data/my_analysis.pkl", "rb") as f:
    data = pickle.load(f)

# Access specific plot data
raw_data = data["raw_data"]  # Returns tuple of args passed to plt.plot
x, y = raw_data[0], raw_data[1]
```

### Supported Matplotlib Functions

#### pyplot functions:
`plot`, `scatter`, `bar`, `hist`, `imshow`, `contour`, `contourf`, `pcolormesh`, `fill_between`, `errorbar`, `boxplot`, `violin`, `pie`, `polar`, `loglog`, `semilogx`, `semilogy`

#### Axes methods:
`plot`, `scatter`, `bar`, `hist`, `imshow`, `contour`, `contourf`, `pcolormesh`, `fill_between`, `errorbar`, `boxplot`, `violin`

### Managing Multiple Figures

```python
# Create separate figure data objects
fig1 = df.start_figure("figure_1")
plt.plot(x1, y1, name="data1")
fig1.save("./data/figure1.pkl")  # Use the .save() method

fig2 = df.start_figure("figure_2")
plt.plot(x2, y2, name="data2")
fig2.save("./data/figure2.pkl")
```

## Real-World Example

```python
import driedfigs as df
import matplotlib.pyplot as plt
import numpy as np

# Simulate some research data
time = np.linspace(0, 10, 100)
measurement = np.sin(time) + np.random.normal(0, 0.1, 100)
model = np.sin(time)

# Start capturing
df.start_figure("experiment_results")

# Create publication-quality plot
plt.figure(figsize=(10, 6))
plt.plot(time, measurement, 'o', alpha=0.5, label="Measurements", name="raw_measurements")
plt.plot(time, model, '-', linewidth=2, label="Model", name="theoretical_model")
plt.xlabel("Time (s)")
plt.ylabel("Signal (V)")
plt.legend()
plt.title("Experimental Results vs. Theoretical Model")

# Save the data for reproducibility
df.save_figuredata("./manuscript/data/experiment_results.pkl")

# Save the figure
plt.savefig("./manuscript/figures/experiment_results.pdf")
plt.savefig("./manuscript/figures/experiment_results.png", dpi=300)

# Now you can upload ./manuscript/ with your paper submission!
```


## API Reference

### Main Functions

- **`start_figure(name, verbose=False)`**: Begin capturing data for a new figure
  - Returns a `FigureData` instance
  - Automatically installs hooks on first use

- **`save_figuredata(output_path, figuredata=None)`**: Save captured data to a pickle file
  - If `figuredata` is None, uses the currently active FigureData

### FigureData Class

- **`save(output_path)`**: Save this figure's data to file
- **`figure_name`**: Name of the figure
- **`figure_data`**: Dictionary mapping artist names to their data
- **`is_active`**: Whether this FigureData is currently active
