Metadata-Version: 2.5
Name: plot_io
Version: 0.9.0
Summary: Plots for PDF, EPS, SVG, PNG, JPEG, JPEG XL and AVIF, drawn with vec_io
Project-URL: Repository, https://github.com/ajgouhier/plot_io
Author-email: Arthur Gouhier <ajgouhier@gmail.com>
License-Expression: BSD-3-Clause
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: vec-io>=0.4.0
Provides-Extra: dev
Requires-Dist: flake8; extra == 'dev'
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: ziglang==0.15.1; extra == 'dev'
Description-Content-Type: text/markdown

# plot_io

Plotting library in Python. No dependency other than [vec_io](https://github.com/ajgouhier/vec_io).

![box plots](examples/box_plots.png)

* **Familiar:** a figure of plots; `scatter`, `line`, `fill`, `box`, `heatmap`, `bar`,
  `hist`, `kde` (1-D and 2-D), `ecdf`, `violin`, `strip`, `swarm`, `area`, `errorbar`, `stem`,
  `regression`, `hist2d`, `hexbin`, `contour`, `pie`; reference lines, spans, text and
  annotations; `color`, `marker`, `opacity`, `limits`, `scale`, `legend`.
* **Units:** lengths in px, pt, in, mm, `em`, `%` of the axes or of another setting, data
  units and durations (`width="1.5in"`, `tick_length="0.5em"`, `width="60%"`,
  `cap_length="12h"`); `var()` and `calc()` relate settings (`"calc(var(--gap) * 2)"`).
* **One way to set things:** keywords at creation or `set(...)` on any object; figure
  settings (font, palette, line width…) are inherited by plots and series.
* **Layouts:** an explicit grid of ids with spans, or automatic rows and columns; plots
  sized individually; axis lines (or plot borders) aligned across the figure; margins from
  the real text metrics; shared axes; secondary axes; facets of a table.
* **Titles, legends and colorbars on any side:** several titles per plot; titles,
  legends and colorbar keys shared by any rectangle of plots, nested as headers;
  `share_legend` for one legend per row, column or figure; markup (`<i>`, `<b>`,
  `<sup>`, …) in any text.
* **Data:** columns of any table by name (`data=`: dicts, pandas, polars, numpy), one
  series per group (`group=`), numbers given as colors or sizes, point density (scipy's
  `gaussian_kde`, in C) for colors, sizes, opacity and drawing order.
* **Axes:** linear, log, log2, log10, sqrt, exp, symlog, logit, time and category scales;
  limits such as `"0/max"` or `"q:0.01/q:0.99"`; ticks with shared decimals and ×10ᵏ
  factors, calendar ticks for dates.
* **Colors:** a colorblind-safe palette, color maps interpolated in Oklab, colorbars;
  marker sizes by area, equal for every shape.
* **Styles:** `style="matplotlib"`, `"seaborn"`, `"ggplot2"`, `"altair"`, `"matlab"`,
  `"chartjs"`, `"plotly"`, `"bokeh"`, `"observable"`, `"excel"`, `"fivethirtyeight"`,
  `"economist"`, `"tufte"` and variants give the look of those libraries; styles are
  presets of settings, and your own build on them.
* **Output:** PDF, EPS (transparency flattened), SVG, PNG, JPEG, JPEG XL and AVIF, by the
  file's extension.
* **Large data:** one million points mapped and drawn in C; `simplify="auto"` drops what
  cannot be seen, `rasterize=` turns a series into an image inside the PDF.
* **Genome tracks:** bigWig signals, intervals, gene models and Hi-C triangles over a
  region, read from gwseq_io's readers (or any object with their methods) without
  depending on gwseq_io.

## Install

```sh
pip install /path/to/vec_io      # vec_io 0.4.0 or later
pip install .
```

Python 3.11+. Text is shaped (kerned) and its fonts embedded by vec_io; it needs an
installed outline font, the default family list (`Helvetica, Nimbus Sans, Arial,
Liberation Sans, Arimo`) taking its first installed family ([API.md](API.md#1-conventions)). JPEG XL
and AVIF output need vec_io's optional libjxl and libavif.

## Quick start

```python
import math
import random

import plot_io as pl

rng = random.Random(1)
x = [rng.uniform(0, 10) for _ in range(100)]
y = [v + rng.gauss(0, 1) for v in x]
s = pl.Scatter(x, y, name="data")
s.plot.x_axis(label="Time (s)")
s.plot.save("scatter.pdf")
```

```python
t = [i / 10 for i in range(1, 101)]
v = [math.exp(-k / 4) * (2 + math.sin(3 * k)) for k in t]
upper, lower = [a * 1.3 for a in v], [a * 0.7 for a in v]
va, vb = [rng.gauss(0, 1) for _ in range(50)], [rng.gauss(1, 1) for _ in range(50)]
matrix = [[math.sin(i / 2) * math.cos(j / 3) for j in range(6)] for i in range(4)]
columns, rows = list("ABCDEF"), ["w", "x", "y", "z"]
z = [a - b for a, b in zip(x, y)]

fig = pl.Figure(layout=[[1, 1, 2], [3, 4, 2]])
p = fig.plot(1, title="Signal")
p.line(t, v, name="raw")
p.fill(t, upper, lower, opacity=0.3)
p.y_axis(scale="log10", label="Power")
fig.plot(2).box({"a": va, "b": vb})
h = fig.plot(3).heatmap(matrix, x=columns, y=rows, cmap="magma", colorbar=True)
fig.plot(4).scatter(x, y, c=z, cmap="viridis")
fig.save("figure.pdf")
fig.save("figure.png", dpi="2x")                         # 192 dpi; a number is dots per inch
fig.save("figure.eps")                                   # PostScript: transparency flattened
fig.save("figure.svg")
data = fig.write("pdf", compression="small")             # bytes
```

```python
fig = pl.Figure(style="ggplot2")                         # or a dict: {"base": "seaborn", "font_size": 9}
```

```python
categories, counts = ["a", "b", "c"], [3, 5, 2]
p = pl.Plot(width="3in", font_size="9pt", vars={"accent": "#c00"})
p.x_axis(tick_length="0.5em", minor_tick_length="50%")
p.bar(categories, counts, width="60%", color="var(--accent)")
p.x_axis.explain("tick_length")                          # where a value comes from
```

```python
genes = {"umap_x": x, "umap_y": y}
df = {"x": x, "y": y, "cluster": [int(a) % 3 for a in x],
      "condition": ["ctrl" if a < 5 else "drug" for a in y],
      "replicate": [i % 2 + 1 for i in range(len(x))]}
p = pl.Plot()
p.scatter("umap_x", "umap_y", data=genes, c="density", draw_order="density")   # columns
p.scatter("x", "y", data=df, group="cluster", marker="auto")              # one series per group
fig, panels = pl.facet(df, col="condition", row="replicate")               # small multiples
panels.scatter("x", "y")
```

## Documentation

* [API.md](API.md): the complete reference, with every setting.
* [EXAMPLES.md](EXAMPLES.md): worked examples.

## Development

```sh
python -m unittest discover -s tests -t tests
flake8 --select F plot_io tests && mypy
python tools/bench.py --check       # timings against the targets
python tools/settings_doc.py        # regenerate the settings tables of API.md
python tools/visual_check.py --save # render the golden scenes and examples as references
python tools/visual_check.py        # ... and later compare with them (pixels, tolerances)
PLOT_IO_GOLDEN=update python -m unittest discover -s tests -t tests -p test_golden.py
python tools/record_stats.py > tests/stats_reference.json  # with numpy, scipy, statsmodels
```

The tests use only their own fonts (`tests/fonts`: PioTest, from vec_io's tests, through
`VEC_IO_FONT_DIRS`; installed fonts are ignored), so their text is the same on every
machine. The goldens (`tests/golden.json`) are keyed by meaning: the laid-out geometry of
each scene (rounded to 1/64 px), its 96 dpi pixels and vec_io's SVG form of its page (the
last two compared where the fonts match those recorded, and re-recorded with a notice
when only vec_io's version differs and every geometry is unchanged). A missing
`golden.json`, or a recorded scene no longer drawn, fails.
`PLOT_IO_GOLDEN=dump:DIR` writes the geometry of each scene to `DIR` to see what changed.

The tests that read real files with gwseq_io run when it is importable: installed, or a
built checkout beside this one (`../gwseq_io/crates/gwseq-io-py/python`, added to the path
by `tests/test_genome.py`; elsewhere, put that directory on `PYTHONPATH`). Otherwise they
are skipped.

The statistics kernels and color mapping are in plot_io's own C library, `plio`
(`plot_io/_native`, built like vec_io's: zig, freestanding, committed binaries for five
targets). Rebuilding and its checks:

```sh
pip install ziglang==0.15.1         # or: pip install -e .[dev]
python tools/build_native.py        # bindings from src/plio_api.h, then all targets
python tools/build_native.py --check
python tools/crosscheck.py --qemu PATH/qemu-aarch64-static   # same results on aarch64
```

See [plot_io/_native/README.md](plot_io/_native/README.md).
