Skip to main content

Small, hackable Python library that emits matplotlib-style SVG plots.

Project description

plotlet

A small, hackable Python library that emits matplotlib-style SVG plots.

Why

matplotlib is the right tool when you want the kitchen sink. plotlet's niche is custom plot types — genome tracks, Manhattan plots, phylogenetic trees, anything matplotlib's extension API makes painful. The whole library has a deliberately tiny, exposed core: adding a new plot type is a 3-step recipe, not an architecture project.

It's a scaffold, not a feature catalog: the core ships ~5 standard plots and the infrastructure for extending. Custom plot types live in your own project (or cookbook/), not upstream. See docs/PHILOSOPHY.md for the full framing.

import plotlet as pt

data = {
    "x":      [1, 2, 3, 4, 5, 1, 2, 3, 4, 5],
    "y":      [1, 4, 9, 16, 25, 1, 8, 27, 64, 125],
    "series": ["squares"] * 5 + ["cubes"] * 5,
}

c = pt.chart(data, title="Hello", xlabel="x", ylabel="y", legend=True, grid=True)
c.line(x="x", y="y", hue="series")
c                                        # auto-renders in Jupyter

Install

pip install plotlet

Properties

  • Lightweight. fonttools for font handling. numpy / pandas / polars inputs work transparently if you have them.
  • Static SVG output. No interactivity, no animation. Same script → byte-identical SVG.
  • Cross-machine reproducible. Bundled DejaVu Sans + text-as-paths means rendering is identical on Linux, macOS, Windows, headless CI.
  • Jupyter-native. Figure._repr_html_ auto-renders the last expression in a cell.
  • Tiny output. Each plot is ~50 KB SVG, self-contained.
  • Compositional. Multi-panel layouts via |, /, pt.grid; share scales with share_x= / share_y=; layout-level legend with pt.legend() covering both discrete swatches and continuous gradients (the colorbar).

API

pt.chart(data, **opts) returns a Chart bound to a table — any object that supports data[col_name] returning an iterable (pandas / polars DataFrames, dict-of-lists, dict-of-arrays). All methods return self; _repr_html_ makes the chart auto-render as the last expression in a Jupyter cell.

Frame options

Pass at construction (pt.chart(data, title=..., grid=True, ...)) or as chained setters (c.title(...), etc.):

title, xlabel, ylabel, xlim=(a, b), ylim=(a, b), xscale="linear"|"log"|"category" (chained: c.xscale("category", order=[...], padding=0)), yscale=..., grid=True/False, legend=True/False, width, height

String-valued data on either axis (scatter(["a","b","c"], ...), bar, …) auto-switches to a categorical scale, alphabetical by default. padding=0 makes category bands contiguous (heatmap-track look).

Tick customization: c.xticks([0, 5, 10], ["A","B","C"], rotation=45, fontsize=12, direction="out", marks=False). Pass [] to hide. yticks(...) works the same way.

Mark methods

call options
.line(x=, y=, hue=, **opts) color, label, linewidth, linestyle ("-", "--", ":", "-."), marker ("o", "s", "^", "v", "x", "+"), markersize
.scatter(x=, y=, hue=, **opts) color, label, s (size), alpha, marker
.bar(x=, y=, **opts) color, label, alpha
.hist(x=, **opts) bins, color, alpha, label
.fill_between(x=, y1=, y2=, **opts) color, alpha, label
.axhline(y, **opts) / .axvline(x, **opts) color, linewidth, linestyle, alpha, label, axes-fraction xmin/xmax (or ymin/ymax)
.axhspan(ymin, ymax, **opts) / .axvspan(xmin, xmax, **opts) color, alpha, label, axes-fraction xmin/xmax (or ymin/ymax)
.imshow(data, **opts) cmap (any matplotlib name, default "viridis"), vmin, vmax, extent=(left, right, bottom, top)

hue=<col> (on .line / .scatter) splits into one call per unique value with auto-labels and tab10 colors. Reference lines and spans default to black; spans use alpha=0.2. They're drawn outside the data color cycle and don't participate in autoscaling — they're decorations on the frame, not data.

.imshow(data) renders a 2-D array as a colored grid. Small grids (nrows × ncols ≤ 10000) emit one <rect> per cell and stay vector-clean at any zoom; larger grids encode as a single base64 PNG and quantize to 256 levels. Image row 0 is rendered at the top of its rectangle; the y axis stays Cartesian (small at bottom). All ~180 matplotlib colormaps are vendored — see pt.list_colormaps().

Subplots

Compose multi-panel layouts with operators on Chart:

a | b                  # side-by-side
a / b                  # stacked
a | b | c              # left-fold flatten — one row of three, not nested

pt.grid([[a, b],       # 2-D grid; cells may be `None`
         [c, d]])

# Share x or y across panels — collapses the gap between them
# and forces both onto the source's scale.
top  = pt.chart()
main = pt.chart(share_x=top)
top / main             # vertically stacked, x-axis joined

# Layout-level legend (covers colorbar and discrete swatches in
# one constructor — geometry follows from the source's color mapping).
hm = pt.chart(); hm.imshow(matrix, cmap="viridis")
hm | pt.legend(hm)             # heatmap + colorbar (gradient strip)

# Multi-source: groups by chart, using each chart's title as
# section header. `names={chart: "Override"}` renames a header,
# `names={chart: None}` hides it, `group_by_chart=False` flattens.
(hm | top) | pt.legend()       # auto-collects from siblings
parent = a | b; parent.legend()  # sugar for parent | pt.legend()

A composed chart owns its children; render the parent ((a | b).show() or .to_svg() / .save_svg(...)). Calling .show() on a child raises. See docs/SUBPLOTS.md for the design rationale.

Render / save

c.show()                     # explicit display() inside a cell
c.to_svg()                   # raw SVG string
c.save_svg("plot.svg")       # SVG file
c.write_html("plot.html")    # standalone HTML

Color shortcuts

  • "C0""C9" → tab10 (matches matplotlib)
  • Named: "blue", "orange", "green", "red", "purple", "brown", "pink", "gray", "olive", "cyan"
  • Single-letter: "k", "w", "b", "g", "r"
  • Any hex / CSS color string passes through

Adding a new plot type

plotlet's central hackability claim: a custom plot type is a 3-step recipe (~50–100 lines) that gets axes, scales, legend, grid, and composability for free. The recommended home is your own project, or cookbook/ as reference. Full guide: docs/EXTENDING.md.

Testing

python tests/test_chart.py            # check vs. committed baselines
python tests/test_chart.py --update   # regenerate (review the diff!)
python tests/test_chart.py --gallery  # build tests/baseline_images/chart/index.html
python tests/test_subplots.py         # subplot baselines + composition invariants

Non-goals

  • No interactivity (hover, zoom, click). Static rendering is the point.
  • Not competing with matplotlib on standard plots; matplotlib is bigger and battle-tested.
  • Not a 3D plotter, not a dashboard tool.
  • Not a feature catalog — new plot types belong in user projects or cookbook/, not in the core.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plotlet-0.1.3.tar.gz (537.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plotlet-0.1.3-py3-none-any.whl (529.9 kB view details)

Uploaded Python 3

File details

Details for the file plotlet-0.1.3.tar.gz.

File metadata

  • Download URL: plotlet-0.1.3.tar.gz
  • Upload date:
  • Size: 537.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for plotlet-0.1.3.tar.gz
Algorithm Hash digest
SHA256 bf14120d3ea269fc30ebd8354d69453b26e171cd725e130a7a5b1afcab5e9c25
MD5 a66954aa3bbb7b83a1ec97d5a7cd53c3
BLAKE2b-256 d78b6d4beb8063041419b1e499568bebb373f7968bdb16fa8a068ef8f7faabdc

See more details on using hashes here.

File details

Details for the file plotlet-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: plotlet-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 529.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for plotlet-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 bc4583faaa99b2b4909b61531266fbe33e2564ef18f66d7bdf806e4014c7242e
MD5 a7c1829a5d91dee83deed1cc051573af
BLAKE2b-256 1b3cd3e64a37fca2c9ca2d3e64c39978ca8b16c1c81ed547971e5bb5f391f004

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page