Pure-Python charting library with pluggable backends (SVG core, optional Pillow raster) and base64/data-URI output for HTML email.
Project description
ChartHandler (Python)
Pure-Python charting with pluggable backends and base64 / data-URI output — designed so charts drop straight into HTML emails and into any web framework (Django, Flask, and ASGI apps under uvicorn).
from charthandler import Chart
# A PNG <img> tag you can paste into an HTML email — no external request.
html = Chart.pie({"Chrome": 63, "Firefox": 19, "Safari": 18}).title("Browser share").to_email_img()
Why this design
- The SVG backend is pure Python with zero dependencies — installs instantly anywhere
(slim Docker, Alpine, serverless) and works identically under Django, Flask, FastAPI/uvicorn,
etc. (a chart is just
str/bytes, so it's framework-agnostic). - Raster (PNG/JPEG/GIF/WebP) is an optional extra via Pillow — needed because SVG doesn't render in some email clients (Outlook desktop). Install only if you need it.
Features
- Chart types: pie, donut, bar, stacked bar, line, area, scatter, and combo (mixed bars + line/area with an independently-scaled secondary axis).
- Outputs: raw bytes, file, base64,
data:URI, ready-to-embed<img>tag. - Fully type-hinted (
py.typed), tested, mypy-strict + ruff clean.
Install
pip install pythoncharthandler # SVG only — zero dependencies
pip install "pythoncharthandler[raster]" # + PNG/JPEG/GIF/WebP via Pillow
Quickstart
from charthandler import Chart, Axis, Series, LegendPosition
# Pie -> SVG string (no dependencies)
Chart.pie({"Chrome": 63, "Firefox": 19, "Safari": 18}).title("Share").to_svg()
# Bar -> PNG bytes (needs the [raster] extra)
Chart.bar([12, 19, 7, 22, 15]).categories(["Jan", "Feb", "Mar", "Apr", "May"]).to_png()
# Multi-series line, saved (format inferred from the extension)
Chart.line([Series.from_values("2024", [10, 14, 9, 18])]) \
.add_series(Series.from_values("2025", [13, 11, 17, 21])) \
.categories(["Q1", "Q2", "Q3", "Q4"]).save("signups.svg")
# Stacked bar
Chart.stacked_bar([
Series.from_values("Direct", [12, 19, 15]),
Series.from_values("Organic", [20, 24, 28]),
]).categories(["Jan", "Feb", "Mar"]).to_png()
# Scatter from (x, y) pairs
Chart.scatter([(1, 5), (2, 9), (4, 3)]).add_points("B", [(1, 2), (3, 6)]).to_svg()
# Combo: grouped bars + a line on an independently-scaled right axis
Chart.combo() \
.add_bar("Revenue", [120, 190, 70, 220]) \
.add_line("Conversion %", [3.2, 4.1, 2.8, 5.0], axis=Axis.RIGHT) \
.title("Revenue vs conversion").categories(["Q1", "Q2", "Q3", "Q4"]) \
.to_email_img()
Output methods
| Method | Returns | Notes |
|---|---|---|
to_svg() |
str |
SVG markup |
to_png() / to_jpeg() |
bytes |
needs the [raster] extra |
to_data_uri(fmt=Format.PNG) |
str |
data:<mime>;base64,… |
to_html_img(fmt=Format.PNG, **attrs) |
str |
<img src="data:…" …> |
to_email_img(**attrs) |
str |
PNG <img> — use this for email |
save(path) |
None |
format inferred from the extension |
render(fmt) |
RenderedChart |
for full control |
Using it in web frameworks
FastAPI / uvicorn:
from fastapi import Response
from charthandler import Chart
@app.get("/chart.png")
def chart() -> Response:
png = Chart.bar({"Mon": 8, "Tue": 12, "Wed": 5}).title("This week").to_png()
return Response(content=png, media_type="image/png")
Flask:
@app.get("/chart.png")
def chart():
return Chart.bar([8, 12, 5]).to_png(), 200, {"Content-Type": "image/png"}
Django:
from django.http import HttpResponse
def chart(request):
return HttpResponse(Chart.pie({"A": 60, "B": 40}).to_png(), content_type="image/png")
For inline charts in a template/email, pass chart.to_data_uri() and use
<img src="{{ chart_uri }}">.
Charts in HTML email
Email clients can't fetch external images offline, and several (Outlook desktop, some
Gmail setups) won't render inline SVG. The reliable approach is a base64 PNG embedded
directly in the markup (to_email_img() does exactly this — it needs the [raster] extra):
img = Chart.bar({"Mon": 8, "Tue": 12, "Wed": 5}).title("This week").to_email_img(alt="Activity")
html = f"<h1>Your report</h1>{img}"
# -> <img src="data:image/png;base64,iVBORw0KGgo..." alt="Activity" />
Run the demo (Docker)
A FastAPI gallery is bundled. With Docker it serves on http://localhost:2200/:
docker compose up -d --build
# http://localhost:2200/ — gallery (every type, inline PNG)
# http://localhost:2200/charts/combo.png — single chart as PNG
# http://localhost:2200/charts/pie.svg — single chart as SVG
Without Docker:
pip install "pythoncharthandler[raster,demo]"
uvicorn examples.fastapi_app:app --reload # http://127.0.0.1:8000/
Development
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
pytest # tests
mypy # strict type-check
ruff check . # lint
A FastAPI demo lives in examples/fastapi_app.py (run with
uvicorn examples.fastapi_app:app --reload).
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pythoncharthandler-0.1.0.tar.gz.
File metadata
- Download URL: pythoncharthandler-0.1.0.tar.gz
- Upload date:
- Size: 25.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6d09e06574e90614908bf4ab0bb4fd6c7924b34bc29b2a24cf919eefc27e2dd
|
|
| MD5 |
a8e6db48ebfd0b53536adbf8d7bf9cb0
|
|
| BLAKE2b-256 |
2b0ea3016814f00148d8c28246d278f874e4c41f5dddf29d60c030803320b736
|
Provenance
The following attestation bundles were made for pythoncharthandler-0.1.0.tar.gz:
Publisher:
publish.yml on hbagheri/PythonChartHandler
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pythoncharthandler-0.1.0.tar.gz -
Subject digest:
e6d09e06574e90614908bf4ab0bb4fd6c7924b34bc29b2a24cf919eefc27e2dd - Sigstore transparency entry: 1584063702
- Sigstore integration time:
-
Permalink:
hbagheri/PythonChartHandler@daff16715bade29028cb3f91be02c24ad5466216 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/hbagheri
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@daff16715bade29028cb3f91be02c24ad5466216 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pythoncharthandler-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pythoncharthandler-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d06a8d2e65b4a6d7292de140c2cb8154798ac0453f0ad8dd34c5f87b068ba1be
|
|
| MD5 |
ca8151b3a1584b1411bc428cc0735da7
|
|
| BLAKE2b-256 |
9d1c050d2ac1a848fa7c7bcf648f51b6f26af41a5961c5ea26366a98c2f432f8
|
Provenance
The following attestation bundles were made for pythoncharthandler-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on hbagheri/PythonChartHandler
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pythoncharthandler-0.1.0-py3-none-any.whl -
Subject digest:
d06a8d2e65b4a6d7292de140c2cb8154798ac0453f0ad8dd34c5f87b068ba1be - Sigstore transparency entry: 1584063843
- Sigstore integration time:
-
Permalink:
hbagheri/PythonChartHandler@daff16715bade29028cb3f91be02c24ad5466216 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/hbagheri
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@daff16715bade29028cb3f91be02c24ad5466216 -
Trigger Event:
push
-
Statement type: