Metadata-Version: 2.3
Name: cplots
Version: 0.0.6
Summary: C-Plots: A backend-agnostic plotting library.
Author: Rodrigo Calderon
Author-email: Rodrigo Calderon <calderon.cosmology@gmail.com>
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Dist: cplots[matplotlib,plotly,altair,getdist,pandas,polars,chainconsumer,anesthetic,dynamics] ; extra == 'all'
Requires-Dist: altair>=5.0 ; extra == 'altair'
Requires-Dist: anesthetic>=2.0 ; extra == 'anesthetic'
Requires-Dist: camb>=1.4 ; extra == 'camb'
Requires-Dist: chainconsumer>=1.0 ; extra == 'chainconsumer'
Requires-Dist: narwhals>=1.14 ; extra == 'chainconsumer'
Requires-Dist: classy ; extra == 'classy'
Requires-Dist: cython>=3.2.5 ; extra == 'classy'
Requires-Dist: setuptools>=82.0.1 ; extra == 'classy'
Requires-Dist: wheel>=0.47.0 ; extra == 'classy'
Requires-Dist: typer>=0.12 ; extra == 'cli'
Requires-Dist: rich>=13.0 ; extra == 'cli'
Requires-Dist: questionary>=2.0 ; extra == 'cli'
Requires-Dist: pyyaml>=6.0 ; extra == 'cli'
Requires-Dist: camb ; extra == 'cosmo'
Requires-Dist: classy ; extra == 'cosmo'
Requires-Dist: pytest>=8.0 ; extra == 'dev'
Requires-Dist: mypy>=1.10 ; extra == 'dev'
Requires-Dist: ruff>=0.4 ; extra == 'dev'
Requires-Dist: cplots[cli] ; extra == 'dev'
Requires-Dist: mkdocs-material>=9.5 ; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.25 ; extra == 'docs'
Requires-Dist: mkdocs-marimo>=0.2.1 ; extra == 'docs'
Requires-Dist: scipy>=1.10 ; extra == 'dynamics'
Requires-Dist: getdist>=1.4 ; extra == 'getdist'
Requires-Dist: matplotlib>=3.7 ; extra == 'matplotlib'
Requires-Dist: pandas>=1.5 ; extra == 'pandas'
Requires-Dist: plotly>=5.0 ; extra == 'plotly'
Requires-Dist: polars>=0.20 ; extra == 'polars'
Requires-Dist: ultraplot>=0.1 ; extra == 'ultraplot'
Requires-Python: >=3.10
Provides-Extra: all
Provides-Extra: altair
Provides-Extra: anesthetic
Provides-Extra: camb
Provides-Extra: chainconsumer
Provides-Extra: classy
Provides-Extra: cli
Provides-Extra: cosmo
Provides-Extra: dev
Provides-Extra: docs
Provides-Extra: dynamics
Provides-Extra: getdist
Provides-Extra: matplotlib
Provides-Extra: pandas
Provides-Extra: plotly
Provides-Extra: polars
Provides-Extra: ultraplot
Description-Content-Type: text/markdown

<p align="center">
  <img src="https://raw.githubusercontent.com/rcalderonb6/cplots/main/docs/assets/logo-wordmark.svg" width="65%" alt="cplots">
</p>

<p align="center">
  <strong>A backend-agnostic cosmological plotting library.</strong><br>
  Write your analysis once; render with <code>matplotlib</code>, <code>plotly</code>, or <code>altair</code> — no lock-in.
</p>

<p align="center">
  <a href="https://pypi.org/project/cplots/"><img src="https://img.shields.io/pypi/v/cplots.svg?color=blue" alt="PyPI version"></a>
  <a href="https://pypi.org/project/cplots/"><img src="https://img.shields.io/pypi/pyversions/cplots.svg" alt="Python versions"></a>
  <a href="https://rcalderonb6.github.io/cplots/"><img src="https://img.shields.io/badge/docs-rcalderonb6.github.io%2Fcplots-blue" alt="Docs"></a>
  <a href="https://github.com/rcalderonb6/cplots/actions/workflows/docs.yml"><img src="https://github.com/rcalderonb6/cplots/actions/workflows/docs.yml/badge.svg" alt="Deploy docs"></a>
</p>

<!-- [![image](http://img.shields.io/badge/license-MIT.svg?style=flat)](https://github.com/rcalderonb6/cplots/blob/main/LICENSE)  -->
---

## How it works

Every plot flows through the same layers, whether it starts from arrays or from a
Boltzmann/dark-energy solver:

```mermaid
flowchart TD
    raw["Arrays · DataFrames · xarray · GetDist"]
    solver["Solver results<br/>CLASS · CAMB · eft4py"]
    obs["<b>Observables</b><br/>one vocabulary, with units"]
    data["<b>PlotData</b><br/>immutable input containers"]
    spec["<b>PlotSpec</b><br/>backend-agnostic IR"]
    backend["<b>Backend</b><br/>matplotlib · plotly · altair · ultraplot"]

    solver -- as_observables --> obs
    raw -- to_plot_data --> data
    obs --> data
    data -- "Plot.spec()" --> spec
    spec -- "render · show · save" --> backend
```

Plot subclasses never import backends. Backends never import plot subclasses.
Swap the backend at call time, via a context manager, or globally — the plot code stays unchanged.

---

## Features

- **Plot types**
  - general: `XYPlot`, `XYZColored`, `XYZColoredGrid`, `PhasePortrait`
  - cosmology: `PKSpectrum`, `ClSpectrum`, `PrimordialSpectrum`, `BackgroundEvolution`,
    `BackgroundPanels`, `ThermodynamicsEvolution`
  - inference: `TrianglePlot` (getdist, chainconsumer or anesthetic engines), `PosteriorBands`
  - residual panels: `ResidualFigure`, `plot.with_residuals(...)`
- **Four backends** — publication figures via **matplotlib** or **ultraplot**, interactive HTML via
  **plotly**, Vega-Lite JSON via **altair** (multi-panel figures: matplotlib, plotly, altair)
- **Solver adapters** — CLASS / hi_class, CAMB and [eft4py](https://github.com/rcalderonb6/eft4py)
  outputs through one interface: shared quantity names (`H`, `dA`, `w_de`, `alpha_M`, …),
  units tracked and converted, any redshift grid via `z=`
- **Code comparisons** — overlay several codes with `{label: source}`; units are matched
  automatically, and `BackgroundPanels` gives each kind of quantity its own panel
- **Multi-panel figures**: compose with `(a | b) / c` or an explicit grid with colspan/rowspan, shared
  axes and custom ratios (full support on matplotlib and plotly; altair has no rowspan or shared axes)
- **Formatting and styles**: chainable, immutable formatting (`plot.axes(xlim=...).legend(...)`) and a
  `Style` cascade (global, then `with cplots.style(...)`, then figure, then plot) applied per render.
  Every plot starts from `cplots.DEFAULT`: Computer Modern text and math, FiveThirtyEight colours,
  boxed axes with inward ticks, `magma` colormaps
- **LaTeX labels** — labels (including the defaults, e.g. `$P(k)$`) render as math in matplotlib;
  plotly gets Unicode, or MathJax with `latex_labels=True`
- **Command line** — `cplots distributions triangle CHAIN` and `cplots lines xy FILE`
  (`pip install "cplots[cli]"`)
- **Data adapters** — NumPy arrays, pandas / polars DataFrames, xarray Datasets, GetDist `MCSamples`
- **Extensible, light imports** — backends register via the `cplots.backends` entry point; data and
  solver adapters with `@cplots.to_plot_data.register` / `@cplots.as_observables.register` (a solver
  adapter subclasses `Observables` and implements one hook). Optional libraries are only imported
  when used.

---

## Quick example

```python
import numpy as np
import cplots

k  = np.logspace(-3, 0, 200)
pk = k ** -1.5

# Display with default backend (matplotlib)
cplots.PKSpectrum(k, pk, labels="z=0").show()

# Interactive HTML with LaTeX labels
cplots.PKSpectrum(
    k, pk,
    xlabel=r"$k\;[h\,\mathrm{Mpc}^{-1}]$",
    ylabel=r"$P(k)\;[h^{-3}\,\mathrm{Mpc}^3]$",
    latex_labels=True,
).save("pk.html", backend="plotly")

# Multi-panel figure
z  = np.linspace(0, 3)
Hz = 67.34 * np.sqrt(0.3 * (1 + z)**3 + 0.7)

fig = cplots.Figure.grid([
    [cplots.PKSpectrum(k, pk), cplots.BackgroundEvolution(z, {"H": Hz})],
])
fig.show()
```

### Switching backends

```python
# Per call
plot.show(backend="plotly")

# Context manager
with cplots.backend("altair"):
    plot.show()

# Global default
cplots.set_backend("plotly")
```

---

## Cosmology solvers

Hand cplots a computed solver result; `as_observables` picks the adapter, and every
code answers the same `background()` call in the same vocabulary:

```python
out = cplots.as_observables(cosmo)          # classy.Class -> ClassOutput
bg = out.background(quantities=["H", "dA"], units={"H": "km/s/Mpc"}, z=np.linspace(0, 3, 100))

# Overlay codes: one line style per source, CAMB's km/s/Mpc and CLASS's 1/Mpc matched for you
cplots.BackgroundPanels(
    {"hi_class": cosmo, "eft4py": grid, "CAMB": camb_results},
    ["H", "dA"],
    units={"H": "km/s/Mpc"},
).show()

# Dark-energy quantities, from hi_class and an eft4py BackgroundGrid
cplots.BackgroundEvolution({"hi_class": cosmo, "eft4py": grid}, ["w_de"]).show()
```

`P(k)`, `Cℓ`, primordial and thermodynamics outputs are available where a code provides them
(`out.pk()`, `out.cl()`, …). See the [Solver Adapters guide](https://rcalderonb6.github.io/cplots/user-guide/solvers/)
and the [eft4py notebook](notebooks/eft4py_background.py).

---

## Installation

```
pip install cplots[matplotlib]              # matplotlib only
pip install cplots[matplotlib,plotly]       # two backends
pip install cplots[all]                     # matplotlib, plotly, altair, pandas, polars, triangle engines, scipy
pip install cplots[ultraplot]               # ultraplot backend (not included in [all])
pip install cplots[camb]                    # CAMB adapter
pip install cplots[classy]                  # CLASS adapter (builds classy)
pip install cplots[cosmo]                   # both solvers (not included in [all])
pip install cplots[dynamics]                # scipy integrators for PhasePortrait
```

[eft4py](https://github.com/rcalderonb6/eft4py) is not an extra: install it separately
(`pip install "eft4py>=0.0.6"`, Python ≥ 3.11).

**Development install:**

```
git clone https://github.com/rcalderonb6/cplots
pip install -e ".[matplotlib,dev]"
```

---

## Documentation

Full documentation, including the API reference, user guide, and interactive examples, is available at
[rcalderonb6.github.io/cplots](https://rcalderonb6.github.io/cplots/):

- [Getting started](https://rcalderonb6.github.io/cplots/getting-started/)
- [Examples gallery](https://rcalderonb6.github.io/cplots/examples/a-simple-plot/)
- [Solver adapters](https://rcalderonb6.github.io/cplots/user-guide/solvers/) (CLASS, CAMB, eft4py, writing your own)

<!-- ![GitHub forks](https://img.shields.io/github/forks/rcalderonb6/cplots?style=social)
![GitHub Repo stars](https://img.shields.io/github/stars/rcalderonb6/cplots?style=social) -->