Sera
One Rust engine. Three pillars. Zero glue code.
SeraPlot for visualization, SeraML for machine learning, SeraDFrame for dataframes — compiled into a single native binary, calling each other directly instead of serializing across library boundaries.
Why one engine instead of three libraries
Plotly, scikit-learn, and pandas are each excellent at what they do — and each is a separate library with its own object model. Connecting them means converting DataFrames to NumPy arrays to lists and back, at every boundary. Sera exists because that conversion cost is real, and because a chart, a trained model, and the table that fed both of them don’t need to live in different worlds.
sp.kmeans() computes the clustering and renders the chart in the same Rust call — no round-trip through scikit-learn, no intermediate serialization. That is the whole design principle behind Sera: build once, natively, and let every pillar call the others directly.
| Pillar | What it is | Where to go deeper |
|---|---|---|
| SeraPlot | The rendering engine — 61 2D chart families, 24 3D types, 2 map types, each with several variants. Self-contained HTML/SVG output, no JS bundle. | Intro SeraPlot · Showcase |
| SeraML | A scikit-learn-shaped machine learning layer written in Rust — linear models, trees, ensembles (random forest, gradient boosting, AdaBoost), SVM, k-NN, Naive Bayes, PCA, clustering, anomaly detection, model selection, metrics, preprocessing. Plus a model registry, PowerBI/Tableau export, and GPU/distributed backends that scikit-learn itself doesn’t ship. | Machine Learning |
| SeraDFrame | A native columnar dataframe, built for the same pipeline rather than adapted from one — no pandas dependency, no copy at the boundary when it feeds a chart or a model. | SeraDFrame |
How to develop well with Sera
The three pillars share the same conventions on purpose, so that switching between them costs nothing:
- One call, one result.
sp.bar(...),sp.linear_regression(...),df.groupby(...)all follow the same flat-function shape — no builder object to assemble first, no separate.fit()then.transform()unless the algorithm genuinely needs state across calls. - Aliases are free. Every function is reachable under several names (
sp.bar/sp.bars/sp.bar_chart/sp.bar_unified) resolved through a shared alias registry — pick the name that reads best in your code, they compile to the same call. - Native output, always. A chart is real HTML/SVG, a model result is a real Python dict, a DataFrame column is real Rust-backed memory — never an opaque wrapper object you have to unwrap to get to the actual data.
- The fastest path is the default path. Because computation happens in Rust before anything crosses into Python, the ergonomic way to write code and the fast way to write code are the same code.
The controller: one config call, every chart obeys it
Every pillar reads from the same global controller instead of repeating options per call. Set it once — font, palette, animation, locale — and every Chart built afterward inherits it automatically, with any single call still free to override a value locally.
import seraplot as sp
sp.config(
font="Inter", font_size=13,
palette="viridis", background="#0d1117",
animation=True, animation_duration=420,
crosshair=True, zoom=True, tooltip=True,
locale="en-US", thousands_sep=",",
responsive=True, export_button=True,
)
sp.bar("Revenue", labels, values) # inherits every setting above
sp.bar("Revenue", labels, values, font_size=16) # overrides just this one
sp.config() exposes 30+ knobs this way — typography, palette/background, gridlines and despine, text auto-formatting and rotation, bar corner radius, watermark, drop shadow, crosshair/zoom/tooltip behavior, locale-aware number formatting, responsive layout, export button visibility. sp.reset_config() reverts to defaults, sp.theme() swaps a whole preset in one call. See Chart Methods for the full controller reference and every chainable per-chart method, and Configuration for the config surface in depth.
Accessibility and browser support, actually checked
Not a claim — verified this way: axe-core runs against real Chromium for every chart family with zero serious/critical violations; text and non-text contrast is measured (relative luminance, not eyeballed) against WCAG AA; keyboard navigation (roving tabindex, arrow/Home/End) works on data points and legends; the whole catalog loads with zero console errors on real Chromium, Firefox, and WebKit engines, not just one.
See it in action
Where to start
- Installation —
pip install seraplot - Quick Start — first chart in 3 lines
- Intro SeraPlot — the charting engine in depth, with head-to-head benchmarks
- Showcase — every chart family and variant, with a preview image
- Machine Learning — SeraML’s estimator catalog
- SeraDFrame — the native dataframe
Pourquoi un seul moteur plutôt que trois bibliothèques
Plotly, scikit-learn et pandas sont chacun excellents dans leur domaine — et chacun est une bibliothèque séparée avec son propre modèle d’objets. Les connecter implique de convertir des DataFrames en tableaux NumPy puis en listes et inversement, à chaque frontière. Sera existe parce que ce coût de conversion est réel, et parce qu’un graphique, un modèle entraîné et la table qui a nourri les deux n’ont pas besoin de vivre dans des mondes différents.
sp.kmeans() calcule le clustering et rend le graphique dans le même appel Rust — aucun aller-retour vers scikit-learn, aucune sérialisation intermédiaire. C’est tout le principe de conception derrière Sera : construire une seule fois, nativement, et laisser chaque pilier appeler les autres directement.
| Pilier | Ce que c’est | Pour aller plus loin |
|---|---|---|
| SeraPlot | Le moteur de rendu — 61 familles de graphiques 2D, 24 types 3D, 2 types de cartes, chacun avec plusieurs variantes. Sortie HTML/SVG autonome, sans bundle JS. | Intro SeraPlot · Vitrine |
| SeraML | Une couche de machine learning façon scikit-learn écrite en Rust — modèles linéaires, arbres, ensembles (random forest, gradient boosting, AdaBoost), SVM, k-NN, Naive Bayes, PCA, clustering, détection d’anomalies, sélection de modèle, métriques, preprocessing. Plus un registre de modèles, un export PowerBI/Tableau, et des backends GPU/distribués que scikit-learn lui-même n’a pas. | Machine Learning |
| SeraDFrame | Un dataframe colonnaire natif, conçu pour le même pipeline plutôt qu’adapté depuis un autre — pas de dépendance pandas, pas de copie à la frontière quand il alimente un graphique ou un modèle. | SeraDFrame |
Comment bien développer avec Sera
Les trois piliers partagent les mêmes conventions volontairement, pour que passer de l’un à l’autre ne coûte rien :
- Un appel, un résultat.
sp.bar(...),sp.linear_regression(...),df.groupby(...)suivent tous la même forme de fonction à plat — pas d’objet builder à assembler d’abord, pas de.fit()puis.transform()séparés sauf si l’algorithme a réellement besoin d’état entre les appels. - Les alias sont gratuits. Chaque fonction est accessible sous plusieurs noms (
sp.bar/sp.bars/sp.bar_chart/sp.bar_unified) résolus via un registre d’alias partagé — choisissez le nom qui se lit le mieux dans votre code, ils compilent vers le même appel. - Sortie native, toujours. Un graphique est du vrai HTML/SVG, un résultat de modèle est un vrai dict Python, une colonne de DataFrame est de la vraie mémoire portée par Rust — jamais un objet wrapper opaque qu’il faut déballer pour accéder à la donnée réelle.
- Le chemin le plus ergonomique est le chemin le plus rapide. Parce que le calcul se fait en Rust avant de traverser vers Python, la façon la plus naturelle d’écrire du code et la façon la plus rapide sont le même code.
Le contrôleur : un appel de config, tous les graphiques obéissent
Chaque pilier lit depuis le même contrôleur global plutôt que de répéter les options à chaque appel. Configurez une fois — police, palette, animation, locale — et chaque Chart construit ensuite hérite automatiquement, tout en restant libre de surcharger une valeur localement.
import seraplot as sp
sp.config(
font="Inter", font_size=13,
palette="viridis", background="#0d1117",
animation=True, animation_duration=420,
crosshair=True, zoom=True, tooltip=True,
locale="fr-FR", thousands_sep=" ",
responsive=True, export_button=True,
)
sp.bar("Revenu", labels, values) # hérite de tous les réglages ci-dessus
sp.bar("Revenu", labels, values, font_size=16) # surcharge juste celui-ci
sp.config() expose 30+ réglages de cette façon — typographie, palette/fond, gridlines et despine, formatage/rotation automatique du texte, rayon des coins de barre, watermark, ombre portée, comportement crosshair/zoom/tooltip, formatage numérique localisé, mise en page responsive, visibilité du bouton d’export. sp.reset_config() revient aux défauts, sp.theme() change tout un preset en un appel. Voir Méthodes des graphiques pour la référence complète du contrôleur et chaque méthode chainable par graphique, et Configuration pour la surface de config en détail.
Accessibilité et compatibilité navigateur, réellement vérifiées
Pas une affirmation — vérifié ainsi : axe-core s’exécute contre un vrai Chromium pour chaque famille de graphique, zéro violation serious/critical ; le contraste texte et non-texte est mesuré (luminance relative, pas à l’œil) contre WCAG AA ; la navigation clavier (tabindex flottant, flèches/Home/End) fonctionne sur les points de données et les légendes ; tout le catalogue charge sans erreur console sur de vrais moteurs Chromium, Firefox et WebKit, pas un seul.
À voir en action
Par où commencer
- Installation —
pip install seraplot - Démarrage rapide — premier graphique en 3 lignes
- Intro SeraPlot — le moteur de graphiques en détail, avec benchmarks face à face
- Vitrine — toutes les familles de graphiques et variantes, avec un aperçu image
- Machine Learning — le catalogue d’estimateurs SeraML
- SeraDFrame — le dataframe natif
Installation
Requirements
- Python 3.8+
- pip 21+
SeraPlot ships as a compiled Rust extension (.pyd / .so) bundled in the wheel. There is no compiler required on the user side — the binary is pre-built for each platform.
Install
Standard Python package installer — works in any environment:
pip install seraplot
pip install seraplot
pip install --upgrade seraplot
python -m venv .venv && .venv\Scripts\activateuv is a next-generation Python package manager written in Rust — resolves and installs packages in milliseconds.
uv add seraplot
pip install uv once to install uv, then use uv add in any project.Install from the conda-forge channel, or declare it in your environment file:
conda install -c conda-forge seraplot
Or add to environment.yml:
dependencies:
- pip:
- seraplot
Why the install is this simple
SeraPlot has zero required Python dependencies. The Rust extension is entirely self-contained — the HTML output embeds its own JavaScript inline and does not load anything from a CDN.
numpy, pandas, or scipy — nothing to conflict with your existing stack.
pip install plotly downloads ~15 MB. pip install seraplot downloads ~2 MB.
Prérequis
- Python 3.8+
- pip 21+
SeraPlot se distribue sous forme d’extension Rust compilée (.pyd / .so) incluse dans le wheel. Aucun compilateur n’est requis côté utilisateur — le binaire est pré-compilé pour chaque plateforme.
Installer
Gestionnaire de paquets Python standard — fonctionne dans tous les environnements :
pip install seraplot
pip install seraplot
pip install --upgrade seraplot
python -m venv .venv && .venv\Scripts\activateuv est un gestionnaire de paquets Python nouvelle génération écrit en Rust — résout et installe les paquets en quelques millisecondes.
uv add seraplot
pip install uv une seule fois pour installer uv, puis utilisez uv add dans chaque projet.Installez depuis le canal conda-forge, ou déclarez-le dans votre fichier d'environnement :
conda install -c conda-forge seraplot
Ou ajoutez dans environment.yml :
dependencies:
- pip:
- seraplot
Pourquoi l’installation est aussi simple
SeraPlot n’a aucune dépendance Python requise. L’extension Rust est entièrement autonome — le HTML embarque son propre JavaScript sans rien charger depuis un CDN.
numpy, pandas ou scipy — rien qui puisse entrer en conflit avec votre stack.
pip install plotly télécharge ~15 Mo. pip install seraplot télécharge ~2 Mo.
SeraPlot
Plot anything. Train anything. Ship anywhere.
A Rust-native engine for visualization, machine learning, and zero-friction delivery — 6,000× faster than Plotly, 200× smaller, zero dependencies.
Seraplot, More than a charting library
SeraPlot is a complete data toolkit written in Rust. The same engine powers your visualizations, your machine learning, and the way you ship results to other people.
| Pillar | What you get |
|---|---|
| Plot | 57 chart types — 33 in 2D, 17 in 3D (WebGL), 2 maps. Built-in themes, palettes, animation, zoom, crosshair, export. |
| Train | Scikit-learn-compatible ML in Rust — DBSCAN, K-Means, RandomForest, GradientBoosting, SVM, PCA, GridSearchCV, train/test split. 1.3× to 686× faster. |
| Stream & scale | Live updates, downsampling for millions of points, drift detection, AutoML, diff mode, facet grids. |
| Ship | Self-contained 21 KB HTML — no CDN, no backend, works offline, by email, in S3, in PDFs, in Notion, in air-gapped CI. |
| Integrate | Python, JavaScript, TypeScript, Rust. Drop-in seraplot.matplotlib as plt migration. Pandas / NumPy native. |
| Author | Native VS Code extension — live preview, gallery, theme studio, snippets, auto-detection of labels / values from your code. |
| Persist & export | Save to HTML, PNG, SVG, PDF, pickle. Re-load trained ML models. CSP-safe output. |
| Stay accessible | A11y-tagged SVG, semantic HTML, keyboard navigation, locale-aware number formatting. |
One library replaces: matplotlib + plotly + dash + streamlit + seaborn + parts of scikit-learn — with one
pip installand zero runtime dependencies.
Same chart — three libraries
import seraplot as sp
sp.bar("Revenue by Product", labels, values).save("chart.html")
import plotly.express as px
fig = px.bar(x=labels, y=values, title="Revenue by Product")
fig.update_layout(template="plotly_white")
fig.write_html("chart.html")
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(9, 5))
ax.bar(labels, values, color="#6366f1")
ax.set_title("Revenue by Product")
ax.set_ylabel("Revenue")
plt.tight_layout()
plt.savefig("chart.png")
| SeraPlot | Plotly | Matplotlib | |
|---|---|---|---|
| Lines of code | 2 | 4 | 7 |
| Output | HTML | HTML | PNG |
| File size | 21 KB | 4.7 MB | ~150 KB |
| Interactive | ✅ | ✅ | ❌ |
| Dependencies | 0 | 6+ | 3+ |
| 1-line migration | ✅ | — | — |
Why Seraplot?
As you’ve probably understood by now, Seraplot is a tool designed to be extremely customizable, while also being much faster and more resource-efficient than existing solutions. It also provides a wide range of helpful features, such as the Seraplot extension for VSCode, which allows you to generate plots or ML methods very quickly and live, between each save of your scripts.
In addition, Seraplot is available across multiple languages such as: JS/TS, C (C# & C++), Java, Rust, Python, R & Scala. The main goal is to be highly accessible: from one language to another, the commands remain the same for greater simplicity.
In summary, Seraplot is a much more practical and independent tool that enables the generation of 2D & 3D plots, while also aiming to provide machine learning-related methods that you will find throughout the documentation. More surprises await you, such as the ability to choose different themes, a chunk system in case of crashes to resume from the error point, and even multiple aliases to use the same method (e.g., sp.build_bar_chart / sp.bar_chart / sp.bar / sp.bars).
1000 charts. Measured.
Same code, same random data, same machine. Full HTML output timed.
import seraplot as sp
categories = ["Electronics", "Clothing", "Food", "Books", "Sports", "Toys", "Health", "Auto"]
data = [...] # 1000 pre-generated lists
for i in range(1000):
sp.bar(f"Report #{i+1}", categories, data[i]).html
1000 charts in 6 ms — 6 µs/chart
import plotly.graph_objects as go
categories = ["Electronics", "Clothing", "Food", "Books", "Sports", "Toys", "Health", "Auto"]
data = [...] # same 1000 pre-generated lists
for i in range(1000):
fig = go.Figure(data=[go.Bar(x=categories, y=data[i])])
fig.update_layout(title=f"Report #{i+1}", template="plotly_dark")
fig.to_html(full_html=True, include_plotlyjs="cdn")
1000 charts in 37,023 ms — 6,170× slower
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
categories = ["Electronics", "Clothing", "Food", "Books", "Sports", "Toys", "Health", "Auto"]
data = [...] # same 1000 pre-generated lists
for i in range(1000):
fig, ax = plt.subplots(figsize=(9, 5))
ax.bar(categories, data[i])
ax.set_title(f"Report #{i+1}")
fig.savefig(f"chart_{i}.png")
plt.close()
1000 charts in 60,352 ms — 10,058× slower
| Scale | SeraPlot | Plotly | Matplotlib |
|---|---|---|---|
| 1,000 charts | 6 ms | 37 s | 60 s |
| 10,000 charts | ~60 ms | ~6 min | ~10 min |
| 100,000 charts | ~600 ms | ~1 h | ~1.7 h |
Render core speed
Benchmark: Diabetes dataset (n=768, 40 runs). Rust render time — chart object creation, not full HTML serialization.
| Chart | SeraPlot | Plotly figure | Plotly → HTML | Matplotlib |
|---|---|---|---|---|
| Pie | 4.2 | 725 | 33,416 | 15,085 |
| Bar | 2.8 | 658 | 18,166 | 13,596 |
| Grouped Bar | 5.0 | 558 | 17,981 | 17,445 |
| Histogram | 12.4 | 2,496 | 32,762 | 37,973 |
| Scatter | 17.0 | 3,916 | 21,615 | 14,141 |
| Violin | 16.7 | 2,616 | 21,347 | 21,211 |
| Box Plot | 18.4 | 2,329 | 21,799 | 15,590 |
| KDE | 26.3 | 2,981 | 19,807 | 40,108 |
| Radar | 11.8 | 962 | 17,679 | 20,942 |
| Lollipop | 6.3 | 8,382 | 25,096 | 9,072 |
| Candlestick | 8.8 | 1,478 | 17,934 | N/A |
| Ridgeline | 88.8 | N/A | N/A | N/A |
All times in µs.
Output file size
Plotly embeds its entire JavaScript bundle in every HTML file. SeraPlot only includes the JS needed for that specific chart type.
Matplotlib outputs PNG/SVG/PDF (50-500 KB) — not interactive HTML.
What SeraPlot actually is
SeraPlot is not a wrapper around Plotly, Chart.js, or D3.
It is a Rust-native rendering engine that generates minimal HTML + JS per chart. A Pie chart gets Pie JS. A Bar chart gets Bar JS. Nothing else is bundled.
That’s why the output is 20 KB instead of 4.7 MB.
One line migration
import seraplot.matplotlib as plt
Everything else stays the same.
plt.bar(), plt.scatter(), plt.hist(), plt.show(), plt.savefig() — unchanged.
Deploy from an API
from fastapi import FastAPI
import seraplot as sp
app = FastAPI()
@app.get("/chart")
def revenue_chart():
return sp.bar("Revenue", labels, values).html
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
import plotly.express as px
app = FastAPI()
@app.get("/chart", response_class=HTMLResponse)
def revenue_chart():
fig = px.bar(x=labels, y=values, title="Revenue")
return fig.to_html(full_html=True)
from fastapi import FastAPI
from fastapi.responses import FileResponse
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import tempfile
app = FastAPI()
@app.get("/chart")
def revenue_chart():
fig, ax = plt.subplots(figsize=(9, 5))
ax.bar(labels, values)
ax.set_title("Revenue")
path = tempfile.mktemp(suffix=".png")
plt.savefig(path)
plt.close()
return FileResponse(path, media_type="image/png")
Plotly returns 4.7 MB per request. Matplotlib requires disk I/O and returns a static PNG. SeraPlot returns 21 KB of interactive HTML directly from RAM.
Everything SeraPlot does
- 57 chart types — every 2D chart has a 3D WebGL variant
- Drop-in matplotlib API —
import seraplot.matplotlib as plt - Pandas & NumPy native — pass DataFrames directly
- 7 built-in themes — dark, light, scientific, apple, notion, minimal, neon
- Global config —
sp.config()sets font, zoom, crosshair, animation across all charts - Zero dependencies — pure Rust renderer
- 200× smaller files — no bundled JS runtime
- Multi-language — Python, JavaScript/TypeScript (npm), Rust, R, Scala, C#, C++, Java
- DBSCAN up to 600× faster than scikit-learn
- Native Machine learning — some ml methods is include by default in this tool
- Works everywhere — Python ≥ 3.8, any OS
See it in action
Navigation
- Installation —
pip install seraplot - Quick Start — first chart in 3 lines
- Chart Methods — all universal Chart methods
- 2D Charts — 33 chart types
- 3D Charts — 17 chart types, WebGL GPU renderer
- Machine Learning — DBSCAN up to 600x faster than scikit-learn
Seraplot - Bien plus qu’une bibliothèque de graphiques
« Tout tracer. Tout entraîner. Partout déployer. »
SeraPlot est une boîte à outils data complète écrite en Rust. Le même moteur propulse vos visualisations, votre machine learning, et la façon dont vous livrez les résultats à vos collègues.
| Pilier | Ce que vous obtenez |
|---|---|
| Tracer | 57 types de graphiques — 33 en 2D, 17 en 3D (WebGL), 2 cartes. Thèmes intégrés, palettes, animations, zoom, crosshair, export. |
| Entraîner | ML compatible scikit-learn en Rust — DBSCAN, K-Means, RandomForest, GradientBoosting, SVM, PCA, GridSearchCV, train/test split. De 1,3× à 686× plus rapide. |
| Streamer & passer à l’échelle | Mises à jour en direct, downsampling pour des millions de points, détection de drift, AutoML, mode diff, grilles de facettes. |
| Déployer | HTML autonome de 21 Ko — pas de CDN, pas de backend, fonctionne hors-ligne, par e-mail, sur S3, dans des PDF, dans Notion, en CI isolée. |
| Intégrer | Python, JavaScript, TypeScript, Rust. Migration directe seraplot.matplotlib as plt. Pandas / NumPy nativement. |
| Coder | Extension VS Code native — aperçu en direct, galerie, studio de thèmes, snippets, détection automatique des labels / values depuis votre code. |
| Persister & exporter | Export HTML, PNG, SVG, PDF, pickle. Rechargement des modèles ML entraînés. Sortie compatible CSP. |
| Rester accessible | SVG balisé a11y, HTML sémantique, navigation clavier, formatage numérique localisé. |
Une seule librairie remplace : matplotlib + plotly + dash + streamlit + seaborn + une partie de scikit-learn — avec un seul
pip installet zéro dépendance d’exécution.
Même graphique — trois bibliothèques
import seraplot as sp
sp.bar("Chiffre d'affaires par produit", labels, values).save("chart.html")
import plotly.express as px
fig = px.bar(x=labels, y=values, title="Chiffre d'affaires par produit")
fig.update_layout(template="plotly_white")
fig.write_html("chart.html")
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(9, 5))
ax.bar(labels, values, color="#6366f1")
ax.set_title("Chiffre d'affaires par produit")
ax.set_ylabel("Chiffre d'affaires")
plt.tight_layout()
plt.savefig("chart.png")
| SeraPlot | Plotly | Matplotlib | |
|---|---|---|---|
| Lignes de code | 2 | 4 | 7 |
| Sortie | HTML | HTML | PNG |
| Taille fichier | 21 Ko | 4,7 Mo | ~150 Ko |
| Interactif | ✅ | ✅ | ❌ |
| Dépendances | 0 | 6+ | 3+ |
| Migration 1 ligne | ✅ | — | — |
Pourquoi Seraplot ?
Comme vous l’aurez compris en arrivant jusqu’ici, Seraplot est un outil qui a pour objectif d’être extrêmement personnalisable, mais aussi beaucoup plus rapide et moins gourmand que ce qui existe déjà, en plus de proposer tout un panel d’aides, comme l’extension Seraplot dans VSCode, qui vous permettra de générer des plots ou des méthodes ML très rapidement et en live, entre chaque sauvegarde de vos scripts.
En plus de cela, Seraplot se voit distribué dans différents langages comme : JS/TS, C (C# & C++), Java, Rust, Python, R & Scala. L’objectif étant vraiment d’être ultra accessible : d’un langage à un autre, les commandes restent les mêmes pour plus de simplicité.
Pour résumer, Seraplot est un outil beaucoup plus pratique de ce qui existe déjà et complétement indépendant, en plus de compilé différente fonctionnalitée. En autre il permet la génération de plots 2D & 3D, mais aussi qui tend à proposer des méthodes liées au ML, que vous pourrez retrouver au cours de votre documentation. D’autres surprises vous attendent, que ce soit la possibilité de choisir différents thèmes, ou bien le système de chunks en cas de crash pour reprendre au point d’erreur, ou encore pour n’en citer que un dernier, le fait d’avoir différents alias pour utiliser une même méthode (ex : sp.build_bar_chart / sp.bar_chart / sp.bar / sp.bars).
1 000 graphiques. Mesurés.
Même code, mêmes données aléatoires, même machine. Sortie HTML complète chronométrée.
import seraplot as sp
categories = ["Électronique", "Vêtements", "Alimentation", "Livres", "Sport", "Jouets", "Santé", "Auto"]
data = [...]
for i in range(1000):
sp.bar(f"Rapport #{i+1}", categories, data[i]).html
1 000 graphiques en 6 ms — 6 µs/graphique
import plotly.graph_objects as go
categories = ["Électronique", "Vêtements", "Alimentation", "Livres", "Sport", "Jouets", "Santé", "Auto"]
data = [...]
for i in range(1000):
fig = go.Figure(data=[go.Bar(x=categories, y=data[i])])
fig.update_layout(title=f"Rapport #{i+1}", template="plotly_dark")
fig.to_html(full_html=True, include_plotlyjs="cdn")
1 000 graphiques en 37 023 ms — 6 170× plus lent
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
categories = ["Électronique", "Vêtements", "Alimentation", "Livres", "Sport", "Jouets", "Santé", "Auto"]
data = [...]
for i in range(1000):
fig, ax = plt.subplots(figsize=(9, 5))
ax.bar(categories, data[i])
ax.set_title(f"Rapport #{i+1}")
fig.savefig(f"chart_{i}.png")
plt.close()
1 000 graphiques en 60 352 ms — 10 058× plus lent
| Échelle | SeraPlot | Plotly | Matplotlib |
|---|---|---|---|
| 1 000 graphiques | 6 ms | 37 s | 60 s |
| 10 000 graphiques | ~60 ms | ~6 min | ~10 min |
| 100 000 graphiques | ~600 ms | ~1 h | ~1,7 h |
Vitesse du moteur de rendu
Benchmark : dataset Diabetes (n=768, 40 itérations). Temps de rendu Rust — création de l’objet graphique, pas la sérialisation HTML complète.
| Graphique | SeraPlot | Plotly figure | Plotly → HTML | Matplotlib |
|---|---|---|---|---|
| Camembert | 4,2 | 725 | 33 416 | 15 085 |
| Barres | 2,8 | 658 | 18 166 | 13 596 |
| Barres groupées | 5,0 | 558 | 17 981 | 17 445 |
| Histogramme | 12,4 | 2 496 | 32 762 | 37 973 |
| Nuage de points | 17,0 | 3 916 | 21 615 | 14 141 |
| Violon | 16,7 | 2 616 | 21 347 | 21 211 |
| Boîte à moustaches | 18,4 | 2 329 | 21 799 | 15 590 |
| KDE | 26,3 | 2 981 | 19 807 | 40 108 |
| Radar | 11,8 | 962 | 17 679 | 20 942 |
| Sucette | 6,3 | 8 382 | 25 096 | 9 072 |
| Bougie | 8,8 | 1 478 | 17 934 | N/A |
| Ridgeline | 88,8 | N/A | N/A | N/A |
Toutes les valeurs en µs.
Taille des fichiers de sortie
Plotly embarque tout son bundle JavaScript dans chaque fichier HTML. SeraPlot n’inclut que le JS nécessaire au type de graphique spécifique.
Matplotlib produit du PNG/SVG/PDF (50–500 Ko) — pas du HTML interactif.
Ce qu’est réellement SeraPlot
SeraPlot n’est pas un wrapper autour de Plotly, Chart.js ou D3.
C’est un moteur de rendu natif Rust qui génère du HTML + JS minimal par graphique. chaque chart reçoit soit js dédiée. Rien d’autre n’est embarqué.
C’est pour ça que la sortie fait 20 Ko au lieu de 4,7 Mo.
Migration en une ligne
import seraplot.matplotlib as plt
Tout le reste reste identique.
plt.bar(), plt.scatter(), plt.hist(), plt.show(), plt.savefig() — inchangés.
Déployer depuis une API
from fastapi import FastAPI
import seraplot as sp
app = FastAPI()
@app.get("/chart")
def revenue_chart():
return sp.bar("Chiffre d'affaires", labels, values).html
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
import plotly.express as px
app = FastAPI()
@app.get("/chart", response_class=HTMLResponse)
def revenue_chart():
fig = px.bar(x=labels, y=values, title="Chiffre d'affaires")
return fig.to_html(full_html=True)
from fastapi import FastAPI
from fastapi.responses import FileResponse
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import tempfile
app = FastAPI()
@app.get("/chart")
def revenue_chart():
fig, ax = plt.subplots(figsize=(9, 5))
ax.bar(labels, values)
ax.set_title("Chiffre d'affaires")
path = tempfile.mktemp(suffix=".png")
plt.savefig(path)
plt.close()
return FileResponse(path, media_type="image/png")
Plotly retourne 4,7 Mo par requête. Matplotlib nécessite des I/O disque et retourne un PNG statique. SeraPlot retourne 21 Ko de HTML interactif directement depuis la RAM.
Tout ce que SeraPlot fait
- 57 types de graphiques — chaque graphique 2D a une variante 3D WebGL
- API matplotlib drop-in —
import seraplot.matplotlib as plt - Pandas & NumPy natifs — passez les DataFrames directement
- 7 thèmes intégrés — dark, light, scientific, apple, notion, minimal, neon
- Configuration globale —
sp.config()définit la police, le zoom, le réticule, l’animation pour tous les graphiques - Zéro dépendance — moteur de rendu Rust pur
- Fichiers 200× plus petits — pas de runtime JS embarqué
- Multi-langage — Python, JavaScript/TypeScript (npm), Rust, R, Scala, C#, C++, Java
- DBSCAN jusqu’à 600× plus rapide que scikit-learn
- Machine learning natif — Plusieurs methods ml, sont déjà existante dans le framework
- Fonctionne partout — Python ≥ 3.8, tout OS
À voir en action
Navigation
- Installation —
pip install seraplot - Démarrage rapide — premier graphique en 3 lignes
- Méthodes des graphiques — toutes les méthodes universelles Chart
- Graphiques 2D — 33 types
- Graphiques 3D — 17 types, rendu GPU WebGL
- Machine Learning — DBSCAN jusqu’à 600× plus rapide que scikit-learn
Showcase
Chart methods
Chart methods & global config
Every Chart object returned by SeraPlot supports the same fluent API. Set defaults once with sp.config() and tweak per chart with chainable methods. All methods return a new Chart, so chaining is always safe.
Global config
Set defaults once. Every chart created afterwards inherits this configuration automatically.
sp.config.add_alias("bar", "barchart")
sp.config.add_alias("line", "linechart")
sp.config.save() # writes to ~/.seraplot/config.json
import seraplot as sp # aliases are auto-loaded from ~/.seraplot/config.json
Themes
Curated presets that combine palette, background and gridlines.
| Theme | Mood |
|---|---|
"dark" | High-contrast dark dashboard |
"light" | Soft pastel light backdrop |
"scientific" | Publication-style monochrome |
"apple" | Glassy iOS-inspired palette |
"notion" | Calm Notion-style neutrals |
"minimal" | Bare lines, no chrome |
"neon" | Vibrant cyberpunk neons |
sp.config(theme=...)globalsp.config(theme="dark", gridlines=True, font="Inter")
chart = sp.bar("Sales", labels=["Q1","Q2","Q3"], values=[120,180,150])
chart.show()
sp.reset_theme() to return to the framework default at any point.Chart methods
Generated from #[sera_doc] in the Rust source
Every card below is generated straight from the #[sera_doc(…)] annotation on the matching Rust method — name, parameters and description always match the actual implementation.
Magic properties
.html propertyread-onlysrc = chart.html
len(chart) → intpropertyprint(len(chart))
bool(chart) → boolpropertyTrue when the chart has rendered output.if chart:
chart.show()
Annotations (cross-chart)
SVG overlays inherited by every plot
annotations=[...] (kwarg)newglobal0.0–1.0); set "frac": false to use raw pixels. Supported kind: "hline", "vline", "line", "arrow", "rect", "text".chart = sp.line(
"Sales", labels=months, values=sales,
annotations=[
{"kind":"hline", "y":0.5, "color":"#22c55e", "dash":"6 4", "text":"Target"},
{"kind":"vline", "x":0.62, "color":"#f59e0b", "text":"Launch"},
{"kind":"rect", "x":0.05, "y":0.65, "x2":0.40, "y2":0.92,
"color":"#6366f1", "fill":"#6366f1", "opacity":0.10},
{"kind":"arrow", "x":0.45, "y":0.30, "x2":0.85, "y2":0.18, "color":"#ef4444"},
{"kind":"text", "x":0.46, "y":0.28, "text":"Outlier", "color":"#ef4444"},
],
)
Annotation fields| Field | Type | Default | Description |
|---|---|---|---|
kind | str | required | hline / vline / line / arrow / rect / text |
x, y | float | 0.5 | Anchor point (frac of canvas if frac=True, pixels otherwise) |
x2, y2 | float | 1.0 | End point for line / arrow / rect |
color | str | "#ef4444" | Stroke / text color (CSS) |
fill | str | "none" | Fill color (rect only) |
stroke_width | float | 1.5 | Stroke width |
dash | str | "" | SVG dash array, e.g. "6 4" |
opacity | float | 1.0 | 0.0 - 1.0 |
text | str | None | Optional label rendered next to the primitive |
font_size | float | 11.0 | Label font size |
frac | bool | true | Coordinate space: fractional (0-1) or pixel |
apply_annotations() hook - no per-chart wiring required.Composers (Grid + Slideshow)
Group charts into stories
Compose any number of pre-built charts into a responsive CSS-grid, each chart isolated in its own iframe.
bar = sp.bar("Sales", labels=["Q1","Q2","Q3","Q4"], values=[120,180,150,210])
line = sp.line("Trend", labels=months, values=sales)
pie = sp.pie("Mix", labels=["A","B","C"], values=[40,35,25])
sp.grid([bar, line, pie], cols=3, gap=14, title="Dashboard").show()
Build a navigable HTML carousel with prev / next buttons and an auto-advance progress bar — tell a story without a slide deck.
slides = [sp.bar(f"Slide {i+1}", labels=["A","B"], values=[i, i+1]) for i in range(4)]
sp.build_slideshow(slides, interval_ms=2200, title="Quarterly Story").show()
Configuration
Two layers, and every page in this section names which one it documents:
- Global, standalone functions —
sp.set_global_background(...),sp.theme(...),sp.config(...)— apply before charts are built and affect everything created afterward. See Chart Methods for the full generated list. - Chainable
Chartmethods —chart.export_svg(path),chart.hover_json(payload),chart.downsample()— apply to one already-built chart and return a newChart, the same way every other per-chart method chains.
Every code sample in this section is checked against a real build rather than written from memory — where a feature exists only on the JS/WASM side, or isn’t wired up as a Python function yet, that is stated explicitly instead of shown as if it worked.
Deux couches, et chaque page de cette section précise laquelle elle documente :
- Fonctions globales autonomes —
sp.set_global_background(...),sp.theme(...),sp.config(...)— s’appliquent avant la construction des graphiques et affectent tout ce qui est créé ensuite. Voir Méthodes des graphiques pour la liste complète générée. - Méthodes chaînables sur
Chart—chart.export_svg(path),chart.hover_json(payload),chart.downsample()— s’appliquent à un chart déjà construit et retournent un nouveauChart, comme toute autre méthode par graphique.
Chaque exemple de code de cette section est vérifié contre un vrai build plutôt qu’écrit de mémoire — quand une fonctionnalité n’existe que côté JS/WASM, ou n’est pas encore câblée comme fonction Python, c’est dit explicitement plutôt que présenté comme si ça fonctionnait.
Background Configuration
Functions
| Function | Description |
|---|---|
set_global_background(color) | Sets a global background color applied to all charts created after the call. |
reset_global_background() | Clears the global background, reverting to each chart’s own default. |
| Parameter | Type | Description |
|---|---|---|
color | str | CSS color string (hex "#rrggbb", "rgb(...)", named color) |
Example
import seraplot as sp
sp.set_global_background("#0f172a")
bar = sp.build_bar_chart("Revenue", labels=["A", "B"], values=[300, 200])
pie = sp.build_pie_chart("Share", labels=["A", "B"], values=[60, 40])
sp.reset_global_background()
sp.set_global_background(...) is equivalent to sp.config(background=...), and
is overridden by sp.theme(name) if a theme is applied afterward — see
Themes for the built-in background/palette/gridlines bundles.
Fonctions
| Fonction | Description |
|---|---|
set_global_background(color) | Définit une couleur de fond globale appliquée à tous les graphiques créés après l’appel. |
reset_global_background() | Efface le fond global, revenant à la valeur par défaut de chaque graphique. |
| Paramètre | Type | Description |
|---|---|---|
color | str | Couleur CSS (hex "#rrggbb", "rgb(...)", nom de couleur) |
Exemple
import seraplot as sp
sp.set_global_background("#0f172a")
barre = sp.build_bar_chart("Revenus", labels=["A", "B"], values=[300, 200])
camembert = sp.build_pie_chart("Parts", labels=["A", "B"], values=[60, 40])
sp.reset_global_background()
sp.set_global_background(...) équivaut à sp.config(background=...), et est
écrasé par sp.theme(name) si un thème est appliqué ensuite — voir
Thèmes pour la liste des thèmes intégrés (fond + palette +
quadrillage).
Palette & Colors
Overview
SeraPlot represents colors as 24-bit RGB integers (int) or CSS strings (str).
| Format | Example | Usage |
|---|---|---|
| Hex integer | 0x6366f1 | color_hex, palette lists |
| CSS hex string | "#6366f1" | background, bg_color |
| CSS named color | "navy" | background, bg_color |
Built-in Palettes
There is no standalone sp.PALETTE_* constant — the built-in color sets ship
bundled inside a theme (background + palette + gridlines together). Call
sp.theme(name) to apply one, or read Themes for the full list
and every palette’s exact hex values.
import seraplot as sp
print(sp.themes())
# ['dark', 'light', 'scientific', 'apple', 'notion', 'minimal', 'neon']
sp.theme("dark")
chart = sp.build_bar_chart("Revenue", labels=["A", "B", "C"], values=[100, 200, 150])
sp.reset_theme()
Pass any list of hex ints as the palette parameter to override the palette
for a single chart, independent of the active theme:
chart = sp.build_bar_chart(
"Revenue",
labels=["A", "B", "C"],
values=[100, 200, 150],
palette=[0x6366f1, 0x22d3ee, 0xf43f5e],
)
Color Utility Reference
| Parameter name | Accepts | Description |
|---|---|---|
color_hex | int | Single element color |
palette | list[int] | Multi-element color list |
background | str | Chart canvas background |
bg_color | str | 3D canvas background |
color_low / color_mid / color_high | int | Min/mid/max heatmap colors |
color_up / color_down | int | Rising/falling candle colors |
color_pos / color_neg / color_total | int | Positive/negative/total bar (waterfall) |
Example
import seraplot as sp
chart = sp.build_heatmap(
"Correlation",
labels=["A", "B", "C"],
col_labels=["A", "B", "C"],
values=[1, 0.8, 0.2, 0.8, 1, 0.5, 0.2, 0.5, 1],
color_low=0xfaf5ff,
color_mid=0xa78bfa,
color_high=0x4c1d95,
)
Aperçu
SeraPlot représente les couleurs sous forme d’entiers RGB 24 bits (int) ou de chaînes CSS (str).
| Format | Exemple | Utilisation |
|---|---|---|
| Entier hex | 0x6366f1 | color_hex, listes palette |
| Chaîne CSS hex | "#6366f1" | background, bg_color |
| Nom CSS | "navy" | background, bg_color |
Palettes intégrées
Il n’existe pas de constante sp.PALETTE_* autonome — les jeux de couleurs
intégrés sont livrés à l’intérieur d’un thème (fond + palette + quadrillage
ensemble). Appelez sp.theme(name) pour en appliquer un, ou consultez
Thèmes pour la liste complète et les valeurs hex exactes de
chaque palette.
import seraplot as sp
print(sp.themes())
# ['dark', 'light', 'scientific', 'apple', 'notion', 'minimal', 'neon']
sp.theme("dark")
chart = sp.build_bar_chart("Revenus", labels=["A", "B", "C"], values=[100, 200, 150])
sp.reset_theme()
Passez n’importe quelle liste d’entiers hex comme paramètre palette pour
surcharger la palette d’un seul chart, indépendamment du thème actif :
chart = sp.build_bar_chart(
"Revenus",
labels=["A","B","C"],
values=[100,200,150],
palette=[0x6366f1, 0x22d3ee, 0xf43f5e],
)
Référence des paramètres de couleur
| Nom du paramètre | Accepte | Description |
|---|---|---|
color_hex | int | Couleur d’un élément unique |
palette | list[int] | Liste de couleurs multi-éléments |
background | str | Fond du canevas HTML |
bg_color | str | Fond du canevas 3D |
color_low | int | Couleur valeur min (heatmaps, choroplèthes) |
color_mid | int | Couleur valeur médiane |
color_high | int | Couleur valeur max |
color_up | int | Couleur bougie montante |
color_down | int | Couleur bougie descendante |
color_pos | int | Barre positive (cascade) |
color_neg | int | Barre négative (cascade) |
color_total | int | Barre totale (cascade) |
Exemple
import seraplot as sp
chart = sp.build_heatmap(
"Corrélation",
labels=["A","B","C"],
col_labels=["A","B","C"],
values=[1, 0.8, 0.2, 0.8, 1, 0.5, 0.2, 0.5, 1],
color_low=0xfaf5ff,
color_mid=0xa78bfa,
color_high=0x4c1d95,
)
Themes
API
| Function | Description |
|---|---|
sp.theme(name) | Apply a built-in theme — sets background, palette, and gridlines globally |
sp.reset_theme() | Revert to defaults (no background, default palette, no gridlines) |
sp.themes() | Returns a list of all available theme names |
import seraplot as sp
sp.theme("dark")
chart = sp.bar("Revenue", labels=["Q1", "Q2", "Q3"], values=[120, 145, 98])
sp.reset_theme()
All 7 themes
Full palette per theme
Examples
import seraplot as sp
sp.theme("dark")
sp.bar("Revenue", labels=["Q1", "Q2", "Q3", "Q4"], values=[120, 145, 98, 180]).show()
sp.theme("neon")
sp.scatter(title="Clusters", x=[1, 2, 3, 4, 5, 6], y=[2, 5, 3, 8, 7, 9]).show()
sp.theme("scientific")
sp.line(title="Population Growth", x_labels=["2020", "2021", "2022", "2023"], values=[100, 112, 121, 135]).show()
sp.reset_theme()
print(sp.themes())
# ['dark', 'light', 'scientific', 'apple', 'notion', 'minimal', 'neon']
Notes
sp.theme()sets the global background, palette, and gridlines. It is equivalent to callingsp.config(background=..., palette=..., gridlines=...)with the preset values.- Themes persist until
sp.reset_theme()orsp.config()overrides them. - You can further override individual properties after calling a theme:
sp.theme("dark")
sp.config(font_size=16, border_radius=12)
API
| Fonction | Description |
|---|---|
sp.theme(name) | Applique un thème intégré — définit le fond, la palette et le quadrillage globalement |
sp.reset_theme() | Revient aux valeurs par défaut (pas de fond, palette par défaut, pas de quadrillage) |
sp.themes() | Retourne la liste de tous les noms de thèmes disponibles |
import seraplot as sp
sp.theme("dark")
graphique = sp.bar("Revenus", labels=["T1", "T2", "T3"], values=[120, 145, 98])
sp.reset_theme()
Les 7 thèmes disponibles
Palettes complètes
Exemples
import seraplot as sp
sp.theme("dark")
sp.bar("Revenus", labels=["T1", "T2", "T3", "T4"], values=[120, 145, 98, 180]).show()
sp.theme("neon")
sp.scatter(title="Clusters", x=[1, 2, 3, 4, 5, 6], y=[2, 5, 3, 8, 7, 9]).show()
sp.theme("scientific")
sp.line(title="Croissance démographique", x_labels=["2020", "2021", "2022", "2023"], values=[100, 112, 121, 135]).show()
sp.reset_theme()
print(sp.themes())
# ['dark', 'light', 'scientific', 'apple', 'notion', 'minimal', 'neon']
Notes
sp.theme()définit le fond global, la palette et le quadrillage. C’est équivalent àsp.config(background=..., palette=..., gridlines=...)avec les valeurs du préréglage.- Les thèmes persistent jusqu’à
sp.reset_theme()ou un appelsp.config()qui les écrase. - Vous pouvez continuer à surcharger des propriétés individuelles après avoir appliqué un thème :
sp.theme("dark")
sp.config(font_size=16, border_radius=12)
Custom Tooltips
Signature
chart.hover_json(slots_json: str) -> Chart
hover_json is a chainable Chart method — not a standalone sp. function.
It takes a single JSON string and returns a new Chart with the tooltip
override applied, the same chaining shape as every other Chart method
(chart.subtitle(...).hover_json(...).show()).
Description
Overrides the tooltip content shown per data point, independent of the values actually plotted — useful when the label/value combination on the axes isn’t the whole story (currency formatting, a delta vs. last period, a category not otherwise on the chart).
Example
import json
import seraplot as sp
chart = sp.bar(title="Revenus par produit", labels=["Alpha", "Beta", "Gamma"], values=[420, 380, 290])
hover_payload = json.dumps({
"fields": ["Produit", "Revenu (€)", "Croissance"],
"values": [
["Alpha", 420, "+12%"],
["Beta", 380, "+5%"],
["Gamma", 290, "-3%"],
],
})
chart = chart.hover_json(hover_payload)
Signature
chart.hover_json(slots_json: str) -> Chart
hover_json est une méthode chaînable sur Chart — pas une fonction sp.
autonome. Elle prend une seule chaîne JSON et retourne un nouveau Chart
avec l’override de tooltip appliqué, la même forme chaînable que toute autre
méthode Chart (chart.subtitle(...).hover_json(...).show()).
Description
Remplace le contenu du tooltip affiché par point de donnée, indépendamment des valeurs réellement tracées — utile quand la combinaison label/valeur des axes ne raconte pas toute l’histoire (formatage monétaire, un delta vs la période précédente, une catégorie absente du graphique lui-même).
Exemple
import json
import seraplot as sp
chart = sp.bar(title="Revenus par produit", labels=["Alpha", "Beta", "Gamma"], values=[420, 380, 290])
hover_payload = json.dumps({
"fields": ["Produit", "Revenu (€)", "Croissance"],
"values": [
["Alpha", 420, "+12%"],
["Beta", 380, "+5%"],
["Gamma", 290, "-3%"],
],
})
chart = chart.hover_json(hover_payload)
Auto-Display (Jupyter)
Signature
sp.set_auto_display(enabled: bool) -> None
Description
Controls whether Chart objects are automatically rendered inline in Jupyter notebooks
when they are the last expression of a cell.
| State | Behavior |
|---|---|
True (default) | chart at end of cell → rendered immediately |
False | Must call display(chart) or chart.show() explicitly |
Parameters
| Parameter | Type | Description |
|---|---|---|
enabled | bool | True to enable auto-display, False to disable |
Examples
sp.set_auto_display(False)
charts = []
for name, values in datasets.items():
charts.append(sp.build_bar_chart(name, labels=["A","B","C"], values=values))
for c in charts:
c.show()
Signature
sp.set_auto_display(enabled: bool) -> None
Description
Contrôle si les objets Chart sont automatiquement rendus dans les cellules Jupyter.
| État | Comportement |
|---|---|
True (défaut) | Le graphique en fin de cellule est rendu immédiatement |
False | Il faut appeler display(chart) ou chart.show() explicitement |
Paramètres
| Paramètre | Type | Description |
|---|---|---|
enabled | bool | True pour activer, False pour désactiver |
Exemples
sp.set_auto_display(False)
graphiques = []
for nom, valeurs in jeux_de_donnees.items():
graphiques.append(sp.build_bar_chart(nom, labels=["A","B","C"], values=valeurs))
for g in graphiques:
g.show()
Downsampling (LTTB)
Reduce massive datasets while preserving visual shape using the Largest-Triangle-Three-Buckets algorithm. A 10M-point scatter chart becomes 5K points indistinguishable to the eye.
Python
import seraplot as sp
chart = sp.scatter(title="Big scatter", x_values=big_x, y_values=big_y).downsample()
downsample is a chainable Chart method, not a standalone sp. function —
there is no Python sp.lttb(); the algorithm is only registered on the
JS/WASM side today (downsampleLttb, below).
JavaScript
import { downsampleLttb } from "seraplot";
const reduced = JSON.parse(downsampleLttb(JSON.stringify({ x, y, threshold: 5000 })));
Why LTTB?
| Method | Preserves peaks | Speed | Visual fidelity |
|---|---|---|---|
| Random sample | No | Fast | Poor |
| Every-Nth | Maybe | Fast | OK |
| LTTB | Yes | Fast | Excellent |
Réduit les datasets massifs en préservant la forme visuelle avec l’algorithme Largest-Triangle-Three-Buckets. Un scatter de 10M points devient 5K points indistinguables à l’œil.
Python
import seraplot as sp
chart = sp.scatter(title="Grand scatter", x_values=big_x, y_values=big_y).downsample()
downsample est une méthode chaînable sur Chart, pas une fonction sp.
autonome — il n’y a pas de sp.lttb() en Python ; l’algorithme n’est
enregistré que côté JS/WASM aujourd’hui (downsampleLttb, ci-dessous).
JavaScript
import { downsampleLttb } from "seraplot";
const reduced = JSON.parse(downsampleLttb(JSON.stringify({ x, y, threshold: 5000 })));
Pourquoi LTTB ?
| Méthode | Préserve les pics | Vitesse | Fidélité visuelle |
|---|---|---|---|
| Échantillon aléatoire | Non | Rapide | Mauvaise |
| Tous les N | Peut-être | Rapide | OK |
| LTTB | Oui | Rapide | Excellente |
Live Streaming
LiveStream is a bounded ring-buffer accumulator that turns a series of
(x, y) samples arriving over time into a continuously redrawn chart. Memory
stays constant whatever the duration of the stream, since older samples are
dropped past max_points.
What makes it smooth in a notebook: push() / extend() / clear() don’t
just re-render — they repaint the same Jupyter output cell in place via
IPython’s display_id mechanism (display() once, then update_display()
on every following call). No new cells get appended, no scroll-jump, no
clear-then-flash; the chart updates where it already is.
Constructor
sp.LiveStream(
kind: str = "line", # "line" or "scatter"
title: str = "",
max_points: int = 500, # ring buffer size
color_hex: int = 0x6366F1,
width: int = 900,
height: int = 420,
)
| Parameter | Type | Default | Description |
|---|---|---|---|
kind | str | "line" | Chart kind. "line" or "scatter". |
title | str | "" | Chart title rendered on every update. |
max_points | int | 500 | Maximum samples kept in the buffer. Older samples are dropped from the head. |
color_hex | int | 0x6366F1 | Series color as a 24-bit RGB integer. |
width | int | 900 | Canvas width in pixels. |
height | int | 420 | Canvas height in pixels. |
Methods
| Method | Effect |
|---|---|
push(x, y) | Append a single sample, then redraw in place. |
extend(xs, ys) | Append two lists in lock-step, then redraw in place. |
clear() | Empty the buffer, then redraw the (now empty) chart in place. |
render() -> Chart | Render the current buffer to a standalone Chart, without touching the live display. |
n (getter) | Current sample count. |
html (getter) | Current chart HTML as a string. |
Every mutating call enforces the max_points cap by dropping the oldest
samples first — so the buffer is always bounded.
Example: live IoT sensor dashboard in Jupyter
import seraplot as sp
import time, random
stream = sp.LiveStream(kind="line", title="Temperature sensor (°C)", max_points=60)
value = 21.0
for tick in range(300):
value += random.uniform(-0.4, 0.4)
stream.push(tick, value)
time.sleep(0.05)
Run this in a single notebook cell: the chart appears once, then updates
smoothly in place on every push() — no flicker, no new output cells.
See v2/examples/iot_live_dashboard.py for a fuller multi-sensor simulation.
LiveStream est un accumulateur à buffer circulaire borné qui transforme une
série d’échantillons (x, y) arrivant dans le temps en un graphique redessiné
en continu. La mémoire reste constante quelle que soit la durée du flux,
puisque les échantillons les plus anciens sont supprimés au-delà de
max_points.
Ce qui le rend fluide dans un notebook : push() / extend() / clear() ne
font pas que re-rendre — ils repeignent la même cellule de sortie Jupyter
sur place via le mécanisme display_id d’IPython (display() une fois, puis
update_display() à chaque appel suivant). Aucune nouvelle cellule n’est
ajoutée, pas de saut de défilement, pas de clear-puis-flash ; le graphique se
met à jour là où il est déjà.
Constructeur
sp.LiveStream(
kind: str = "line", # "line" ou "scatter"
title: str = "",
max_points: int = 500, # taille du buffer circulaire
color_hex: int = 0x6366F1,
width: int = 900,
height: int = 420,
)
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
kind | str | "line" | Type de graphique. "line" ou "scatter". |
title | str | "" | Titre rendu à chaque mise à jour. |
max_points | int | 500 | Nombre maximum d’échantillons gardés. Les plus anciens sont supprimés en tête. |
color_hex | int | 0x6366F1 | Couleur de la série en entier RGB 24 bits. |
width | int | 900 | Largeur du canvas en pixels. |
height | int | 420 | Hauteur du canvas en pixels. |
Méthodes
| Méthode | Effet |
|---|---|
push(x, y) | Ajoute un échantillon unique, puis redessine sur place. |
extend(xs, ys) | Ajoute deux listes en parallèle, puis redessine sur place. |
clear() | Vide le buffer, puis redessine le graphique (désormais vide) sur place. |
render() -> Chart | Rend le buffer courant sous forme de Chart autonome, sans toucher à l’affichage live. |
n (getter) | Nombre d’échantillons courant. |
html (getter) | HTML courant du graphique sous forme de chaîne. |
Chaque appel mutant applique la limite max_points en supprimant d’abord les
échantillons les plus anciens — le buffer est donc toujours borné.
Exemple : tableau de bord IoT live dans Jupyter
import seraplot as sp
import time, random
stream = sp.LiveStream(kind="line", title="Capteur de température (°C)", max_points=60)
value = 21.0
for tick in range(300):
value += random.uniform(-0.4, 0.4)
stream.push(tick, value)
time.sleep(0.05)
Exécute ceci dans une seule cellule de notebook : le graphique apparaît une
fois, puis se met à jour de manière fluide sur place à chaque push() — sans
scintillement, sans nouvelle cellule de sortie.
Voir v2/examples/iot_live_dashboard.py pour une simulation multi-capteurs
plus complète.
Chart Diff
Compare two charts structurally — useful for visual CI / regression testing. Implemented natively in Rust (plot::utils::chart_diff).
chart_diff is currently wired for the WASM/JS build only — it is not yet exposed as a Python method (Chart.diff() does not exist). Documented here as-is rather than papered over with a Python example that would not run.
JavaScript / WASM
import { chartDiff } from "seraplot";
const result = JSON.parse(chartDiff(JSON.stringify({ a: htmlA, b: htmlB })));
console.log(result);
Returns:
{
"ok": true,
"identical": false,
"size_a": 4521,
"size_b": 4523,
"common_prefix": 4400,
"similarity": 0.97
}
a/b are compared on the <svg>...</svg> slice extracted from each chart’s .html. common_prefix is the number of leading bytes the two SVGs share before the first difference; similarity is that shared length divided by the longer of the two.
Compare deux charts structurellement — utile pour les CI visuelles / tests de régression. Implémenté nativement en Rust (plot::utils::chart_diff).
chart_diff n’est aujourd’hui câblé que pour le build WASM/JS — pas encore exposé comme méthode Python (Chart.diff() n’existe pas). Documenté tel quel plutôt que masqué derrière un exemple Python qui ne s’exécuterait pas.
JavaScript / WASM
import { chartDiff } from "seraplot";
const result = JSON.parse(chartDiff(JSON.stringify({ a: htmlA, b: htmlB })));
console.log(result);
Retourne :
{
"ok": true,
"identical": false,
"size_a": 4521,
"size_b": 4523,
"common_prefix": 4400,
"similarity": 0.97
}
a/b sont comparés sur la tranche <svg>...</svg> extraite du .html de chaque chart. common_prefix est le nombre d’octets identiques au début des deux SVG avant la première différence ; similarity est cette longueur commune divisée par la plus longue des deux.
Drift Detection
Detect distribution shift between a reference dataset and a current one using the Kolmogorov-Smirnov two-sample test — implemented natively in Rust (plot::utils::drift_ks).
drift_ks is currently wired for the WASM/JS build only — it is not yet exposed as a Python function (sp.drift does not exist). Documented here as-is rather than papered over with a Python example that would not run.
JavaScript / WASM
import { driftKs } from "seraplot";
const r = JSON.parse(driftKs(JSON.stringify({ reference, current })));
console.log(r);
Returns:
{
"ok": true,
"ks_statistic": 0.18,
"p_value": 0.003,
"drift_detected": true,
"n_reference": 1000,
"n_current": 1000
}
drift_detected is true when p_value < 0.05.
Détecte un drift de distribution entre un dataset de référence et un dataset actuel via le test à deux échantillons de Kolmogorov-Smirnov — implémenté nativement en Rust (plot::utils::drift_ks).
drift_ks n’est aujourd’hui câblé que pour le build WASM/JS — pas encore exposé comme fonction Python (sp.drift n’existe pas). Documenté tel quel plutôt que masqué derrière un exemple Python qui ne s’exécuterait pas.
JavaScript / WASM
import { driftKs } from "seraplot";
const r = JSON.parse(driftKs(JSON.stringify({ reference, current })));
console.log(r);
Retourne :
{
"ok": true,
"ks_statistic": 0.18,
"p_value": 0.003,
"drift_detected": true,
"n_reference": 1000,
"n_current": 1000
}
drift_detected est true quand p_value < 0.05.
Export (SVG / PNG / HTML)
Export is exposed two ways: as Chart methods in Python (verified below against
a real build), and as standalone for_each_fn!-registered functions callable
from JavaScript / the C-FFI. The two are not a 1:1 mirror of each other today —
chart_info is the one function that is genuinely callable from both sides.
chart.save(path) / chart.export_html(path)
Write the complete HTML of the chart to disk. The file is fully self-contained: it embeds the SVG, its scripts and styles. It can be opened in any browser, attached to an email, or served statically — no server, no CDN.
import seraplot as sp
chart = sp.bar(title="Revenue", labels=["Q1", "Q2"], values=[120, 180])
chart.save("revenue.html")
chart.export_svg(path)
Extracts the <svg>...</svg> block from the chart and writes it to path.
Useful for embedding into a LaTeX / Word / Illustrator workflow, or
post-processing the geometry (CSS overrides, masks, custom filters).
chart.export_svg("plot.svg")
chart.export_png(path)
Rasterizes the chart to PNG via cairosvg. Requires pip install cairosvg —
raises a clear error naming the missing dependency if it is not installed,
rather than failing silently.
chart.export_png("plot.png")
sp.chart_info(chart) -> str
Returns a JSON string with structural metadata about the rendered chart. Handy for logging, telemetry, or asserting chart complexity in unit tests.
import json
info = json.loads(sp.chart_info(chart))
print(info)
{"size": 25488, "paths": 0, "rects": 3, "circles": 0, "has_svg": true}
JavaScript
Only buildBarChart (and the equivalent build* function per chart family)
and exportHtmlFile are registered on the JS/WASM side today — there is no
JS equivalent of export_svg/export_png/chart_info yet.
import * as sp from "seraplot";
const chart = sp.buildBarChart(JSON.stringify({
title: "Revenue",
labels: ["Q1", "Q2"],
values: [120, 180],
}));
exportHtmlFile exists but currently always fails at runtime — the crate
targets wasm32-unknown-unknown, which has no filesystem access to back
std::fs::write, in Node or the browser alike:
sp.exportHtmlFile(JSON.stringify({ html: chart, path: "out.html" }));
// => '{"ok":false,"error":"operation not supported on this platform"}'
Write the returned HTML string to disk on the JS side instead
(fs.writeFileSync("out.html", chart) in Node, or a Blob download in the
browser).
L’export est exposé de deux façons : comme méthodes sur Chart en Python
(vérifié ci-dessous contre un vrai build), et comme fonctions autonomes
enregistrées via for_each_fn!, appelables depuis JavaScript / le C-FFI. Les
deux ne sont pas un miroir 1:1 aujourd’hui — chart_info est la seule
fonction réellement appelable des deux côtés.
chart.save(path) / chart.export_html(path)
Écrit l’intégralité du HTML du chart sur disque. Le fichier est auto-suffisant : il embarque le SVG, ses scripts et ses styles. Il s’ouvre dans n’importe quel navigateur, peut être joint à un mail ou servi en statique — aucun serveur, aucun CDN.
import seraplot as sp
chart = sp.bar(title="Chiffre d'affaires", labels=["T1", "T2"], values=[120, 180])
chart.save("revenue.html")
chart.export_svg(path)
Extrait le bloc <svg>...</svg> du chart et l’écrit sous path. Utile pour
l’intégrer dans un flux LaTeX / Word / Illustrator, ou post-traiter la
géométrie (overrides CSS, masques, filtres custom).
chart.export_svg("plot.svg")
chart.export_png(path)
Rasterise le chart en PNG via cairosvg. Nécessite pip install cairosvg —
lève une erreur claire nommant la dépendance manquante plutôt que d’échouer
silencieusement.
chart.export_png("plot.png")
sp.chart_info(chart) -> str
Retourne une chaîne JSON contenant des métadonnées structurelles sur le chart rendu. Pratique pour le logging, la télémétrie ou pour tester la complexité d’un chart dans des tests unitaires.
import json
info = json.loads(sp.chart_info(chart))
print(info)
{"size": 25488, "paths": 0, "rects": 3, "circles": 0, "has_svg": true}
JavaScript
Seuls buildBarChart (et l’équivalent build* par famille de chart) et
exportHtmlFile sont enregistrés côté JS/WASM aujourd’hui — pas encore
d’équivalent JS pour export_svg/export_png/chart_info.
import * as sp from "seraplot";
const chart = sp.buildBarChart(JSON.stringify({
title: "Chiffre d'affaires",
labels: ["T1", "T2"],
values: [120, 180],
}));
exportHtmlFile existe mais échoue toujours à l’exécution aujourd’hui — le
crate cible wasm32-unknown-unknown, qui n’a aucun accès au système de
fichiers pour appuyer std::fs::write, ni dans Node ni dans le navigateur :
sp.exportHtmlFile(JSON.stringify({ html: chart, path: "out.html" }));
// => '{"ok":false,"error":"operation not supported on this platform"}'
Écrivez plutôt la chaîne HTML retournée sur disque côté JS
(fs.writeFileSync("out.html", chart) en Node, ou un téléchargement Blob
dans le navigateur).
ML Model Persistence
sp.ml_save_model() / sp.ml_load_model() are real, verified functions backed
by an in-process, name-versioned model registry — not a file-path envelope.
Every save under the same name gets the next version automatically; load
without a version returns the latest.
sp.ml_save_model(name, kind, payload, params, metrics, tags) -> dict
| Argument | Type | Description |
|---|---|---|
name | str | Registry key. Repeated saves under the same name auto-increment version. |
kind | str | Free-form model identifier (e.g. "knn_classifier", "kmeans"). |
payload | str | Arbitrary JSON-serialized state — typically the dict a training call returned. |
params | dict[str, str] | Hyperparameters, stored alongside for reference. |
metrics | dict[str, float] | Training/eval metrics, stored alongside. |
tags | list[str] | Free-form labels for later filtering. |
Returns {"name": ..., "version": ...}.
import json
import seraplot as sp
result = sp.knn_classifier(data=X_train, target=y_train, k=5)
saved = sp.ml_save_model(
name="knn_v1", kind="knn_classifier",
payload=json.dumps(result), params={"k": "5"}, metrics={}, tags=[],
)
print(saved) # {'name': 'knn_v1', 'version': 1}
sp.ml_load_model(name, version=None) -> dict
Loads the latest save for name, or a specific version if given.
loaded = sp.ml_load_model(name="knn_v1")
# {'found': True, 'name': 'knn_v1', 'version': 1, 'kind': 'knn_classifier', 'payload': '...'}
restored = json.loads(loaded["payload"])
Missing name/version returns {"found": False} rather than raising.
End-to-end: train → save → reload
import json
import seraplot as sp
result = sp.knn_classifier(data=X_train, target=y_train, k=5)
sp.ml_save_model(
name="knn_v1", kind="knn_classifier",
payload=json.dumps(result), params={"k": "5"}, metrics={}, tags=[],
)
loaded = sp.ml_load_model(name="knn_v1")
restored = json.loads(loaded["payload"])
assert restored == result
Saving again under the same name bumps the version rather than overwriting:
sp.ml_save_model(name="knn_v1", kind="knn_classifier", payload=json.dumps(result), params={}, metrics={}, tags=[])
# {'name': 'knn_v1', 'version': 2}
sp.ml_load_model(name="knn_v1", version=1) # the original save is still there
sp.ml_save_model() / sp.ml_load_model() sont des fonctions réelles et
vérifiées, adossées à un registre de modèles en mémoire, indexé par nom et
versionné — pas une enveloppe fichier. Chaque sauvegarde sous le même name
obtient automatiquement la version suivante ; charger sans version
retourne la dernière.
sp.ml_save_model(name, kind, payload, params, metrics, tags) -> dict
| Argument | Type | Description |
|---|---|---|
name | str | Clé du registre. Des sauvegardes répétées sous le même nom incrémentent version automatiquement. |
kind | str | Identifiant libre du modèle (ex. "knn_classifier", "kmeans"). |
payload | str | État sérialisé en JSON arbitraire — typiquement le dict retourné par un appel d’entraînement. |
params | dict[str, str] | Hyperparamètres, stockés pour référence. |
metrics | dict[str, float] | Métriques d’entraînement/évaluation, stockées pour référence. |
tags | list[str] | Labels libres pour un filtrage ultérieur. |
Retourne {"name": ..., "version": ...}.
import json
import seraplot as sp
result = sp.knn_classifier(data=X_train, target=y_train, k=5)
saved = sp.ml_save_model(
name="knn_v1", kind="knn_classifier",
payload=json.dumps(result), params={"k": "5"}, metrics={}, tags=[],
)
print(saved) # {'name': 'knn_v1', 'version': 1}
sp.ml_load_model(name, version=None) -> dict
Charge la dernière sauvegarde pour name, ou une version précise si fournie.
loaded = sp.ml_load_model(name="knn_v1")
# {'found': True, 'name': 'knn_v1', 'version': 1, 'kind': 'knn_classifier', 'payload': '...'}
restored = json.loads(loaded["payload"])
Un nom/version absent retourne {"found": False} plutôt que de lever une exception.
Bout-en-bout : entraînement → sauvegarde → recharge
import json
import seraplot as sp
result = sp.knn_classifier(data=X_train, target=y_train, k=5)
sp.ml_save_model(
name="knn_v1", kind="knn_classifier",
payload=json.dumps(result), params={"k": "5"}, metrics={}, tags=[],
)
loaded = sp.ml_load_model(name="knn_v1")
restored = json.loads(loaded["payload"])
assert restored == result
Sauvegarder à nouveau sous le même name incrémente la version plutôt que d’écraser :
sp.ml_save_model(name="knn_v1", kind="knn_classifier", payload=json.dumps(result), params={}, metrics={}, tags=[])
# {'name': 'knn_v1', 'version': 2}
sp.ml_load_model(name="knn_v1", version=1) # la sauvegarde originale existe toujours
Pickle / Serialization
Chart objects are picklable via __getstate__ / __setstate__ — works with joblib, multiprocessing, Ray, Streamlit reruns.
Python
import seraplot as sp
import pickle
chart = sp.bar("Revenue", labels=["a", "b", "c"], values=[1, 2, 3])
blob = pickle.dumps(chart)
restored = pickle.loads(blob)
restored.save("restored.html")
Internally, only the HTML string is serialized — minimal payload, no transient state.
ML models
Trained models are a separate concern from Chart pickling — see ML Model Persistence for sp.ml_save_model() / sp.ml_load_model().
Les objets Chart sont sérialisables via __getstate__ / __setstate__ — compatible joblib, multiprocessing, Ray, reruns Streamlit.
Python
import seraplot as sp
import pickle
chart = sp.bar("Revenu", labels=["a", "b", "c"], values=[1, 2, 3])
blob = pickle.dumps(chart)
restored = pickle.loads(blob)
restored.save("restored.html")
En interne, seule la chaîne HTML est sérialisée — payload minimal, aucun état transitoire.
Modèles ML
Les modèles entraînés sont une préoccupation distincte du pickling de Chart — voir ML Model Persistence pour sp.ml_save_model() / sp.ml_load_model().
Accessibility (a11y)
Inject ARIA roles, <title> and <desc> into the chart SVG for screen readers and B2B compliance (WCAG 2.1).
Python
import seraplot as sp
chart = (
sp.bar(labels=["EU","US","APAC"], values=[10,20,30])
.a11y(title="Quarterly revenue by region",
desc="Bar chart, EU 10M, US 20M, APAC 30M")
)
The resulting SVG includes role="group" (set by default so keyboard-navigable data points and legend buttons remain valid descendants — role="img" forbids focusable children per the SVG-AAM spec), plus aria-label, <title>, <desc> from .a11y() — recognized by NVDA, JAWS, VoiceOver.
Injecte les rôles ARIA, <title> et <desc> dans le SVG pour les lecteurs d’écran et la conformité B2B (WCAG 2.1).
Python
import seraplot as sp
chart = (
sp.bar(labels=["EU","US","APAC"], values=[10,20,30])
.a11y(title="Revenu trimestriel par région",
desc="Bar chart, EU 10M, US 20M, APAC 30M")
)
Le SVG résultant inclut role="group" (posé par défaut pour que les points de données et boutons de légende navigables au clavier restent des descendants valides — role="img" interdit tout enfant focusable selon la spec SVG-AAM), plus aria-label, <title>, <desc> ajoutés par .a11y() — reconnus par NVDA, JAWS, VoiceOver.
CSP-safe Mode
Strict Content Security Policies block inline <script>. The csp_safe() method extracts JS into a <script type="application/json"> payload + a single nonce-able loader.
Python
import seraplot as sp
chart = sp.line(x_labels=["1", "2", "3", "4"], values=[10, 20, 15, 25]).csp_safe()
Apply your CSP script-src 'nonce-sp-nonce' and the chart still renders.
Les CSP strictes bloquent les <script> inline. La méthode csp_safe() extrait le JS dans un payload <script type="application/json"> + un loader unique compatible nonce.
Python
import seraplot as sp
chart = sp.line(x_labels=["1", "2", "3", "4"], values=[10, 20, 15, 25]).csp_safe()
Applique ta CSP script-src 'nonce-sp-nonce' et le chart se rend toujours.
2D Charts
SeraPlot provides 41 two-dimensional chart types, from basic bar and line charts to specialized plots like ridgeline, dumbbell, sankey, chord, dendrogram, venn, correlogram, hive, pulse, and orbita.
| Chart | Function |
|---|---|
| Bar Chart | bar() |
| Horizontal Bar | hbar() |
| Line Chart | line() |
| Scatter Chart | scatter() |
| Histogram | histogram() |
| Grouped Bar | grouped_bar() |
| Stacked Bar | stacked_bar() |
| Heatmap | heatmap() |
| Pie Chart | pie() |
| Donut Chart | donut() |
| Box Plot | boxplot() |
| Violin Chart | violin() |
| Slope Chart | slope() |
| Sunburst | sunburst() |
| Funnel | funnel() |
| Treemap | treemap() |
| Multi-line Chart | multiline() |
| Area Chart | area() |
| Waterfall | waterfall() |
| Bullet Chart | bullet() |
| Word Cloud | wordcloud() |
| Candlestick | candlestick() |
| Dumbbell | dumbbell() |
| Bubble | bubble() |
| Gauge | gauge() |
| Parallel Coordinates | parallel() |
| Lollipop | lollipop() |
| KDE | kde() |
| Ridgeline | ridgeline() |
| Radar | radar() |
| Sankey | sankey() |
| Chord Diagram | chord() |
| Circle Packing | circle_pack() |
| Arc Diagram | arc_diagram() |
| Dendrogram | dendrogram() |
| Venn Diagram | venn() |
| Correlogram | correlogram() |
| Hive Plot | hive() |
| Pulse Chart | pulse() |
| Orbita Chart | orbita() |
SeraPlot propose 41 types de graphiques 2D, des barres et courbes basiques aux graphiques spécialisés (ridgeline, haltère, sankey, corde, dendrogramme, venn, corrélogramme, ruche, pulse et orbita).
| Graphique | Fonction |
|---|---|
| Graphique en barres | bar() |
| Barres horizontales | hbar() |
| Courbe | line() |
| Nuage de points | scatter() |
| Histogramme | histogram() |
| Barres groupées | grouped_bar() |
| Barres empilées | stacked_bar() |
| Heatmap | heatmap() |
| Camembert | pie() |
| Anneau | donut() |
| Boîte à moustaches | boxplot() |
| Violon | violin() |
| Pente | slope() |
| Sunburst | sunburst() |
| Entonnoir | funnel() |
| Treemap | treemap() |
| Multi-courbes | multiline() |
| Aires | area() |
| Cascade | waterfall() |
| Bullet | bullet() |
| Nuage de mots | wordcloud() |
| Bougie | candlestick() |
| Haltère | dumbbell() |
| Bulles | bubble() |
| Jauge | gauge() |
| Coordonnées parallèles | parallel() |
| Sucette | lollipop() |
| KDE | kde() |
| Ridgeline | ridgeline() |
| Radar | radar() |
| Sankey | sankey() |
| Diagramme de corde | chord() |
| Packing de cercles | circle_pack() |
| Diagramme d’arc | arc_diagram() |
| Dendrogramme | dendrogram() |
| Diagramme de Venn | venn() |
| Corrélogramme | correlogram() |
| Graphe en ruche | hive() |
| Pulse radial | pulse() |
| Orbita | orbita() |
Bar Charts
Signature
sp.bar(title, labels=None, values=None, *, variant="basic", series=None, series_names=None, theme="none", **kwargs) -> Chart
Aliases: sp.bar_chart(), sp.bars(), sp.bar_unified(), sp.bars_unified(), sp.bar_family().
Description
sp.bar() is the unified entry point for the SeraPlot bar-chart family. It renders standalone Rust-generated HTML/SVG charts. The variant keyword selects the renderer, and shared chart options are applied by the common chart pipeline.
The default renderer is a vertical categorical bar chart. The same API also covers every bar variant registered in Rust.
Variants
Data
labels are category labels for bar variants. Single-series variants use values. Multi-series variants use series, where each inner list is one series, and series_names supplies legend names.
When series is missing but series_names is provided, values is interpreted as a flattened matrix split by len(labels): the first category-length block is the first series, the next block is the second series, and so on.
Parameters
Themes
Returns
Chart object with an .html property and a .show() method.
Parameters
Horizontal bars — better for long category names. Alias: "h".
Multiple series side-by-side per category. Alias: "group".
Series stacked vertically — shows part-to-whole within each category. Alias: "stack".
100% stacked bars — every column fills from 0 to 100%, showing each series as a share of the total. Alias: "rel".
Groups of stacked sub-bars per category. offset_groups assigns a stack-group name to each series.
Variable-width stacked bars (mosaic plot). widths encodes one dimension, stacked segments encode share. Aliases: "mekko", "mosaic".
A bar made of repeated icons. Each icon represents units_per_icon units. Alias: "icon".
Two-level hierarchical x axis. super_categories groups adjacent bars under a bracket label. Alias: "multi".
Bars arranged radially around a center, length proportional to value. Pass show_values=True for a value at each bar’s tip, gridlines=True for labeled concentric rings. Aliases: "circular_basic", "radial_bar", "polar_bar".
Circular bars split into groups via color_groups, with an extra gap between groups. Same show_values/gridlines options as circular. Aliases: "radial_grouped", "circular_groups".
Two horizontal bar sets mirrored left/right around a shared category axis, from the first two entries of series. Aliases: "pyramid", "age_pyramid".
Horizontal bars extending left or right from a zero line, colored by sign — color_high (default red, 0xF43F5E) for positive, color_low (default blue, 0x636EFA) for negative — value printed inside the bar (white) when it’s wide enough or just outside otherwise. Aliases: "signed", "delta", "bidirectional". Pass series (with category_labels) instead of values for a stacked multi-series diverging chart — each series stacks on its own side of zero by its own sign, one color per series from palette, with a real legend naming each series (not a generic “positive/negative” label). Optional error_low/error_high (one entry per bar) draw a whisker; optional overlay_line (+ overlay_line_label) draws a connected line across the bars, matching each row by position. The value label is controlled by show_values (bool) like every other bar variant — it defaults to True here so existing charts keep their look, unlike other variants where it defaults to False.
Bar + boxplot fusion: a semi-transparent bar up to each category’s mean, with a real box (Q1/median/Q3, whiskers) overlaid on top showing the distribution behind that mean — pass series as one raw sample array per category (same shape as boxplot’s grouped input) instead of single aggregate values. Aliases: "bar_box", "boxbar", "bar_boxplot".
Line Charts
Signature
sp.line(title, labels=None, values=None, *, variant="basic", series=None, **kwargs) -> Chart
Description
sp.line() is the unified entry point for the entire line-chart family. The variant keyword selects the rendering strategy — every other argument is shared across variants.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Single series connecting ordered data points.
Several series sharing the same x-axis. Pass series=[(name, values), ...].
Step (staircase) line — ideal for piecewise-constant data. Use step_shape to control corner direction.
Catmull-Rom smoothed curve. spline_tension (0–1) controls how tight the curve hugs the points.
Area chart — fills the region under the line. fill_opacity controls transparency; stack_fill=True stacks multiple series.
Small inline chart — no axes, perfect for dashboards. spark_cols arranges multiple series in a grid.
Custom stroke pattern. dash_pattern="8,4" means 8px on, 4px off. Use "2,3" for dotted.
Line plot with prominent markers. marker_size (px) controls dot size; show_points=True is implicit.
Line breaks where values exceed gap_threshold. Useful for time series with missing samples.
Line + confidence/forecast band fusion: pass series as low/high pairs ([group1_low, group1_high, group2_low, group2_high, ...]) instead of single traces. Each group gets a shaded area between its two bounds, thin dashed edge lines, and a solid midline with markers — a forecast interval, min/max envelope or confidence band, natively.
Each segment is colored on a diverging scale by its own local slope, with a matching soft gradient fill beneath, automatic peak/trough callouts, and a glowing pulse on the latest point — momentum, read at a glance instead of computed by eye.
Auto-segments the series into narrative chapters at its own significant turning points, shades each by net direction, and captions it with a plain-language badge — the chart writes its own headline instead of asking the reader to compute one. epoch_pos_color / epoch_neg_color / epoch_flat_color (hex ints) recolor the rising/falling/flat chapters.
Compares the series against its own ideal glidepath to a pace_target, shading ahead-of-pace and behind-of-pace stretches in different colors, then extrapolates the recent trend past the last real point to project whether it clears the target. pace_ahead_color / pace_behind_color recolor the comparison.
Area Chart
Signature
sp.area(title, x_labels=None, series=None, *, variant="basic", series_names=None, palette=None, **kwargs) -> Chart
Aliases: sp.area, sp.area_chart, sp.area_family, sp.area_unified, sp.build_area_chart
Description
sp.area() is the unified entry point for the entire area-chart family. The variant keyword selects the rendering strategy — every other argument keeps the same name across variants. Area charts fill the space between a line and the baseline, making the emphasis on cumulative magnitude rather than the line chart’s point-to-point comparison. SeraPlot renders everything in pure Rust SVG with native multi-series overlay, stacking, 100%-normalized stacking, smooth splines, step interpolation and gradient fills.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Each series drawn as an independent semi-transparent filled area from its own value down to the baseline - overlapping regions blend visually, useful to compare overlapping magnitudes directly.
Series drawn on top of one another so the top boundary tracks the running total - reads both individual contribution and combined magnitude at once.
100%-stacked area - every x position sums to 100%, showing the changing composition (share of total) instead of absolute magnitude.
Catmull-Rom smoothed boundary through every point instead of straight segments - a softer, more organic silhouette for trend-focused series.
Step-interpolated boundary - the fill jumps at each data point instead of interpolating, correct for values that hold constant between samples (inventory levels, active counts...).
Smooth spline boundary filled with a vertical gradient fading from the series color to transparent at the baseline - a modern dashboard look.
100%-stacked area with bold black borders around every band instead of colored ones - the ggplot2 geom_area look, ideal for many groups (works well beyond two) where the outline is what separates adjacent bands visually.
True signed stacking - unlike stacked, negative values are never clamped to zero, so oscillating series (sin/cos-like data crossing above and below the baseline) stack correctly on both sides of zero. Matches matplotlib's stackplot() behavior for signed data.
Overlays every series translucently, then traces a bold highlighted line through whichever series is highest at each point - a "crown trace" that jumps from series to series as the lead changes, with a marker and name badge at every handoff. Answers "who's winning, and when did it change" at a glance instead of asking the reader to compare curves.
Signature
sp.area(title, x_labels=None, series=None, *, variant="basic", series_names=None, palette=None, **kwargs) -> Chart
Alias : sp.area, sp.area_chart, sp.area_family, sp.area_unified, sp.build_area_chart
Description
sp.area() est le point d’entrée unifié de toute la famille des graphiques en aires. Le mot-clé variant sélectionne la stratégie de rendu — tous les autres arguments gardent le même nom d’une variante à l’autre. Les graphiques en aires remplissent l’espace entre une courbe et la ligne de base, mettant l’accent sur la magnitude cumulée plutôt que sur la comparaison point à point du graphique en ligne. SeraPlot rend tout en SVG Rust pur, avec superposition multi-séries native, empilement, empilement normalisé à 100%, courbes lissées, interpolation en escalier et remplissages en dégradé.
Variantes
Paramètres
Retour
Chart — objet exposant .html et .show().
Chaque série dessinée comme une aire remplie semi-transparente indépendante depuis sa propre valeur jusqu'à la ligne de base - les zones qui se chevauchent se mélangent visuellement, utile pour comparer directement des magnitudes qui se recouvrent.
Séries dessinées les unes sur les autres pour que la frontière supérieure suive le total cumulé - lit à la fois la contribution individuelle et la magnitude combinée.
Aire empilée à 100% - chaque position x totalise 100%, montrant la composition changeante (part du total) plutôt que la magnitude absolue.
Frontière lissée par Catmull-Rom passant par chaque point au lieu de segments droits - une silhouette plus douce et organique pour les séries orientées tendance.
Frontière interpolée en escalier - le remplissage saute à chaque point de donnée au lieu d'interpoler, correct pour des valeurs constantes entre échantillons (niveaux de stock, compteurs actifs...).
Frontière lissée remplie d'un dégradé vertical s'estompant de la couleur de la série vers la transparence à la ligne de base - un look de tableau de bord moderne.
Aire empilée à 100% avec des bordures noires marquées autour de chaque bande au lieu de bordures colorées - le look geom_area de ggplot2, idéal pour de nombreux groupes (fonctionne bien au-delà de deux) où le contour est ce qui sépare visuellement les bandes adjacentes.
Empilement signé véritable - contrairement à stacked, les valeurs négatives ne sont jamais ramenées à zéro, si bien que des séries oscillantes (type sinus/cosinus traversant la ligne de base) s'empilent correctement des deux côtés de zéro. Reproduit le comportement de stackplot() de matplotlib pour des données signées.
Superpose chaque série en transparence, puis trace une ligne en surbrillance à travers celle qui est la plus haute à chaque point - une « ligne de couronne » qui saute de série en série à chaque changement de tête, avec un repère et un badge du nom à chaque passation. Répond à « qui est en tête, et quand ça a changé » d'un coup d'œil au lieu de faire comparer les courbes au lecteur.
Scatter Charts
Signature
sp.scatter(title, x_values, y_values, *, variant="basic", categories=None, labels=None, color_values=None, **kwargs) -> Chart
Description
sp.scatter() is the unified entry point for the entire scatter family. The variant keyword selects the rendering strategy — every other argument keeps the same name across variants. Scatter plots are the canonical way to display the joint distribution of two numeric variables; SeraPlot adds optional grouping, continuous color, distinct marker shapes, on-point labels and OLS regression — all in pure Rust SVG, thousands of times faster than Plotly.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Two independent categorical variables: categories drives color, categories2 drives marker shape - matching seaborn's "hue and style with different variables" example.
Points colored by a continuous numeric variable (color_values) interpolated between color_low and color_high, with a gradient legend bar - seaborn's numeric hue mapping.
Splits the data into one small-multiple panel per unique categories value, all sharing the same x/y domain - a native equivalent of seaborn's relplot() faceting.
Both marker radius and color are driven by the same continuous variable (color_values, scaled between min_size and max_size) with a combined size+color legend - seaborn's hue= and size= mapped to the same column, with sizes=(min, max).
Plots several numeric columns (series, named via series_names) against one shared x_values axis, one color per column with a legend - the native equivalent of calling seaborn.scatterplot(data=wide_dataframe) directly on a wide-form table.
Scatter + rug plot fusion: each point gets a short tick mark projected onto both axes, right at the plot edges — a Tufte-style view of the marginal distributions along x and y without the overhead of a separate marginal panel (unlike joint(), no iframe composition needed).
Signature
sp.scatter(title, x_values, y_values, *, variant="basic", categories=None, labels=None, color_values=None, **kwargs) -> Chart
Description
sp.scatter() est le point d’entrée unifié de toute la famille scatter. Le mot-clé variant sélectionne la stratégie de rendu — tous les autres arguments gardent le même nom d’une variante à l’autre. Les nuages de points sont la façon canonique d’afficher la distribution conjointe de deux variables numériques ; SeraPlot ajoute groupement optionnel, couleur continue, formes de marqueurs distinctes, étiquettes sur les points et régression OLS — le tout en SVG Rust pur, des milliers de fois plus rapide que Plotly.
Variantes
Paramètres
Retour
Chart — objet exposant .html et .show().
Deux variables catégorielles indépendantes : categories pilote la couleur, categories2 pilote la forme du marqueur - comme l'exemple seaborn "hue and style" avec des variables différentes.
Points colorés selon une variable numérique continue (color_values) interpolée entre color_low et color_high, avec une barre de légende en dégradé - le mapping de teinte numérique de seaborn.
Sépare les données en un panneau petit-multiple par valeur unique de categories, tous partageant le même domaine x/y - un équivalent natif du facettage relplot() de seaborn.
Le rayon du marqueur et sa couleur sont pilotés par la même variable continue (color_values, mise à l'échelle entre min_size et max_size) avec une légende combinée taille+couleur - l'équivalent de hue= et size= pointant sur la même colonne en seaborn, avec sizes=(min, max).
Trace plusieurs colonnes numériques (series, nommées via series_names) sur un même axe x_values partagé, une couleur par colonne avec une légende - l'équivalent natif d'appeler seaborn.scatterplot(data=wide_dataframe) directement sur un tableau au format large.
Fusion scatter + rug plot : chaque point projette une petite marque sur les deux axes, au bord du graphique — une vue façon Tufte des distributions marginales en x et en y sans le coût d'un panneau marginal séparé (contrairement à joint(), aucune composition iframe nécessaire).
Bubble Charts
Signature
sp.bubble(title, x_values, y_values, sizes, *, variant="basic", categories=None, labels=None, color_values=None, **kwargs) -> Chart
Description
sp.bubble() is the unified entry point for the entire bubble-chart family. The variant keyword selects the rendering strategy — all other arguments remain consistent across variants. A bubble chart extends a 2D scatter plot with a third numeric dimension represented as bubble area (not radius), following best practices for perceptual accuracy.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
A categorical bubble matrix: x_categories and y_categories place bubbles on a grid, each split into a left/right half-circle by a binary categories value, sized by sizes - a native version of matplotlib's MarkerStyle(fillstyle="left"/"right") split-marker bubble chart, plus a magenta size-scale legend column.
Histogram Charts
Signature
sp.histogram(title, values, *, variant="basic", bins=0, overlay_values=None, color_groups=None, series_names=None, **kwargs) -> Chart
Description
sp.histogram() is the unified entry point for the entire histogram family. The variant keyword selects the rendering strategy — every other argument keeps the same name across variants. Histograms are the canonical way to visualize the distribution of a single numeric variable; SeraPlot adds horizontal layout, density normalization, cumulative distribution, stacked groups, A/B overlay and step outline — all in pure Rust SVG, thousands of times faster than Plotly.
Variants
Parameters
Themes
Pass theme= to restyle any variant above — themes are a cross-cutting rendering
pass, not a separate variant, and apply the same way across every chart family.
Returns
Chart — object with .html property and .show() method.
Signature
sp.histogram(title, values, *, variant="basic", bins=0, overlay_values=None, color_groups=None, series_names=None, **kwargs) -> Chart
Description
sp.histogram() est le point d’entrée unifié de toute la famille histogramme. Le mot-clé variant sélectionne la stratégie de rendu — tous les autres arguments gardent le même nom d’une variante à l’autre. Les histogrammes sont la façon canonique de visualiser la distribution d’une variable numérique ; SeraPlot ajoute layout horizontal, normalisation densité, distribution cumulative, groupes empilés, superposition A/B et contour en escalier — le tout en SVG Rust pur, des milliers de fois plus rapide que Plotly.
Variantes
Paramètres
Thèmes
Passez theme= pour restyliser n’importe quelle variante ci-dessus — les thèmes sont
une passe de rendu transversale, pas une variante séparée, et s’appliquent de la même
façon sur toutes les familles de graphiques.
Retour
Chart — objet exposant .html et .show().
Heatmap
Signature
sp.heatmap(title, labels=None, values=None, *, variant="basic", col_labels=None, **kwargs) -> Chart
Description
sp.heatmap() is the unified entry point for the entire heatmap family. The variant keyword selects the rendering strategy — every other argument stays consistent across variants. Cell colors are computed in pure Rust, no NumPy required. The matrix is passed as a flat list of length len(labels) * len(col_labels) (row-major).
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Rows and columns are reordered by average-linkage hierarchical clustering (Euclidean distance on each row/column vector) so similar rows/columns sit next to each other, and the merge tree is drawn as a real dendrogram in the left and top margins — seaborn's clustermap / structured_heatmap look, fully native.
Columns are reordered by the same average-linkage hierarchical clustering as the cluster variant, then laid out as wedges around a ring instead of a straight axis, with the merge tree drawn as a real radial dendrogram in the center — rows become concentric colored bands. col_labels is optional: when omitted it falls back to labels, same as cluster, for a symmetric matrix.
A grouped punchcard: each row's label is read as "Group::Subgroup::Item" (e.g. genre/artist/album) and rendered as a real nested outline on the left, with a thin divider between groups and the group name set vertically alongside its own block — every row keeps a small checkbox marker. Every column gets its own fixed color from the palette instead of a shared value colorscale, so intensity is read from bubble size and opacity while color reads the category. Every cell still draws a small dot even near zero, so faint columns stay visible as a guide down the grid instead of disappearing.
Pie Charts
Signature
sp.pie(title, labels=None, values=None, *, variant="basic", series=None, **kwargs) -> Chart
Description
sp.pie() is the unified entry point for the entire pie-chart family. The variant keyword selects the rendering strategy — all other arguments remain consistent across variants.
Variants
Parameters
Floating labels
Pass labeled=True to any variant built on the shared angle-sweep engine (basic, donut, exploded, kpi, pattern, semi) to replace the in-wedge percentage text with an outside callout: a thin connector line from the wedge edge to a label showing the percentage and the category name, colored to match its slice. This is the same style used by the reference “total breakdown” donut screenshots — it is not a separate variant, it is a display option any of those variants can turn on.
import seraplot as sp
c = sp.pie(
labels=["Acquisition", "Conversion", "Retention", "Referral", "Other"],
values=[42, 26, 16, 11, 5],
variant="donut",
center_text="27.3K",
center_subtext="TOTAL",
labeled=True,
)
Returns
Chart — object with .html property and .show() method.
The "square pie" — a 10x10 grid of 100 cells filled proportionally to each category's share (largest-remainder allocation, so the cell count always sums to exactly 100), with a color-matched legend. Easier to read precise percentages from than wedge angles.
Box Plot
Signature
sp.boxplot(title, labels=None, values=None, *, variant="basic", series=None, **kwargs) -> Chart
Description
sp.boxplot() is the unified entry point for the entire box-plot family. The variant keyword selects the rendering strategy — every other argument stays consistent across variants. Quartiles, 1.5×IQR whiskers and outliers are computed in pure Rust without NumPy or pandas.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Violin Plot
Signature
sp.violin(title, labels=None, values=None, *, variant="box", **kwargs) -> Chart
Description
sp.violin() is the unified entry point for the entire violin-plot family. The variant keyword selects the rendering strategy — every other argument stays consistent across variants. The kernel-density estimation, quartiles and statistics are computed in pure Rust, no NumPy or pandas required.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Raincloud layout - a translucent half-violin KDE silhouette on the left of each category paired with individual jittered points strictly on the right, so the density shape and the raw sample are both visible without ever overlapping.
KDE — Kernel Density Estimate
Signature
sp.kde(title, values=None, *, x=None, y=None, variant="basic", categories=None, bandwidth=0.0, filled=True, fill_opacity=50, bins=30, n_points=80, palette=None, **kwargs) -> Chart
Description
sp.kde() is the unified entry point for the entire Kernel Density Estimate family. The variant keyword selects the rendering strategy — every other argument keeps the same name across variants. KDE produces a smooth, continuous density estimate from a sample of points using a Gaussian kernel with Scott’s rule for automatic bandwidth selection. SeraPlot renders the curves as pure Rust SVG, with native multi-series, normalization, CDF, rug, histogram overlay and gradient fills.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Filled curve, single or multi-series.
Stroke-only curves for clean overlays.
Stair-stepped density (rectangular look).
KDE curve with rug ticks at sample positions.
KDE curve overlaid on a normalized histogram.
Each series normalized so its area integrates to 1.
Cumulative density (CDF) curve in [0, 1].
Bivariate (2D) kernel density estimate — a product-kernel Gaussian evaluated on a grid and rendered as a shaded density surface with the raw points overlaid. Pass categories to overlay one density surface per group, each in its own color with a legend.
Bivariate KDE quantized into discrete iso-density bands with a visible ring border at each band boundary — matches seaborn's kdeplot(x=, y=, hue=, fill=True) stepped-level look, as opposed to contour's continuous gradient.
Each group's density curve stacked on top of the previous one's (cumulative running total), all evaluated on one shared x-grid - matches seaborn's kdeplot(hue=, multiple="stack").
100%-stacked density - at every x position the groups sum to 100%, showing the changing share of the total instead of absolute density - matches seaborn's kdeplot(hue=, multiple="fill").
Signature
sp.kde(title, values=None, *, x=None, y=None, variant="basic", categories=None, bandwidth=0.0, filled=True, fill_opacity=50, bins=30, n_points=80, palette=None, **kwargs) -> Chart
Description
sp.kde() est le point d’entrée unifié pour toute la famille KDE (Kernel Density Estimate). Le mot-clé variant sélectionne la stratégie de rendu — tous les autres arguments conservent le même nom d’une variante à l’autre. La KDE produit une estimation de densité continue lissée à partir d’un échantillon de points avec un noyau gaussien et la règle de Scott pour le choix automatique de la bande passante. SeraPlot rend les courbes en SVG Rust natif, avec multi-séries, normalisation, CDF, rug, histogramme superposé et remplissage en dégradé.
Variantes
Paramètres
Retour
Chart — objet avec propriété .html et méthode .show().
Courbe pleine, mono ou multi-séries.
Courbes en trait seul pour des superpositions épurées.
Densité en escalier (rendu rectangulaire).
Courbe KDE avec ticks rug aux positions des points.
Courbe KDE par-dessus un histogramme normalisé.
Chaque série normalisée pour que son aire vaille 1.
Densité cumulée (CDF) dans [0, 1].
Estimation de densité bivariée (2D) — un noyau gaussien produit évalué sur une grille et rendu comme une surface de densité ombrée, avec les points bruts superposés. Passez categories pour superposer une surface de densité par groupe, chacune dans sa propre couleur avec une légende.
KDE bivariée quantifiée en bandes iso-densité discrètes avec une bordure visible à chaque frontière de bande — reproduit le rendu par niveaux de kdeplot(x=, y=, hue=, fill=True) de seaborn, par opposition au dégradé continu de contour.
La courbe de densité de chaque groupe est empilée sur celle du précédent (total cumulé), toutes évaluées sur une même grille x partagée - reproduit kdeplot(hue=, multiple="stack") de seaborn.
Densité empilée à 100% - à chaque position x, les groupes totalisent 100%, montrant la part changeante du total plutôt que la densité absolue - reproduit kdeplot(hue=, multiple="fill") de seaborn.
Ridgeline — Joyplot / Stacked KDE
Signature
sp.ridgeline(title, categories, values, *, variant="basic", overlap=0.5, bandwidth=0.0, n_points=60, fill_opacity=56, palette=None, priority=None, **kwargs) -> Chart
Description
sp.ridgeline() is the unified entry point for the entire ridgeline family — also known as joyplot. The variant keyword selects the rendering strategy — every other argument keeps the same name across variants. A ridgeline plot stacks one KDE curve per category along a shared X axis with a controllable vertical overlap, making it ideal to compare distributions across many groups (years, regions, segments…). SeraPlot renders everything in pure Rust SVG, with quartile/mean overlays, rug ticks, gradient fills and a built-in viridis colormap. priority takes a list of row indices (in their displayed order) to draw last, on top of every other ridge - use it to force a specific distribution to visually climb over its neighbors regardless of its position in the stack.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Stacked filled ridges (one per category) with white underlay.
Stroke-only ridges, no fill — clean outline view.
Marks Q1, median (solid), and Q3 vertical lines on each ridge.
Dashed line + dot at the mean of each distribution.
Filled ridge with rug ticks below the baseline at sample positions.
Auto viridis palette across ridges (or custom palette).
Forced low overlap — ridges are separated for clarity.
Signature
sp.ridgeline(title, categories, values, *, variant="basic", overlap=0.5, bandwidth=0.0, n_points=60, fill_opacity=56, palette=None, priority=None, **kwargs) -> Chart
Description
sp.ridgeline() est le point d’entrée unifié pour toute la famille ridgeline — aussi appelé joyplot. Le mot-clé variant sélectionne la stratégie de rendu — tous les autres arguments conservent le même nom d’une variante à l’autre. Un ridgeline empile une courbe KDE par catégorie sur un axe X partagé avec un recouvrement vertical réglable, idéal pour comparer des distributions à travers plusieurs groupes (années, régions, segments…). SeraPlot rend tout en SVG Rust natif, avec marqueurs quartiles/moyenne, ticks rug, dégradés et palette viridis intégrée. priority prend une liste d’index de lignes (dans leur ordre affiché) à dessiner en dernier, par-dessus toutes les autres crêtes - utile pour forcer une distribution donnée à visuellement monter sur ses voisines quelle que soit sa position dans l’empilement.
Variantes
Paramètres
Retour
Chart — objet avec propriété .html et méthode .show().
Crêtes empilées remplies (une par catégorie) avec fond blanc.
Crêtes en trait seul, sans remplissage — vue épurée.
Trace les verticales Q1, médiane (pleine) et Q3 sur chaque crête.
Trait pointillé + point à la moyenne de chaque distribution.
Crête remplie avec ticks rug sous la ligne de base aux positions des points.
Palette viridis automatique sur les crêtes (ou palette personnalisée).
Recouvrement forcé bas — crêtes séparées pour la lisibilité.
Radar — Spider / Star Chart
Signature
sp.radar(title, axes, series, *, series_names=None, variant="basic", filled=True, fill_opacity=50, palette=None, **kwargs) -> Chart
Description
sp.radar() is the unified entry point for the entire radar / spider / star chart family. The variant keyword selects the rendering strategy — every other argument keeps the same name across variants. Radar charts are ideal for multivariate comparison across 3+ axes — performance profiles, KPIs, skill maps, scoring systems. SeraPlot draws everything in pure Rust SVG with concentric grid rings, axis lines, automatic ring tick labels, optional legend and per-series palette colors. The polar-bar variant turns the chart into a categorical polar histogram, the stacked variant builds a cumulative composition view.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Filled polygon per series with stroke and dot markers — the standard radar.
Stroke-only polygons, no fill — clean overlay for many series.
Strong fill, no stroke, sorted back-to-front by total area for clarity.
Light stroke + bold outlined markers — emphasis on data points.
Dashed outline polygons — useful for projections, targets, baselines.
Cumulative stacking on each axis — visualizes part-of-whole composition.
Radial bars per axis grouped by series — categorical polar histogram.
Radar + uncertainty band fusion: instead of one line per series, each group gets a shaded ring between a low and a high bound per axis, with a dashed midline and markers — pass series as low/high pairs ([group1_low, group1_high, group2_low, group2_high, ...]). Ideal for confidence intervals, min/max ranges or forecast spreads across multiple dimensions at once.
Each axis grows a symmetric leaf-shaped petal per series instead of a shared polygon vertex — length still encodes the value, but the rounded silhouette reads as a bloom. Historical vs. projected categories get solid vs. outlined badge markers automatically.
Signature
sp.radar(title, axes, series, *, series_names=None, variant="basic", filled=True, fill_opacity=50, palette=None, **kwargs) -> Chart
Description
sp.radar() est le point d’entrée unifié pour toute la famille radar / spider / star. Le mot-clé variant sélectionne la stratégie de rendu — tous les autres arguments conservent le même nom d’une variante à l’autre. Le radar est idéal pour comparer plusieurs séries sur 3 axes ou plus — profils de performance, KPI, cartographie de compétences, systèmes de notation. SeraPlot dessine tout en SVG Rust natif avec anneaux de grille concentriques, axes, labels automatiques de graduation, légende optionnelle et couleurs de palette par série. La variante polar_bar transforme le radar en histogramme polaire catégoriel, la variante stacked construit une vue de composition cumulative.
Variantes
Paramètres
Retour
Chart — objet avec propriété .html et méthode .show().
Polygone rempli par série avec contour et points — le radar standard.
Polygones en trait seul, sans remplissage — overlay net pour plusieurs séries.
Remplissage fort sans contour, trié de l'arrière vers l'avant par aire totale.
Trait léger + marqueurs détourés — accent sur les points de données.
Polygones à contour pointillé — utile pour projections, cibles, références.
Empilement cumulatif sur chaque axe — visualise une composition part/tout.
Barres radiales par axe groupées par série — histogramme polaire catégoriel.
Fusion radar + bande d'incertitude : au lieu d'une ligne par série, chaque groupe reçoit un anneau ombré entre une borne basse et haute par axe, avec une ligne médiane en pointillés et des marqueurs — passez series comme des paires bas/haut ([groupe1_bas, groupe1_haut, groupe2_bas, groupe2_haut, ...]). Idéal pour des intervalles de confiance, des plages min/max ou des écarts de prévision sur plusieurs dimensions à la fois.
Chaque axe fait pousser un pétale symétrique en forme de feuille par série au lieu d'un sommet de polygone partagé — la longueur encode toujours la valeur, mais la silhouette arrondie se lit comme une fleur. Les catégories historiques vs. projetées reçoivent automatiquement des badges pleins vs. contour.
Slope — Before / After Comparison Chart
Signature
sp.slope(title, labels, left, right, *, variant="basic", left_label="Before", right_label="After", palette=None, show_text=True, **kwargs) -> Chart
Description
sp.slope() renders the entire slope-chart family: two parallel value axes (left / right) with one connector per row. The variant keyword swaps the connector style without changing any other parameter. Slope charts excel at before/after comparisons, A/B test outcomes, ranking shifts, KPI changes between periods, and any pair-wise change across many entities.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Tips
- Use
sort_order="asc"/"desc"to reorder rows byleftvalue before drawing. - The
"diverging"and"thick"variants encode magnitude visually — perfect for executive summaries. - For rank shifts (positions in a league), prefer
"bumps"rather than"basic". - Combine
palette=with"monochrome"to match brand colours per category.
Direction-coloured straight lines (green up, red down) with endpoint dots.
Uniform palette colour per row, no direction tint — ideal for categorical narratives.
Top 3 movers (largest |Δ|) drawn in vivid colour, the rest dimmed in grey.
Bump chart: y axis encodes the rank (1..n) at each side instead of the raw value.
Cubic-Bezier S-curves between the two endpoints — smoother visual flow.
Stroke-width proportional to |right - left| — magnitude becomes the visual weight.
Centered delta bars: positive bars grow right (green), negative grow left (red).
L-shape connector: horizontal then vertical then horizontal — rectilinear flow.
Signature
sp.slope(title, labels, left, right, *, variant="basic", left_label="Before", right_label="After", palette=None, show_text=True, **kwargs) -> Chart
Description
sp.slope() produit toute la famille des slope charts : deux axes de valeurs parallèles (gauche / droite) avec un connecteur par ligne. Le mot-clé variant permute le style du connecteur sans changer aucun autre paramètre. Idéal pour comparer avant/après, résultats A/B, changements de classement, KPI entre périodes, et toute évolution par paire sur de nombreuses entités.
Variantes
Paramètres
Retour
Chart — objet avec propriété .html et méthode .show().
Astuces
- Utilisez
sort_order="asc"/"desc"pour réordonner les lignes selonleftavant le rendu. - Les variantes
"diverging"et"thick"encodent visuellement la magnitude — parfaites pour un résumé exécutif. - Pour les changements de rang (positions dans un classement), préférez
"bumps"à"basic". - Combinez
palette=avec"monochrome"pour aligner les couleurs sur les catégories de marque.
Lignes droites colorées selon la direction (vert haut, rouge bas) avec points aux extrémités.
Couleur uniforme par ligne tirée de la palette, sans coloration directionnelle.
Top 3 des plus grands Δ mis en avant en couleur vive, le reste estompé en gris.
Bump chart : l’axe y encode le rang (1..n) à chaque côté plutôt que la valeur.
Courbes de Bézier cubiques en S entre les deux extrémités — transition douce.
Épaisseur du trait proportionnelle à |droite - gauche| — la magnitude devient le poids visuel.
Barres delta centrées : positives à droite (vert), négatives à gauche (rouge).
Connecteur en L : horizontal puis vertical puis horizontal — flux rectiligne.
Funnel — Conversion / Pipeline Chart
Signature
sp.funnel(title, labels, values, *, variant="basic", palette=None, show_text=True, **kwargs) -> Chart
Description
sp.funnel() renders the entire funnel-chart family: a stacked sequence of stages where each step’s width encodes a value. The variant keyword switches the geometry without changing any other parameter. Funnels are the standard for conversion analytics (visitors → signups → paid), recruiting pipelines, sales pipelines, process drop-off and any descending-cohort analysis.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Tips
- Sort stages descending before passing them in (or use
sort_order="desc"). - Use
"conversion"when the audience cares about stage-to-stage retention rate. - The
"pyramid"variant works best when values follow a steep decay. - For broad audiences,
"chevron"reads as a sales pipeline more naturally than the trapezoid.
Classic centered trapezoid pyramid — each step’s top width inherits the previous bottom width.
Centered rectangles per stage (no diagonal slope), width ∝ value/max.
Trapezoids with rounded outer corners — softer, pill-like aesthetic.
Pentagon arrow shapes pointing right — sales-pipeline / process flow style.
Continuous pyramid: each level narrows progressively to a point at the bottom.
Vertically flipped funnel — widest stage at the bottom (growth pyramid).
Basic trapezoid plus drop-off percentage between consecutive stages displayed in red.
Several independent funnels side by side, each with its own stage count and its own scale - the native equivalent of composing multiple go.Funnel() traces in Plotly, without ever building a trace by hand. Pass series (one value list per funnel), series_names (funnel names) and category_series (one stage-label list per funnel, since funnels can have different stage counts). Automatically selected whenever series has more than one entry and category_series is given - use "grouped" instead when every group shares the same stages. text_info combines "value", "percent_initial", "percent_previous" and "percent_total" with +.
One funnel, several colored groups sharing the exact same stages - each stage's band splits into adjacent, touching segments (no gap) whose widths are proportional to each group's own value on one shared linear scale, so the combined shape tapers naturally like a single funnel. The native equivalent of px.funnel(df, x="number", y="stage", color="office") in Plotly - works with any number of groups, not just two. Auto-selected whenever series has more than one entry and category_series is not given.
Signature
sp.funnel(title, labels, values, *, variant="basic", palette=None, show_text=True, **kwargs) -> Chart
Description
sp.funnel() produit toute la famille des entonnoirs : une séquence d’étapes empilées dont la largeur encode une valeur. Le mot-clé variant permute la géométrie sans changer aucun autre paramètre. Standard pour l’analyse de conversion (visiteurs → inscrits → payants), pipelines de recrutement, pipelines commerciaux, fuites de processus et toute analyse de cohorte décroissante.
Variantes
Paramètres
Retour
Chart — objet avec propriété .html et méthode .show().
Astuces
- Triez les étapes décroissantes avant de les passer (ou utilisez
sort_order="desc"). - Utilisez
"conversion"quand l’audience s’intéresse au taux de rétention entre étapes. - La variante
"pyramid"fonctionne mieux avec des valeurs en forte décroissance. - Pour un public large,
"chevron"se lit plus naturellement comme un pipeline commercial qu’un trapèze.
Pyramide trapézoïdale centrée classique — le haut de chaque étape hérite du bas de la précédente.
Rectangles centrés par étape (sans pente diagonale), largeur ∝ valeur/max.
Trapèzes avec coins arrondis — esthétique douce de type pilule.
Pentagones en flèche pointant à droite — style pipeline commercial / processus.
Pyramide continue : chaque niveau se rétrécit progressivement jusqu’à une pointe.
Entonnoir inversé verticalement — étape la plus large en bas (pyramide de croissance).
Trapèze de base avec le pourcentage de chute entre étapes affiché en rouge.
Plusieurs entonnoirs indépendants côte à côte, chacun avec son propre nombre d'étapes et sa propre échelle - l'équivalent natif de composer plusieurs traces go.Funnel() en Plotly, sans jamais construire de trace à la main. Passez series (une liste de valeurs par entonnoir), series_names (noms des entonnoirs) et category_series (une liste d'étiquettes d'étapes par entonnoir, puisque les entonnoirs peuvent avoir des nombres d'étapes différents). Sélectionné automatiquement dès que series a plus d'une entrée et que category_series est fourni - utilisez "grouped" quand tous les groupes partagent les mêmes étapes. text_info combine "value", "percent_initial", "percent_previous" et "percent_total" avec +.
Un seul entonnoir, plusieurs groupes colorés partageant exactement les mêmes étapes - la bande de chaque étape se divise en segments adjacents et jointifs (sans espace) dont les largeurs sont proportionnelles à la valeur de chaque groupe sur une même échelle linéaire partagée, si bien que la forme combinée se rétrécit naturellement comme un seul entonnoir. L'équivalent natif de px.funnel(df, x="number", y="stage", color="office") en Plotly - fonctionne avec n'importe quel nombre de groupes, pas seulement deux. Sélectionné automatiquement dès que series a plus d'une entrée et que category_series n'est pas fourni.
Waterfall — Running-Total Bridge Chart
Signature
sp.waterfall(title, labels, values, *, variant="basic", show_text=True, **kwargs) -> Chart
Description
sp.waterfall() renders the entire waterfall-chart family: a sequence of bars where each step adds (positive) or subtracts (negative) from a running total. The variant keyword selects the geometry without touching any other parameter. Waterfalls are the standard for P&L bridges, variance analysis, cohort decomposition, fee/tax breakdowns and any “from A to B, what changed?” narrative.
Totals — set a value to
0and use a label containingtotal,net,final,grossorebitdato mark a subtotal bar; it is rendered with the totals color and anchored on the running sum.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Classic running-sum bars with dashed connectors between consecutive steps.
Bars touch each other forming a continuous staircase, no connectors needed.
Stick + circle marker at the end of each step. Excellent ink-to-data ratio.
Triangle on top (positives) or bottom (negatives) emphasizes direction at a glance.
Bars + signed percentage badge (Delta vs previous running total) above each step.
Rotated 90 degrees: each step becomes a horizontal row stacking downward, anchored to the previous running total. Editorial layout for reports with long labels or vertical storytelling.
Waterfall + running-total trend line fusion — the same increment/decrement bars, semi-transparent, with a smooth line connecting each step's running total drawn on top (white halo + solid color), plus a dot marker per step. Reveals the trajectory at a glance without losing the per-step breakdown.
Signature
sp.waterfall(title, labels, values, *, variant="basic", show_text=True, **kwargs) -> Chart
Description
sp.waterfall() rassemble toute la famille des graphiques waterfall : une suite de barres ou chaque etape ajoute (positif) ou retranche (negatif) au cumul courant. Le mot-cle variant change la geometrie sans toucher aux autres parametres. Les waterfalls sont la reference pour les ponts de P&L, l analyse d ecarts, la decomposition de cohortes, le detail des frais/taxes et tout recit du type “de A vers B, qu est-ce qui a change ?”.
Totaux — mettez la valeur a
0et utilisez un libelle contenanttotal,net,final,grossouebitdapour marquer une barre de sous-total ; elle est rendue avec la couleur des totaux et ancree sur le cumul courant.
Variantes
Paramètres
Retour
Chart — objet avec une propriete .html et une methode .show().
Barres classiques a somme cumulee avec connecteurs pointilles entre etapes.
Barres jointives formant un escalier continu, sans connecteur.
Tige + cercle en fin de chaque etape. Excellent ratio encre/donnee.
Triangle dessus (positifs) ou dessous (negatifs) pour souligner la direction en un coup d oeil.
Barres + badge de pourcentage signe (Delta vs cumul precedent) au-dessus de chaque etape.
Waterfall pivote a 90 degres : les etapes deviennent des lignes empilees verticalement, chacune partant du cumul precedent. Layout editorial pour rapports avec libelles longs ou storytelling vertical.
Fusion waterfall + ligne de cumul progressif — les memes barres d'increment/decrement, semi-transparentes, avec une ligne lissee reliant le cumul de chaque etape dessinee par-dessus (halo blanc + couleur pleine), plus un marqueur point par etape. Revele la trajectoire d'un coup d'oeil sans perdre le detail par etape.
Sunburst — Hierarchical Ring Chart
Signature
sp.sunburst(title, labels, parents, values, *, variant="basic", palette=None, **kwargs) -> Chart
Description
sp.sunburst() is the unified entry point for the entire sunburst-chart family. A sunburst represents a hierarchy as concentric rings: the innermost ring is the root, each outer ring is a deeper level, and angular size encodes value. The variant keyword selects the visual style without changing any other parameter. Sunbursts are the standard for visualizing nested taxonomies (org charts, file systems, market segmentation, expense categories, phylogenetic trees) and outperform classic pie charts as soon as a real hierarchy exists.
Hierarchy encoding —
labelslists every node,parentsgives the parent label of each node (“” for a root). Leaf values are taken fromvalues; internal node values are auto-rolled-up from descendants when set to 0.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Classic concentric rings with depth-based opacity and white separators.
Larger central hole with the formatted grand total displayed at the center.
Wireframe wedges: white fill + colored stroke that thins on deeper rings.
Angular and radial margins between every wedge for crisp petal-like separation.
Standard palette but opacity decreases with depth, focusing the eye on top levels.
Single-color rings differentiated only by depth-based opacity. Editorial look.
Click a ring segment to zoom into that branch, animating every other segment to its new angle/radius. Click the center to zoom back out.
Signature
sp.sunburst(title, labels, parents, values, *, variant="basic", palette=None, **kwargs) -> Chart
Description
sp.sunburst() est le point d entree unifie pour toute la famille des graphiques sunburst. Un sunburst represente une hierarchie sous forme d anneaux concentriques : l anneau interieur est la racine, chaque anneau exterieur est un niveau plus profond, et l angle code la valeur. Le mot-cle variant change le style sans toucher aux autres parametres. Les sunbursts sont la reference pour visualiser des taxonomies imbriquees (organigrammes, systemes de fichiers, segmentation marche, categories de depenses, arbres phylogenetiques) et surpassent le camembert des qu une vraie hierarchie existe.
Encodage de la hierarchie —
labelsliste tous les noeuds,parentsdonne le libelle du parent de chaque noeud (“” pour une racine). Les valeurs des feuilles viennent devalues; les noeuds internes a 0 sont calcules automatiquement comme la somme de leurs descendants.
Variantes
Paramètres
Retour
Chart — objet avec une propriete .html et une methode .show().
Anneaux concentriques classiques avec opacite degressive selon la profondeur.
Large trou central avec total general formate (k/M) au centre.
Quartiers en fil de fer : fond blanc + contour colore amincissant en profondeur.
Marges angulaires et radiales entre quartiers pour une separation nette en petales.
Palette standard mais opacite decroissante en profondeur pour concentrer le regard.
Anneaux monochromes differencies uniquement par opacite. Rendu editorial.
Cliquer un segment zoome sur cette branche, en animant chaque autre segment vers son nouvel angle/rayon. Cliquer le centre pour dezoomer.
Treemap — Hierarchical Proportional Tiles
Signature
sp.treemap(title, labels, values, *, parents=None, variant="basic", palette=None, **kwargs) -> Chart
Description
sp.treemap() is the unified entry point for the entire treemap-chart family. A treemap divides a rectangle into proportional sub-rectangles whose area encodes value; when a parents list is given the layout becomes hierarchical (each parent gets its own block, leaves are squarified within). The variant keyword switches the visual style without touching the data. Treemaps are the standard for visualizing budgets, market cap, disk usage, portfolio weights, file systems and any ‘whole = sum of parts’ breakdown.
Hierarchical mode — pass
parents(one parent label per leaf, can be empty string""for a flat treemap). Internal totals are auto-computed from leaves. Sort leaves with thesort_orderparameter ("desc"recommended).
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Classic squarified treemap with rounded corners and white separators between tiles.
Edge-to-edge mosaic with no stroke and no rounding for a dense, magazine-style block.
Wireframe tiles: translucent fill with bold colored stroke and dark labels for print-ready look.
Each tile inset with extra padding so the structure breathes; rounded corners and color fill.
Draws parent group rectangles with header labels around their children, emphasising hierarchy.
Color encodes value (cool blue -> hot red) instead of identity, turning the treemap into a heatmap.
Single hue with opacity decreasing by rank; editorial, minimalist, perfect for slides.
Treemap + period-over-period change fusion: pass comparisons (prior-period values, aligned with values) and every tile big enough gets a small colored badge (▲/▼ + %) in its corner — the Finviz-style "market map" look, natively.
Real Voronoi treemap: a weighted power diagram is relaxed over many iterations until each organic polygon's area matches its value, no rectangles involved. Pass parents to split the disc into value-sized angular sectors (first sector gets full category color via categories plus an optional hatch flag via categories2; every other sector renders in neutral gray) — built for "how much of the whole is in this hazardous/flagged/priority group" breakdowns.
Signature
sp.treemap(title, labels, values, *, parents=None, variant="basic", palette=None, **kwargs) -> Chart
Description
sp.treemap() est le point d entree unifie pour toute la famille treemap. Un treemap decoupe un rectangle en sous-rectangles proportionnels dont l aire code la valeur ; lorsqu une liste parents est fournie le rendu devient hierarchique (chaque parent recoit son propre bloc, les feuilles y sont squarifiees). Le mot-cle variant change le style sans toucher aux donnees. Les treemaps sont la reference pour visualiser budgets, capitalisations boursieres, occupation disque, poids de portefeuille, systemes de fichiers et toute decomposition ‘tout = somme des parties’.
Mode hierarchique — passez
parents(un libelle parent par feuille, chaine vide""pour un treemap plat). Les totaux internes sont auto-calcules. Triez les feuilles avecsort_order("desc"recommande).
Variantes
Paramètres
Retour
Chart — objet avec une propriete .html et une methode .show().
Treemap squarifie classique avec coins arrondis et separateurs blancs entre les tuiles.
Mosaique bord-a-bord sans contour ni arrondi pour un bloc dense type magazine.
Tuiles en fil de fer : fond translucide, contour colore epais et libelles sombres, style imprimable.
Chaque tuile en retrait avec marges supplementaires pour aerer la structure ; coins arrondis.
Dessine les rectangles parents avec libelle d en-tete autour de leurs enfants, met en avant la hierarchie.
La couleur code la valeur (bleu froid -> rouge chaud) au lieu de l identite, treemap en heatmap.
Teinte unique avec opacite decroissante par rang ; minimaliste et editorial, ideal pour slides.
Fusion treemap + variation d'une periode a l'autre : passez comparisons (valeurs de la periode precedente, alignees avec values) et chaque case assez grande recoit un petit badge colore (▲/▼ + %) dans son coin — le look "market map" façon Finviz, nativement.
Un vrai treemap de Voronoi : un diagramme de puissance pondere est relaxe sur de nombreuses iterations jusqu a ce que l aire de chaque polygone organique corresponde a sa valeur, sans le moindre rectangle. Passez parents pour decouper le disque en secteurs angulaires proportionnels a leur valeur (le premier secteur recoit la couleur de categorie via categories plus un hachurage optionnel via categories2 ; tous les autres secteurs restent gris neutre) — concu pour les repartitions du type "quelle part de l ensemble est dangereuse/marquee/prioritaire".
Candlestick — OHLC Time Series
Signature
sp.candlestick(title, labels, open, high, low, close, *, variant="basic", palette=None, **kwargs) -> Chart
Description
sp.candlestick() is the unified entry point for the entire candlestick-chart family. A candlestick chart shows OHLC (Open, High, Low, Close) bars over time and is the de facto standard for financial markets, crypto, commodities, energy spot prices and any time-series with intra-period spread. The variant keyword switches the visual style without touching the data — including derived views like Heikin-Ashi smoothing, close-only line, mountain area and high-low range bars.
Color convention — by default green = up (
close >= open) and red = down. Override withpalette=[up_color, down_color]. Bars are rendered left-to-right in input order; usesort_order="asc"to sort by close price.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
Classic OHLC candles: solid body, thin wick, green for up bars and red for down bars.
Japanese-style hollow up candles (white fill + colored stroke) and filled down candles.
Western OHLC bars: vertical wick with left tick = open, right tick = close, no body.
Heikin-Ashi smoothed candles: filters market noise to highlight trends and reversals clearly.
Wireframe candles: translucent body with bold colored stroke; lighter visual footprint.
Close-price line chart with markers — same data, smoother trend reading without OHLC noise.
Close-price area chart with vertical gradient under the line; great for hero / cover charts.
High-low range bars only (no open/close), single color — pure volatility visualization.
Candlestick + volume histogram fusion — the standard crypto/trading terminal layout. Candles fill the upper ~78% of the plot; a volume bar strip (colored by that period's up/down candle) fills the remaining band beneath, sharing the same x-axis. Pass a volume array alongside open/high/low/close.
Ghosted candles as background texture behind two smooth moving-average trend curves (short + long period), with the strongest local peaks of the short curve auto-detected and marked with a circle, leader line, and bold value label — the "story slide" look for a report or a landing page, built entirely from real OHLC data, no hand-placed annotations.
Candles, an optional volume pane, and five simultaneous moving averages (5/10/20/40/70-period, each its own color with a legend) overlaid on the same price axis — the full trading-terminal reading, matching what Grafana's candlestick panel offers (candles, volume, moving-average overlays) built natively in SVG.
Signature
sp.candlestick(title, labels, open, high, low, close, *, variant="basic", palette=None, **kwargs) -> Chart
Description
sp.candlestick() est le point d entree unifie pour toute la famille des chandeliers. Un graphique en chandeliers affiche des barres OHLC (Ouverture, Haut, Bas, Cloture) dans le temps et constitue le standard de fait pour les marches financiers, la crypto, les matieres premieres, le spot energie et toute serie temporelle avec spread intra-periode. Le mot-cle variant change le style sans toucher aux donnees — y compris des vues derivees comme le lissage Heikin-Ashi, la ligne de cloture, l aire mountain et les barres haut-bas.
Convention de couleur — par defaut vert = hausse (
close >= open) et rouge = baisse. Surchargez avecpalette=[couleur_hausse, couleur_baisse]. Les barres sont rendues de gauche a droite dans l ordre d entree ;sort_order="asc"pour trier par prix de cloture.
Variantes
Paramètres
Retour
Chart — objet avec une propriete .html et une methode .show().
Bougies OHLC classiques : corps plein, meche fine, vert pour hausse et rouge pour baisse.
Style japonais : bougies haussieres creuses (fond blanc + contour colore) et baissieres pleines.
Barres OHLC americaines : meche verticale, tick gauche = ouverture, droit = cloture, sans corps.
Bougies Heikin-Ashi lissees : filtre le bruit pour mettre en evidence tendances et retournements.
Bougies en fil de fer : corps translucide avec contour colore epais ; rendu plus aere.
Ligne des prix de cloture avec marqueurs — meme donnee, lecture de tendance plus lisse.
Aire des prix de cloture avec degrade vertical sous la courbe ; ideal pour visuel de couverture.
Barres haut-bas uniquement (sans ouverture/cloture), une seule couleur — visualisation de la volatilite.
Fusion chandelier + histogramme de volume — la mise en page standard des terminaux crypto/trading. Les chandeliers occupent les ~78% superieurs du graphique ; une bande de barres de volume (colorees selon la hausse/baisse de la periode) occupe le reste, partageant le meme axe x. Passez un tableau volume en plus de open/high/low/close.
Chandeliers estompes en texture de fond derriere deux courbes de tendance lissees (moyenne courte + longue periode), avec les pics locaux les plus marquants de la courbe courte detectes automatiquement et signales par un cercle, une ligne de rappel, et une etiquette de valeur en gras — l'allure "diapositive de recit" pour un rapport ou une page produit, construite entierement a partir de vraies donnees OHLC, sans annotation placee a la main.
Chandeliers, une bande de volume optionnelle, et cinq moyennes mobiles simultanees (periodes 5/10/20/40/70, chacune sa couleur avec legende) superposees sur le meme axe de prix — la lecture complete de terminal de trading, correspondant a ce qu'offre le panneau chandelier de Grafana (chandeliers, volume, moyennes mobiles superposees), construite nativement en SVG.
Dumbbell - Before / After Two-Point Comparison
Signature
sp.dumbbell(title, labels, start, end, *, variant="basic", series_name_start="Start", series_name_end="End", **kwargs) -> Chart
Description
sp.dumbbell() is the unified entry point for the dumbbell-chart family. Each row plots two values - typically a before and an after - linked by a connector, making it the chart of choice for change, gap or comparison-over-time analyses (salary equity, turnaround KPIs, A/B uplifts, etc.). The variant keyword switches the visual treatment without touching the data.
Variants
Parameters
Returns
Chart - object with .html property and .show() method.
Classic two-dot dumbbell with a gray connecting bar; the workhorse of before/after comparisons.
Arrowhead points from start to end so direction of change is immediate.
Bar between dots is colored by sign (green up, red down) to encode direction and magnitude.
Square weighted endpoints on a thick gray axis - editorial barbell look for slides.
Soft halo around endpoints with thin connector for a luminous, modern feel.
Dashed connector with hollow ring markers - lightweight and airy.
Adds a numeric rank in front of every label - perfect for top-N comparisons.
Signature
sp.dumbbell(title, labels, start, end, *, variant="basic", series_name_start="Start", series_name_end="End", **kwargs) -> Chart
Description
sp.dumbbell() est le point d entree unique pour la famille dumbbell. Chaque ligne montre deux valeurs - typiquement avant/apres - reliees par un connecteur, ce qui en fait le choix naturel pour visualiser un changement, un ecart ou une evolution (equite salariale, KPIs de redressement, uplifts A/B, etc.). Le mot-cle variant change le style visuel sans toucher aux donnees.
Variantes
Paramètres
Retour
Chart - objet avec propriete .html et methode .show().
Dumbbell classique a deux points et barre grise - la base des comparaisons avant/apres.
Une fleche pointe du depart vers l arrivee, la direction du changement est immediate.
La barre entre les points prend la couleur du signe (vert hausse, rouge baisse).
Halteres carres sur un axe epais - look editorial pour presentations.
Halo doux autour des extremites avec connecteur fin - style lumineux et moderne.
Connecteur en pointilles et marqueurs en anneaux - leger et aere.
Ajoute un rang numerique devant chaque label - ideal pour comparer un top-N.
Bullet - Compact KPI vs Target
Signature
sp.bullet(title, labels, values, *, targets=None, max_vals=None, ranges=None, comparisons=None, variant="basic", **kwargs) -> Chart
Description
sp.bullet() is the unified entry point for the bullet-chart family. Inspired by Edward Tufte, a bullet packs an actual value, a target, qualitative ranges and a scale into a single horizontal row - perfect for KPI dashboards where space is precious. The variant keyword switches the visual treatment (zones, traffic light, thermometer, progress pill, dot, ghost-bar comparison) without touching the data.
Variants
Parameters
Returns
Chart - object with .html property and .show() method.
Classic Tufte bullet: track + qualitative range + value bar + target tick.
Three graduated qualitative bands (poor / satisfactory / good) drawn behind the value bar.
Vertical thermometer style with a bulb base - dramatic for KPIs in a row.
Traffic-light segmented track (red / amber / green) for status dashboards.
Sparkline-thin pill bar with target tick only - ultra-clean inline indicator.
Single dot on a track instead of a bar - dot-plot variant of the bullet.
Pill-shape gradient progress bar with a percentage label centered inside.
Adds a ghost bar (e.g. previous period via comparisons) behind the current value.
Signature
sp.bullet(title, labels, values, *, targets=None, max_vals=None, ranges=None, comparisons=None, variant="basic", **kwargs) -> Chart
Description
sp.bullet() est le point d entree unique pour la famille bullet. Inspire par Edward Tufte, le bullet condense valeur, cible, zones qualitatives et echelle dans une seule ligne horizontale - parfait pour des dashboards KPIs serres. Le mot-cle variant change l aspect (zones, feu tricolore, thermometre, pillule de progression, point, comparaison par barre fantome) sans toucher aux donnees.
Variantes
Paramètres
Retour
Chart - objet avec propriete .html et methode .show().
Bullet de Tufte classique : piste + zone qualitative + barre de valeur + tick de cible.
Trois bandes qualitatives graduees (faible / correct / bon) derriere la barre de valeur.
Style thermometre vertical avec bulbe - tres parlant pour des KPIs alignes.
Piste segmentee feu tricolore (rouge / orange / vert) pour tableaux de bord.
Barre pillule fine type sparkline avec uniquement le tick cible - indicateur inline epure.
Un seul point sur la piste au lieu d une barre - variante dot-plot du bullet.
Barre de progression pillule en degrade avec pourcentage centre.
Ajoute une barre fantome (par ex. periode precedente via comparisons) derriere la valeur courante.
Gauge - Single-Value Arc Indicator
Signature
sp.gauge(title, *, value, min_val=0.0, max_val=100.0, label="", variant="basic", comparison=0.0, **kwargs) -> Chart
Description
sp.gauge() is the unified entry point for the gauge family. A gauge maps a single scalar to a colored arc with optional thresholds - perfect for status / health / utilization KPIs. The variant keyword switches the geometry (half, three-quarter, full ring), the embellishments (needle, ticks, glow) and the layering (single arc vs. concentric arcs for value-vs-target).
Variants
Parameters
Returns
Chart - object with .html property and .show() method.
Half-circle gauge with needle and color thresholds - the speedometer everyone knows.
Full-circle donut progress arc - elegant ring KPI for dashboards.
270-degree arc - more arc length for finer reading than a half-circle.
No needle, no ticks - oversized value text on a clean colored arc.
Half-arc with ruler tick marks every 5% and major labels every 25%.
Battery / signal-bar style with discrete chunks lighting up by threshold.
Neon glow effect on the active arc - dramatic dark dashboard look.
Two concentric arcs: outer = current, inner = comparison or target.
Gauge + trend sparkline fusion: any other gauge style (arc, needle, value readout — pick it with base_style, e.g. "tick", "radial", "glow"...) with a bordered history panel drawn beneath: gradient-filled trend line, highlighted latest point, and an up/down delta badge comparing it to the start of the history array — the current reading in context, without needing a second chart.
Signature
sp.gauge(title, *, value, min_val=0.0, max_val=100.0, label="", variant="basic", comparison=0.0, **kwargs) -> Chart
Description
sp.gauge() est le point d entree unique pour la famille jauge. Une jauge associe un scalaire unique a un arc colore avec des seuils optionnels - parfait pour des KPIs de statut / sante / utilisation. Le mot-cle variant change la geometrie (demi, trois-quart, anneau complet), les ornements (aiguille, ticks, glow) et la composition (arc simple ou arcs concentriques pour valeur-vs-cible).
Variantes
Paramètres
Retour
Chart - objet avec propriete .html et methode .show().
Jauge demi-cercle avec aiguille et seuils colores - le compteur que tout le monde connait.
Arc de progression circulaire complet - KPI elegant en anneau pour tableaux de bord.
Arc de 270 degres - plus de longueur pour une lecture plus fine qu un demi-cercle.
Sans aiguille ni ticks - valeur en grand sur un arc colore epure.
Demi-arc avec graduations regle tous les 5% et labels majeurs tous les 25%.
Style batterie / barre de reseau avec segments discrets s allumant par seuil.
Effet neon sur l arc actif - look dashboard sombre tres marquant.
Deux arcs concentriques : externe = courant, interne = comparaison ou cible.
Fusion gauge + sparkline de tendance : n'importe quel autre style de gauge (arc, aiguille, valeur affichée — choisi via base_style, ex. "tick", "radial", "glow"...) avec un panneau d'historique dessiné en dessous : ligne de tendance à remplissage dégradé, dernier point mis en évidence, et un badge de variation ▲/▼ comparant à l'écart au début du tableau history — la mesure actuelle mise en contexte, sans avoir besoin d'un second graphique.
Lollipop - Categorical Value Sticks
Signature
sp.lollipop(title, labels, values, *, variant="basic", color_groups=None, **kwargs) -> Chart
Description
sp.lollipop() is the unified entry point for the lollipop family. Each item becomes a thin stick capped by a dot - lighter ink than a bar chart for the same ranking, and the family includes circular, diverging and grouped editorial layouts (the Office variant reproduces the season-rating panel pattern). To spotlight a single point on any variant, chain the generic .highlight(index) method instead of picking a dedicated variant for it.
Variants
Parameters
Returns
Chart - object with .html property and .show() method.
Vertical sticks topped with dots - the canonical lollipop for ranked categorical values.
Horizontal Cleveland dot plot - long labels read naturally and dots align cleanly along value axis.
Sticks pivot around the mean: green points sit above, red points below - perfect for deviation analysis.
Polar layout where each category is an angular spoke - eye-catching for small alphabets and dashboard tiles.
Group-aware lollipops with per-group mean line and color band - inspired by The Office IMDb season chart.
Horizontal sticks pivoting on zero, colored by sign alone (orange ≥ 0, sky blue < 0) - the seaborn-gallery "lollipop with conditional color" recipe.
Sticks colored by a continuous diverging colormap (magnitude from zero), a smoothed moving-average trend line, and an arrow annotation on the most extreme point - matching the "lollipop with colormap and arrow" gallery recipe.
Signature
sp.lollipop(title, labels, values, *, variant="basic", color_groups=None, **kwargs) -> Chart
Description
sp.lollipop() est le point d entree unique pour la famille lollipop. Chaque item devient un baton fin termine par un point - moins d encre qu un bar chart pour le meme classement, et la famille couvre des layouts circulaires, divergents et editoriaux groupes (la variante Office reproduit le motif des saisons IMDb de The Office). Pour mettre en avant un seul point sur n’importe quelle variante, chainez la methode generique .highlight(index) plutot que de choisir une variante dediee.
Variantes
Paramètres
Retour
Chart - objet avec propriete .html et methode .show().
Batons verticaux surmontes de points - le lollipop canonique pour valeurs categorielles classees.
Cleveland dot plot horizontal - les longs libelles se lisent naturellement, les points s alignent sur l axe des valeurs.
Batons pivotent autour de la moyenne: vert au-dessus, rouge en-dessous - parfait pour les ecarts.
Disposition polaire ou chaque categorie est un rayon - tres lisible pour petits jeux et dashboards.
Lollipops groupes avec moyenne par groupe et bande de couleur - inspire du chart IMDb de The Office.
Batons horizontaux pivotant sur zero, colores selon le seul signe (orange ≥ 0, bleu ciel < 0) - la recette "lollipop with conditional color" de la galerie seaborn.
Batons colores par un degrade divergent continu (magnitude depuis zero), une courbe de tendance lissee, et une fleche d'annotation sur le point le plus extreme - la recette "lollipop with colormap and arrow".
Parallel Coordinates - Multivariate Profile Lines
Signature
sp.build_parallel(title, axes, series, *, variant="basic", **kwargs) -> Chart
Description
sp.build_parallel() renders a parallel-coordinates chart - one vertical axis per dimension, one polyline per row. Six variants cover the classical use cases: straight lines, smooth Bezier curves, categorical coloring, single-row highlight, density-blended overlay, and gradient coloring driven by any axis. Perfect for high-dimensional EDA, profile comparison, and class separability inspection.
Variants
Parameters
Returns
Chart - object with .html property and .show() method.
Straight polylines through every axis - the textbook parallel-coordinates chart.
Bezier-smoothed lines reduce visual clutter for dense datasets.
One color per category - perfect for comparing classes side by side.
Spotlights one series and dims the others - great for storytelling.
Translucent lines reveal density bands inside thousands of profiles.
Smooth cubic bezier curves with per-series gradient coloring. Reduces visual clutter compared to straight lines while preserving individual series identity.
Filled bezier bands between adjacent axes. Each series is rendered as a translucent ribbon + thin solid stroke, creating a flowing Sankey-lite effect.
Signature
sp.build_parallel(title, axes, series, *, variant="basic", **kwargs) -> Chart
Description
sp.build_parallel() rend un graphique parallel-coordinates - un axe vertical par dimension, une polyligne par ligne. Six variantes couvrent les cas classiques : lignes droites, courbes Bezier, couleur par categorie, mise en avant d une ligne, overlay de densite, et degrade pilote par un axe. Ideal pour l EDA haute-dimension, la comparaison de profils, et l inspection de separabilite de classes.
Variantes
Paramètres
Retour
Chart - objet avec propriete .html et methode .show().
Polylignes droites sur tous les axes - le parallel-coordinates canonique.
Lignes Bezier qui reduisent l encombrement visuel sur grands jeux.
Une couleur par categorie - parfait pour comparer des classes.
Met en avant une serie et estompe les autres - ideal pour storytelling.
Lignes translucides revelent les bandes de densite sur des milliers de profils.
Courbes de Bezier cubiques avec degrade de couleur par serie. Reduit l encombrement visuel par rapport aux lignes droites tout en preservant l identite de chaque serie.
Bandes de Bezier remplies entre les axes adjacents. Chaque serie est rendue comme un ruban translucide + trait fin, creant un effet de flux style Sankey simplifie.
Word Cloud - Six Rendering Architectures
Signature
sp.build_wordcloud(title, words, frequencies, *, variant="basic", shape="rect", **kwargs) -> Chart
Description
sp.build_wordcloud() packs weighted tokens into six rendering architectures. Basic is the canonical spiral packer driven by a parametric shape= mask (rect, circle, heart, bird, glasses, diamond, star). Bubble gives each word its own color-filled disc sized by frequency - a packed-bubble layout. Context is an InfraNodus-style text-network cloud: words positioned by a force-directed layout driven by co-occurrence edges so semantically close words cluster spatially, colored by community. Image accepts any binary pixel mask (logo, icon, photo). LabelMap draws a datamapplot-style clustered scatter with leader-line labels. Network renders a keyword co-occurrence graph with bezier-curved edges.
Shapes (for variant basic)
The basic variant accepts a shape= argument that selects the silhouette mask:
| Shape | Aliases | Description |
|---|---|---|
"rect" | rect / rectangle / box / default | Rectangular Archimedean spiral - the textbook word cloud. |
"circle" | circle / round / disk / ball | Words packed inside a perfect disc. |
"heart" | heart / love / valentine | Cardioid heart silhouette. |
"bird" | bird / twitter / tweet / icon | Composite-disk stylised bird silhouette. |
"glasses" | glasses / sunglasses / shades / specs | Sunglasses silhouette (two ellipses + bridge). |
"diamond" | diamond / rhombus / lozenge | Rotated square / rhombus silhouette. |
"star" | star / starburst / 5-point | 5-pointed star silhouette. |
Variants
Parameters
Returns
Chart - object with .html property and .show() method.
Spiral packing inside a parametric shape mask. Pick the silhouette via the `shape=` argument (rect, circle, heart, bird, glasses, diamond, star).
Each word gets a colored disc sized by frequency - a packed-bubble word cloud. Words float in labeled circles, no overlap.
Text-network word cloud (InfraNodus style): words positioned by force-directed layout based on co-occurrence edges, colored by semantic cluster - semantically close words appear spatially close.
Words flow inside a custom binary image mask - upload any silhouette (logo, icon, photo) and the cloud takes its shape.
Datamapplot-style topic map - clustered scatter of points colored per category, with cluster labels positioned via leader lines.
Keyword co-occurrence graph - golden-angle node layout, bezier-curved edges, frequency-sized circles. Editorial style of academic keyword maps.
Neural-network word cloud on a dark background. Words become glowing nodes; faint connecting edges link nearest neighbors, evoking a synaptic graph.
Words placed on a phyllotaxis (sunflower-seed) spiral on a starfield background, largest/most frequent words nearest the center — a constellation-like layout instead of a packed rectangle.
Signature
sp.build_wordcloud(title, words, frequencies, *, variant="basic", shape="rect", **kwargs) -> Chart
Description
sp.build_wordcloud() propose six architectures de rendu. Basic est le packer spirale canonique pilote par un masque shape= (rect, circle, heart, bird, glasses, diamond, star). Bubble donne a chaque mot un disque colore dimensionne par frequence - un layout bubble-packed. Context est un nuage texte-reseau style InfraNodus : mots positionnes par layout force-dirige base sur les aretes de co-occurrence, colores par communaute. Image accepte n importe quel masque binaire de pixels. LabelMap dessine un scatter clusterise style datamapplot avec etiquettes en lignes de rappel. Network rend un graphe de co-occurrence de mots-cles avec aretes bezier.
Formes (pour la variante `basic`)
La variante basic accepte un argument shape= :
| Forme | Alias | Description |
|---|---|---|
"rect" | rect / rectangle / box / default | Rectangular Archimedean spiral - the textbook word cloud. |
"circle" | circle / round / disk / ball | Words packed inside a perfect disc. |
"heart" | heart / love / valentine | Cardioid heart silhouette. |
"bird" | bird / twitter / tweet / icon | Composite-disk stylised bird silhouette. |
"glasses" | glasses / sunglasses / shades / specs | Sunglasses silhouette (two ellipses + bridge). |
"diamond" | diamond / rhombus / lozenge | Rotated square / rhombus silhouette. |
"star" | star / starburst / 5-point | 5-pointed star silhouette. |
Variantes
Paramètres
Retour
Chart - objet avec propriete .html et methode .show().
Packing en spirale dans un masque parametrique. Choisissez la silhouette via l argument `shape=` (rect, circle, heart, bird, glasses, diamond, star).
Chaque mot obtient un disque colore dimensionne par frequence - un nuage de bulles pack. Les mots flottent dans des cercles etiquetes, sans chevauchement.
Nuage de mots texte-reseau (style InfraNodus) : mots positionnes par layout force-dirige base sur les aretes de co-occurrence, colores par cluster semantique - les mots proches semantiquement sont proches spatialement.
Mots a l interieur d un masque binaire personnalise - uploadez n importe quelle silhouette et le nuage en prend la forme.
Carte thematique style datamapplot - scatter de points clusterises colores par categorie, avec etiquettes de cluster positionnees via lignes de rappel.
Graphe de co-occurrence de mots-cles - layout en angle d or, aretes courbes bezier, cercles dimensionnes par frequence. Style editorial des cartes de mots-cles academiques.
Nuage de mots style reseau de neurones sur fond sombre. Les mots deviennent des noeuds lumineux relies par des aretes fines evoquant un graphe synaptique.
Mots places sur une spirale de phyllotaxie (graines de tournesol) sur fond etoile, les mots les plus frequents pres du centre - une disposition en constellation plutot qu'un rectangle compact.
Sankey Diagram
Signature
sp.sankey(title, labels, edges_i, edges_j, edges_w, *, variant="basic", **kwargs) -> Chart
Aliases: sp.sankey, sp.sankeys, sp.sankey_chart, sp.sankey_diagram, sp.flow_chart
Description
Sankey diagrams visualize flows between nodes. Node widths and link widths are proportional to flow volumes. Edges are defined by source indices (edges_i), target indices (edges_j), and weights (edges_w). Nodes are laid out in columns by BFS depth.
Variants
Data
labels (list[str]) — Node names. edges_i (list[int]) — Source node indices. edges_j (list[int]) — Target node indices. edges_w (list[float]) — Flow weights. width / height (int) — Chart dimensions.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Standard bezier ribbon links
Increased node spacing
Wider nodes and ribbons
Thin outline style
Reorders nodes within each depth column by descending total throughput, so the dominant flows cluster together instead of sitting in input order — makes it easy to spot which nodes carry the most volume.
Every source starts as its own thin row on the left and every ribbon is pulled through a single fixed pinch point before fanning back out into a radial arc of target wedges on the right — each wedge's angular width is proportional to the square root of its total incoming flow (a mild compression so one outlier target can't visually swallow the rest of the arc). Built for bipartite many-sources-to-few-categories data, e.g. foods flowing into the nutrients they're rich in.
Sources are laid out as a dot-matrix grid instead of a node list — one marker per record, sized by its own weight and colored by the category it ultimately flows to — with thin low-opacity ribbons converging on a handful of large target circles sized by total share and labeled with their percentage of the whole. Built for record-level "big data" flows: many individual rows collapsing into a few outcome buckets.
A single source radiates to every target around a full circle instead of two flat columns — each target sits at its own angle with a short pill sized and colored by weight (a continuous colorscale, not a fixed palette), connected back to the hub by a thin bowed spoke. Built for one-to-many schedules where each target also carries its own label, e.g. a departure board: one hub airport, every outbound flight arranged around the dial with its destination, time and duration.
Chord Diagram
Signature
sp.chord(title, labels, matrix, *, variant="basic", **kwargs) -> Chart
Aliases: sp.chord, sp.chord_chart, sp.chord_diagram
Description
Chord diagrams show relationships between entities using arcs and ribbons around a circle. The matrix is an N×N flow matrix where matrix[i][j] is the flow from node i to node j.
Variants
Data
labels (list[str]) — Node names. matrix (list[list[float]]) — N×N flow matrix. width / height (int) — Chart dimensions (default 700×700).
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Standard filled ribbons
Wider ribbon links
Arc-only (no filled ribbons)
Single-color monochrome
Draws a small arrowhead on each ribbon pointing toward whichever side receives more — reads the matrix's row/column asymmetry directly off the diagram instead of just the ribbon's natural taper.
Splits the circle into two arcs — grouped items on one side, a colored attribute ring (sized by how many items link to it) on the other — with curved links between them and item labels rotated radially for readability.
Circle Packing
Signature
sp.circle_pack(title, labels, parents, values, *, variant="basic", **kwargs) -> Chart
Aliases: sp.circle_pack, sp.circle_packing, sp.pack, sp.bubble_pack
Description
Circle packing represents hierarchical data as nested circles, where the area of each circle is proportional to its value. Parent–child relationships are defined by the parents list (empty string = root node).
Variants
Data
labels (list[str]) — Node names. parents (list[str]) — Parent name for each node ("" = root). values (list[float]) — Size of each leaf node. width / height (int) — Chart dimensions.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Filled nested circles with depth-based opacity
Single-level bubble layout (no nesting)
Stroke-only circles
Runs a real greedy circle-packing layout: each circle is placed tangent to the best pair of already-placed circles (falling back to a spiral search when no tangent placement fits), producing a genuinely nested, non-overlapping bubble cluster instead of the grid-like flat layout.
Fills only the leaf circles with color and shrinks every container circle to a faint dashed outline — declutters the hierarchy chrome so attention goes straight to the actual data points instead of the grouping structure.
Reimagines circle packing as a grouped commit swarm: parents becomes any grouping bucket instead of a tree parent, and every leaf is one real commit — a genuine front-chain circle-packing pass clusters each group's commits into its own organic, non-overlapping blob, auto-arranged in a grid. Circle size encodes lines changed, fill color encodes the commit type (feat / fix / docs / refactor / chore / style / perf / test), and automated commits render as hexagons instead of circles. Compose several single-group swarms with the canvas frieze()/timeline() method to string them along a real chronology instead. The bundled demo plots SeraPlot's own real git history.
Turns circle packing into a two-axis category grid: parents and categories become any pair of categorical fields (say, region × orbit class), and every cell runs its own real front-chain circle-packing pass, scaled to how much of the data actually lands there — a busy combination visibly earns more room instead of just cramming more dots into the same box. Circle size encodes a magnitude (values), fill color encodes one category (categories2), and marker shape (circle / star / diamond / triangle / …) encodes a second, independent one (symbols) — three readable encodings stacked on top of the two axes. An optional continuous field (color_values, e.g. a launch year) fades older points and keeps recent ones at full strength. The bundled demo plots a synthetic satellite catalog.
Arc Diagram
Signature
sp.arc_diagram(title, labels, edges_i, edges_j, edges_w, *, variant="basic", **kwargs) -> Chart
Aliases: sp.arc_diagram, sp.arc_chart, sp.arc_plot, sp.arc_graph, sp.linear_network
Description
Arc diagrams place nodes on a horizontal axis and draw quadratic bezier arcs above (and optionally below) the axis to represent connections. They are particularly effective for showing sequential or ordered relationships.
Variants
Data
labels (list[str]) — Node names. edges_i (list[int]) — Source node indices. edges_j (list[int]) — Target node indices. edges_w (list[float]) — Edge weights. width / height (int) — Chart dimensions.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Arcs above the axis
Alternating arcs above and below
Stroke width proportional to edge weight
Thin, low-opacity arcs with small strokeless nodes — strips away the visual weight so overlapping arcs stay legible in dense diagrams.
Draws a small arrowhead where each arc lands on its target node — turns the diagram into a proper directed graph, e.g. for dependency or citation edges where "which way" matters as much as "how much".
Dendrogram
Signature
sp.dendrogram(title, labels, *, matrix=None, parents=None, clusters=3, variant="vertical", **kwargs) -> Chart
Aliases: sp.dendrogram, sp.dendro, sp.tree, sp.tree_diagram, sp.hierarchy, sp.hierarchical
Description
Dendrograms display hierarchical tree structures using right-angle elbow connectors (vertical/horizontal) or smooth bezier curves (elegant) or a radial circular layout. Pass matrix – one row of numeric coordinates per label – for a genuine average-linkage agglomerative clustering with real merge heights (matching hclust/scipy), including automatic coloring of the top clusters groups with the trunk above the cut shown in neutral gray. Without matrix, parents describes a plain hand-specified hierarchy (each entry names its parent’s label, empty string for a root) with no real distance semantics.
Variants
Data
labels (list[str]) — Node names. parents (list[str]) — Parent name per node ("" = root). width / height (int) — Chart dimensions.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Root at top, leaves at bottom (elbow connectors)
Root at left, leaves at right
Circular radial tree layout
Tighter spacing, smaller font
Smooth cubic bezier curves
Connects parent to child with a single straight diagonal line — a third connector style alongside the right-angle elbows (vertical/horizontal) and smooth beziers (elegant).
Venn Diagram
Signature
sp.venn(title, labels, values, *, variant="basic", **kwargs) -> Chart
Aliases: sp.venn, sp.venn_diagram, sp.euler, sp.set_diagram, sp.overlap
Description
Venn diagrams show set relationships using overlapping circles. Supply one value per set for circle sizes. The "euler" variant scales circle radii proportionally to the first N values.
Variants
Data
labels (list[str]) — Set names. values (list[float]) — Set sizes (first N entries used for Euler radii). width / height (int) — Chart dimensions.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Semi-transparent overlapping circles
Proportional circle areas (Euler diagram)
Fully opaque circles
Forces every circle into its stroke-only outline form regardless of the configured fill opacity, for a clean contour-only read of the set overlaps.
Masks each circle down to the region that belongs to it alone and renders that region at full color, while shared/overlapping areas fade into the background — makes it obvious what's unique to each set versus what's shared.
Correlogram
Signature
sp.correlogram(title, labels, matrix, *, variant="circle", **kwargs) -> Chart
Aliases: sp.correlogram, sp.corrplot, sp.correlation_matrix, sp.corr, sp.correlation_map
Description
A correlogram visualizes a correlation matrix as a grid. Each cell encodes the Pearson correlation coefficient (–1 to +1) using color (red = positive, blue = negative) and either circle area, square fill, or text. matrix is a nested N×N list — one inner list per row.
Variants
Data
labels (list[str]) — Variable names (length N). matrix (list[list[float]]) — N×N correlation matrix, one row per inner list. width / height (int) — Chart dimensions.
Every variant is really just a preset of three lower-level params you can mix freely on the base circle variant instead of picking a named one: cell_shape ("circle" | "square" | "ellipse" | "pie" | "number") controls how a single cell is drawn, cell_shape2 sets a second shape for the lower triangle when layout="mixed", and layout ("full" | "upper" | "lower" | "mixed") controls which half of the matrix gets filled — e.g. sp.correlogram(labels=..., matrix=..., cell_shape="ellipse", layout="upper").
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Filled squares (standard heatmap)
Numeric correlation values only
Circles + text overlay
Each cell is an ellipse tilted "/" for positive correlation, "\" for negative, flattening toward a line as |r| approaches 1 and toward a circle as it approaches 0.
Upper triangle as pie wedges (wedge angle = |r|, color = sign), lower triangle as flat colored squares, diagonal left blank - the classic mixed correlogram layout, unsorted.
Upper-triangle-only circles (lower triangle and diagonal left blank) with a color/value legend bar alongside instead of numeric labels on the cells.
Hive Plot
Signature
sp.hive(title, axes, labels, categories, values, edges_i, edges_j, edges_w, *, variant="basic", **kwargs) -> Chart
Aliases: sp.hive, sp.hive_plot, sp.hive_chart, sp.hive_graph, sp.radial_network
Description
Hive plots organize network nodes on radial axes by category. Each axis corresponds to one node group (axes). Node position along the axis is determined by values (0–1). Edges between nodes are drawn as straight or curved lines through the center.
Variants
Data
axes (list[str]) — Axis (group) names. labels (list[str]) — Node names. categories (list[str]) — Group assignment per node. values (list[float]) — Node position along axis (0–1). edges_i (list[int]) — Source node indices. edges_j (list[int]) — Target node indices. edges_w (list[float]) — Edge weights. width / height (int) — Chart dimensions.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Straight edge lines
Cubic bezier curves through center
Stroke width proportional to edge weight
Straight (uncurved), thin, low-opacity edges with small strokeless nodes — a decluttered reading of dense hive plots that trades the curved-edge chrome for raw connectivity.
Draws a small arrowhead where each edge lands on its target node — hive plots are frequently used for directed graphs (network traffic, citations), and this makes the direction readable at a glance instead of implied.
Circos Plot
Signature
sp.circos(title, labels, categories, axes, matrix, series, series_names, edges_i, edges_j, *, variant="basic", **kwargs) -> Chart
Aliases: sp.circos, sp.circos_plot, sp.multitrack_circle, sp.genome_browser, sp.circular_tracks
Description
A multi-track circular plot in the spirit of Circos genome browsers: items sit on a shared ring, grouped into colored cluster arcs, and every other dataset dimension becomes its own concentric track — bar tracks, a sequential heatmap, a normalized composition ring, single-value tracks (ratio, rank, degree) on their own color ramp, and a network of co-occurrence chords through the center. Every ring is independently hoverable.
Variants
Data
labels (list[str]) — Item names around the ring. categories (list[str]) — Cluster assignment per item, drawn as colored boundary arcs. axes (list[str]) — Column names for the heatmap/composition matrix (e.g. age groups). matrix (list[list[float]]) — One row per item, one value per axes column; also drives the derived average/spread/composition tracks. series (list[list[float]]) — One or more bar tracks, one value per item. series_names (list[str]) — Name per bar track. edges_i / edges_j (list[int]) — Source/target item indices for the center co-occurrence chords. width / height (int) — Chart dimensions.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Full multi-track layout: item labels, co-occurrence degree, average value + spread, cluster boundaries, bar tracks, a ratio and a rank track, a sequential heatmap, a normalized composition ring, and center co-occurrence chords.
Pulse Chart
Signature
sp.pulse(title, labels, values, *, variant="radial", **kwargs) -> Chart
Aliases: sp.pulse, sp.pulse_chart, sp.radial_bar, sp.clock_chart, sp.rhythm, sp.radial_rhythm
Description
The Pulse chart is an original SeraPlot chart type that maps temporal or cyclic data onto a radial clock-face layout. Each slice is a time period (hour, day, month…) and the bar height encodes the intensity value. The "wave" variant connects data points into a smooth radial polygon.
Variants
Data
labels (list[str]) — Period labels (e.g. days, hours). values (list[float]) — Intensity per period (any scale). width / height (int) — Chart dimensions (default 560×560).
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Clock-face arc bars per period
Smooth closed radial polygon
Radial scatter with connecting spokes
Extends every slice into a continuous, gap-free ring at full opacity with no stroke — reads as a solid dial rather than a set of separated arc bars.
Stroke-only arc slices with no fill — the same radial clock-face layout, stripped down to outlines for a lighter, print-friendly look.
Orbita Chart
Signature
sp.orbita(title, series_names, labels, matrix, *, variant="classic", **kwargs) -> Chart
Aliases: sp.orbita, sp.orbit, sp.orbit_chart, sp.orbital, sp.multi_orbit, sp.concentric
Description
The Orbita chart is an original SeraPlot chart type that places multiple series on concentric ring orbits. Each series (orbit) maps categories to angular positions. The result is a planetary-system-style comparison across series and categories simultaneously, ideal for multi-period cross-category analysis.
matrix is a nested S×C list — one inner list per series (S = number of series, C = number of categories).
Variants
Data
series_names (list[str]) — One name per orbit (e.g. years). labels (list[str]) — Category names (angular positions). matrix (list[list[float]]) — S×C value matrix, one row per series. width / height (int) — Chart dimensions (default 580×580).
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Fixed-size dots on orbits
Dot radius proportional to value
Closed polygon trail connecting series dots
Gaussian blur glow effect on dots
Drops the dashed orbit rings and radial spoke lines, and shrinks each point to a small strokeless dot — a decluttered read focused purely on relative position.
Colors each point green or red depending on whether its value rose or fell versus the same category on the previous orbit — turns concentric orbits (e.g. one per year) into a trend view instead of a static snapshot.
Event Plot — Discrete Event Ticks per Row
Signature
sp.eventplot(title, x_values, categories, *, variant="basic", **kwargs) -> Chart
Aliases: sp.eventplot, sp.event_plot, sp.raster_plot, sp.spike_plot, sp.build_eventplot
Description
sp.eventplot() draws a short vertical tick for every discrete event, one row per category — the standard chart for point-in-time occurrences without duration (neuron spike trains, log/error timestamps, user session starts). Unlike gantt(), events have no end time. Reuses x_values and the existing categories field on ChartArgs (the same grouping mechanism used by bubble()’s categorical variant) — no new parameter shape, rows are formed automatically from distinct category values in order of first appearance.
Variants
Data
x_values (list[float]) — Event positions. categories (list[str]) — Row assignment per event.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Tick marks only, one color per row.
Tick marks plus a smoothed density curve per row, computed with the same native kernel density estimator as [`kde()`](kde.md) (`scott_bw` bandwidth selection, Gaussian kernel) — a rug plot and a KDE in one chart.
Draws a thin line connecting each row's events in chronological order, on top of the usual ticks — traces the sequence/trajectory through time, not just where events landed.
Gantt — Project Timeline Chart
Signature
sp.gantt(title, labels, start, end, *, variant="basic", categories=None, color_values=None, **kwargs) -> Chart
Aliases: sp.gantt, sp.gantt_chart, sp.broken_barh, sp.timeline_chart, sp.project_timeline, sp.build_gantt
Description
sp.gantt() draws one horizontal bar per task spanning from start to end on a shared numeric timeline (day index, epoch, or any consistent unit — no date-typed axis required). Tasks are sorted like every other chart via sort_order, and rows can be grouped by an optional categories list (e.g. team or phase), which colors bars by category with an automatic legend instead of per-row palette rotation. Reuses the labels/start/end/categories fields already on ChartArgs (the same start/end pair used by dumbbell()) — no new parameter shape.
Variants
Data
labels (list[str]) — Task names. start (list[float]) — Task start (numeric timeline unit). end (list[float]) — Task end. categories (list[str]) — Optional group per task; colors bars by group with a legend. color_values (list[float]) — Completion fraction (0–1) per task, used by the "progress" variant.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Flat colored bar per task.
Outlined bar with an inner fill proportional to `color_values` (0–1 completion fraction) and a `%` label.
Renders zero-duration tasks (`start == end`) as a diamond marker instead of a degenerate bar — the standard way project-planning tools distinguish milestones from work items.
Hexbin — Hexagonal Density Binning
Signature
sp.hexbin(title, x_values, y_values, *, variant="basic", gridsize=20, colorscale=None, **kwargs) -> Chart
Aliases: sp.hexbin, sp.hexbins, sp.hexbin_chart, sp.hexagonal_binning, sp.build_hexbin
Description
sp.hexbin() bins a 2D scatter cloud into a regular hexagonal grid and colors each hexagon by point density (count), the standard alternative to a scatter plot once point overlap makes individual markers unreadable. Points are assigned to hexagon cells directly in pixel space using the true nearest-center rule (two candidate offset grids, closest wins), so cells tile without gaps or overlap regardless of the data’s aspect ratio. Cell color reuses the same continuous colorscale engine as heatmap() and bubble(variant="gradient") — any of viridis / plasma / inferno / magma / cividis / turbo / rdbu / blues / reds / greens works via colorscale=.
Variants
Data
x_values (list[float]) — X coordinates. y_values (list[float]) — Y coordinates.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Filled hexagons only, compact grid, right-side density legend.
White cell borders; count printed inside each hexagon once cells are large enough to fit text.
Hexagons drawn at 72% size with a visible gap between neighbors — a "confetti" look instead of a solid tiled surface.
Dims every cell except the densest ~15% (full opacity, white outline, count label) — draws the eye straight to the hotspots instead of the full density gradient.
Bins below min_count are skipped entirely (left transparent) instead of drawn faint - a hard threshold rather than a dimmed gradient, matching R's hexbin(mincnt=).
Each cell's count is classed into an order-of-magnitude band (ones/tens/hundreds/thousands/10 thousands), colored and sized by band, with a smaller nested hexagon inside in the previous band's color - matching R hexbin's nested/centroid styles - plus a size+color legend.
Cell color is mapped on log(count + 1) instead of the raw count, matching matplotlib's hexbin(bins="log") — compresses the huge dynamic range that skewed point clouds produce so low-density cells stay visually distinguishable instead of collapsing near zero.
Cell color encodes the average of a third variable (values=) inside each bin instead of the point count — the native equivalent of matplotlib's hexbin(C=..., reduce_C_function=numpy.mean), for when the quantity of interest isn't density itself.
White dashed cell borders over a full continuous colorscale (defaults to magma) with no plot border — matches matplotlib's hexbin(edgecolor="white", linestyle="dotted", linewidth=1.5) styling exactly.
Adds 1D density strips above and to the right of the hexbin grid — a joint-plot style combination, showing the marginal distribution of each axis alongside the 2D density.
Tessellates the scattered points into a bounded Voronoi diagram instead of a regular hex grid — each cell's fill encodes local density (values= if supplied, otherwise the inverse of the cell's own area), so density is read from irregular organic cell sizes rather than a uniform lattice. Site points are drawn as small dots on top, echoing the "particle tracking" framing of density-Voronoi science posters.
Draws the Voronoi tessellation as stroke-only outlines — no fill — overlaid across several independently-jittered site positions, so shared edges between frames build up into a dense tangled mesh in the high-density core while the sparse periphery stays a single clean web of long lines. A gray-to-red-to-orange-to-yellow ramp and soft glow pick out the densest region; short red streaks mark low-density outlier points.
Estimates a smooth 2D density field from the scattered points (a Gaussian-kernel distance field, bandwidth auto-derived from point count and plot area) and bands it into discrete iso-level cells — every cell is colored by the highest density threshold it clears, so overlapping seeds fuse into organic merged "islands" instead of staying as separate circles. Cell edges are lightly jittered per-cell for a hand-cut, faceted look, and the outermost frame borrows architectural-drawing conventions: a diagonal-hatch margin, circled grid-reference bubbles, and a data-derived "NODES" tally (seed count, band count, peak-band cell count, coverage, bandwidth).
Joint — Bivariate Panel + Marginals
Signature
sp.joint(title, x, y, *, variant="hexbin", marginal="histogram", panel_variant="", marginal_variant="", bins=24, colorscale=None, categories=None, **kwargs) -> Chart
Aliases: sp.joint, sp.jointplot, sp.joint_plot, sp.bivariate, sp.build_joint
Description
sp.joint() composes a bivariate main panel with top and right marginal strips — the SeraPlot equivalent of seaborn’s jointplot() / JointGrid. variant= (the panel, “inside”) and marginal= (the top/right strips, “outside”) are independent, fully open choices — not a fixed enum. Each accepts the name of any registered SeraPlot family from services/plot/statistical/chart_registry.rs (currently ~50: hexbin, scatter, kde, histogram, bar, violin, boxplot, heatmap, orbita, splom, … the same inventory facet() dispatches through), and panel_variant= / marginal_variant= forward straight through as that family’s own variant=, so hexbin’s outlined/spaced/highlight styles, histogram’s cumulative, etc. all work exactly as they do standalone.
sp.joint(x, y, variant="hexbin", marginal="kde")
sp.joint(x, y, variant="kde", marginal="histogram")
sp.joint(x, y, variant="scatter", marginal="bar")
sp.joint(x, y, variant="hexbin", panel_variant="outlined", marginal="kde")
Each region — panel, top strip, right strip — is a real, independently-rendered SeraPlot chart embedded in its own frame, calling the exact same native build_* function facet() uses for its cells; nothing is reimplemented. That also means regions are not pixel-aligned to a shared coordinate system the way a hand-tuned single-SVG composition would be — each chart keeps its own axes/padding — an inherent tradeoff for genuine “any family, any slot” freedom. Not every family produces something meaningful in every slot — that limitation belongs to the target family’s own data shape, not to joint() itself. As a marginal, a family only ever receives the single axis of values being summarized (plus generic synthetic labels/categories/series/sizes/words so families expecting those shapes still degrade gracefully); families whose own data model is inherently 2D/matrix/hierarchical/paired — candlestick, chord, circle_pack, correlogram, dendrogram, dumbbell, gantt, hive, heatmap, icicle, orbita, parcats, radar, sankey, scatterternary, slope, splom, sunburst — render blank there, the same honest way orbita renders blank as a panel without a hierarchy.
For a true bivariate (2D) density surface — not just kde’s own 1D curve — use variant="kde", panel_variant="contour": kde()’s contour variant fits a product-kernel Gaussian KDE jointly over x and y and shades a smooth density surface with the raw points overlaid, matching seaborn’s smooth_bivariate_kde / joint_kde examples. Legacy preset names (layered_bivariate, joint_kde, kde_smooth, smooth_bivariate_kde, …) already resolve to exactly this.
The preset names from earlier releases (hexbin_marginal, joint_histogram / histogram2d, layered_bivariate, joint_kde, kde_smooth, multiple_bivariate_kde, marginal_ticks, regression_marginals) still work as variant= values and resolve to a real family under the hood (resolve_legacy_panel() in joint/variant.rs), so existing code keeps working. heat_scatter — seaborn’s correlation matrix drawn as sized/colored scatter dots — isn’t a joint/marginal plot at all; use heatmap(variant="bubble") directly.
Data
x (list[float]) — X coordinates. y (list[float]) — Y coordinates.
Parameters
Returns
Chart — object with .html property and .show() method.
The raw entry point — variant= is simply the panel family's own name, and any registered family can fill it.
Hexagonal density panel with KDE curve marginals instead of the default histograms.
A 1D KDE panel with histogram marginals — mixing families freely, not just bivariate-native ones.
Scatter panel with bar-chart marginals.
panel_variant= forwards to the panel family's own variant — here hexbin's outlined cell style, combined with KDE marginals.
The pre-existing layered_bivariate preset name still resolves (to variant="kde", panel_variant="contour" under the hood — a genuine bivariate density surface, not a flat 1D curve) — old code keeps working, and now renders correctly.
Facet Grid — Small Multiples for Any Chart
Signature
sp.facet(family, *, variant=None, facet_by, title="", cols=3, cell_width=320, cell_height=280, **kwargs) -> Chart
Aliases: sp.facet, sp.facet_grid, sp.facetgrid, sp.small_multiples, sp.build_facet
Description
sp.facet() is not a chart family of its own — it is the framework’s generic small-multiples
mechanism, the SeraPlot equivalent of seaborn’s FacetGrid. It takes the name of any existing
2D chart family (family=), splits every data array in the call by a facet_by group key, and
calls that family’s own builder once per group, laying the results out in a grid. Every current
and future chart family gets faceting for free — there is no per-chart code, no separate variant
enum, and no duplicated geometry: services/plot/statistical/facet/mod.rs dispatches straight to
the target family’s real build_* function (histogram::build_histogram,
line::build_line, joint::build_joint, …), so any **kwargs valid for that family
(colorscale=, bins=, variant=, …) is simply forwarded through unfiltered.
Only arguments whose array length matches facet_by get split; everything else (scalars like
bins=, variant=, colorscale=) is copied to every cell unchanged. cols= sets the grid’s
column count, cell_width= / cell_height= size each panel.
Currently wired families: area, bar, boxplot, bubble, bullet, candlestick, dumbbell,
eventplot, funnel, gantt, gauge, heatmap, hexbin, histogram, icicle, joint,
kde, line, lollipop, parallel, pie, radar, ridgeline, scatter, slope, splom,
stackplot, sunburst, treemap, violin, waterfall, wordcloud — see
facet::dispatch() for the exact match table.
Data
Any array field accepted by the target family= (values, labels, x, y, …), plus
facet_by (list[str]) — the group label for each row, same length as the arrays being split.
Recipes
Not every case needs its own SeraPlot demo — some seaborn gallery examples are already one call away from an existing chart page:
- Multiple ECDFs (overlaid, not facetted) —
histogram(variant="cumulative", color_groups=...)orkde(variant="cumulative", categories=...): seaborn overlays several ECDF lines in one panel via category grouping, which is the samecolor_groups=/categories=convention already used for stacking and multi-series density. - Pair grid (KDE diagonal, paired point plots, dot-plot matrix) —
splom(variant="density")orsplom(variant="basic"): SPLOM is SeraPlot’s scatterplot-matrix family and already covers the pairwise-comparison layout these seaborn examples show. - Radial facets —
facet(family="bar", facet_by=...): SeraPlot doesn’t have a native polar/rose histogram variant yet, so this reproduces the faceting technique with a regular bar chart per facet rather than a pixel-perfect polar match. - Facetted time series — same mechanism as the Faceted Lineplot recipe below; swap in a time-ordered
labels=array.
Recipes
One histogram panel per facet_by group — matches seaborn's faceted_histogram example.
One line panel per group — matches seaborn's faceted_lineplot / timeseries_facets examples (use a time-ordered labels= for the latter).
Faceting scales to any number of groups — matches seaborn's many_facets example.
One KDE curve per condition — matches seaborn's multiple_conditional_kde example.
Facets a bivariate joint(variant="histogram2d") panel by a third variable — matches seaborn's three_variable_histogram example, combining the facet mechanism with the [`joint`](joint.md) family's `histogram2d` alias.
Icicle — Hierarchical Banded Chart
Signature
sp.icicle(title, labels, parents, values, *, variant="basic", palette=None, **kwargs) -> Chart
Aliases: sp.icicle, sp.icicles, sp.icicle_chart, sp.icicle_family, sp.build_icicle
Description
sp.icicle() renders a hierarchy as stacked bands: each depth level of the tree is one horizontal row, and the width of each node inside its row is proportional to its value. It is a rectangular alternative to sunburst() and shares the exact same input schema (labels / parents / values), so any dataset already used with sunburst() or treemap() works unchanged.
Hierarchy encoding —
labelslists every node,parentsgives the parent label of each node (""for a root). Leaf values come fromvalues; internal-node values at0are auto-rolled-up from descendants.
Variants
Data
labels (list[str]) — Node labels (one per row). parents (list[str]) — Parent label of each node ("" for roots). values (list[float]) — Leaf values; internal zeros are auto-rolled-up.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Root row at the top, depth-based opacity, white separators between bands.
Rounded corners with a small gap between every node, both across and between rows.
Root column on the left, depth grows rightward instead of downward.
The same hierarchy in polar coordinates instead of cartesian — depth becomes ring radius, horizontal span becomes angular span. The classic icicle/sunburst duality: identical data, identical layout math (`xspan`, `depth`), different coordinate system.
Colors every node by its value rank among its same-depth peers (indigo → red, lowest to highest) instead of by raw share of the grand total — makes cross-branch comparisons at a given level obvious even when one branch is much bigger than another.
Parallel Categories — Categorical Flow Diagram
Signature
sp.parcats(title, axes, category_series, *, variant="basic", **kwargs) -> Chart
Aliases: sp.parcats, sp.parallel_categories, sp.parcats_chart, sp.parallel_categories_chart, sp.build_parcats
Description
sp.parcats() is the categorical counterpart to parallel(): instead of numeric axes with one polyline per row, each axis holds a set of discrete category values, and ribbons flow between adjacent axes with width proportional to how many rows share that pair of categories — the standard chart for tracing how categorical attributes co-occur across a dataset (e.g. gender → survival → class). Internally it builds a node for every distinct (axis, category) pair and an edge for every consecutive-axis transition, then reuses the exact same layered layout engine and bezier ribbon renderer as sankey() (sankey::common::compute_layout / sankey_link_path) — the two charts share their positioning math, not just their visual family.
Data shape —
category_seriesis a list of rows, each row a list of category values with one entry per axis (category_series[row][axis]), mirroring exactly the row-major shape already used byparallel(series=...).
Variants
Data
axes (list[str]) — Axis names, left to right. category_series (list[list[str]]) — One row per observation, one category value per axis.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Flat semi-transparent ribbons colored by their source node.
Dims every ribbon except each node's single heaviest outgoing flow, which is boosted to full opacity with a thin white outline — traces the dominant path through the categories.
Scatter Ternary — Three-Component Composition Plot
Signature
sp.scatterternary(title, x_values, y_values, z_values, *, variant="basic", x_label="A", y_label="B", z_label="C", **kwargs) -> Chart
Aliases: sp.scatterternary, sp.scatter_ternary, sp.ternary, sp.ternary_plot, sp.ternary_scatter, sp.build_scatter_ternary
Description
sp.scatterternary() plots three-component compositions (e.g. soil sand/silt/clay, alloy element fractions, poll shares) inside an equilateral triangle — the standard chart whenever three parts sum to a whole. Each point’s three values are barycentric weights normalized internally (a+b+c need not equal 1 or 100, only their ratio matters), converted to Cartesian coordinates and rendered with a full gridline mesh (three families of lines at 20/40/60/80%, one parallel to each triangle side). Reuses the exact same x_values/y_values/z_values and x_label/y_label/z_label inputs already used by 3D scatter charts — no new parameter shape introduced.
Variants
Data
x_values (list[float]) — First component (A, top vertex). y_values (list[float]) — Second component (B, right vertex). z_values (list[float]) — Third component (C, left vertex). x_label (str) — Label for the top vertex. y_label (str) — Label for the right vertex. z_label (str) — Label for the left vertex. color_values (list[float]) — Continuous values for the "gradient" variant.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Single palette color, semi-transparent markers.
Point radius scales with `color_values` (4.5× range between smallest and largest) instead of a fixed `point_size` — a fourth variable encoded as size on top of the three ternary axes.
Same layout as basic, with each point's label printed beside it — useful once you need to identify specific observations, not just see the overall distribution.
SPLOM — Scatter Plot Matrix
Signature
sp.splom(title, axes, series, *, variant="basic", colorscale=None, **kwargs) -> Chart
Aliases: sp.splom, sp.scatter_matrix, sp.splom_chart, sp.pairplot, sp.scatterplot_matrix, sp.build_splom
Description
sp.splom() lays out every pairwise combination of a set of numeric dimensions as an M×M grid of small scatter plots in a single self-contained chart — the standard first look at a multivariate numeric dataset. It reuses exactly the same row-major data shape as parallel() (axes + series, one row per observation with one value per axis), so any dataset already wired up for parallel coordinates works unchanged. Diagonal cells show the axis name instead of a self-scatter.
Variants
Data
axes (list[str]) — Dimension names. series (list[list[float]]) — One row per observation, one value per dimension.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Off-diagonal cells are plain scatter plots in a single palette color.
Each off-diagonal cell's background is tinted by the Pearson correlation coefficient between its two dimensions (via the same continuous colorscale engine as [`heatmap()`](heatmap.md)), with points overlaid in a neutral color — a heatmap and a SPLOM in one chart.
Every point drawn at 14% opacity with no stroke — overlapping points accumulate into darker regions, revealing density in datasets too large for solid dots to stay readable.
Adds a least-squares trend line to every off-diagonal panel — the classic pairplot-with-regression view for spotting linear relationships across every pair of variables at once.
Stackplot — Stacked Area / Streamgraph
Signature
sp.stackplot(title, x_labels, series, *, variant="basic", series_names=None, **kwargs) -> Chart
Aliases: sp.stackplot, sp.stack_plot, sp.stacked_area, sp.build_stackplot
Description
sp.stackplot() draws multiple series as cumulatively stacked areas over a shared x-axis — reuses the exact same x_labels/series/series_names input shape as multiline(), so any dataset already used with multiline() works unchanged. Negative values are clamped to 0 before stacking (a stack has no meaningful negative contribution).
Variants
Data
x_labels (list[str]) — Shared x-axis point labels. series (list[list[float]]) — One list of values per series, same length as x_labels. series_names (list[str]) — Legend label per series.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Traditional zero-baseline stacking, each series added on top of the previous one's cumulative total.
Centered ("silhouette") baseline — at every x-point the stack is centered around zero (`baseline = -total/2`) instead of starting at zero, giving the flowing ThemeRiver look.
100%-stacked — every series is divided by the x-point's total before stacking, so the top of the stack is always 1.0. Shows share of total over time instead of absolute magnitude.
Polar-wrapped stacking — each x-point becomes an angle around a circle instead of a position along an axis, and the cumulative bands grow outward as concentric rings from a small central hole. A genuinely different read on the same stacked data: total magnitude becomes an overall silhouette size, and each series' share becomes a colored band thickness at that angle.
Cartesian stacking rendered as smooth, glowing ribbons: quadratic-bezier-smoothed band edges, a top-to-bottom depth gradient per series, and a soft drop-shadow separating each layer — a more atmospheric, editorial look than the sharp-edged basic stack.
Plot Web — Force-Directed Bubble Network
Signature
sp.plot_web(title, x_values, y_values, *, variant="scatter", sizes=None, labels=None, groups=None, **kwargs) -> Chart
Aliases: sp.plot_web, sp.web_plot, sp.plotweb, sp.carbon_web, sp.web_chart, sp.flow_web
Description
sp.plot_web() places each data point as a sized bubble node and lets the two positional axes (x_values/y_values) drive layout instead of a fixed cartesian grid — the scatter variant reads them as literal coordinates on a light-trail canvas, the radial variant re-projects them onto concentric rings around a center point. Bubble radius scales between min_r and max_r from the sizes array (or a constant radius if omitted), and groups assigns a categorical color from palette per node.
Variants
Data
x_values (list[float]) — Horizontal position (scatter) or angle-driving value (radial). y_values (list[float]) — Vertical position (scatter) or radius-driving value (radial). sizes (list[float]) — Per-node value driving bubble radius between min_r and max_r. labels (list[str]) — Per-node hover label. groups (list[str]) — Per-node category, colored from palette.
Parameters
Themes
Returns
Chart — object with .html property and .show() method.
Parameters
Nodes positioned directly from `x_values`/`y_values`, connecting light-trail background.
Nodes re-projected onto concentric rings, angle and radius derived from the same input coordinates.
3D Charts
SeraPlot provides 17 three-dimensional chart types rendered with full WebGL acceleration.
| Chart | Function |
|---|---|
| Scatter 3D | scatter3d() |
| Bar 3D | bar3d() |
| Line 3D | line3d() |
| Radar 3D | radar3d() |
| Lollipop 3D | lollipop3d() |
| KDE 3D | kde3d() |
| Ridgeline 3D | ridgeline3d() |
| Bubble 3D | bubble3d() |
| Pie 3D | pie3d() |
| Violin 3D | violin3d() |
| Heatmap 3D | heatmap3d() |
| Candlestick 3D | candlestick3d() |
| Dumbbell 3D | dumbbell3d() |
| Funnel 3D | funnel3d() |
| Sunburst 3D | sunburst3d() |
| Stacked Bar 3D | stacked_bar3d() |
| Globe 3D | globe3d() |
SeraPlot propose 17 types de graphiques tridimensionnels rendus avec acc\u00e9l\u00e9ration WebGL compl\u00e8te.
| Graphique | Fonction |
|---|---|
| Nuage de points 3D | scatter3d() |
| Barres 3D | bar3d() |
| Courbe 3D | line3d() |
| Radar 3D | radar3d() |
| Sucette 3D | lollipop3d() |
| KDE 3D | kde3d() |
| Ridgeline 3D | ridgeline3d() |
| Bulles 3D | bubble3d() |
| Camembert 3D | pie3d() |
| Violon 3D | violin3d() |
| Heatmap 3D | heatmap3d() |
| Bougie 3D | candlestick3d() |
| Halt\u00e8re 3D | dumbbell3d() |
| Entonnoir 3D | funnel3d() |
| Sunburst 3D | sunburst3d() |
| Barres empil\u00e9es 3D | stacked_bar3d() |
| Globe 3D | globe3d() |
Scatter Chart 3D
Signature
sp.scatter3d(title, x=None, y=None, z=None, *, color_hex=0x6366F1, palette=None, bg_color="#1a1a2e", width=900, height=600, x_label="", y_label="", z_label="", **kwargs) -> Chart
Aliases: sp.build_scatter3d_chart(), sp.scatter_3d(), sp.scatter3d_chart().
Description
sp.scatter3d() scatters individual points in 3D space, useful for clustering and outlier/regression dream methods.
Parameters
Returns
Chart object with an .html property and a .show() method.
Bar Chart 3D
Signature
sp.bar3d(title, x=None, y=None, z=None, *, color_hex=0x6366F1, palette=None, bg_color="#1a1a2e", width=900, height=600, x_label="", y_label="", z_label="", **kwargs) -> Chart
Aliases: sp.build_bar3d_chart(), sp.bar_3d(), sp.bar3d_chart(), sp.bar3d_family(), sp.bars3d().
Description
sp.bar3d() renders bars as extruded rectangular prisms on a WebGL-style canvas scene.
Parameters
Returns
Chart object with an .html property and a .show() method.
Line Chart 3D
Signature
sp.line3d(title, x=None, y=None, z=None, *, color_hex=0x6366F1, palette=None, bg_color="#1a1a2e", width=900, height=600, x_label="", y_label="", z_label="", **kwargs) -> Chart
Aliases: sp.build_line3d_chart(), sp.line_3d(), sp.line3d_chart(), sp.line3d_family(), sp.lines3d().
Description
sp.line3d() traces a connected line through points in 3D space.
Parameters
Returns
Chart object with an .html property and a .show() method.
Radar Chart 3D
Signature
sp.radar3d(title, axes=None, series=None, *, series_names=None, palette=None, bg_color="#1a1a2e", width=700, height=600, max_val=None, fill_opacity=0.25, ring_gap=1.0, **kwargs) -> Chart
Aliases: sp.build_radar3d_chart(), sp.radar_3d(), sp.radar3d_chart(), sp.radar3d_family().
Description
sp.radar3d() renders a 3D radar (spider) chart in a WebGL-like canvas scene. Each series becomes one ring of points around the shared axes, stacked along the depth axis instead of overlaid flat like the 2D radar.
Parameters
ring_gap controls the distance between series rings along the depth axis: 1.0 (default) keeps the original one-unit spacing per series, while lower values pull the rings closer together, down to 0.0 where every ring collapses onto the same plane.
Returns
Chart object with an .html property and a .show() method.
Lollipop Chart 3D
Signature
sp.lollipop3d(title, x=None, y=None, z=None, *, color_labels=None, x_label="", y_label="", z_label="", bg_color="#1a1a2e", width=900, height=560, **kwargs) -> Chart
Aliases: sp.build_lollipop3d_chart(), sp.lollipop_3d(), sp.lollipop3d_chart().
Description
sp.lollipop3d() renders a stick-and-ball lollipop marker for each point in 3D space.
Parameters
Returns
Chart object with an .html property and a .show() method.
KDE Chart 3D
Signature
sp.kde3d(title, categories=None, values=None, *, x_label="", y_label="", z_label="", bg_color="#1a1a2e", width=900, height=560, **kwargs) -> Chart
Aliases: sp.build_kde3d_chart(), sp.kde_3d(), sp.kde3d_chart(), sp.density3d().
Description
sp.kde3d() renders kernel-density estimate ridges as layered 3D surfaces, one per category.
Parameters
Returns
Chart object with an .html property and a .show() method.
Ridgeline Chart 3D
Signature
sp.ridgeline3d(title, categories=None, labels=None, values=None, *, x_label="", y_label="", z_label="", bg_color="#1a1a2e", width=900, height=560, **kwargs) -> Chart
Aliases: sp.build_ridgeline3d_chart(), sp.ridgeline_3d(), sp.ridgeline3d_chart(), sp.joy_plot3d().
Description
sp.ridgeline3d() renders overlapping density ridges as layered 3D surfaces.
Parameters
Returns
Chart object with an .html property and a .show() method.
Bubble Chart 3D
Signature
sp.bubble3d(title, x=None, y=None, z=None, sizes=None, *, palette=None, bg_color="#1a1a2e", width=900, height=600, x_label="", y_label="", z_label="", **kwargs) -> Chart
Aliases: sp.build_bubble3d_chart(), sp.bubble_3d().
Description
sp.bubble3d() scatters points in 3D space with marker size mapped to an extra value dimension.
Parameters
Returns
Chart object with an .html property and a .show() method.
Pie Chart 3D
Signature
sp.pie3d(title, labels=None, values=None, *, sort_order="none", bg_color="#1a1a2e", width=900, height=560, **kwargs) -> Chart
Aliases: sp.build_pie3d_chart(), sp.pie_3d(), sp.pie3d_chart(), sp.pie3d_family(), sp.pies3d().
Description
sp.pie3d() renders a pie chart as an extruded 3D disc with wedge depth.
Parameters
Returns
Chart object with an .html property and a .show() method.
Violin Chart 3D
Signature
sp.violin3d(title, categories=None, labels=None, values=None, *, x_label="", y_label="", z_label="", bg_color="#1a1a2e", width=900, height=560, **kwargs) -> Chart
Aliases: sp.build_violin3d_chart(), sp.violin_3d(), sp.violin3d_chart(), sp.violins3d().
Description
sp.violin3d() renders distribution violins as layered 3D surfaces, one per category.
Parameters
Returns
Chart object with an .html property and a .show() method.
Heatmap Chart 3D
Signature
sp.heatmap3d(title, labels=None, categories=None, matrix=None, *, x_labels=None, x_label="", y_label="", z_label="", bg_color="#1a1a2e", width=900, height=560, **kwargs) -> Chart
Aliases: sp.build_heatmap3d_chart(), sp.heatmap_3d(), sp.heatmap3d_chart(), sp.heatmaps3d().
Description
sp.heatmap3d() renders a value matrix as a grid of extruded 3D cells.
Parameters
Returns
Chart object with an .html property and a .show() method.
Candlestick Chart 3D
Signature
sp.candlestick3d(title, labels=None, open=None, high=None, low=None, close=None, *, x_label="Price", y_label="Bar", z_label="", bg_color="#1a1a2e", width=900, height=560, **kwargs) -> Chart
Aliases: sp.build_candlestick3d_chart(), sp.candlestick_3d(), sp.candlestick3d_chart(), sp.ohlc3d().
Description
sp.candlestick3d() renders OHLC candlesticks as 3D bars along a depth axis.
Parameters
Returns
Chart object with an .html property and a .show() method.
Dumbbell Chart 3D
Signature
sp.dumbbell3d(title, labels=None, start=None, end=None, *, y_label="", bg_color="#1a1a2e", width=900, height=560, **kwargs) -> Chart
Aliases: sp.build_dumbbell3d_chart(), sp.dumbbell_3d(), sp.dumbbell3d_chart().
Description
sp.dumbbell3d() connects a start and end value per category with a 3D dumbbell shape.
Parameters
Returns
Chart object with an .html property and a .show() method.
Funnel Chart 3D
Signature
sp.funnel3d(title, labels=None, values=None, *, sort_order="none", bg_color="#1a1a2e", width=900, height=560, **kwargs) -> Chart
Aliases: sp.build_funnel3d_chart(), sp.funnel_3d(), sp.funnel3d_chart().
Description
sp.funnel3d() renders a conversion funnel as stacked 3D frustum segments.
Parameters
Returns
Chart object with an .html property and a .show() method.
Sunburst Chart 3D
Signature
sp.sunburst3d(title, labels=None, parents=None, values=None, *, bg_color="#1a1a2e", width=900, height=560, **kwargs) -> Chart
Aliases: sp.build_sunburst3d_chart(), sp.sunburst_3d(), sp.sunburst3d_chart().
Description
sp.sunburst3d() renders a hierarchical sunburst as concentric 3D rings.
Parameters
Returns
Chart object with an .html property and a .show() method.
Stacked Bar Chart 3D
Signature
sp.stacked_bar3d(title, labels=None, series=None, *, x_label="", y_label="", z_label="", bg_color="#1a1a2e", width=900, height=560, **kwargs) -> Chart
Aliases: sp.build_stacked_bar3d_chart(), sp.stacked_bar_3d(), sp.stacked_bar3d_chart().
Description
sp.stacked_bar3d() stacks multiple series into segmented 3D bars.
Parameters
Returns
Chart object with an .html property and a .show() method.
Globe Chart 3D
Signature
sp.globe3d(title, lats=None, lons=None, values=None, *, project="orthographic", bg_color="#0a0a1a", width=900, height=600, **kwargs) -> Chart
Aliases: sp.build_globe3d_chart(), sp.globe_3d(), sp.globe3d_chart(), sp.globe().
Description
sp.globe3d() plots geo-located values as a heat-colored 3D globe.
Parameters
Returns
Chart object with an .html property and a .show() method.
Map Charts
SeraPlot provides geographic chart types for visualizing spatial data on any registered region set — from whole-world country data down to a single country’s states or provinces. Every chart type below reads its variants, parameters and region sets live from the same register/inventory system the 2D charts use.
| Chart | Function |
|---|---|
| Bubble Map | bubble_map() |
| Choropleth | choropleth() |
| Flow Map | flow_map() |
| Contour Map | contour_map() |
| Vector Field Map | vector_field_map() |
Choropleth and Bubble Map both include a real orthographic (“globe from space”) and polar (azimuthal, pole-centered) projection, computed from an actual pixel-to-latitude/longitude inversion of the world map — not a cosmetic re-skin. Contour Map and Vector Field Map take raw lats/lons samples directly rather than named regions — for continuous scalar fields (temperature, pressure, precipitation) and vector fields (wind, currents) that don’t line up with any political boundary.
Any country, any region
map selects which registered geographic region set to draw ("world" for individual countries, "usa_states" for US states, and any future region set added the same way); region restricts drawing to one named group inside that set (a continent or political/economic bloc for "world", a Census region for "usa_states").
Bridging to 2D charts
sp.region_labels(map=..., group=...) returns the same ordered label list a map for that region draws — build a map and a matching bar/line chart from the exact same named entities:
import seraplot as sp
labels = sp.region_labels(map="usa_states", group="West")
values = [my_data[l] for l in labels]
geo = sp.bubble_map("Western Sales", labels=labels, values=values, map="usa_states", region="West", variant="proportional")
bars = sp.bar("Western Sales", labels=labels, values=values)
SeraPlot propose des types de graphiques géographiques pour visualiser des données spatiales sur n’importe quel ensemble de régions enregistré — des données par pays à l’échelle mondiale jusqu’aux états ou provinces d’un seul pays. Chaque type de graphique ci-dessous lit ses variantes, paramètres et ensembles de régions en direct depuis le même système de register/inventory que les graphiques 2D.
| Graphique | Fonction |
|---|---|
| Carte à bulles | bubble_map() |
| Choropleth | choropleth() |
| Carte de flux | flow_map() |
| Carte de contours | contour_map() |
| Carte de champ vectoriel | vector_field_map() |
Choropleth et Bubble Map incluent tous deux une vraie projection orthographique (« globe vu de l’espace ») et polaire (azimutale, centrée sur un pôle), calculée à partir d’une véritable inversion pixel vers latitude/longitude de la carte du monde — pas un simple habillage cosmétique. Carte de contours et Carte de champ vectoriel prennent directement des échantillons bruts lats/lons plutôt que des régions nommées — pour des champs scalaires continus (température, pression, précipitations) et des champs vectoriels (vent, courants) qui ne s’alignent sur aucune frontière politique.
N’importe quel pays, n’importe quelle région
map sélectionne quel ensemble de régions géographiques enregistré dessiner ("world" pour les pays individuels, "usa_states" pour les états américains, et tout futur ensemble de régions ajouté de la même façon) ; region restreint le dessin à un seul groupe nommé de cet ensemble (un continent ou un bloc politique/économique pour "world", une région de recensement pour "usa_states").
Passerelle vers les graphiques 2D
sp.region_labels(map=..., group=...) retourne la même liste de labels ordonnée qu’une carte dessine pour cette région — construisez une carte et un graphique à barres/lignes assorti depuis exactement les mêmes entités nommées :
import seraplot as sp
labels = sp.region_labels(map="usa_states", group="West")
values = [my_data[l] for l in labels]
geo = sp.bubble_map("Ventes de l'Ouest", labels=labels, values=values, map="usa_states", region="West", variant="proportional")
bars = sp.bar("Ventes de l'Ouest", labels=labels, values=values)
Bubble Map
Signature
sp.bubble_map(title, labels=None, values=None, *, variant="proportional", map="world", region=None, center_lat=None, center_lon=None, **kwargs) -> Chart
Aliases: sp.bubble_map, sp.bubblemap, sp.bubble_map_chart, sp.geo_bubble, sp.geo_bubble_map, sp.build_bubble_map
Description
sp.bubble_map() marks matched regions on a map with a proportionally-sized circle at each centroid by default — real bubbles, not filled shapes; pick variant="filled" instead when it’s the regions themselves the reader should compare, not a magnitude. map selects which registered geographic region set to draw; region optionally restricts drawing to one named group inside that set. All three are read live from the same register/inventory system the rest of the framework uses — nothing below is hardcoded.
Variants
Region Sets
Parameters
Returns
Chart — object with .html property and .show() method.
The default, and the real graduated-symbol map: the base is muted, and a circle sits on each matched region’s centroid, its radius scaled by sqrt(value) so area — not radius — reads proportionally to magnitude. min_bubble_size / max_bubble_size set the radius range in pixels.
Not a bubble at all by design: each matched region filled solid with its own palette color and labeled at its centroid — a categorical highlight map, for when it’s the regions themselves the reader should compare rather than a magnitude. Reach for choropleth instead if the fill color should encode a continuous value.
Proportional circles projected onto an orthographic sphere — every centroid is converted to real latitude/longitude and re-projected with actual globe math, so bubbles on the far side simply aren’t drawn. center_lat / center_lon aim the view (default 15, 10). Only region sets that expose a coordinate inversion support this — today, "world".
A hollow circle instead of a filled one — stroke-width carries the magnitude, not just radius, so a thin ring reads small and a thick ring reads large even where two circles overlap and one would otherwise hide behind the other.
Two faint concentric rings behind the solid marker, fading out with distance — a static radar-ping look, well suited to data that reads as an alert or a hotspot rather than a plain quantity.
A density map, not a values map: pass raw lats/lons samples instead of labels/values and each point is binned into a hexagonal grid — real axial hex-coordinate binning with cube-coordinate rounding, the same technique a proper geographic hexbin uses, not a nearest-center search. Each hexagon is sized and colored by how many samples landed inside it, with the count itself labeled once a cell holds more than one.
Choropleth Map
Signature
sp.choropleth(title, labels=None, values=None, *, variant="sequential", map="world", region=None, center_lat=None, center_lon=None, **kwargs) -> Chart
Aliases: sp.choropleth, sp.choropleths, sp.choropleth_map, sp.choropleth_chart, sp.geo_map, sp.build_choropleth
Description
sp.choropleth() fills country or region polygons by a scalar value. variant selects the color-classification strategy; map selects which registered geographic region set to draw; region optionally restricts drawing to one named group inside that set (e.g. a continent or a census region). All three are read live from the same register/inventory system the rest of the framework uses — nothing below is hardcoded.
Variants
Region Sets
Parameters
Returns
Chart — object with .html property and .show() method.
Continuous heat gradient from the lowest to the highest value in the visible set — the classic choropleth, one smooth color ramp.
Splits the values into bins discrete quantile classes (equal counts per class, not equal-width) and paints each class its own step of a viridis scale — the standard cartographic technique for reading patterns at a glance instead of guessing shades on a gradient.
Red–white–blue diverging scale centered on diverging_midpoint (default 0) — built for signed data where the story is above/below a reference point: year-over-year change, deviation from a target, a delta rather than a magnitude.
The globe, seen from space: every polygon vertex is converted from map pixels to real latitude/longitude and re-projected with actual orthographic sphere math, then clipped to the visible hemisphere. center_lat / center_lon aim the view (default 15, 10). Only region sets that expose a coordinate inversion support this — today, "world".
Azimuthal equidistant, centered on a pole by default (center_lat=90, center_lon=0) — the classic polar-projection read, where distance from the center is true distance from the chosen point.
Two value series at once instead of one: values and secondary_values each split into three bins, looked up in a 3×3 color grid instead of a single ramp, so a single glance separates “high on both”, “high on one, low on the other”, and “low on both” — a technique real cartographers use and almost no charting library ships.
One dot per fixed unit of value, scattered at random inside the real region outline (rejection-sampled against the actual polygon, not just its bounding box) instead of one flat fill color — texture and density carry the magnitude the way an old-school population map does.
Not data-driven at all — the real day/night terminator, solved from the spherical law of cosines for where the sun sits exactly on the horizon, not a cosmetic gradient band. center_lat/center_lon are read as the current subsolar point (the latitude tracks the season, the longitude the time of day); the correct pole is shaded by comparing which one the sun’s declination actually leaves in darkness, and a sun glyph marks the subsolar point itself. labels/values are ignored entirely for this variant.
Flow Map
Signature
sp.flow_map(title, labels=None, *, edges_i=None, edges_j=None, edges_w=None, variant="arc", map="world", region=None, **kwargs) -> Chart
Aliases: sp.flow_map, sp.flowmap, sp.flow_map_chart, sp.geo_flow, sp.connection_map, sp.great_circle_map
Description
sp.flow_map() draws origin → destination flows between matched regions. labels names every node; edges_i / edges_j are index pairs into labels for each flow’s origin and destination (the same edge-list shape sankey() uses); edges_w is each flow’s magnitude, which sets line width. variant selects the curve style; map selects which registered geographic region set to draw; region optionally restricts drawing to one named group inside that set. All of this is read live from the same register/inventory system the rest of the framework uses — nothing below is hardcoded.
Variants
Region Sets
Parameters
Returns
Chart — object with .html property and .show() method.
Each flow bows outward along a quadratic curve, the classic “flight path” look — reads cleanly even when several flows share an endpoint, since they fan out instead of stacking on the same line.
The direct-line reading: origin and destination joined by a plain segment, width still scaled to each flow’s magnitude.
The arc plus real motion: a thin white dashed stroke rides on top of every flow, driven by a stroke-dashoffset @keyframes animation embedded right in the svg, so direction reads as travel instead of a static line (falls back to still when the viewer has prefers-reduced-motion on).
Every flow becomes a real tapered band instead of a stroked line — wide at the origin, narrowing toward the destination, sampled along the same curve the arc variant bows through and offset perpendicular to it at each point. Reads like a river or a Sankey band laid over the map.
Not pairwise connections at all — a single ordered path through lats/lons, storm-track style, with field as the intensity at each point (wind speed, category, whatever the reader should read off the color). Every point gets its own marker on a low/mid/high color tier, and the most recent leg ends in a direction arrowhead. labels/edges_i/edges_j/edges_w are ignored for this variant.
Contour Map
Signature
sp.contour_map(title, lats=None, lons=None, field=None, *, variant="filled", bins=6, color_low=None, color_high=None, **kwargs) -> Chart
Aliases: sp.contour_map, sp.contourmap, sp.contour_map_chart, sp.geo_contour, sp.isarithmic_map, sp.scalar_field_map, sp.build_contour_map
Description
sp.contour_map() takes scattered lats/lons/field samples — not named regions — and interpolates them into a continuous surface draped over the world outline: temperature, pressure, precipitation, anything that varies smoothly across space rather than stopping at a border. Interpolation is real inverse-distance-weighting over a grid spanning the data’s own bounding box (padded, not the whole world), so the surface only ever describes the region actually sampled. bins controls how many discrete color bands (filled) or contour levels (isolines) the value range is split into.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
The default: the interpolated grid rendered as colored cells, a smooth heat-style drape from color_low to color_high across the sampled region — the fastest way to read where a field is high or low at a glance, without needing to trace individual lines.
Real contour lines, not an approximation: genuine marching squares over the same interpolated grid, tracing bins threshold levels by linearly interpolating exactly where each grid cell’s edges cross the level — the same technique a real isobar or isotherm map uses, saddle cases included.
Vector Field Map
Signature
sp.vector_field_map(title, lats=None, lons=None, u=None, v=None, *, variant="arrows", color_low=None, color_high=None, **kwargs) -> Chart
Aliases: sp.vector_field_map, sp.vectorfieldmap, sp.vector_field_map_chart, sp.wind_map, sp.quiver_map, sp.geo_vector_field, sp.build_vector_field_map
Description
sp.vector_field_map() draws a direction-and-magnitude sample at each lats/lons point from its u (east-west) and v (north-south) components — wind, ocean currents, anything that has a direction as well as a size at a location. Magnitude scales both length and color from color_low to color_high; direction accounts for the map’s own north-is-up orientation, not raw pixel angles.
Variants
Parameters
Returns
Chart — object with .html property and .show() method.
The default, and the classic weather-map quiver: one shaft-and-arrowhead per sample, pointing the true u/v direction, length and color both scaled by magnitude so the strongest vectors are immediately the most visible ones.
True flow integration, not a static hint field: a grid of seed points is traced forward through the inverse-distance-weighted u/v field step by step, each curving path following the local direction it’s actually moving through until the flow dies out or it leaves the sampled region — the same idea a real streamline or particle-flow visualization uses, not a decorative curve.
Canvas Composition
Canvas is SeraPlot’s composition layer: a free-form surface where you place
multiple independently-built Chart objects, images, shapes and text, wire
them together, and export the whole thing as one Chart. It is the tool for
building dashboards, annotated stories and multi-panel figures that a single
chart function can’t produce on its own.
import seraplot as sp
bar = sp.grouped_bar("", labels=[...], values=[...], series_names=[...])
cv = sp.canvas(1280, 800, "#0a0a0f")
bars_ref = cv.place(bar, 60, 60, 560, 420, name="revenue")
chart = cv.build()
Every placement/drawing method accepts an optional name keyword. A named
element gets a data-sp-name="..." attribute in the rendered HTML, which is
what makes it addressable by every other method below (nudge, resize,
style, script, group, link, refill, dev-mode dragging, …). Naming
is free and has no runtime cost — name anything you might want to touch again.
Constructing a canvas
cv = sp.canvas(width: int, height: int, bg: str = "#0a0a0f")
Placing charts and images
| Method | Effect |
|---|---|
place(chart, x, y, w, h, rotation=0, opacity=1, clip="", group="", name="") | Places a Chart on the canvas at (x, y) sized w×h. Returns a chart_ref (int) used by pin/connect/attach_*. |
image(src, x, y, w, h, rotation=0, opacity=1, clip="", group="", name="") | Places a PNG/JPEG/GIF/WebP/SVG image. src is a local file path (read and base64-embedded), a data: URI, or an http(s):// URL. |
slot(name, x, y, w, h) | Reserves a named region without placing anything yet. |
fill(slot_name, chart, ...) | Places a chart using a previously-declared slot’s geometry. |
grid(x, y, w, h, rows, cols, gap_x=0, gap_y=0) | Declares a rows × cols grid of slots named cell_{row}_{col} and returns the list of names in row-major order — no manual coordinate math for dashboards. |
refill(name, chart) -> bool | Replaces a named, already-placed chart’s content in place — same position, size, rotation, style. Use this (not place/fill again) whenever you’re refreshing a panel with new data. |
clip accepts "circle", "diamond", "hex", "tri", "pentagon" for
non-rectangular chart/image framing.
Calling place/fill again with a name that’s already taken by a placed
chart updates that chart in place instead of stacking a duplicate — so
fill(slot, new_chart, name="panel") is safe to call repeatedly.
Micro-tools: shared conventions
text, line, curve, connector, circle, ring, rect, polygon,
path, arrow, annotate and gradient are the low-level drawing
primitives everything else in Canvas (including voronoi(), below) is
built from. They draw directly on the canvas SVG, and share two keywords:
layer="fg"|"bg"—"bg"renders under everyplace()d chart/image,"fg"(the default) renders on top. Use"bg"for backdrops, card panels and watermark-style decoration;"fg"for annotations, callouts and anything that should sit above the data.name=""— makes the element addressable afterward bynudge,resize,style,script,group,link, and draggable indev()mode. Naming is free; name anything you might want to touch again.
None of them require a Chart — a canvas full of nothing but these
primitives is a valid way to hand-draw a diagram SeraPlot has no dedicated
chart function for (see the closing example on this page).
Lines, curves & connectors
Four ways to draw between points — pick based on how many points and how rigid the shape between them needs to be.
line — straight segment
cv.line(x1, y1, x2, y2, color="#ffffff", width=1.5, dash="", opacity=1.0,
cap="round", layer="fg", hover_group="", name="")
| Parameter | Type | Default | Description |
|---|---|---|---|
x1, y1, x2, y2 | float | required | Endpoints, in canvas pixels |
color | str | "#ffffff" | Stroke color |
width | float | 1.5 | Stroke width |
dash | str | "" | SVG stroke-dasharray, e.g. "6,4" for a dashed line |
opacity | float | 1.0 | 0–1 |
cap | str | "round" | Line-end style: "round", "butt", or "square" |
hover_group | str | "" | Adds an invisible wide hit-area alongside the (often thin) visible stroke, so the line reacts to hover as part of a link() group |
name | str | "" | Addressable name |
The plain straight-line primitive — axes, dividers, guide lines, or one leg of a hand-built diagram.
curve — smooth line through N points
cv.curve(points, color="#ffffff", width=1.5, opacity=1.0, tension=1.0,
fill="none", layer="fg", name="")
| Parameter | Type | Default | Description |
|---|---|---|---|
points | list[[x, y]] | required | Three or more waypoints the curve passes through |
tension | float | 1.0 | Catmull-Rom tension: 0 collapses to straight segments between points, 1 is a standard smooth spline, higher values pull the curve into more pronounced bulges past each point |
fill | str | "none" | Fills the area under the curve when set — a hand-drawn area-chart look |
(color / width / opacity / layer / name — same as line) |
Unlike connector (below), which always routes between exactly two points,
curve interpolates through an arbitrary polyline of waypoints. Reach for
it for hand-drawn trend lines, sparkline-style decoration, or free-form
organic strokes that aren’t tied to a Chart’s own axes:
cv.curve([[40, 300], [140, 120], [260, 260], [400, 80]],
color="#22c55e", width=3, tension=0.8, name="trend-doodle")
connector — S-curve between two points
cv.connector(x1, y1, x2, y2, color="#ffffff", width=1.5, opacity=1.0,
bend=0.5, layer="fg", name="")
| Parameter | Type | Default | Description |
|---|---|---|---|
bend | float | 0.5 | Fraction along the dominant axis (whichever of dx/dy is larger) where the bezier control points sit — 0.5 gives a symmetric S-curve; values toward 0 or 1 skew the curve’s midpoint toward one end |
(others — same as line) |
The “flowchart wire” primitive: one cubic bezier that always produces a
clean S- or L-shaped route between two points, regardless of their relative
position. Use it to link two place()d panels or two named elements
without hand-computing control points — connect() (under Connecting
two charts, below) draws the exact same curve, but reads its endpoints
from registered pins instead of raw coordinates.
arrow — directional line with an arrowhead
cv.arrow(x1, y1, x2, y2, color="#ffffff", width=1.5, head_size=4.0,
opacity=1.0, layer="fg", name="")
| Parameter | Type | Default | Description |
|---|---|---|---|
head_size | float | 4.0 | Arrowhead size in px — the marker scales with this, not with width |
(others — same as line) |
A line with an SVG <marker> arrowhead baked onto its end, for pointing
at something rather than just connecting two things.
Shapes
Five ways to fill or stroke a region, from most constrained to most free-form.
circle / ring
cv.circle(cx, cy, r, fill="none", stroke="#ffffff", stroke_width=1.5,
opacity=1.0, layer="fg", hover_group="", name="")
cv.ring(cx, cy, inner_r, outer_r, fill="#ffffff", stroke="none",
stroke_width=0.0, opacity=1.0, layer="fg", name="")
ring is a donut: the filled region strictly between inner_r and
outer_r, built from two arcs combined with fill-rule="evenodd" rather
than a solid <circle>. Use it for radial progress rings, avatar frames,
or halo highlights — anywhere circle’s solid disc would cover whatever
sits underneath it.
rect
cv.rect(x, y, w, h, fill="none", stroke="#ffffff", stroke_width=1.5,
rx=0.0, opacity=1.0, rotation=0.0, layer="fg", name="")
rx rounds the corners; rotation (degrees) spins the rect around its own
center. The two together cover card backgrounds, chips/badges, and simple
category-key swatches.
polygon
cv.polygon(points, fill="none", stroke="#ffffff", stroke_width=1.5,
opacity=1.0, layer="fg", name="")
A closed shape through an arbitrary list[[x, y]] of vertices — the
primitive voronoi() itself is built from (each cell it returns is one
polygon() call under the hood). Use it directly for triangular/diamond
markers, custom badge shapes, or any closed region rect/circle can’t
express.
path
cv.path(d, fill="none", stroke="#ffffff", stroke_width=1.5, opacity=1.0,
layer="fg", name="")
The escape hatch: d is a raw SVG path-data string ("M ... L ... A ... Z")
for shapes none of the other primitives cover — logos, icons, arcs with a
specific sweep, or geometry computed by your own code. This is exactly how
icicle()’s "radial" variant draws its annular
sectors internally: hand-built M/A/L/Z strings, no separate arc
primitive needed.
Text & annotations
text
cv.text(content, x, y, size=24.0, color="#ffffff", weight="normal",
anchor="start", rotation=0.0, letter_spacing=0.0,
font="sans-serif", opacity=1.0, layer="fg", name="")
anchor is the SVG text-anchor ("start", "middle", "end") relative
to (x, y) — "middle" centers a title over a panel, "end" right-aligns
a value next to an axis.
annotate — leader-line label
cv.annotate(text, ax, ay, tx, ty, color="#ffffff", size=13.0,
line_dash="", line_width=1.0, bg="", layer="fg", name="")
| Parameter | Type | Default | Description |
|---|---|---|---|
ax, ay | float | required | The point being annotated — where the leader line starts |
tx, ty | float | required | Where the text itself sits — where the leader line ends |
text | str | required | Supports \n for multi-line labels |
line_dash | str | "" | Dash pattern for the leader line, e.g. "4,3" |
bg | str | "" | Background color behind the text; ""/"none" draws no box |
Unlike a plain text() + line() pair, annotate() auto-routes a clean
two-segment elbow between (ax, ay) and (tx, ty) (picking the elbow
point from whichever axis has the larger offset) and sizes its own
background box to fit the text. The tool for “this specific point,
labeled, with a callout line” — a bar’s peak, a scatter outlier.
annotate_at() (under Connecting two charts, below) is the
pin-aware version of this same primitive, for labeling a point inside a
place()d chart instead of a raw canvas coordinate.
Color: gradient
cv.gradient(id, from_color, to_color, x1=0.0, y1=0.0, x2=1.0, y2=0.0)
Registers an SVG linearGradient definition — x1/y1/x2/y2 live in the
0..1 objectBoundingBox space, so (0,0)→(1,0) is left-to-right and
(0,0)→(0,1) is top-to-bottom. It draws nothing by itself; call it once,
then reference fill=f"url(#{id})" on any subsequent rect/circle/
polygon/path:
cv.gradient("card-glow", "#6366f1", "#0a0a0f", x1=0, y1=0, x2=0, y2=1)
cv.rect(40, 40, 300, 200, fill="url(#card-glow)", rx=18, name="card")
Composing micro-tools: a radial dial
None of the primitives above need a Chart at all — a canvas built only
from them is a fully valid way to hand-draw a widget SeraPlot has no
dedicated chart function for. ring only ever draws a complete annulus,
so a partial-sweep progress dial needs path with a hand-computed SVG arc
— exactly the “escape hatch” role described above:
import math
import seraplot as sp
def arc_path(cx, cy, r, pct):
start = -math.pi / 2
end = start + 2 * math.pi * pct
x1, y1 = cx + r * math.cos(start), cy + r * math.sin(start)
x2, y2 = cx + r * math.cos(end), cy + r * math.sin(end)
large_arc = 1 if pct > 0.5 else 0
return f"M {x1:.2f},{y1:.2f} A {r},{r} 0 {large_arc},1 {x2:.2f},{y2:.2f}"
cv = sp.Canvas(300, 300, bg="#0a0a0f")
cv.gradient("dial-g", "#6366f1", "#22d3ee", x1=0, y1=0, x2=1, y2=1)
cv.ring(150, 150, 100, 112, fill="#1e293b", name="track")
cv.path(arc_path(150, 150, 106, 0.72), fill="none", stroke="url(#dial-g)",
stroke_width=12, name="progress")
cv.text("72%", 150, 158, size=34, color="#f8fafc", weight="800",
anchor="middle", name="pct-label")
chart = cv.build()
ring draws the static background track, path draws the live progress
arc on top of it (stroked with the gradient defined a line earlier), and
text centers the number — three primitives from three different sections
above, one small self-contained gauge.
The hand-rolled arc_path above is exactly what arc/wedge below do
internally — reach for them first; path stays the escape hatch for shapes
neither one covers.
Radial drawing: arc, wedge, ribbon, polar
arc, wedge, ribbon, polar and radial_gradient share one angle
convention: degrees, 0° at the top, increasing clockwise — the same
convention pie/donut/gauge charts already use, so radial compositions read
the same way.
| Method | Effect |
|---|---|
arc(cx, cy, r, start_deg, end_deg, color="#ffffff", width=1.5, opacity=1, cap="round", layer="fg", name="") | A stroked circular arc — spokes, progress rings, radial tick marks. |
wedge(cx, cy, r_inner, r_outer, start_deg, end_deg, fill="#ffffff", stroke="none", stroke_width=0, opacity=1, layer="fg", group="", name="") | A filled donut segment; r_inner=0 collapses it to a pie slice. The building block for radial bar charts — one wedge per bar, r_outer mapped to the value. group (or a later link() call by name) makes it join a hover-glow group like circle/rect/text can. |
ribbon(cx, cy, r, a_start, a_end, b_start, b_end, fill="#ffffff", opacity=0.7, layer="fg", name="") | A curved band connecting two arc spans on the same circle through its center — chord-diagram-style links between categories. |
polar(cx, cy, r, deg) -> (x, y) | Pure coordinate math, no drawing — converts a radial position to (x, y) so you can place any other primitive (text, circle, line, a placed Chart) at a computed angle instead of hand-deriving trig every time. |
radial_gradient(id, from_color, to_color, cx=0.5, cy=0.5, r=0.5) | A radial counterpart to gradient — reference it the same way, fill="url(#id)", for glows and center-out fades. |
cv = sp.canvas(600, 600, "#0a0a12")
cx, cy = 300, 300
cv.radial_gradient("glow", "#312e81", "#0a0a12", r=0.75)
cv.circle(cx, cy, 260, fill="url(#glow)", name="glow-bg")
values = [8, 15, 6, 22, 11, 18, 4, 13]
n = len(values)
for i, v in enumerate(values):
a0 = i * 360 / n + 2
a1 = (i + 1) * 360 / n - 2
cv.wedge(cx, cy, 60, 60 + v * 7, a0, a1,
fill=f"hsl({i * 360 // n}, 70%, 60%)", name=f"bar-{i}")
lx, ly = cv.polar(cx, cy, 60 + v * 7 + 16, (a0 + a1) / 2)
cv.text(str(v), lx, ly, size=11, color="#f8fafc", anchor="middle", name=f"lbl-{i}")
cv.ribbon(cx, cy, 58, 10, 30, 190, 210, fill="#a78bfa", opacity=0.35, name="link-a")
chart = cv.build()
This is the same shape as the radial pieces at
visualcinnamon.com — a center, a set of
wedges swept around it, labels placed with polar, and ribbons crossing
between spans. Nothing here is a dedicated “radial bar chart” type; it’s the
five primitives above composed by hand, the same way the rest of Canvas
works — including mixing in a placed Chart at a polar-computed position
if the story calls for it.
Radial gallery: spiral, sunburst, network
Three more shapes built from the same handful of primitives — no new API
below this line, just polar, curve, wedge and connector combined
differently each time.
A spiral — each point’s radius grows with its index instead of staying fixed, the technique behind timeline-as-spiral pieces like Searching for Birds:
cx, cy = 300, 300
n = 60
cv = sp.canvas(600, 600, "#0a0a12")
pts = []
for i in range(n):
deg = i * 12
r = 20 + i * 4.2
pts.append(list(cv.polar(cx, cy, r, deg)))
cv.curve(pts, color="#a78bfa", width=2, tension=0.8, name="spiral")
for i in range(0, n, 4):
x, y = pts[i]
cv.circle(x, y, 3 + (i / n) * 5, fill="#22d3ee", name=f"pt-{i}")
chart = cv.build()
A sunburst — two rings of wedge, the outer ring’s spans computed from
the inner ring’s proportions instead of an even split, giving a hierarchical
part-of-a-part-of-a-whole read:
cx, cy = 300, 300
cv = sp.canvas(600, 600, "#0a0a12")
groups = [("Frontend", 40), ("Backend", 35), ("Data", 25)]
subgroups = {
"Frontend": [("React", 20), ("CSS", 12), ("A11y", 8)],
"Backend": [("API", 18), ("Auth", 10), ("Jobs", 7)],
"Data": [("ETL", 14), ("ML", 11)],
}
total = sum(v for _, v in groups)
cursor = 0.0
for name, v in groups:
span = v / total * 360
cv.wedge(cx, cy, 60, 130, cursor, cursor + span - 2, fill="#6366f1", name=f"inner-{name}")
lx, ly = cv.polar(cx, cy, 95, cursor + span / 2)
cv.text(name, lx, ly, size=11, color="#fff", anchor="middle", name=f"inner-lbl-{name}")
sub_total = sum(sv for _, sv in subgroups[name])
sub_cursor = cursor
for sname, sv in subgroups[name]:
sub_span = sv / sub_total * span
cv.wedge(cx, cy, 135, 200, sub_cursor, sub_cursor + sub_span - 1,
fill="#22d3ee", opacity=0.85, name=f"outer-{sname}")
lx, ly = cv.polar(cx, cy, 168, sub_cursor + sub_span / 2)
cv.text(sname, lx, ly, size=9, color="#0a0a12", anchor="middle", name=f"outer-lbl-{sname}")
sub_cursor += sub_span
cursor += span
chart = cv.build()
A radial network — nodes placed on a circle with polar, random pairs
joined with connector’s bend for a soft curve instead of a straight
chord; the same “relationships between things” story ribbon tells, drawn
node-and-edge instead of band-and-arc:
import random
cx, cy = 300, 300
n = 14
cv = sp.canvas(600, 600, "#0a0a12")
cv.radial_gradient("net-glow", "#1e1b4b", "#0a0a12", r=0.85)
cv.circle(cx, cy, 280, fill="url(#net-glow)", name="bg")
nodes = [cv.polar(cx, cy, 220, i * 360 / n) for i in range(n)]
edges = set()
while len(edges) < 22:
a, b = random.sample(range(n), 2)
edges.add((min(a, b), max(a, b)))
for a, b in edges:
ax, ay = nodes[a]
bx, by = nodes[b]
cv.connector(ax, ay, bx, by, color="#4c1d95", width=1, opacity=0.5, bend=0.15, name=f"edge-{a}-{b}")
for i, (x, y) in enumerate(nodes):
cv.circle(x, y, 8, fill="#a78bfa", stroke="#0a0a12", stroke_width=2, name=f"node-{i}")
chart = cv.build()
Real-world composition: a RéciTAC-style network
A radial network diagram organizing a research program’s disciplines,
universities, actions and impact outcomes around a dense central network of
“stories” and “people”. None of it needs a dedicated “network chart” type:
wedge+polar build the outer capsule-segment rings — angular width
proportional to story count per discipline, so the ring itself carries data
instead of just decorating — polygon+rect build the hexagon impact
clusters and their pill-shaped outcome labels, connector draws every curved
edge, and link() ties related elements — a hexagon cluster and every story
it touches, a university and its disciplines, an action’s color segments —
into shared hover-glow, dim-the-rest groups (hovering any grouped element now
fades everything outside its group to near-transparent, a Canvas-level
generalization of Chart.hover_family()’s dim-the-rest effect, which only
natively works on hierarchical/flow charts like icicle and sankey). The story
nodes themselves are five real, place()d sp.bubble() charts — one per
discipline, arranged in a rosette so each contributes its own color-clustered
mass to a shared dense core instead of reading as one undifferentiated blob —
rather than hand-drawn circles. Splitting the hairball this way also makes it
addressable by link(): hovering a discipline’s ring segment now dims every
other discipline’s cluster, isolating just its own. Each sub-chart’s actual
rendered positions are read back out of its own SVG (by data-idx, since
bubble() reorders its DOM by category) so every connector line lands
exactly on a real dot, with a fully transparent same-position circle() as
the link() target bubble()’s own iframe can’t expose directly, and
.no_hover() disables each chart’s own baked-in double-click zoom, which
would otherwise shift a dot’s position inside its iframe independently of the
canvas-level connector lines drawn to its build-time coordinates. Three more
real charts get place()d straight into the
canvas’s empty corners and lower edge rather than boxed on top of it: a
sp.bubble() “story constellation” stripped of axes/background and
circle-clipped so it reads as one more circular motif, a sp.histogram()
“people per story” tilted into its own corner, and a sp.barh() “stories per
discipline” sitting low and centered — all three summarizing the same dataset
the diagram encodes, from different angles. Composition isn’t an alternative
to SeraPlot’s chart functions, it’s a way to combine them in one scene.
The full, runnable version (with the synthetic dataset, the university/action
hover groups, and both embedded charts) lives at
notebook/canva/recitac_remake.ipynb.
The trimmed sketch below shows the core technique — outer ring, one hexagon
cluster, a handful of hairball nodes:
import random
import seraplot as sp
def tangent_rot(angle):
r = (angle - 90) % 360
return r - 180 if 90 < r < 270 else r
W = H = 1700
CX = CY = W / 2
DISCIPLINES = [("Social Science", "#f59e0b"), ("Health", "#16a34a"), ("Engineering", "#38bdf8")]
cv = sp.Canvas(W, H, "#ffffff")
cv.radial_gradient("glow", "#fef9f0", "#ffffff", cx=0.5, cy=0.58, r=0.6)
cv.circle(CX, CY, 640, fill="url(#glow)", layer="bg")
R = 700
span = 120 / len(DISCIPLINES)
for i, (name, color) in enumerate(DISCIPLINES):
a0, a1 = 200 + i * span, 200 + (i + 1) * span - 3
cv.wedge(CX, CY, R - 9, R + 9, a0, a1, fill=color, name=f"disc-{i}")
lx, ly = cv.polar(CX, CY, R - 26, (a0 + a1) / 2)
cv.text(name, lx, ly, size=11, anchor="middle", rotation=tangent_rot((a0 + a1) / 2))
def hexagon(cx, cy, r):
return [list(cv.polar(cx, cy, r, k * 60)) for k in range(6)]
hx, hy = CX - 190, CY - 240
cv.polygon(hexagon(hx, hy, 125), fill="#ecfdf5", stroke="#22c55e", stroke_width=2.5, name="hex-TRUST")
cv.rect(hx - 60, hy - 20, 108, 52, fill="#22c55e", rx=14, name="outcome-TRUST-0")
cv.text("Trust in\nreciprocity", hx - 6, hy + 6, size=10.5, color="#fff", anchor="middle")
stories = []
for i in range(12):
ang, rad = random.uniform(0, 360), random.uniform(60, 300)
x, y = cv.polar(CX, CY, rad, ang)
color = random.choice(DISCIPLINES)[1]
cv.circle(x, y, 10, fill=color, stroke="#fff", stroke_width=1.5, name=f"story-{i}")
cv.connector(x, y, hx, hy, color=color, width=0.8, opacity=0.15, bend=0.3)
stories.append(f"story-{i}")
cv.link("impact-TRUST", ["hex-TRUST", "outcome-TRUST-0"] + stories)
bubbles = sp.bubble(
categories=[random.choice(DISCIPLINES)[0] for _ in range(20)],
x_values=[random.uniform(0, 100) for _ in range(20)],
y_values=[random.uniform(0, 10) for _ in range(20)],
sizes=[random.uniform(10, 40) for _ in range(20)],
palette=[int(c.lstrip("#"), 16) for _, c in DISCIPLINES],
width=700, height=700,
).no_axes().no_background().gridlines(False)
cv.place(bubbles, 20, 100, 280, 280, clip="circle", name="panel-bubbles")
chart = cv.build().zoom()
The dataset is synthetic — the point is the composition pattern, not a
literal port of Nadieh’s real research-program data (which comes from a
private Google Sheet). Everything scales with the data: add a discipline and
every ring/legend/embedded chart picks it up automatically; add a story and a
new node, a new set of connectors, and a new hover-group member appear on the
next build().
.zoom() on the built Chart (called above, cv.build().zoom()) turns
on mouse-wheel/pinch zoom and drag-to-pan for the whole composition — useful
once a canvas is dense enough that hovering individual hexagons or story
nodes benefits from zooming in first.
A tip for very large canvases: chart on its own (or chart.show())
sizes its inline <iframe> via CSS aspect-ratio, which some notebook
frontends resolve unreliably for big square canvases like this one (1700×1700),
silently cropping the output instead of shrinking it to fit. chart.save(path)
plus IPython.display.IFrame(src=path, width=..., height=...) sidesteps that
by reserving an explicit pixel size upfront — the canvas’s own internal
viewport-fit script then scales the full composition down to whatever that
turns out to be, so you always see it in full.
A constellation of real charts: satellites on dashed leaders
A different composition shape than RéciTAC’s rings-and-hairball: one central
sp.scatter() plotting every model’s mean quality against its mean
emissions, and one small satellite sp.scatter() per model — its own raw
event cloud, no_axes()/no_title()/hide_grid()/no_legend()/
no_background()’d down to just the dots — arranged in a ring around it.
Each satellite connects back to its model’s exact point on the central plot
with a dashed line().
The satellite-to-point link uses hover_group directly on line()/circle()/
place() instead of a separate link() call: pass the same hover_group="sat-3"
string to every element that should glow together, and they’re linked from the
moment they’re created — no name= + follow-up cv.link(...) pass needed.
Both mechanisms end up in the same place; hover_group is the one-line version
when you know the group at creation time.
import math
import random
import seraplot as sp
random.seed(3)
MODELS = [("Titan-7B", "#6366f1"), ("Mixtral-Sparse", "#7c3aed"),
("Codex-T5", "#0891b2"), ("VisionSpeak-VL", "#0ea5e9"),
("DiffuGen-2", "#d97706"), ("BertCore", "#059669")]
means = [(random.uniform(55, 95), random.uniform(3.8, 5.2)) for _ in MODELS]
central = sp.scatter(
"Quality vs log(emissions)",
x=[m[0] for m in means], y=[m[1] for m in means],
labels=[name for name, _ in MODELS], groups=[name for name, _ in MODELS],
x_label="Quality score", y_label="log10 gCO2e", width=520, height=400,
)
CW = CH = 900
CX = CY = 450
R = 340
cv = sp.Canvas(CW, CH, "#ffffff")
cv.place(central, CX - 260, CY - 200, 520, 400)
for i, (name, color) in enumerate(MODELS):
ang = math.radians(-90 + i * 360 / len(MODELS))
mx, my = CX + R * math.cos(ang), CY + R * math.sin(ang)
px = CX - 260 + 60 + (means[i][0] - 55) / 40 * 400
py = CY - 200 + 30 + (1 - (means[i][1] - 3.8) / 1.4) * 330
hg = f"sat-{i}"
cv.line(px, py, mx, my, color=color, width=1.2, dash="2 5", hover_group=hg)
mini = sp.scatter(
"", x=[random.gauss(0, 1) for _ in range(30)],
y=[random.gauss(0, 1) for _ in range(30)],
labels=[name] * 30, groups=[name] * 30, palette=[int(color[1:], 16)],
width=150, height=110,
).no_axes().no_title().hide_grid().no_legend().no_background().no_hover()
cv.place(mini, mx - 75, my - 55, 150, 110, group=hg)
cv.text(name, mx, my - 62, size=9, color=color, anchor="middle", weight="bold")
chart = cv.build()
The dataset for the live preview above is synthetic too — recreated from the
same ai_story data-story project’s constellation.py, whose original CSVs
no longer exist. 14 architectures, not 6, and each satellite’s event cloud is
real per-model data (up to 60 events) rather than a Gaussian stand-in — the
technique is identical, just with more rows and a second sidecar dataset
joined in by model_id.
Composing real charts: a mission-control dashboard
place() embeds a full Chart — not just a primitive — inside a canvas,
which means canvas composition isn’t limited to hand-drawn shapes: real
sp.line(), sp.bar(), sp.gauge(), sp.area(), sp.donut(),
sp.barh() panels can sit framed, connected, and annotated by the exact
same primitives used everywhere else on this page. The part that actually
sells “dashboard” over “charts in boxes” is the same trick as the RéciTAC
network above: a shared center every core panel connects to. One
glowing hub, four color-matched spokes (connector + a circle anchor at
each end), each spoke tinted to match the panel it comes from via the
chart-level chainable methods (palette(), gridlines(),
width()/height(), title_color() — see
Chart Methods) applied before
place(). Beyond the four hub panels, a KPI ribbon with its own inline
sp.line() sparklines sits above the grid, two more real charts flank it
on the right on a subtler hover_group-linked connection, and a full-width
throughput panel closes the composition at the bottom:
import random
import seraplot as sp
random.seed(3)
W, H = 1950, 1380
cv = sp.Canvas(W, H)
cv.radial_gradient("dashBg", "#1a2140", "#04050a", cx=0.5, cy=0.42, r=1.0)
cv.rect(0, 0, W, H, fill="url(#dashBg)", layer="bg")
cv.radial_gradient("hubGlow", "#22d3ee", "#04050a", cx=0.5, cy=0.5, r=0.5)
cv.text("Mission Control", 48, 56, size=30, color="#f8fafc", weight="800")
cv.text("Every panel wired into one live hub — sp.Canvas place() + connectors + real SeraPlot charts",
48, 84, size=13, color="#64748b")
cv.text("updated 2s ago", W - 60, 50, size=11, color="#475569", anchor="end")
cv.circle(W - 260, 47, 5, fill="#22c55e", name="live-dot")
cv.circle(W - 260, 47, 5, fill="none", stroke="#22c55e", stroke_width=1.5, opacity=0.6, name="live-pulse")
cv.text("LIVE", W - 246, 52, size=12, color="#22c55e", weight="700", letter_spacing=1.5)
KPI = [
("ACTIVE USERS", "12,940", "+6.1%", "#6366f1", [820, 860, 901, 934, 990, 1120, 1180, 1290, 1330, 1320]),
("MRR", "$184.2k", "+3.4%", "#22d3ee", [140, 148, 152, 149, 158, 165, 170, 176, 180, 184]),
("UPTIME", "99.982%", "+0.02%", "#f59e0b", [99.9, 99.91, 99.95, 99.93, 99.96, 99.97, 99.98, 99.97, 99.98, 99.982]),
("OPEN INCIDENTS", "5", "-2 today", "#f472b6", [9, 8, 8, 7, 6, 7, 6, 6, 5, 5]),
]
KPI_Y = 118
KPI_W, KPI_H = 340, 92
for i, (label, value, delta, color, series) in enumerate(KPI):
kx = 48 + i * (KPI_W + 20)
cv.rect(kx, KPI_Y, KPI_W, KPI_H, fill="#0b1022", stroke="rgba(255,255,255,.07)",
stroke_width=1, rx=14, layer="bg", name=f"kpi-{i}")
cv.rect(kx, KPI_Y, 4, KPI_H, fill=color, rx=2, layer="bg")
cv.text(label, kx + 20, KPI_Y + 26, size=10.5, color="#64748b", weight="700", letter_spacing=1.2)
cv.text(value, kx + 20, KPI_Y + 58, size=24, color="#f8fafc", weight="800")
cv.text(delta, kx + 20, KPI_Y + 78, size=11.5, color=color, weight="600")
spark = sp.line(labels=[str(j) for j in range(len(series))], values=series,
color_hex=int(color.lstrip("#"), 16), width=150, height=64) \
.no_axes().no_title().hide_grid().no_legend().no_background().no_hover()
cv.place(spark, kx + KPI_W - 168, KPI_Y + 16, 150, 64, name=f"kpi-spark-{i}")
GRID_Y0 = 250
PW, PH = 660, 320
GAP_X, GAP_Y = 100, 80
COL0, COL1 = 60, 60 + PW + GAP_X
ROW0, ROW1 = GRID_Y0, GRID_Y0 + PH + GAP_Y
HX, HY, HR = (COL0 + PW + GAP_X / 2), (ROW0 + PH + GAP_Y / 2), 92
PALETTE = [0x6366f1, 0x22d3ee, 0xf59e0b, 0xf472b6]
HEX = [f"#{c:06x}" for c in PALETTE]
PANELS = [("trend", COL0, ROW0, HEX[0]), ("revenue", COL1, ROW0, HEX[1]),
("health", COL0, ROW1, HEX[2]), ("incidents", COL1, ROW1, HEX[3])]
def panel_frame(x, y, w, h, color, name):
cv.rect(x - 16, y - 16, w + 32, h + 32, fill="#0b1022", stroke="rgba(255,255,255,.06)",
stroke_width=1, rx=18, layer="bg", name=name)
cv.rect(x - 16, y - 16, w + 32, 4, fill=color, rx=2, layer="bg")
for name, x, y, color in PANELS:
panel_frame(x, y, PW, PH, color, f"panel-{name}")
trend = sp.line(labels=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
values=[820, 932, 901, 934, 1290, 1330, 1320],
).width(PW).height(PH).palette(PALETTE).gridlines(False).background("#0b1022")
revenue = sp.bar(labels=["Core", "Cloud", "API", "Mobile", "Support"],
values=[420, 680, 310, 240, 150], title="Revenue by Segment",
).width(PW).height(PH).palette(PALETTE).gridlines(False).background("#0b1022").title_color("#e2e8f0")
health = sp.gauge(value=87).width(PW).height(PH).background("#0b1022")
incidents = sp.area(labels=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], values=[5, 3, 6, 2, 4, 1, 2],
title="Open Incidents",
).width(PW).height(PH).palette([PALETTE[3]]).gridlines(False).background("#0b1022").title_color("#e2e8f0")
cv.place(trend, COL0, ROW0, PW, PH, name="chart-trend")
cv.place(revenue, COL1, ROW0, PW, PH, name="chart-revenue")
cv.place(health, COL0, ROW1, PW, PH, name="chart-health")
cv.place(incidents, COL1, ROW1, PW, PH, name="chart-incidents")
cv.text("Weekly Active Users", COL0 + 20, ROW0 + 26, size=13, color="#e2e8f0", weight="700")
cv.text("System Health", COL0 + 20, ROW1 + 26, size=13, color="#e2e8f0", weight="700")
ANCHORS = {
"trend": (COL0 + PW, ROW0 + PH / 2),
"revenue": (COL1, ROW0 + PH / 2),
"health": (COL0 + PW, ROW1 + PH / 2),
"incidents": (COL1, ROW1 + PH / 2),
}
for name, x, y, color in PANELS:
ax, ay = ANCHORS[name]
cv.connector(ax, ay, HX, HY, color=color, width=2, opacity=0.55, bend=0.28, name=f"spoke-{name}")
cv.circle(ax, ay, 6, fill=color, stroke="#04050a", stroke_width=2, name=f"anchor-{name}")
cv.circle(ax, ay, 11, fill="none", stroke=color, stroke_width=1, opacity=0.4)
cv.circle(HX, HY, HR + 34, fill="url(#hubGlow)", opacity=0.35)
cv.ring(HX, HY, HR + 18, HR + 22, fill="#22d3ee", opacity=0.25)
cv.ring(HX, HY, HR + 6, HR + 9, fill="#22d3ee", opacity=0.45)
start = 0.0
for name, _, _, color in PANELS:
end = start + 90
cv.wedge(HX, HY, HR - 10, HR - 4, start, end, fill=color, opacity=0.85)
start = end
cv.circle(HX, HY, HR - 14, fill="#0b1022", stroke="#f8fafc", stroke_width=2)
cv.text("87", HX, HY - 4, size=40, color="#22d3ee", weight="800", anchor="middle")
cv.text("SYSTEM SCORE", HX, HY + 22, size=10, color="#64748b", anchor="middle", letter_spacing=1.5)
cv.link("hub-cluster", ["anchor-trend", "anchor-revenue", "anchor-health", "anchor-incidents"])
cv.annotate("Trending up 61% since Monday", COL0 + 190, ROW0 + 170, COL0 + 40, ROW0 + PH + 55,
color="#94a3b8", size=12, line_dash="4,3", bg="#0b1022")
cv.annotate("5 open, 2 critical", COL1 + 460, ROW1 + 90, COL1 + 40, ROW1 + PH + 55,
color="#94a3b8", size=12, line_dash="4,3", bg="#0b1022")
SIDE_X = COL1 + PW + GAP_X
SIDE_W = W - SIDE_X - 60
panel_frame(SIDE_X, ROW0, SIDE_W, PH, "#a78bfa", "panel-region")
panel_frame(SIDE_X, ROW1, SIDE_W, PH, "#34d399", "panel-latency")
region = sp.donut(labels=["NA", "EU", "APAC", "LATAM"], values=[40, 28, 20, 12],
title="Regional Split", width=SIDE_W, height=PH,
palette=[0x6366f1, 0x22d3ee, 0xf59e0b, 0xf472b6]) \
.background("#0b1022").title_color("#e2e8f0")
latency = sp.barh(labels=["p50", "p90", "p95", "p99"], values=[42, 118, 210, 480],
title="Latency (ms)", width=SIDE_W, height=PH,
palette=[0x34d399]) \
.gridlines(False).background("#0b1022").title_color("#e2e8f0")
cv.place(region, SIDE_X, ROW0, SIDE_W, PH, group="side-link", name="panel-region-chart")
cv.place(latency, SIDE_X, ROW1, SIDE_W, PH, group="side-link", name="panel-latency-chart")
SIDE_LINK = [(ROW0, "#a78bfa"), (ROW1, "#34d399")]
for row_y, color in SIDE_LINK:
ax, ay = COL1 + PW, row_y + PH / 2
bx, by = SIDE_X, row_y + PH / 2
cv.line(ax, ay, bx, by, color="#94a3b8", width=1, dash="2 5", hover_group="side-link")
cv.circle(ax, ay, 5, fill=color, stroke="#04050a", stroke_width=2)
cv.circle(bx, by, 5, fill=color, stroke="#04050a", stroke_width=2)
STRIP_Y = ROW1 + PH + GAP_Y
STRIP_H = H - STRIP_Y - 60
panel_frame(60, STRIP_Y, W - 120, STRIP_H, "#22d3ee", "panel-throughput")
hours = [f"{h:02d}:00" for h in range(0, 24, 2)]
throughput = [random.randint(800, 2600) for _ in hours]
throughput_chart = sp.bar(labels=hours, values=throughput, title="Requests/sec — last 24h",
width=W - 120, height=STRIP_H,
color_hex=0x22d3ee) \
.gridlines(False).background("#0b1022").title_color("#e2e8f0")
cv.place(throughput_chart, 60, STRIP_Y, W - 120, STRIP_H, name="panel-throughput-chart")
chart = cv.build()
The hub itself is a small radial gauge in disguise — four wedge slices
(one per panel, in that panel’s color) inside two ring pulse tracks,
built from exactly the same primitives as the “Composing micro-tools”
dial and the RéciTAC donut rings above. Reusing one visual language across
every worked example on this page is the actual point: primitives don’t
know or care whether they’re drawing a progress dial, a discipline ring,
or a dashboard hub.
panel_frame() draws a rounded card plus a thin color-matched top border
— that accent color is the same one used for that panel’s spoke and
palette(), so the eye connects “this line is orange” to “this panel is
orange” to “this spoke is orange” without a legend. The one easy-to-miss
detail behind all of it: any decoration meant to sit behind a placed
chart (background fill, panel frames) needs layer="bg" explicitly —
rect()’s default layer="fg" renders on top of placed charts by
design (so connectors and callouts can cross over them), which will
silently hide a panel’s contents if the panel itself is drawn on the
foreground layer.
Two things needed fixing to get here from the original four-panel
version. First, title_color(): sp.bar()/sp.area()/sp.donut()
happily took an explicit title color, but sp.line()/sp.gauge()
rendered their title in a barely visible default shade regardless —
worked around by skipping the chart’s own title= entirely for those two
and drawing the panel name as a plain cv.text() at the same position
instead, which also matches the KPI tiles’ hand-drawn labels. Second, a
chart’s own y-axis tick generator reserves space proportional to the
plot’s own requested pixel size, not its place()d footprint — the
bottom throughput strip’s tick labels overlapped badly until its
sp.bar() was requested at the strip’s full native size (width=W-120, height=STRIP_H) rather than an undersized box meant to be stretched.
The two right-hand panels (Regional Split, Latency (ms)) aren’t wired
into the hub — a fifth and sixth spoke would have overloaded the one
visual idea the hub is supposed to communicate — but they’re not
orphaned either: a single hover_group="side-link" on both panels and
their connecting line links them exactly like the RéciTAC satellites, so
hovering either panel dims everything outside that pair via the same
Canvas-level dim-the-rest hover behavior described above.
Organic layouts: Voronoi
voronoi(sites, x, y, w, h, fills=None, stroke=..., stroke_width=..., opacity=...)
computes a bounded Voronoi diagram — one cell per site, each cell the region
closer to that site than to any other — and adds every cell to the canvas as
a polygon() in one call, returning their element indices for later
addressing (hover groups, derive(), etc.).
import random
cv = sp.Canvas(900, 540)
sites = [[random.uniform(30, 870), random.uniform(30, 510)] for _ in range(22)]
palette = ["#6366f1", "#ec4899", "#22c55e", "#f59e0b", "#06b6d4", "#8b5cf6", "#ef4444"]
fills = [palette[i % len(palette)] for i in range(len(sites))]
cv.voronoi(sites, 0, 0, 900, 540, fills=fills, stroke="#0d1117", stroke_width=2, opacity=0.88)
Cell size follows site density automatically — cluster sites tightly to shrink their cells, useful for a treemap-like “one cell per record, colored by category, sized by local density” layout without a separate packing algorithm. Implemented natively (iterative half-plane clipping against every other site, no external geometry crate) rather than pulled in as a dependency.
Custom CSS / JS
| Method | Effect |
|---|---|
style(name, css) | Injects [data-sp-name="name"]{ css } into the canvas’s <style>. Pass name="" to inject a raw, unscoped CSS block (e.g. @keyframes). |
script(js) | Appends a raw <script>js</script> before </body> — full manual control for users who want to hand-write interactivity. |
Groups and inter-plot linking
Two different mechanisms, both driven by element names:
group(group_name, member_names) / move_group(group_name, dx, dy) —
moves several named elements together as a rigid unit. nudge(name, dx, dy)
and resize(name, dw, dh) do the same for a single element. Pins registered
on a chart before a move/resize are shifted along with it automatically.
link(group_name, member_names) -> int — ties elements across
different panels into one hover group: hovering any linked element (a
Chart, Rect, Text, Circle, Wedge or Polygon) glows/pulses all the
others in the same group. Returns how many of the given names were actually
linkable (Line, RawPath and other pure decoration types don’t currently
support it). circle(...) and polygon(...) also accept a hover_group=
kwarg to join a group right at creation time, without a separate link()
call — both paths stamp the same data-sp-grp/data-group attributes, so
grouped circles/polygons are also picked up by any chart’s own chainable
.group_hover_opacity(dim) (see Chart Methods),
letting a single hover dim every non-matching mark on the canvas while the
native glow/pulse handles the matching ones.
cv.link("story", ["revenue_chart", "trend_chart", "kpi_card"])
cv.circle(120, 80, 6, fill="#2dd4bf", hover_group="alice", name="c1")
Groups with many matching elements (a busy chart with hundreds of marks
sharing one hover_group) automatically skip the per-element glow/enlarge
on hover and fall back to the plain family dim — the highlight stays cheap
regardless of group size.
tooltip — attach a native hover card
tooltip(name, title, kv=[], avatar="", subtitle="", image="", video="", html="") -> bool
retroactively attaches a hover card to an already-created circle(...) or
polygon(...), reusing the exact #sp-tip card every chart-family hover
already renders — no extra CSS or JS to write. title and each kv
(key, value) pair are escaped and laid out automatically; avatar renders
as a small circular chip next to the title (a person, a team, a repo
owner…); subtitle is a left-aligned line under the title, the right home
for a longer sentence a kv row would otherwise wrap awkwardly; image/
video attach a full-width banner-style media block below the card body;
html is a raw, unescaped last-resort slot for anything the structured
fields can’t express. Returns False if name isn’t a taggable Circle/
Polygon.
cv.circle(cx, cy, 6, fill="#2dd4bf", hover_group="alice", name="c1")
cv.tooltip("c1", "Alice Martin", [("Role", "Engineer"), ("Since", "2019")],
avatar="https://.../alice.png", subtitle="Joined the data team")
frieze / timeline / chronology — inter-plot chronological layout
Three names for the same primitive: lay out labels in a boustrophedon
(snake) grid — left-to-right, then right-to-left on the next row, and so on —
connected by straight segments within a row and an S-curve at each row wrap,
with one enclosing ring and label per cell. Returns the (x, y, ring_radius)
anchor of every cell, so any other chart or shape can be drawn/placed right
on top of it — a natural fit for stringing several single-group
circle_pack(variant="swarm") clusters, or any other small chart, along a
real chronology instead of relying on a variant’s own built-in layout.
anchors = cv.frieze(week_labels, weights=week_counts, cols=8,
cell_w=280, cell_h=280, ring_color="#7dd3fc")
for (x, y, r), commits_in_week in zip(anchors, weeks):
...
Connecting two charts (pins)
Pins are named anchor points registered inside a placed chart’s coordinate
space, in canvas pixel coordinates. connect()/annotate_at() read pins to
draw a line or label between (or on top of) charts.
| Method | Effect |
|---|---|
pin(chart_ref, name, local_x, local_y) | Registers a pin at a chart-local pixel coordinate. |
pin_frac(chart_ref, name, fx, fy) | Registers a pin at a fractional position (0..1) of the chart’s native size. |
| `pin_xy(chart_ref, name) -> (x, y) | None` |
attach_bar(chart_ref, values, chart_w, chart_h, ...) | Auto-registers bar:{i}:top/center/bottom/left/right pins by reading the actual rendered bar rectangles. |
attach_scatter(chart_ref, x_vals, y_vals, labels, chart_w, chart_h, ...) | Auto-registers point:{i} (and named) pins from the data’s projected positions. |
connect(from_ref, from_name, to_ref, to_name, ...) | Draws a curved connector between two pins, possibly on two different charts. |
annotate_at(chart_ref, pin_name, text, ...) | Draws a leader-line label pointing at a pin. |
Pins go stale when the geometry they were computed from changes.
refill() on a chart clears its pins (so you don’t silently connect to
coordinates that belonged to the old content) — re-pin after refilling if you
still need them. nudge/resize/move_group, on the other hand, do shift
existing pins automatically, since the underlying content hasn’t changed.
Reusable skeletons: template & derive
skeleton = base_canvas.template() # strip Chart/Image elements, keep everything else
dashboard = skeleton.derive() # deep-clone a fresh instance to fill in
dashboard.fill("main", my_chart, name="panel")
template() returns a canvas with all place()d charts and image()s
removed but every decorative element (cards, gradients, titles, slots,
groups, custom CSS/JS) intact — the reusable “class”. derive() deep-clones
any canvas (templated or not) into an independent instance — the
“constructor call”. Build your branded skeleton once, derive() + fill()
it per dataset/variant instead of repeating layout code.
Persistence
| Method | Effect |
|---|---|
save(path) | Serializes the full canvas state (elements, pins, groups, slots, custom CSS/JS) to JSON. |
sp.canvas_load(path) -> Canvas | Rebuilds a canvas from a saved JSON file. |
sp.canvas_save_named(cv, name) -> str | Saves under ~/.seraplot/canvas/{name}.json and updates an index.json manifest. |
sp.canvas_load_named(name) -> Canvas | Loads back via that manifest. |
to_json() -> str | The raw JSON string, if you want to manage storage yourself. |
This is what lets a generated dashboard survive closing and reopening the
app: cv.save(...) once, sp.canvas_load(...) next session reconstructs an
identical canvas — positions, links, styling, everything.
Interactive dev mode
cv.dev()
Renders the canvas with a floating panel: drag any named element to move it,
drag the corner handle on charts/images to resize them, hover shows the
element’s name and its linked group (if any). The panel’s Copy Python
button generates the equivalent cv.nudge(...)/cv.resize(...) calls;
Download JSON exports the same deltas to a file that apply_deltas_json()
can replay headlessly (cv.apply_deltas_json(open(path).read())) — the
route from interactive tweaking to a reproducible script.
Canvas est la couche de composition de SeraPlot : une surface libre où l’on
place plusieurs Chart construits indépendamment, des images, des formes et
du texte, où on les relie entre eux, puis on exporte le tout comme un seul
Chart. C’est l’outil pour construire des dashboards, des histoires
annotées et des figures multi-panneaux qu’une seule fonction de chart ne
peut pas produire seule.
import seraplot as sp
bar = sp.grouped_bar("", labels=[...], values=[...], series_names=[...])
cv = sp.canvas(1280, 800, "#0a0a0f")
bars_ref = cv.place(bar, 60, 60, 560, 420, name="revenue")
chart = cv.build()
Chaque méthode de placement/dessin accepte un mot-clé name optionnel. Un
élément nommé reçoit un attribut data-sp-name="..." dans le HTML généré,
ce qui le rend adressable par toutes les autres méthodes ci-dessous
(nudge, resize, style, script, group, link, refill, le
glisser-déposer du mode dev, …). Nommer est gratuit et sans coût
d’exécution — nommez tout ce que vous pourriez vouloir retoucher.
Créer un canvas
cv = sp.canvas(width: int, height: int, bg: str = "#0a0a0f")
Placer des charts et des images
| Méthode | Effet |
|---|---|
place(chart, x, y, w, h, rotation=0, opacity=1, clip="", group="", name="") | Place un Chart sur le canvas à (x, y) de taille w×h. Renvoie un chart_ref (int) utilisé par pin/connect/attach_*. |
image(src, x, y, w, h, rotation=0, opacity=1, clip="", group="", name="") | Place une image PNG/JPEG/GIF/WebP/SVG. src est un chemin de fichier local (lu et encodé en base64), une URI data:, ou une URL http(s)://. |
slot(name, x, y, w, h) | Réserve une région nommée sans encore rien y placer. |
fill(slot_name, chart, ...) | Place un chart en utilisant la géométrie d’un slot déclaré au préalable. |
grid(x, y, w, h, rows, cols, gap_x=0, gap_y=0) | Déclare une grille rows × cols de slots nommés cell_{row}_{col} et renvoie la liste des noms en ordre ligne par ligne — plus de calcul de coordonnées à la main pour un dashboard. |
refill(name, chart) -> bool | Remplace le contenu d’un chart nommé et déjà placé, en conservant position, taille, rotation, style. À utiliser (au lieu de rappeler place/fill) chaque fois que vous rafraîchissez un panneau avec de nouvelles données. |
clip accepte "circle", "diamond", "hex", "tri", "pentagon" pour
un cadrage non rectangulaire du chart/de l’image.
Rappeler place/fill avec un nom déjà pris par un chart placé met à
jour ce chart en place au lieu d’empiler un doublon — fill(slot, new_chart, name="panel") peut donc être rappelé sans risque.
Micro-outils : conventions communes
text, line, curve, connector, circle, ring, rect, polygon,
path, arrow, annotate et gradient sont les primitives de dessin bas
niveau à partir desquelles tout le reste de Canvas (y compris voronoi(),
plus bas) est construit. Elles dessinent directement sur le SVG du canvas,
et partagent deux mots-clés :
layer="fg"|"bg"—"bg"s’affiche sous chaque chart/imageplace()é,"fg"(par défaut) s’affiche au-dessus. Utilisez"bg"pour les fonds, panneaux-cartes et décorations façon filigrane ;"fg"pour les annotations, callouts et tout ce qui doit rester au-dessus des données.name=""— rend l’élément adressable ensuite parnudge,resize,style,script,group,link, et déplaçable au glisser-déposer en modedev(). Nommer est gratuit ; nommez tout ce que vous pourriez vouloir retoucher.
Aucune de ces primitives ne nécessite un Chart — un canvas ne contenant
que ces primitives est une façon parfaitement valide de dessiner à la main
un diagramme pour lequel SeraPlot n’a pas de fonction de chart dédiée (voir
l’exemple de clôture de cette page).
Lignes, courbes & connecteurs
Quatre façons de dessiner entre des points — le choix dépend du nombre de points et de la rigidité voulue pour la forme qui les relie.
line — segment droit
cv.line(x1, y1, x2, y2, color="#ffffff", width=1.5, dash="", opacity=1.0,
cap="round", layer="fg", hover_group="", name="")
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
x1, y1, x2, y2 | float | requis | Extrémités, en pixels canvas |
color | str | "#ffffff" | Couleur du trait |
width | float | 1.5 | Épaisseur du trait |
dash | str | "" | stroke-dasharray SVG, ex. "6,4" pour un trait pointillé |
opacity | float | 1.0 | 0–1 |
cap | str | "round" | Style d’extrémité : "round", "butt", ou "square" |
hover_group | str | "" | Ajoute une zone de survol invisible plus large que le trait visible (souvent fin), pour que la ligne réagisse au survol en tant que membre d’un groupe link() |
name | str | "" | Nom adressable |
La primitive ligne droite la plus simple — axes, séparateurs, lignes guides, ou un segment d’un diagramme construit à la main.
curve — ligne lissée passant par N points
cv.curve(points, color="#ffffff", width=1.5, opacity=1.0, tension=1.0,
fill="none", layer="fg", name="")
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
points | list[[x, y]] | requis | Trois points ou plus par lesquels la courbe passe |
tension | float | 1.0 | Tension Catmull-Rom : 0 réduit la courbe à des segments droits entre les points, 1 donne une spline lissée standard, des valeurs plus hautes accentuent les bombements après chaque point |
fill | str | "none" | Remplit la zone sous la courbe si défini — effet aire dessinée à la main |
(color / width / opacity / layer / name — comme line) |
Contrairement à connector (ci-dessous), qui relie toujours exactement
deux points, curve interpole à travers une polyligne arbitraire de
points de passage. À utiliser pour des lignes de tendance dessinées à la
main, des décorations façon sparkline, ou des traits organiques libres non
liés aux axes d’un Chart :
cv.curve([[40, 300], [140, 120], [260, 260], [400, 80]],
color="#22c55e", width=3, tension=0.8, name="trend-doodle")
connector — courbe en S entre deux points
cv.connector(x1, y1, x2, y2, color="#ffffff", width=1.5, opacity=1.0,
bend=0.5, layer="fg", name="")
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
bend | float | 0.5 | Fraction, le long de l’axe dominant (celui de dx/dy le plus grand), où se placent les points de contrôle de la bézier — 0.5 donne une courbe en S symétrique ; des valeurs vers 0 ou 1 décalent le milieu de la courbe vers une extrémité |
(autres — comme line) |
La primitive « fil de flowchart » : une seule bézier cubique qui produit
toujours un tracé propre en S ou en L entre deux points, quelle que soit
leur position relative. À utiliser pour relier deux panneaux place()és
ou deux éléments nommés sans calculer les points de contrôle à la main —
connect() (sous Connecter deux charts, plus bas) trace exactement la
même courbe, mais lit ses extrémités depuis des pins enregistrés plutôt
que des coordonnées brutes.
arrow — ligne directionnelle avec pointe de flèche
cv.arrow(x1, y1, x2, y2, color="#ffffff", width=1.5, head_size=4.0,
opacity=1.0, layer="fg", name="")
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
head_size | float | 4.0 | Taille de la pointe en px — le marqueur suit cette valeur, pas width |
(autres — comme line) |
Une line avec une pointe de flèche SVG (<marker>) ajoutée à son
extrémité, pour pointer vers quelque chose plutôt que simplement relier
deux points.
Formes
Cinq façons de remplir ou tracer une région, de la plus contrainte à la plus libre.
circle / ring
cv.circle(cx, cy, r, fill="none", stroke="#ffffff", stroke_width=1.5,
opacity=1.0, layer="fg", hover_group="", name="")
cv.ring(cx, cy, inner_r, outer_r, fill="#ffffff", stroke="none",
stroke_width=0.0, opacity=1.0, layer="fg", name="")
ring est un anneau (donut) : la région remplie strictement entre
inner_r et outer_r, construite à partir de deux arcs combinés avec
fill-rule="evenodd" plutôt qu’un <circle> plein. À utiliser pour des
anneaux de progression radiale, des cadres d’avatar, ou des halos de mise
en valeur — partout où le disque plein de circle masquerait ce qu’il y a
en dessous.
rect
cv.rect(x, y, w, h, fill="none", stroke="#ffffff", stroke_width=1.5,
rx=0.0, opacity=1.0, rotation=0.0, layer="fg", name="")
rx arrondit les coins ; rotation (en degrés) fait pivoter le rectangle
autour de son propre centre. Les deux ensemble couvrent les fonds de
carte, badges/puces, et échantillons de légende de catégorie.
polygon
cv.polygon(points, fill="none", stroke="#ffffff", stroke_width=1.5,
opacity=1.0, layer="fg", name="")
Une forme fermée à travers une liste arbitraire list[[x, y]] de
sommets — la primitive à partir de laquelle voronoi() elle-même est
construite (chaque cellule qu’elle renvoie est un appel polygon() en
coulisses). À utiliser directement pour des marqueurs triangulaires/en
losange, des formes de badge personnalisées, ou toute région fermée que
rect/circle ne peuvent pas exprimer.
path
cv.path(d, fill="none", stroke="#ffffff", stroke_width=1.5, opacity=1.0,
layer="fg", name="")
L’échappatoire : d est une chaîne de données de chemin SVG brute
("M ... L ... A ... Z") pour les formes qu’aucune autre primitive ne
couvre — logos, icônes, arcs avec un balayage spécifique, ou géométrie
calculée par votre propre code. C’est exactement ainsi que la variante
"radial" d’icicle() dessine ses secteurs
annulaires en interne : des chaînes M/A/L/Z construites à la main,
sans primitive d’arc séparée.
Texte & annotations
text
cv.text(content, x, y, size=24.0, color="#ffffff", weight="normal",
anchor="start", rotation=0.0, letter_spacing=0.0,
font="sans-serif", opacity=1.0, layer="fg", name="")
anchor est le text-anchor SVG ("start", "middle", "end") relatif à
(x, y) — "middle" centre un titre au-dessus d’un panneau, "end"
aligne une valeur à droite le long d’un axe.
annotate — étiquette avec ligne de rappel
cv.annotate(text, ax, ay, tx, ty, color="#ffffff", size=13.0,
line_dash="", line_width=1.0, bg="", layer="fg", name="")
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
ax, ay | float | requis | Le point annoté — où commence la ligne de rappel |
tx, ty | float | requis | Où se trouve le texte lui-même — où finit la ligne de rappel |
text | str | requis | Supporte \n pour des étiquettes multi-lignes |
line_dash | str | "" | Motif de tirets pour la ligne de rappel, ex. "4,3" |
bg | str | "" | Couleur de fond derrière le texte ; ""/"none" ne dessine aucun cadre |
Contrairement à une paire text() + line(), annotate() route
automatiquement un coude propre en deux segments entre (ax, ay) et
(tx, ty) (le point de coude choisi selon l’axe ayant le plus grand
écart) et dimensionne son propre cadre de fond pour s’ajuster au texte.
L’outil pour « ce point précis, étiqueté, avec une ligne d’appel » — le
pic d’une barre, un point aberrant sur un scatter. annotate_at() (sous
Connecter deux charts, plus bas) est la version « pin-aware » de cette
même primitive, pour étiqueter un point à l’intérieur d’un chart place()é
plutôt qu’une coordonnée canvas brute.
Couleur : gradient
cv.gradient(id, from_color, to_color, x1=0.0, y1=0.0, x2=1.0, y2=0.0)
Enregistre une définition linearGradient SVG — x1/y1/x2/y2 vivent dans
l’espace objectBoundingBox 0..1, donc (0,0)→(1,0) va de gauche à
droite et (0,0)→(0,1) de haut en bas. Ne dessine rien par elle-même ;
appelez-la une fois, puis référencez fill=f"url(#{id})" sur n’importe
quel rect/circle/polygon/path suivant :
cv.gradient("card-glow", "#6366f1", "#0a0a0f", x1=0, y1=0, x2=0, y2=1)
cv.rect(40, 40, 300, 200, fill="url(#card-glow)", rx=18, name="card")
Composer les micro-outils : un cadran radial
Aucune des primitives ci-dessus n’a besoin d’un Chart — un canvas
construit uniquement à partir d’elles est une façon parfaitement valide de
dessiner à la main un widget pour lequel SeraPlot n’a pas de fonction de
chart dédiée. ring ne dessine jamais qu’un anneau complet, donc un
cadran de progression à balayage partiel nécessite path avec un arc SVG
calculé à la main — exactement le rôle d’« échappatoire » décrit plus haut :
import math
import seraplot as sp
def arc_path(cx, cy, r, pct):
start = -math.pi / 2
end = start + 2 * math.pi * pct
x1, y1 = cx + r * math.cos(start), cy + r * math.sin(start)
x2, y2 = cx + r * math.cos(end), cy + r * math.sin(end)
large_arc = 1 if pct > 0.5 else 0
return f"M {x1:.2f},{y1:.2f} A {r},{r} 0 {large_arc},1 {x2:.2f},{y2:.2f}"
cv = sp.Canvas(300, 300, bg="#0a0a0f")
cv.gradient("dial-g", "#6366f1", "#22d3ee", x1=0, y1=0, x2=1, y2=1)
cv.ring(150, 150, 100, 112, fill="#1e293b", name="track")
cv.path(arc_path(150, 150, 106, 0.72), fill="none", stroke="url(#dial-g)",
stroke_width=12, name="progress")
cv.text("72%", 150, 158, size=34, color="#f8fafc", weight="800",
anchor="middle", name="pct-label")
chart = cv.build()
ring dessine la piste de fond statique, path dessine par-dessus l’arc
de progression réel (tracé avec le gradient défini juste avant), et
text centre le nombre — trois primitives issues de trois sections
différentes ci-dessus, une seule jauge autonome.
Le arc_path codé à la main ci-dessus, c’est exactement ce que font
arc/wedge en interne — utilisez-les en premier ; path reste
l’échappatoire pour les formes qu’aucun des deux ne couvre.
Dessin radial : arc, wedge, ribbon, polar
arc, wedge, ribbon, polar et radial_gradient partagent une
convention d’angle : degrés, 0° en haut, croissant dans le sens horaire
— la même convention que les charts pie/donut/gauge, pour que les
compositions radiales se lisent de la même façon.
| Méthode | Effet |
|---|---|
arc(cx, cy, r, start_deg, end_deg, color="#ffffff", width=1.5, opacity=1, cap="round", layer="fg", name="") | Un arc de cercle tracé — rayons, anneaux de progression, graduations radiales. |
wedge(cx, cy, r_inner, r_outer, start_deg, end_deg, fill="#ffffff", stroke="none", stroke_width=0, opacity=1, layer="fg", group="", name="") | Un segment d’anneau rempli ; r_inner=0 le réduit à une part de camembert. La brique de base des barres radiales — une wedge par barre, r_outer mappé sur la valeur. group (ou un appel link() ultérieur par name) le fait rejoindre un groupe de survol comme circle/rect/text. |
ribbon(cx, cy, r, a_start, a_end, b_start, b_end, fill="#ffffff", opacity=0.7, layer="fg", name="") | Une bande courbe reliant deux plages d’arc sur le même cercle en passant par son centre — liens façon chord diagram entre catégories. |
polar(cx, cy, r, deg) -> (x, y) | Du calcul de coordonnées pur, sans dessin — convertit une position radiale en (x, y) pour placer n’importe quelle autre primitive (text, circle, line, un Chart placé) à un angle calculé, sans refaire la trigonométrie à la main. |
radial_gradient(id, from_color, to_color, cx=0.5, cy=0.5, r=0.5) | Le pendant radial de gradient — se référence pareil, fill="url(#id)", pour des lueurs et fondus centre-vers-bord. |
cv = sp.canvas(600, 600, "#0a0a12")
cx, cy = 300, 300
cv.radial_gradient("glow", "#312e81", "#0a0a12", r=0.75)
cv.circle(cx, cy, 260, fill="url(#glow)", name="glow-bg")
values = [8, 15, 6, 22, 11, 18, 4, 13]
n = len(values)
for i, v in enumerate(values):
a0 = i * 360 / n + 2
a1 = (i + 1) * 360 / n - 2
cv.wedge(cx, cy, 60, 60 + v * 7, a0, a1,
fill=f"hsl({i * 360 // n}, 70%, 60%)", name=f"bar-{i}")
lx, ly = cv.polar(cx, cy, 60 + v * 7 + 16, (a0 + a1) / 2)
cv.text(str(v), lx, ly, size=11, color="#f8fafc", anchor="middle", name=f"lbl-{i}")
cv.ribbon(cx, cy, 58, 10, 30, 190, 210, fill="#a78bfa", opacity=0.35, name="link-a")
chart = cv.build()
C’est la même construction que les pièces radiales de
visualcinnamon.com — un centre, des
wedges disposées tout autour, des labels placés avec polar, et des
ribbons qui traversent entre les plages. Rien ici n’est un type
« radial bar chart » dédié ; ce sont les cinq primitives ci-dessus
composées à la main, de la même façon que le reste de Canvas —
y compris en mélangeant un Chart placé à une position calculée par
polar si l’histoire le demande.
Galerie radiale : spirale, sunburst, réseau
Trois formes de plus construites à partir des mêmes primitives — aucune
nouvelle API en dessous de cette ligne, juste polar, curve, wedge et
connector combinés différemment à chaque fois.
Une spirale — le rayon de chaque point grandit avec son index au lieu de rester fixe, la technique derrière les pièces façon timeline-en-spirale comme Searching for Birds :
cx, cy = 300, 300
n = 60
cv = sp.canvas(600, 600, "#0a0a12")
pts = []
for i in range(n):
deg = i * 12
r = 20 + i * 4.2
pts.append(list(cv.polar(cx, cy, r, deg)))
cv.curve(pts, color="#a78bfa", width=2, tension=0.8, name="spiral")
for i in range(0, n, 4):
x, y = pts[i]
cv.circle(x, y, 3 + (i / n) * 5, fill="#22d3ee", name=f"pt-{i}")
chart = cv.build()
Un sunburst — deux anneaux de wedge, les plages de l’anneau extérieur
calculées à partir des proportions de l’anneau intérieur plutôt qu’un
partage égal, pour une lecture hiérarchique « partie d’une partie d’un
tout » :
cx, cy = 300, 300
cv = sp.canvas(600, 600, "#0a0a12")
groups = [("Frontend", 40), ("Backend", 35), ("Data", 25)]
subgroups = {
"Frontend": [("React", 20), ("CSS", 12), ("A11y", 8)],
"Backend": [("API", 18), ("Auth", 10), ("Jobs", 7)],
"Data": [("ETL", 14), ("ML", 11)],
}
total = sum(v for _, v in groups)
cursor = 0.0
for name, v in groups:
span = v / total * 360
cv.wedge(cx, cy, 60, 130, cursor, cursor + span - 2, fill="#6366f1", name=f"inner-{name}")
lx, ly = cv.polar(cx, cy, 95, cursor + span / 2)
cv.text(name, lx, ly, size=11, color="#fff", anchor="middle", name=f"inner-lbl-{name}")
sub_total = sum(sv for _, sv in subgroups[name])
sub_cursor = cursor
for sname, sv in subgroups[name]:
sub_span = sv / sub_total * span
cv.wedge(cx, cy, 135, 200, sub_cursor, sub_cursor + sub_span - 1,
fill="#22d3ee", opacity=0.85, name=f"outer-{sname}")
lx, ly = cv.polar(cx, cy, 168, sub_cursor + sub_span / 2)
cv.text(sname, lx, ly, size=9, color="#0a0a12", anchor="middle", name=f"outer-lbl-{sname}")
sub_cursor += sub_span
cursor += span
chart = cv.build()
Un réseau radial — des nœuds placés sur un cercle avec polar, des
paires aléatoires reliées via le bend de connector pour une courbe
douce plutôt qu’une corde droite ; la même histoire de « relations entre
les choses » que raconte ribbon, dessinée nœuds-et-arêtes plutôt que
bandes-et-arcs :
import random
cx, cy = 300, 300
n = 14
cv = sp.canvas(600, 600, "#0a0a12")
cv.radial_gradient("net-glow", "#1e1b4b", "#0a0a12", r=0.85)
cv.circle(cx, cy, 280, fill="url(#net-glow)", name="bg")
nodes = [cv.polar(cx, cy, 220, i * 360 / n) for i in range(n)]
edges = set()
while len(edges) < 22:
a, b = random.sample(range(n), 2)
edges.add((min(a, b), max(a, b)))
for a, b in edges:
ax, ay = nodes[a]
bx, by = nodes[b]
cv.connector(ax, ay, bx, by, color="#4c1d95", width=1, opacity=0.5, bend=0.15, name=f"edge-{a}-{b}")
for i, (x, y) in enumerate(nodes):
cv.circle(x, y, 8, fill="#a78bfa", stroke="#0a0a12", stroke_width=2, name=f"node-{i}")
chart = cv.build()
Composition réelle : un réseau façon RéciTAC
Un diagramme réseau radial qui organise les disciplines, les
universités, les actions et les résultats d’impact d’un programme de
recherche autour d’un réseau central dense de « stories » et de
« personnes ». Rien de tout cela ne nécessite un type « graphique réseau »
dédié : wedge+polar construisent les anneaux extérieurs en capsules —
largeur angulaire proportionnelle au nombre de stories par discipline,
donc l’anneau lui-même porte de la donnée au lieu de seulement décorer —
polygon+rect construisent les clusters hexagonaux d’impact et leurs
étiquettes en pilule, connector trace chaque arête courbe, et link()
relie les éléments connexes — un cluster hexagonal et chaque story qui le
touche, une université et ses disciplines, les segments de couleur d’une
action — en groupes de survol partagés qui estompent aussi tout le reste
(survoler un élément d’un groupe fait maintenant tomber l’opacité de tout
ce qui n’en fait pas partie — une généralisation au niveau Canvas de
l’effet d’atténuation de Chart.hover_family(), qui ne fonctionne
nativement que sur les charts hiérarchiques/flux comme icicle et sankey).
Les nœuds story sont eux-mêmes cinq vrais charts sp.bubble() place()és —
un par discipline, disposés en rosace pour que chacun apporte sa propre
masse colorée à un noyau dense commun au lieu de former un seul bloc
indifférencié — plutôt que des cercles dessinés à la main. Découper le
hairball ainsi le rend aussi adressable par link() : survoler le segment
d’anneau d’une discipline estompe maintenant le cluster de chaque autre
discipline, isolant le sien. Les positions réellement rendues de chaque
sous-chart sont relues depuis son propre SVG (via data-idx, car
bubble() réordonne son DOM par catégorie) pour que chaque ligne de
connexion tombe exactement sur un vrai point, avec un circle()
transparent à la même position comme cible de link(), ce que l’iframe
propre de bubble() ne peut pas exposer directement, et .no_hover()
désactive le zoom par double-clic intégré à chaque chart, qui sinon
déplacerait un point à l’intérieur de son iframe indépendamment des lignes
de connexion tracées au niveau du canvas vers ses coordonnées de
construction. Trois autres vrais charts sont place()és directement dans
les coins vides et le bas du canvas plutôt qu’encadrés par-dessus : un
sp.bubble() « constellation de stories » débarrassé de ses axes/fond et
rogné en cercle, un sp.histogram() « personnes par story » incliné dans
son coin, et un sp.barh() « stories par discipline » posé bas et centré —
les trois résumant le même jeu de données que le diagramme encode, sous des
angles différents. La composition n’est pas une alternative aux fonctions
de chart de SeraPlot, c’est un moyen de les combiner dans une seule scène.
La version complète et exécutable (avec le jeu de données synthétique, les
groupes de survol université/action, et les deux charts intégrés) se
trouve dans
notebook/canva/recitac_remake.ipynb.
L’esquisse simplifiée ci-dessous montre la technique de base — anneau
extérieur, un cluster hexagonal, une poignée de nœuds du réseau central :
import random
import seraplot as sp
def tangent_rot(angle):
r = (angle - 90) % 360
return r - 180 if 90 < r < 270 else r
W = H = 1700
CX = CY = W / 2
DISCIPLINES = [("Social Science", "#f59e0b"), ("Health", "#16a34a"), ("Engineering", "#38bdf8")]
cv = sp.Canvas(W, H, "#ffffff")
cv.radial_gradient("glow", "#fef9f0", "#ffffff", cx=0.5, cy=0.58, r=0.6)
cv.circle(CX, CY, 640, fill="url(#glow)", layer="bg")
R = 700
span = 120 / len(DISCIPLINES)
for i, (name, color) in enumerate(DISCIPLINES):
a0, a1 = 200 + i * span, 200 + (i + 1) * span - 3
cv.wedge(CX, CY, R - 9, R + 9, a0, a1, fill=color, name=f"disc-{i}")
lx, ly = cv.polar(CX, CY, R - 26, (a0 + a1) / 2)
cv.text(name, lx, ly, size=11, anchor="middle", rotation=tangent_rot((a0 + a1) / 2))
def hexagon(cx, cy, r):
return [list(cv.polar(cx, cy, r, k * 60)) for k in range(6)]
hx, hy = CX - 190, CY - 240
cv.polygon(hexagon(hx, hy, 125), fill="#ecfdf5", stroke="#22c55e", stroke_width=2.5, name="hex-TRUST")
cv.rect(hx - 60, hy - 20, 108, 52, fill="#22c55e", rx=14, name="outcome-TRUST-0")
cv.text("Trust in\nreciprocity", hx - 6, hy + 6, size=10.5, color="#fff", anchor="middle")
stories = []
for i in range(12):
ang, rad = random.uniform(0, 360), random.uniform(60, 300)
x, y = cv.polar(CX, CY, rad, ang)
color = random.choice(DISCIPLINES)[1]
cv.circle(x, y, 10, fill=color, stroke="#fff", stroke_width=1.5, name=f"story-{i}")
cv.connector(x, y, hx, hy, color=color, width=0.8, opacity=0.15, bend=0.3)
stories.append(f"story-{i}")
cv.link("impact-TRUST", ["hex-TRUST", "outcome-TRUST-0"] + stories)
bubbles = sp.bubble(
categories=[random.choice(DISCIPLINES)[0] for _ in range(20)],
x_values=[random.uniform(0, 100) for _ in range(20)],
y_values=[random.uniform(0, 10) for _ in range(20)],
sizes=[random.uniform(10, 40) for _ in range(20)],
palette=[int(c.lstrip("#"), 16) for _, c in DISCIPLINES],
width=700, height=700,
).no_axes().no_background().gridlines(False)
cv.place(bubbles, 20, 100, 280, 280, clip="circle", name="panel-bubbles")
chart = cv.build().zoom()
Le jeu de données est synthétique — l’intérêt est le motif de
composition, pas un portage littéral des vraies données du programme de
recherche de Nadieh (issues d’une feuille Google Sheets privée). Tout
s’adapte aux données : ajouter une discipline et chaque anneau/légende/
chart intégré la prend en compte automatiquement ; ajouter une story fait
apparaître un nouveau nœud, un nouveau jeu de connecteurs, et un nouveau
membre de groupe de survol au prochain build().
.zoom() sur le Chart construit (appelé ci-dessus,
cv.build().zoom()) active le zoom molette/pincement et le glisser-déposer
pour toute la composition — utile dès qu’un canvas est assez dense pour que
survoler un hexagone ou un nœud individuel bénéficie d’un zoom préalable.
Une astuce pour les très grands canvas : chart seul (ou
chart.show()) dimensionne son <iframe> en ligne via aspect-ratio CSS,
que certains frontends de notebook résolvent de façon peu fiable pour de
grands canvas carrés comme celui-ci (1700×1700), rognant silencieusement
le rendu au lieu de le réduire pour qu’il tienne. chart.save(path)
combiné à IPython.display.IFrame(src=path, width=..., height=...)
contourne ce problème en réservant une taille en pixels explicite dès le
départ — le script interne de mise à l’échelle du canvas réduit alors la
composition complète à cette taille, donc vous la voyez toujours en
intégralité.
Une constellation de vrais charts : satellites sur des lignes en pointillés
Une forme de composition différente des anneaux et du réseau central de
RéciTAC : un sp.scatter() central traçant la qualité moyenne de chaque
modèle contre ses émissions moyennes, et un petit sp.scatter() satellite
par modèle — son propre nuage d’événements bruts, réduit à de simples points
via no_axes()/no_title()/hide_grid()/no_legend()/no_background() —
disposés en anneau autour. Chaque satellite se relie à son point exact sur
le graphique central par une line() en pointillés.
Le lien satellite-point utilise hover_group directement sur line()/
circle()/place() plutôt qu’un appel link() séparé : passez la même
chaîne hover_group="sat-3" à chaque élément qui doit briller ensemble, et
ils sont liés dès leur création — pas besoin de name= puis d’un appel
cv.link(...) après coup. Les deux mécanismes aboutissent au même résultat ;
hover_group est la version en une ligne quand vous connaissez le groupe
dès la création.
import math
import random
import seraplot as sp
random.seed(3)
MODELS = [("Titan-7B", "#6366f1"), ("Mixtral-Sparse", "#7c3aed"),
("Codex-T5", "#0891b2"), ("VisionSpeak-VL", "#0ea5e9"),
("DiffuGen-2", "#d97706"), ("BertCore", "#059669")]
means = [(random.uniform(55, 95), random.uniform(3.8, 5.2)) for _ in MODELS]
central = sp.scatter(
"Quality vs log(emissions)",
x=[m[0] for m in means], y=[m[1] for m in means],
labels=[name for name, _ in MODELS], groups=[name for name, _ in MODELS],
x_label="Quality score", y_label="log10 gCO2e", width=520, height=400,
)
CW = CH = 900
CX = CY = 450
R = 340
cv = sp.Canvas(CW, CH, "#ffffff")
cv.place(central, CX - 260, CY - 200, 520, 400)
for i, (name, color) in enumerate(MODELS):
ang = math.radians(-90 + i * 360 / len(MODELS))
mx, my = CX + R * math.cos(ang), CY + R * math.sin(ang)
px = CX - 260 + 60 + (means[i][0] - 55) / 40 * 400
py = CY - 200 + 30 + (1 - (means[i][1] - 3.8) / 1.4) * 330
hg = f"sat-{i}"
cv.line(px, py, mx, my, color=color, width=1.2, dash="2 5", hover_group=hg)
mini = sp.scatter(
"", x=[random.gauss(0, 1) for _ in range(30)],
y=[random.gauss(0, 1) for _ in range(30)],
labels=[name] * 30, groups=[name] * 30, palette=[int(color[1:], 16)],
width=150, height=110,
).no_axes().no_title().hide_grid().no_legend().no_background().no_hover()
cv.place(mini, mx - 75, my - 55, 150, 110, group=hg)
cv.text(name, mx, my - 62, size=9, color=color, anchor="middle", weight="bold")
chart = cv.build()
Le jeu de données de l’aperçu ci-dessus est également synthétique — recréé
à partir du même projet de data-story ai_story, dont le fichier
constellation.py référençait des CSV qui n’existent plus. 14
architectures au lieu de 6, et le nuage d’événements de chaque satellite
provient de vraies données par modèle (jusqu’à 60 événements) plutôt que
d’une approximation gaussienne — la technique est identique, juste avec
plus de lignes et un second jeu de données jointu par model_id.
Composer de vrais charts : un tableau de bord mission-control
place() intègre un Chart complet — pas seulement une primitive — dans
un canvas, ce qui signifie que la composition canvas ne se limite pas aux
formes dessinées à la main : de vrais panneaux sp.line(), sp.bar(),
sp.gauge(), sp.area(), sp.donut(), sp.barh() peuvent être encadrés,
connectés et annotés par les mêmes primitives que partout ailleurs sur
cette page. Ce qui fait vraiment la différence entre “tableau de bord” et
“charts dans des boîtes”, c’est la même astuce que le réseau RéciTAC
ci-dessus : un centre partagé auquel chaque panneau central se
connecte. Un hub lumineux, 4 rayons colorés (connector + une ancre
circle à chaque bout), chaque rayon teinté pour correspondre à son
panneau via les méthodes chainables de niveau chart (palette(),
gridlines(), width()/height(), title_color() — voir
Méthodes de graphique) appliquées
avant place(). Au-delà des quatre panneaux du hub, un ruban de KPI avec
ses propres mini-graphiques sp.line() en ligne surplombe la grille, deux
vrais charts supplémentaires l’encadrent à droite via une connexion plus
subtile en hover_group, et un panneau de débit pleine largeur referme la
composition en bas :
import random
import seraplot as sp
random.seed(3)
W, H = 1950, 1380
cv = sp.Canvas(W, H)
cv.radial_gradient("dashBg", "#1a2140", "#04050a", cx=0.5, cy=0.42, r=1.0)
cv.rect(0, 0, W, H, fill="url(#dashBg)", layer="bg")
cv.radial_gradient("hubGlow", "#22d3ee", "#04050a", cx=0.5, cy=0.5, r=0.5)
cv.text("Mission Control", 48, 56, size=30, color="#f8fafc", weight="800")
cv.text("Chaque panneau relié à un seul hub vivant — sp.Canvas place() + connecteurs + de vrais charts SeraPlot",
48, 84, size=13, color="#64748b")
cv.text("mis à jour il y a 2s", W - 60, 50, size=11, color="#475569", anchor="end")
cv.circle(W - 260, 47, 5, fill="#22c55e", name="live-dot")
cv.circle(W - 260, 47, 5, fill="none", stroke="#22c55e", stroke_width=1.5, opacity=0.6, name="live-pulse")
cv.text("LIVE", W - 246, 52, size=12, color="#22c55e", weight="700", letter_spacing=1.5)
KPI = [
("UTILISATEURS ACTIFS", "12 940", "+6.1%", "#6366f1", [820, 860, 901, 934, 990, 1120, 1180, 1290, 1330, 1320]),
("MRR", "184.2k $", "+3.4%", "#22d3ee", [140, 148, 152, 149, 158, 165, 170, 176, 180, 184]),
("DISPONIBILITÉ", "99.982%", "+0.02%", "#f59e0b", [99.9, 99.91, 99.95, 99.93, 99.96, 99.97, 99.98, 99.97, 99.98, 99.982]),
("INCIDENTS OUVERTS", "5", "-2 aujourd'hui", "#f472b6", [9, 8, 8, 7, 6, 7, 6, 6, 5, 5]),
]
KPI_Y = 118
KPI_W, KPI_H = 340, 92
for i, (label, value, delta, color, series) in enumerate(KPI):
kx = 48 + i * (KPI_W + 20)
cv.rect(kx, KPI_Y, KPI_W, KPI_H, fill="#0b1022", stroke="rgba(255,255,255,.07)",
stroke_width=1, rx=14, layer="bg", name=f"kpi-{i}")
cv.rect(kx, KPI_Y, 4, KPI_H, fill=color, rx=2, layer="bg")
cv.text(label, kx + 20, KPI_Y + 26, size=10.5, color="#64748b", weight="700", letter_spacing=1.2)
cv.text(value, kx + 20, KPI_Y + 58, size=24, color="#f8fafc", weight="800")
cv.text(delta, kx + 20, KPI_Y + 78, size=11.5, color=color, weight="600")
spark = sp.line(labels=[str(j) for j in range(len(series))], values=series,
color_hex=int(color.lstrip("#"), 16), width=150, height=64) \
.no_axes().no_title().hide_grid().no_legend().no_background().no_hover()
cv.place(spark, kx + KPI_W - 168, KPI_Y + 16, 150, 64, name=f"kpi-spark-{i}")
GRID_Y0 = 250
PW, PH = 660, 320
GAP_X, GAP_Y = 100, 80
COL0, COL1 = 60, 60 + PW + GAP_X
ROW0, ROW1 = GRID_Y0, GRID_Y0 + PH + GAP_Y
HX, HY, HR = (COL0 + PW + GAP_X / 2), (ROW0 + PH + GAP_Y / 2), 92
PALETTE = [0x6366f1, 0x22d3ee, 0xf59e0b, 0xf472b6]
HEX = [f"#{c:06x}" for c in PALETTE]
PANELS = [("trend", COL0, ROW0, HEX[0]), ("revenue", COL1, ROW0, HEX[1]),
("health", COL0, ROW1, HEX[2]), ("incidents", COL1, ROW1, HEX[3])]
def panel_frame(x, y, w, h, color, name):
cv.rect(x - 16, y - 16, w + 32, h + 32, fill="#0b1022", stroke="rgba(255,255,255,.06)",
stroke_width=1, rx=18, layer="bg", name=name)
cv.rect(x - 16, y - 16, w + 32, 4, fill=color, rx=2, layer="bg")
for name, x, y, color in PANELS:
panel_frame(x, y, PW, PH, color, f"panel-{name}")
trend = sp.line(labels=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
values=[820, 932, 901, 934, 1290, 1330, 1320],
).width(PW).height(PH).palette(PALETTE).gridlines(False).background("#0b1022")
revenue = sp.bar(labels=["Core", "Cloud", "API", "Mobile", "Support"],
values=[420, 680, 310, 240, 150], title="Revenue by Segment",
).width(PW).height(PH).palette(PALETTE).gridlines(False).background("#0b1022").title_color("#e2e8f0")
health = sp.gauge(value=87).width(PW).height(PH).background("#0b1022")
incidents = sp.area(labels=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], values=[5, 3, 6, 2, 4, 1, 2],
title="Open Incidents",
).width(PW).height(PH).palette([PALETTE[3]]).gridlines(False).background("#0b1022").title_color("#e2e8f0")
cv.place(trend, COL0, ROW0, PW, PH, name="chart-trend")
cv.place(revenue, COL1, ROW0, PW, PH, name="chart-revenue")
cv.place(health, COL0, ROW1, PW, PH, name="chart-health")
cv.place(incidents, COL1, ROW1, PW, PH, name="chart-incidents")
cv.text("Weekly Active Users", COL0 + 20, ROW0 + 26, size=13, color="#e2e8f0", weight="700")
cv.text("System Health", COL0 + 20, ROW1 + 26, size=13, color="#e2e8f0", weight="700")
ANCHORS = {
"trend": (COL0 + PW, ROW0 + PH / 2),
"revenue": (COL1, ROW0 + PH / 2),
"health": (COL0 + PW, ROW1 + PH / 2),
"incidents": (COL1, ROW1 + PH / 2),
}
for name, x, y, color in PANELS:
ax, ay = ANCHORS[name]
cv.connector(ax, ay, HX, HY, color=color, width=2, opacity=0.55, bend=0.28, name=f"spoke-{name}")
cv.circle(ax, ay, 6, fill=color, stroke="#04050a", stroke_width=2, name=f"anchor-{name}")
cv.circle(ax, ay, 11, fill="none", stroke=color, stroke_width=1, opacity=0.4)
cv.circle(HX, HY, HR + 34, fill="url(#hubGlow)", opacity=0.35)
cv.ring(HX, HY, HR + 18, HR + 22, fill="#22d3ee", opacity=0.25)
cv.ring(HX, HY, HR + 6, HR + 9, fill="#22d3ee", opacity=0.45)
start = 0.0
for name, _, _, color in PANELS:
end = start + 90
cv.wedge(HX, HY, HR - 10, HR - 4, start, end, fill=color, opacity=0.85)
start = end
cv.circle(HX, HY, HR - 14, fill="#0b1022", stroke="#f8fafc", stroke_width=2)
cv.text("87", HX, HY - 4, size=40, color="#22d3ee", weight="800", anchor="middle")
cv.text("SYSTEM SCORE", HX, HY + 22, size=10, color="#64748b", anchor="middle", letter_spacing=1.5)
cv.link("hub-cluster", ["anchor-trend", "anchor-revenue", "anchor-health", "anchor-incidents"])
cv.annotate("Trending up 61% since Monday", COL0 + 190, ROW0 + 170, COL0 + 40, ROW0 + PH + 55,
color="#94a3b8", size=12, line_dash="4,3", bg="#0b1022")
cv.annotate("5 open, 2 critical", COL1 + 460, ROW1 + 90, COL1 + 40, ROW1 + PH + 55,
color="#94a3b8", size=12, line_dash="4,3", bg="#0b1022")
SIDE_X = COL1 + PW + GAP_X
SIDE_W = W - SIDE_X - 60
panel_frame(SIDE_X, ROW0, SIDE_W, PH, "#a78bfa", "panel-region")
panel_frame(SIDE_X, ROW1, SIDE_W, PH, "#34d399", "panel-latency")
region = sp.donut(labels=["NA", "EU", "APAC", "LATAM"], values=[40, 28, 20, 12],
title="Regional Split", width=SIDE_W, height=PH,
palette=[0x6366f1, 0x22d3ee, 0xf59e0b, 0xf472b6]) \
.background("#0b1022").title_color("#e2e8f0")
latency = sp.barh(labels=["p50", "p90", "p95", "p99"], values=[42, 118, 210, 480],
title="Latency (ms)", width=SIDE_W, height=PH,
palette=[0x34d399]) \
.gridlines(False).background("#0b1022").title_color("#e2e8f0")
cv.place(region, SIDE_X, ROW0, SIDE_W, PH, group="side-link", name="panel-region-chart")
cv.place(latency, SIDE_X, ROW1, SIDE_W, PH, group="side-link", name="panel-latency-chart")
SIDE_LINK = [(ROW0, "#a78bfa"), (ROW1, "#34d399")]
for row_y, color in SIDE_LINK:
ax, ay = COL1 + PW, row_y + PH / 2
bx, by = SIDE_X, row_y + PH / 2
cv.line(ax, ay, bx, by, color="#94a3b8", width=1, dash="2 5", hover_group="side-link")
cv.circle(ax, ay, 5, fill=color, stroke="#04050a", stroke_width=2)
cv.circle(bx, by, 5, fill=color, stroke="#04050a", stroke_width=2)
STRIP_Y = ROW1 + PH + GAP_Y
STRIP_H = H - STRIP_Y - 60
panel_frame(60, STRIP_Y, W - 120, STRIP_H, "#22d3ee", "panel-throughput")
hours = [f"{h:02d}:00" for h in range(0, 24, 2)]
throughput = [random.randint(800, 2600) for _ in hours]
throughput_chart = sp.bar(labels=hours, values=throughput, title="Requests/sec — last 24h",
width=W - 120, height=STRIP_H,
color_hex=0x22d3ee) \
.gridlines(False).background("#0b1022").title_color("#e2e8f0")
cv.place(throughput_chart, 60, STRIP_Y, W - 120, STRIP_H, name="panel-throughput-chart")
chart = cv.build()
Le hub lui-même est une petite jauge radiale déguisée — 4 parts de wedge
(une par panneau, dans sa couleur) à l’intérieur de deux pistes ring
pulsées, construites avec exactement les mêmes primitives que le cadran
“Composer les micro-outils” et les anneaux donut RéciTAC ci-dessus.
Réutiliser un seul langage visuel sur chaque exemple travaillé de cette
page est tout l’intérêt : les primitives ne savent pas et ne se soucient
pas de dessiner un cadran de progression, un anneau de discipline, ou un
hub de tableau de bord.
panel_frame() dessine une carte arrondie plus une fine bordure supérieure
teintée — cette couleur d’accent est la même utilisée pour le rayon de ce
panneau et son palette(), pour que l’œil relie “cette ligne est orange”
à “ce panneau est orange” à “ce rayon est orange” sans légende. Le détail
facile à manquer derrière tout ça : toute décoration censée se trouver
derrière un chart placé (fond, cadres de panneau) a besoin de
layer="bg" explicitement — le layer="fg" par défaut de rect() se
dessine au-dessus des charts placés par conception (pour que
connecteurs et annotations puissent les traverser), ce qui masquera
silencieusement le contenu d’un panneau si celui-ci est dessiné sur la
couche de premier plan.
Deux choses ont dû être corrigées pour arriver ici depuis la version à
quatre panneaux d’origine. D’abord, title_color() : sp.bar(),
sp.area() et sp.donut() acceptaient volontiers une couleur de titre
explicite, mais sp.line()/sp.gauge() rendaient leur titre dans une
teinte par défaut à peine visible quoi qu’il arrive — contourné en
sautant complètement le title= propre au chart pour ces deux-là et en
dessinant le nom du panneau comme un simple cv.text() à la même
position, ce qui correspond aussi aux étiquettes dessinées à la main des
tuiles KPI. Ensuite, le générateur de graduations Y d’un chart réserve un
espace proportionnel à la taille en pixels demandée du plot, pas à son
empreinte place()e — les étiquettes de graduation du bandeau de débit du
bas se chevauchaient sérieusement jusqu’à ce que son sp.bar() soit
demandé à la taille native complète du bandeau (width=W-120, height=STRIP_H) plutôt qu’une boîte sous-dimensionnée censée être étirée.
Les deux panneaux de droite (Regional Split, Latency (ms)) ne sont pas
câblés dans le hub — un cinquième et sixième rayon auraient surchargé
l’unique idée visuelle que le hub est censé communiquer — mais ils ne sont
pas orphelins pour autant : un seul hover_group="side-link" sur les deux
panneaux et leur ligne de connexion les relie exactement comme les
satellites RéciTAC, si bien que survoler l’un ou l’autre estompe tout ce
qui est en dehors de cette paire via le même comportement de survol
Canvas décrit plus haut.
Mises en page organiques : Voronoi
voronoi(sites, x, y, w, h, fills=None, stroke=..., stroke_width=..., opacity=...)
calcule un diagramme de Voronoi borné — une cellule par site, chaque cellule
étant la région plus proche de ce site que de tout autre — et ajoute chaque
cellule au canvas comme un polygon() en un seul appel, en renvoyant leurs
indices d’éléments pour un adressage ultérieur (groupes de survol,
derive(), etc.).
import random
cv = sp.Canvas(900, 540)
sites = [[random.uniform(30, 870), random.uniform(30, 510)] for _ in range(22)]
palette = ["#6366f1", "#ec4899", "#22c55e", "#f59e0b", "#06b6d4", "#8b5cf6", "#ef4444"]
fills = [palette[i % len(palette)] for i in range(len(sites))]
cv.voronoi(sites, 0, 0, 900, 540, fills=fills, stroke="#0d1117", stroke_width=2, opacity=0.88)
La taille des cellules suit automatiquement la densité des sites — resserrer des sites rétrécit leurs cellules, utile pour une mise en page façon treemap (“une cellule par enregistrement, colorée par catégorie, dimensionnée par densité locale”) sans algorithme de packing séparé. Implémenté nativement (découpage itératif par demi-plans contre chaque autre site, aucune dépendance de géométrie externe).
CSS / JS custom
| Méthode | Effet |
|---|---|
style(name, css) | Injecte [data-sp-name="name"]{ css } dans le <style> du canvas. Passer name="" pour injecter un bloc CSS brut non scopé (ex. @keyframes). |
script(js) | Ajoute un <script>js</script> brut avant </body> — contrôle manuel complet pour qui veut écrire son interactivité à la main. |
Groupes et liaison inter-plot
Deux mécanismes distincts, tous deux pilotés par les noms d’éléments :
group(group_name, member_names) / move_group(group_name, dx, dy) —
déplace plusieurs éléments nommés ensemble comme un bloc rigide. nudge(name, dx, dy) et resize(name, dw, dh) font pareil pour un seul élément. Les
pins enregistrés sur un chart avant un déplacement/redimensionnement sont
automatiquement décalés avec lui.
link(group_name, member_names) -> int — relie des éléments à
travers des panneaux différents en un seul groupe de survol : survoler
n’importe quel élément lié (Chart, Rect, Text, Circle, Wedge ou
Polygon) fait briller/pulser tous les autres du même groupe. Renvoie le
nombre de noms effectivement liables (Line, RawPath et autres types
purement décoratifs ne le supportent pas encore). circle(...) et
polygon(...) acceptent aussi un paramètre hover_group= pour rejoindre un
groupe dès la création, sans appel link() séparé — les deux chemins posent
les mêmes attributs data-sp-grp/data-group, si bien que les cercles/
polygones groupés sont aussi pris en compte par la méthode chainable
.group_hover_opacity(dim) de n’importe quel chart (voir
Méthodes de Chart) — un seul survol
peut ainsi assombrir toutes les marques hors groupe sur le canvas pendant
que le halo/pulsation natif s’occupe de celles qui correspondent.
cv.link("story", ["revenue_chart", "trend_chart", "kpi_card"])
cv.circle(120, 80, 6, fill="#2dd4bf", hover_group="alice", name="c1")
Les groupes comptant beaucoup d’éléments (un chart chargé avec des centaines
de marques dans le même hover_group) sautent automatiquement le halo/
agrandissement par élément au survol et retombent sur le simple
assombrissement de famille — la mise en avant reste légère quelle que soit
la taille du groupe.
tooltip — attacher une carte de survol native
tooltip(name, title, kv=[], avatar="", subtitle="", image="", video="", html="") -> bool
attache après coup une carte de survol à un circle(...) ou polygon(...)
déjà créé, en réutilisant exactement la carte #sp-tip que tout chart de la
famille chart affiche déjà au survol — aucun CSS ni JS supplémentaire à
écrire. title et chaque paire kv (clé, valeur) sont échappés et mis en
page automatiquement ; avatar s’affiche en petite puce ronde à côté du
titre (une personne, une équipe, un propriétaire de dépôt…) ; subtitle
est une ligne alignée à gauche sous le titre, la bonne place pour une phrase
plus longue qu’une ligne kv ferait sinon s’enrouler maladroitement ;
image/video ajoutent un bloc média en bannière pleine largeur sous le
corps de la carte ; html est un dernier recours brut, non échappé, pour
tout ce que les champs structurés ne peuvent pas exprimer. Renvoie False
si name n’est pas un Circle/Polygon adressable.
cv.circle(cx, cy, 6, fill="#2dd4bf", hover_group="alice", name="c1")
cv.tooltip("c1", "Alice Martin", [("Rôle", "Ingénieure"), ("Depuis", "2019")],
avatar="https://.../alice.png", subtitle="A rejoint l'équipe data")
frieze / timeline / chronology — disposition chronologique inter-plot
Trois noms pour le même outil : dispose labels en grille en serpentin
(boustrophédon) — de gauche à droite, puis de droite à gauche à la ligne
suivante, etc. — reliés par des segments droits au sein d’une ligne et par
une courbe en S à chaque retour à la ligne, avec un anneau englobant et une
étiquette par cellule. Renvoie l’ancre (x, y, rayon_anneau) de chaque
cellule, pour dessiner ou placer n’importe quel autre chart ou forme
directement dessus — idéal pour enfiler plusieurs amas
circle_pack(variant="swarm") mono-groupe, ou tout autre petit chart, sur
une vraie chronologie plutôt que de dépendre de la mise en page intégrée
d’un variant.
anchors = cv.frieze(week_labels, weights=week_counts, cols=8,
cell_w=280, cell_h=280, ring_color="#7dd3fc")
for (x, y, r), commits_in_week in zip(anchors, weeks):
...
Connecter deux charts (pins)
Les pins sont des points d’ancrage nommés enregistrés dans l’espace de
coordonnées d’un chart placé, en coordonnées pixel du canvas.
connect()/annotate_at() lisent les pins pour tracer une ligne ou une
étiquette entre (ou par-dessus) des charts.
| Méthode | Effet |
|---|---|
pin(chart_ref, name, local_x, local_y) | Enregistre un pin à une coordonnée pixel locale au chart. |
pin_frac(chart_ref, name, fx, fy) | Enregistre un pin à une position fractionnaire (0..1) de la taille native du chart. |
| `pin_xy(chart_ref, name) -> (x, y) | None` |
attach_bar(chart_ref, values, chart_w, chart_h, ...) | Enregistre automatiquement les pins bar:{i}:top/center/bottom/left/right en lisant les rectangles de barres réellement rendus. |
attach_scatter(chart_ref, x_vals, y_vals, labels, chart_w, chart_h, ...) | Enregistre automatiquement les pins point:{i} (et nommés) à partir des positions projetées des données. |
connect(from_ref, from_name, to_ref, to_name, ...) | Trace un connecteur courbe entre deux pins, éventuellement sur deux charts différents. |
annotate_at(chart_ref, pin_name, text, ...) | Trace une étiquette avec ligne de rappel pointant vers un pin. |
Les pins deviennent obsolètes quand la géométrie qui les a produits
change. refill() sur un chart efface ses pins (pour ne pas connecter
silencieusement vers des coordonnées appartenant à l’ancien contenu) —
re-pinnez après un refill si vous en avez encore besoin. nudge/resize/
move_group, en revanche, décalent bien les pins existants automatiquement,
puisque le contenu sous-jacent n’a pas changé.
Squelettes réutilisables : template & derive
skeleton = base_canvas.template() # retire les Chart/Image, garde le reste
dashboard = skeleton.derive() # clone profond d'une instance prête à remplir
dashboard.fill("main", my_chart, name="panel")
template() renvoie un canvas dont tous les charts place()és et images
image()ées sont retirés, mais où chaque élément décoratif (cartes,
dégradés, titres, slots, groupes, CSS/JS custom) reste intact — la “classe”
réutilisable. derive() clone en profondeur n’importe quel canvas
(templatisé ou non) en une instance indépendante — “l’instanciation”.
Construisez votre squelette de marque une fois, puis derive() + fill()
par jeu de données/variante au lieu de répéter le code de mise en page.
Persistance
| Méthode | Effet |
|---|---|
save(path) | Sérialise tout l’état du canvas (éléments, pins, groupes, slots, CSS/JS custom) en JSON. |
sp.canvas_load(path) -> Canvas | Reconstruit un canvas depuis un fichier JSON sauvegardé. |
sp.canvas_save_named(cv, name) -> str | Sauvegarde sous ~/.seraplot/canvas/{name}.json et met à jour un manifeste index.json. |
sp.canvas_load_named(name) -> Canvas | Recharge via ce manifeste. |
to_json() -> str | La chaîne JSON brute, pour gérer soi-même le stockage. |
C’est ce qui permet à un dashboard généré de survivre à la fermeture et à la
réouverture de l’application : cv.save(...) une fois, sp.canvas_load(...)
à la session suivante reconstruit un canvas identique — positions, liens,
style, tout.
Mode dev interactif
cv.dev()
Rend le canvas avec un panneau flottant : glissez n’importe quel élément
nommé pour le déplacer, glissez la poignée en coin des charts/images pour
les redimensionner, le survol affiche le nom de l’élément et son groupe lié
(le cas échéant). Le bouton Copy Python du panneau génère les appels
cv.nudge(...)/cv.resize(...) équivalents ; Download JSON exporte les
mêmes deltas dans un fichier que apply_deltas_json() peut rejouer sans
interface (cv.apply_deltas_json(open(path).read())) — le chemin entre
ajustement interactif et script reproductible.
Web App (sp.App)
sp.App is a small, dependency-free reactive dashboard server built directly
into the Rust core — no Flask, no Dash, no Node. .serve() spins up a
Tokio-based HTTP + WebSocket server (hand-rolled HTTP/1.1 parsing and
RFC 6455 framing, no external web framework) that pushes live UI updates
to the browser whenever a registered callback re-runs.
import seraplot as sp
def on_change(period):
values = {"7d": [12, 19, 15], "30d": [40, 55, 38]}[period]
chart = sp.line("Sales", labels=["A", "B", "C"], values=values)
return chart # .html is extracted automatically
app = sp.App("Sales Dashboard")
app.dropdown("period", ["7d", "30d"], value="7d")
app.chart("out", sp.line("Sales", labels=["A", "B", "C"], values=[12, 19, 15]).html)
app.add_callback(inputs=["period"], output="out", handler=on_change)
app.serve(port=8787)
Open http://127.0.0.1:8787/ — changing the dropdown re-runs on_change
server-side and pushes the new chart HTML into the page without a reload.
How it works
App(title)creates a single-page app state with an implicit"/"page..page(path, title=None)registers/switches to additional pages; every component call after it attaches to that page until the next.page().- Component builders (
dropdown,slider,button,text_input,checkbox,chart) render server-side HTML for that widget, register its initial value, and append it to the current page’s layout. All of them returnself, so calls chain. .add_callback(inputs, output, handler)wires a Python callable: whenever any component whose id is ininputschanges,handleris invoked with the current value of every input, typed to its component —floatfor aslider,boolfor acheckbox,strfor everything else — positionally, in the order given toinputs. Its return value becomes the new HTML foroutput— either a raw string, or any object exposing an.htmlattribute (aChartworks directly, no.htmlaccess needed on the caller’s side)..interval(seconds, output, handler)wires a Python callable that fires on a server-side timer instead of a client event — no arguments, same return contract as a callback..push(id, html)sets a component’s HTML and broadcasts it to every connected browser immediately, from outside any callback (e.g. from a background thread). Both bypass the request/response cycle: they reach the browser over the same open WebSocket, unprompted.- Each browser tab that opens
/wsgets its own session — input values are tracked per connection, so two tabs moving the same-id slider don’t clobber each other’s callback inputs. .auth(username, password)gates every request (page loads and the/wsupgrade) behind HTTP Basic Auth; omit it and the app stays open..serve(port=8787, host="127.0.0.1")blocks and starts the server. The browser opens a WebSocket to/ws; every input interaction sends{"type":"event","id":...,"value":...}, the server re-runs matching callbacks and pushes back{"type":"update","id":...,"html":...}— the same message an.interval()tick or a.push()call sends — and a ~15-line bootstrap script doesdocument.getElementById(id).innerHTML = html— no virtual DOM, no client-side framework.
Component reference
| Method | Signature | Notes |
|---|---|---|
App(title="SeraPlot App") | constructor | |
.page(path, title=None) | (str, str | None) | Creates the page on first call, switches the “current page” on every call |
.dropdown(id, options, value=None) | (str, list[str], str | None) | Defaults to options[0] if value omitted |
.slider(id, min, max, step=1.0, value=None) | (str, float, float, float, float | None) | Defaults to min if value omitted |
.button(id, label) | (str, str) | Emits value "click" on press |
.text_input(id, value="", placeholder="") | (str, str, str) | |
.checkbox(id, label, checked=False) | (str, str, bool) | Emits "true"/"false" |
.chart(id, html="") | (str, str) | Registers an output slot; typically seeded with a Chart.html and refreshed via a callback or .push() |
.add_callback(inputs, output, handler) | (list[str], str, Callable) | handler receives one positional argument per entry in inputs, typed to its component (float/bool/str) |
.interval(seconds, output, handler) | (float, str, Callable) | handler takes no arguments; fires on a repeating server-side timer, independent of any client event |
.push(id, html) | (str, str | Chart) | Sets id’s HTML and broadcasts it to every open connection immediately |
.auth(username, password) | (str, str) | Gates every request behind HTTP Basic Auth |
.serve(port=8787, host="127.0.0.1") | (int, str) | Blocking call |
sp.App est un petit serveur de tableau de bord réactif, sans dépendance,
intégré directement au cœur Rust — pas de Flask, pas de Dash, pas de Node.
.serve() démarre un serveur HTTP + WebSocket basé sur Tokio (parsing
HTTP/1.1 et trames RFC 6455 écrits à la main, sans framework web
externe) qui pousse les mises à jour de l’interface vers le navigateur à
chaque nouvelle exécution d’un callback enregistré.
import seraplot as sp
def on_change(period):
values = {"7d": [12, 19, 15], "30d": [40, 55, 38]}[period]
chart = sp.line("Ventes", labels=["A", "B", "C"], values=values)
return chart # .html est extrait automatiquement
app = sp.App("Tableau de bord Ventes")
app.dropdown("period", ["7d", "30d"], value="7d")
app.chart("out", sp.line("Ventes", labels=["A", "B", "C"], values=[12, 19, 15]).html)
app.add_callback(inputs=["period"], output="out", handler=on_change)
app.serve(port=8787)
Ouvrez http://127.0.0.1:8787/ — changer le menu déroulant relance
on_change côté serveur et pousse le nouveau HTML du graphique dans la page
sans rechargement.
Fonctionnement
App(title)crée un état d’application avec une page implicite"/"..page(path, title=None)enregistre/bascule vers d’autres pages ; chaque appel de composant suivant s’attache à cette page jusqu’au.page()suivant.- Les constructeurs de composants (
dropdown,slider,button,text_input,checkbox,chart) génèrent le HTML côté serveur du widget, enregistrent sa valeur initiale et l’ajoutent à la mise en page de la page courante. Tous retournentself, donc les appels s’enchaînent. .add_callback(inputs, output, handler)relie un callable Python : dès qu’un composant dont l’id figure dansinputschange,handlerest appelé avec la valeur courante de chaque input, typée selon son composant —floatpour unslider,boolpour unecheckbox,strpour le reste — en positionnel, dans l’ordre deinputs. Sa valeur de retour devient le nouveau HTML deoutput— une chaîne brute, ou tout objet exposant un attribut.html(unChartfonctionne directement, sans accès.htmlcôté appelant)..interval(seconds, output, handler)relie un callable Python déclenché par un minuteur côté serveur plutôt qu’un événement client — sans argument, même contrat de retour qu’un callback..push(id, html)fixe le HTML d’un composant et le diffuse immédiatement à toutes les connexions ouvertes, depuis l’extérieur de tout callback (par ex. depuis un thread d’arrière-plan). Les deux contournent le cycle requête/réponse : ils atteignent le navigateur sur le même WebSocket ouvert, sans sollicitation préalable.- Chaque onglet de navigateur qui ouvre
/wsobtient sa propre session — les valeurs des inputs sont suivies par connexion, donc deux onglets qui modifient un slider de même id ne s’écrasent pas mutuellement dans les callbacks. .auth(username, password)protège chaque requête (chargements de page et upgrade/ws) derrière une authentification HTTP Basic ; omise, l’application reste ouverte..serve(port=8787, host="127.0.0.1")bloque et démarre le serveur. Le navigateur ouvre un WebSocket vers/ws; chaque interaction envoie{"type":"event","id":...,"value":...}, le serveur relance les callbacks correspondants et repousse{"type":"update","id":...,"html":...}— le même message qu’envoie un tick.interval()ou un appel.push()— et un script d’amorçage d’une quinzaine de lignes faitdocument.getElementById(id).innerHTML = html— pas de DOM virtuel, pas de framework côté client.
Référence des composants
| Méthode | Signature | Remarques |
|---|---|---|
App(title="SeraPlot App") | constructeur | |
.page(path, title=None) | (str, str | None) | Crée la page au premier appel, bascule la « page courante » à chaque appel |
.dropdown(id, options, value=None) | (str, list[str], str | None) | Vaut options[0] par défaut si value omis |
.slider(id, min, max, step=1.0, value=None) | (str, float, float, float, float | None) | Vaut min par défaut si value omis |
.button(id, label) | (str, str) | Émet la valeur "click" au clic |
.text_input(id, value="", placeholder="") | (str, str, str) | |
.checkbox(id, label, checked=False) | (str, str, bool) | Émet "true"/"false" |
.chart(id, html="") | (str, str) | Enregistre un emplacement de sortie ; généralement initialisé avec un Chart.html et rafraîchi via un callback ou .push() |
.add_callback(inputs, output, handler) | (list[str], str, Callable) | handler reçoit un argument positionnel par entrée de inputs, typé selon son composant (float/bool/str) |
.interval(seconds, output, handler) | (float, str, Callable) | handler ne prend aucun argument ; se déclenche sur un minuteur serveur répétitif, indépendant de tout événement client |
.push(id, html) | (str, str | Chart) | Fixe le HTML de id et le diffuse à toutes les connexions ouvertes immédiatement |
.auth(username, password) | (str, str) | Protège chaque requête derrière une authentification HTTP Basic |
.serve(port=8787, host="127.0.0.1") | (int, str) | Appel bloquant |
Sera Pulse — Pricing & Activation
Sera Pulse is the streaming layer on top of every native chart: .push(), .push_vector(), .serve(), .record(), .replay(), .on_anomaly(). The rendering itself (sp.scatter(), sp.bar(), sp.heatmap(), sp.candlestick(), and every other native fast-path chart) stays free and open-source — Pulse only gates the incremental update layer built on top of it.
What’s included
Everything below is under one gate — the same active trial or license unlocks all of it, Solo and Team alike (Team only adds seats and priority support, not extra features):
- Streaming —
push(),push_vector(),serve(),record()/replay(),on_anomaly(),export_standalone(). - Sera Firehose —
sp.firehose(...)+firehose_push(), a ring-buffered chart for very high-frequency streams, rendered through WebGL2. - Sera Live — live cursors, pinned comments, a shared “replay together” on any
.serve()’d chart, and a one-click dark/light theme toggle. - Sera Board —
serve_board()/export_board_html(), a Figma-style infinite whiteboard: draggable/zoomable chart frames, pins, freehand drawings, dropped images, all live and collaborative and saved to a persistent session you can switch between with one click, or exported as a static file. - Sera Secure —
SeraDFrame, AES-256-GCM encryption on entry, plaintext never cached or logged. - SeraReport —
export_pdf(),export_docx(),export_pdf_report(), each with an optional live companion link. - SeraStudio —
export_video(),export_gif(), session recording/replay for shareable chart clips.
Every method above calls into the same license check — one key activates on exactly one machine, and every price below is locked in for good: new paid features join this same catalog at no extra cost, they never move behind a new tier.
Free trial
The first time any Pulse method is called on a machine, a 90-day (3-month) trial starts automatically — no card, no signup, no network call. You’ll see it reflected in:
import seraplot as sp
sp.pulse_status()
# {'state': 'trial', 'days_left': '90'}
Everything (push, push_vector, serve, record, replay, on_anomaly) works exactly the same during the trial as with a paid license — there is no feature difference, only a time limit.
After the trial
Once the 90 days are up, Pulse methods raise a clear PermissionError pointing you to activation rather than failing silently or degrading:
sp.scatter(x=x, y=y).push(idx, x, y)
# PermissionError: seraplot pulse: free trial (90 days) has ended.
# Activate a license with sp.pulse_activate(key) to keep using
# push()/serve()/record()/replay()/on_anomaly().
Activate with the key you receive after purchase:
sp.pulse_activate("eyJjIjoi...ZTFhMTki.MEUCIQDx...")
sp.pulse_status()
# {'state': 'licensed', 'customer': '...', 'plan': 'pro', 'expires_at': '...'}
A license key is a self-contained, cryptographically signed token — activation works fully offline, no phone-home, no telemetry.
Machine-locked plans (see Solo below) are tied to a local machine id rather than being freely copyable between computers:
sp.pulse_machine_id()
# 'DESKTOP-ABC123-alice-...'
Send this id when purchasing a machine-locked key — it gets embedded in the signed license and checked on activation.
Plans
| Plan | Price | Includes | |
|---|---|---|---|
| Trial | Free | Everything, 90 days (3 months), no card required. | Get started |
| Solo | 10,97 €/seat/mo or 109,70 €/seat/yr | push/serve/record/replay/on_anomaly, locked to one machine id (technically enforced, not just a policy). | Buy monthly · Buy yearly |
| Team | 10,97 €/seat/mo or 109,70 €/seat/yr — same rate as Solo | Solo, once per seat — one machine-locked key per teammate, plus priority support. | pick your seats & months below |
Paid via PayPal — click a buy link, pay on PayPal’s hosted checkout page, and your license key generates automatically the moment the payment is confirmed: a signed webhook triggers minting, checked against PayPal’s own signature so nothing can fake a payment. Prefer crypto (USDC/Polygon, self-custodied) or a manual sale instead? Use the support channel.
This price is locked in for good: every paid feature this catalog gains later — Sera Firehose, Sera Secure, SeraReport, SeraStudio, whatever ships next — joins the same Solo/Team price above at no extra cost. There is no higher tier waiting behind a paywall.
Each key activates on exactly one machine — always. Team isn’t a shared multi-seat key, it’s one Solo-equivalent key per teammate, each locked to that teammate’s own machine, billed together.
Type any number of seats and any number of months (1 to 36) — the link charges the plain per-seat-per-month rate times that many seats and months, except exactly 12 months which uses the discounted annual per-seat rate shown above (same rate whether you type 12 here or use a yearly preset elsewhere — never a hidden or different price). Same pattern works for Solo: .../paypal/buy/solo/<months>?seats=<seats>.
Renewing an existing key keeps its customer identity and machine lock and just pushes the expiry out — it’s a genuinely new signed token under the hood (a signature can’t be edited after the fact), but same customer, same machine, same sp.pulse_activate() call on your end. Take your current key and drop it into https://sera-payment.onrender.com/paypal/renew/solo/monthly?key=<your key> (swap solo/monthly for your actual plan/period; works for team too, one seat’s key at a time).
Sera Secure
SecureDFrame/SecureDFrameBuilder/SeraKey are AES-256-GCM encrypted-at-rest counterparts to SeraDFrame — columns stay ciphertext in memory, decrypted only transiently for a single read or chart render. They’re part of the same paid catalog as the streaming methods above and share the same trial/license — no separate purchase, no separate activation:
import seraplot as sp
key = sp.SeraKey.generate()
frame = sp.SecureDFrame({"x": x_values, "y": y_values}, key.to_bytes())
chart_data = frame.to_chart_data("x", "y", key.to_bytes(), max_points=2000)
How it’s laid out
Why teams reach for this
A process dump, a stray core file, a debugger attached at the wrong moment — with a normal SeraDFrame, that’s the whole column, in the clear. With SecureDFrame, it’s ciphertext, full stop. The plaintext window is exactly one read: to_chart_data() decrypts, the chart renders, and there’s nothing left sitting in memory to leak. If you’re charting patient records, transaction history, PII, or anything else you’d rather not explain to a compliance officer after the fact, this is the one-line change (SeraDFrame → SecureDFrame) that makes the difference between “we had a memory leak” and “we had an incident.”
It costs nothing extra to try — it’s in the same trial and the same license as every other Pulse feature on this page, so there’s no separate purchase decision to make. Start the free trial and swap one class name.
SeraReport
export_pdf() converts a chart’s own SVG directly into a native, vector PDF page — no headless browser, no screenshot, just a direct SVG-to-PDF conversion. export_docx() does the same into a Word document, the chart embedded as a real image. Same catalog, same trial/license as everything else on this page:
import seraplot as sp
chart = sp.bar("Sales", labels=["Q1", "Q2", "Q3"], values=[120, 150, 90])
chart.export_pdf("sales.pdf")
chart.export_docx("sales.docx")
Needs the chart to have real SVG content — charts large enough to cross their native canvas-rendering threshold render via <canvas> instead of <svg> (the same boundary export_svg()/export_png() already have). export_pdf_report(charts, path, titles=None) combines several charts into one multi-page PDF, one page each.
Both methods take an interactive flag, True by default: alongside the PDF/DOCX, a companion .html file is written next to it — the chart’s own real interactive page — and a clickable link is embedded in the document (a /GoToR link annotation in the PDF, a hyperlink paragraph in the DOCX) that opens it. Pass interactive=False for a plain static file with no companion:
chart.export_pdf("sales.pdf", interactive=False)
sp.export_pdf_report(charts, "quarterly_report.pdf", titles=titles, interactive=True)
Complete example
export_pdf() is a direct 1:1 conversion — the vector PDF page is exactly the chart’s own SVG, nothing re-rendered or rasterized in between. export_pdf_report() does the same for a whole set of charts at once, one real page per chart. The PDF below was generated by the script underneath it — six different chart families (bar, line, scatter, pie, boxplot, heatmap), one multi-page report, open it right here:
Open in a new tab if your browser doesn't preview PDFs inline.
import seraplot as sp
bar = sp.bar("Quarterly Sales", labels=["Q1", "Q2", "Q3", "Q4"], values=[120, 150, 90, 175], color_hex=0x3B82F6).show_grid().despine()
line = sp.line("Daily Active Users", x_labels=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], values=[820, 932, 901, 934, 1290, 1330, 1320], color_hex=0x22C55E).show_grid().despine()
scatter = sp.scatter("Latency vs Load", x=[10, 20, 30, 40, 50, 60, 70, 80], y=[12, 18, 22, 35, 40, 55, 61, 78], variant="regression").show_grid().despine()
pie = sp.pie("Traffic Sources", labels=["Organic", "Paid", "Referral", "Direct"], values=[45, 25, 15, 15])
boxplot = sp.boxplot("Response Time by Region", labels=["US"] * 6 + ["EU"] * 6 + ["APAC"] * 6, values=[120, 130, 125, 118, 140, 122, 200, 210, 195, 205, 220, 198, 90, 95, 88, 102, 97, 91])
heatmap = sp.heatmap("Weekly Activity", labels=["Mon", "Tue", "Wed", "Thu", "Fri"], col_labels=["8h", "12h", "16h", "20h"], values=[5, 9, 7, 3, 6, 12, 10, 4, 8, 15, 13, 7, 4, 8, 11, 5, 3, 7, 9, 2])
charts = [bar, line, scatter, pie, boxplot, heatmap]
titles = ["Quarterly Sales", "Daily Active Users", "Latency vs Load", "Traffic Sources", "Response Time by Region", "Weekly Activity"]
sp.export_pdf_report(charts, "quarterly_report.pdf", titles=titles)
Sera Firehose
sp.firehose(...) is a chart built for one thing: absorbing a very fast stream of readings — sensor ticks, prices, request latencies — without the browser tab choking on it. It’s a fixed-size ring buffer, server and browser alike: you never track write positions, you never grow an array forever. Once the buffer is full, each new reading silently overwrites the oldest one.
import seraplot as sp
chart = sp.firehose("CPU %", capacity=2_000, min_val=0.0, max_val=100.0)
chart.serve(port=8787)
for reading in sensor_stream():
chart.firehose_push([reading])
firehose_push() is push() with the indices computed for you — same rate limiting, same on_anomaly() detection, same record()/replay(), same .serve() broadcast over the same binary WebSocket frames every other native chart already streams over. Nothing about the transport is new; what’s new is the client: instead of a <canvas> redrawn point-by-point on the CPU, Firehose renders through WebGL2 — one GPU buffer upload per animation frame, one drawArrays call, no per-point JavaScript work no matter how many readings landed since the last frame, in every current browser with nothing extra to install.
The ring buffer itself is a genuinely generic, reusable Rust type (RingBuffer<T>), not a one-off: fixed capacity, no reallocation once built, oldest-overwrite on push. In a release build it sustains over 200 million pushes/second doing pure buffer bookkeeping — the real ceiling for 100k+ readings/second is Python call overhead and the network, never the buffer.
Live preview
This is the actual chart, actually rendering — not a screenshot. A tiny synthetic driver runs entirely in your browser, calling the exact same sp_apply_<id> function firehose_push() streams into, once per animation frame:
Open standalone to see it full-size.
import random
import seraplot as sp
chart = sp.firehose("Live sensor feed", capacity=300, min_val=0.0, max_val=100.0, height=260)
chart.record("session.spls")
value = 50.0
for _ in range(1_200):
value = max(0.0, min(100.0, value + random.uniform(-2.5, 2.5)))
chart.firehose_push([value])
chart.stop_record()
chart.export_standalone("firehose-demo.html", "session.spls")
The embed above uses a small looping driver instead of a finite recording so it never runs out of data on this page, but it calls into the exact same sp_apply_<id> function either way — export_standalone() is the real, supported way to ship a self-contained demo like this one.
Sera Live
A screen-share puts one person’s window, at one person’s scroll position, in front of everyone else squinting at a laggy stream and asking “wait, go back.” Sera Live turns any .serve()’d chart into a real shared room instead — everyone who opens the link is in the chart together, full resolution, on their own screen.
import seraplot as sp
chart = sp.bar("Error rate by region", labels=["us-east", "eu-west", "ap-south"], values=[12, 4, 31])
chart.record("incident.spls")
chart.serve(port=8787)
Send that link and every cursor in the room moves live, in real time, labeled and colored per person. Pin a note straight onto the bar that’s spiking and it appears for everyone instantly — not a comment three scrolls down a thread, a label sitting exactly where the problem is. Circle it, underline it, draw an arrow at the thing everyone should be looking at, freehand, in whichever color makes the point land. Hit Replay together and every connected tab steps through the recorded incident in lockstep — reliving what happened at the same pace, together, instead of five people reconstructing the same timeline five different ways. One click flips the whole room into a dark theme built for a wall-mounted screen or a 2am war room, remembered per browser. Join four minutes late and you haven’t missed anything — every pin and stroke made before you connected replays the moment you land.
None of this is a bolted-on side system: cursors, pins, drawings, and replay all ride the exact same WebSocket .serve() already opens. Pins are real bookmark() annotations, not a throwaway layer — list them later with chart.bookmarks() same as ever. Want the old single-viewer behavior instead, nothing about you or your cursor ever leaving your machine? collab=False turns it off:
chart.serve(port=8787, collab=False)
Closing a tab drops that cursor for everyone else immediately — a 15-second window only ever catches a genuine crash, never a normal close. Pins and replay share the same rate-limiting the data stream itself uses, so a stray script — or two people hitting Replay at the same instant — can’t flood the room. username/password keep a link private, exactly as they would for the stream underneath it.
Sera Board
One chart is plenty for one metric. A real incident never is — error rate, latency, queue depth, deploy markers, related but rarely the same shape, and normally scattered across five browser tabs nobody can see side by side. Sera Board puts every chart on one infinite canvas instead: a sidebar lists them all, each sits in its own frame you drag anywhere, and you zoom from a full command-center view down to one chart filling the screen.
serve_board() is Sera Board joined to Sera Live — the same shared room, the same live cursors, now moving over a whole canvas instead of a single chart:
import seraplot as sp
errors = sp.bar("Error rate", labels=["us-east", "eu-west", "ap-south"], values=[12, 4, 31])
latency = sp.line("p99 latency (ms)", x=list(range(20)), y=[120, 118, 125, 400, 410, 190, 130, 128, 126, 124, 122, 400, 405, 190, 128, 126, 124, 122, 120, 118])
queue = sp.scatter("Queue depth", x=list(range(30)), y=[3, 5, 4, 40, 38, 6, 5, 4, 3, 4] * 3)
sp.serve_board([errors, latency, queue], titles=["Errors", "Latency", "Queue"], session="2026-08-09", port=8787)
Send that link to the team. Pin a comment, drop a screenshot straight from the desktop, or draw on top of a chart, and it sticks to that chart’s own frame — drag the frame anywhere and the annotation follows it. Group the charts that belong to the same story and drag the whole cluster together as one block; every move is shared live and saved, so the layout the team leaves the room in is the layout it opens to next time. Hover any frame and its own tooltips wake up right there, on the spot — the board never fights a chart for the mouse.
Everything — pins, drawings, images, layout — lives in a named session on disk, restored automatically the next time that session is served: close the server, reopen it a week later, the room is exactly where it was left. Jump the whole room to a different saved session with one click, live, no reload and no new link — everyone connected switches together. Need one frame streaming real live data instead of a frozen snapshot while the rest stay static? Point live_urls at it:
errors.serve(port=8788)
sp.serve_board([errors, latency, queue], titles=["Errors", "Latency", "Queue"], live_urls=[f"http://127.0.0.1:8788", None, None], session="2026-08-09", port=8787)
That frame gets a small pulsing green dot and streams for real, continuously; the other two stay static snapshots.
Don’t need it live — just a file to send around? export_board_html() writes the same board as one self-contained .html, no server required: same drag-and-zoom layout, each chart keeps its own hover and tooltips inside its frame.
sp.export_board_html([errors, latency, queue], "war_room.html", titles=["Errors", "Latency", "Queue"])
Presence (cursors) is board-wide rather than per-chart — each embedded chart is its own sandboxed <iframe>, so a purely static frame can’t stream on its own; that’s exactly what live_urls is for. A pin removed from the board disappears from the live view and the saved session, but not from chart.bookmarks() if that chart also keeps its own persistent bookmarks — the board’s layer and a chart’s permanent record stay independent on purpose. The live board and the static export both run on the same catalog and license as everything else on this page.
SeraStudio
chart.export_gif(gif_path, spls_path) renders a .spls recording (from record()) as an animated GIF — one frame per recorded update, only drawing a slot for indices that were actually pushed during the recording, so watching a handful of values update inside a much larger chart still produces a readable animation. Same catalog, same trial/license as everything else on this page:
import seraplot as sp
chart = sp.bar("Sensors", labels=[str(i) for i in range(600)], values=[10.0] * 600)
chart.record("session.spls")
chart.push([3], [14.5])
chart.push([3], [16.0])
chart.stop_record()
chart.export_gif("session.gif", "session.spls")
When the recording carries its original chart-construction source (true for any .spls recorded since the source-tagged v4 format), every frame is replayed through that chart family’s own real renderer — a themed heatmap shows its actual color grid, a variant="pictogram" bar shows real icons — instead of a generic substitute. Recordings from older .spls files, or families with no replay mapping, fall back automatically to a shape-based renderer keyed only on the recording’s push shape (one value per index, an x/y pair, or an N-value vector): bar/heatmap recordings render as magnitude bars, scatter/line/bubble/area recordings render as a moving dot per index, and 4-value vector recordings render as real OHLC candlesticks (green/red for gain/loss; other vector widths fall back to grouped mini-bars) — so nothing fails outright even without a replay mapping. color=(r, g, b) customizes that fallback renderer’s color (ignored whenever a family-correct replay renders the frame). sort_by_value=True re-ranks slots by current value every frame for a “bar chart race” look, compacting the frame down to just the racing rows and carrying each row’s real label along as it re-ranks. chart.gif_frame_count(spls_path) peeks at a recording’s frame count without rendering; chart.gif_preview_frame(spls_path, frame_index, png_path) renders a single frame as a PNG. Aliases: to_gif/save_gif for export_gif, gif_length for gif_frame_count.
For a real video file instead of a GIF, chart.export_video(video_path, spls_path, format="mp4") renders the same family-correct-when-available visualization through a real video codec (ffmpeg must be installed and on PATH) at a constant frame rate — bursts of rapid updates collapse into the latest state, quiet gaps repeat the last known frame, so playback speed always matches what really happened:
chart.record("session.spls")
chart.push([3], [14.5])
chart.push([3], [16.0])
chart.stop_record()
chart.export_video("session.mp4", "session.spls", format="mp4", fps=30, sort_by_value=True)
format is one of "mp4" (H.264, broadly compatible), "webm" (VP9, smaller/web-native), "mov", "mkv", or "avi" — with a shorthand method for each (export_mp4, export_webm, export_mov, export_mkv, export_avi), plus to_video/save_video aliases for export_video itself.
Both export_gif() and export_video() (and gif_preview_frame()) also accept smooth=True: instead of snapping straight to each recorded state, it tweens the geometry of matching elements between two consecutive recorded states — correlated by each row’s real identity, not its on-screen slot, so a racing row keeps its own tween even as sort_by_value reassigns slots around it. Values glide instead of jumping, and a racing row visibly slides past the others as it overtakes them, rather than teleporting to its new rank on the next frame. smooth_speed (default 1.0) scales how many in-between steps a transition gets — 2.0 is twice as fast with fewer steps, 0.5 is twice as slow with more; smooth_ease (default "linear") picks the pacing curve out of "linear", "ease_in", "ease_out", "ease_in_out":
chart.export_video("session_smooth.mp4", "session.spls", format="mp4", fps=60, sort_by_value=True, smooth=True, smooth_speed=1.5, smooth_ease="ease_in_out")
smooth is opt-in and off by default — it costs extra render time per transition (each interpolated tick still rasterizes a full frame), so it is worth it for a polished, presentation-ready export but not needed for a quick preview. It has no effect when no replay source is available (a pre-v4 .spls file, or a family with no replay mapping): those still fall back to the shape-based renderer, unaffected.
For a known, complete dataset rather than a live stream, reveal=True replaces the real update timeline entirely: instead of replaying the recording’s actual push events, it reveals the final touched state progressively in index order over reveal_steps frames, as if the chart were drawing itself — a curve or a set of bars appearing point by point rather than jumping between recorded snapshots. Pace it with the same smooth_tick_ms the export’s fps already derives, so reveal_steps frames at a given fps take reveal_steps / fps seconds regardless of how the underlying .spls recording was actually timed:
chart.export_video("reveal.mp4", "session.spls", format="mp4", fps=30, reveal=True, reveal_steps=150)
breakpoint_at_ms and breakpoint_on_value each hold the output on a frame for breakpoint_pause_ms the first time a condition is met — breakpoint_at_ms on elapsed output time, breakpoint_on_value (as (index, threshold) pairs) the first time a specific tracked index’s decoded value reaches or exceeds a threshold. Every later frame shifts forward by the pause so the rest of the timeline stays intact; each breakpoint fires once. Both compose with reveal and with normal/smooth playback alike, since they operate on whichever frame timeline was already built:
chart.export_video(
"paused.mp4", "session.spls", format="mp4", fps=30,
reveal=True, reveal_steps=150,
breakpoint_at_ms=[2500], breakpoint_on_value=[(3, 90.0)], breakpoint_pause_ms=1200,
)
Video showcases
Seven real exports, generated by the scripts shown under each — click “View code” to read the exact .py that produced it.
Full catalog
Every registered chart family and variant, read live from the same registry that drives the Showcase — search it, then click “View code” for a ready-to-run SeraStudio record() / push() / export_video() snippet built from that variant’s own real construction parameters. Not every family supports push() yet (see the SeraStudio paragraphs above) — the generated snippet still shows the right shape to fill in.
Browse the full catalog
Method reference
Always matches the actual implementation. Use the tabs below to jump straight to a mini-module (streaming, anomaly detection, alerts, bookmarks, remote push, rate limiting, history/reports, health, licensing, Sera Secure, SeraReport, SeraStudio) instead of scanning the full list.
Sera Pulse est la couche de streaming au-dessus de chaque chart natif : .push(), .push_vector(), .serve(), .record(), .replay(), .on_anomaly(). Le rendu lui-même (sp.scatter(), sp.bar(), sp.heatmap(), sp.candlestick(), et tout autre chart natif à chemin rapide) reste gratuit et open-source — Pulse ne verrouille que la couche de mise à jour incrémentale construite par-dessus.
Ce qui est inclus
Tout ce qui suit est sous une seule et même porte — le même essai actif ou la même licence débloque tout, Solo comme Team (Team n’ajoute que des postes et du support prioritaire, pas de fonctionnalités en plus) :
- Streaming —
push(),push_vector(),serve(),record()/replay(),on_anomaly(),export_standalone(). - Sera Firehose —
sp.firehose(...)+firehose_push(), un chart à tampon circulaire pour les flux très haute fréquence, rendu via WebGL2. - Sera Live — curseurs en direct, commentaires épinglés, un « replay together » partagé sur tout chart
.serve(), et un thème clair/sombre commutable en un clic. - Sera Board —
serve_board()/export_board_html(), un tableau blanc infini façon Figma : cadres de charts déplaçables/zoomables, épingles, dessins à main levée, images déposées, tout en direct et collaboratif et sauvegardé dans une session persistante qu’on change en un clic, ou exporté en fichier statique. - Sera Secure —
SeraDFrame, chiffrement AES-256-GCM à l’entrée, le texte en clair n’est jamais mis en cache ni journalisé. - SeraReport —
export_pdf(),export_docx(),export_pdf_report(), chacun avec un lien compagnon interactif optionnel. - SeraStudio —
export_video(),export_gif(), enregistrement/relecture de session pour des extraits de chart partageables.
Chaque méthode ci-dessus appelle la même vérification de licence — une clé s’active sur une seule machine, et chaque prix ci-dessous est fixé pour de bon : les nouvelles fonctionnalités payantes rejoignent ce même catalogue sans coût supplémentaire, elles ne passent jamais derrière un nouveau palier.
Essai gratuit
La première fois qu’une méthode Pulse est appelée sur une machine, un essai de 90 jours (3 mois) démarre automatiquement — sans carte, sans inscription, sans appel réseau. Visible via :
import seraplot as sp
sp.pulse_status()
# {'state': 'trial', 'days_left': '90'}
Tout (push, push_vector, serve, record, replay, on_anomaly) fonctionne exactement comme avec une licence payante pendant l’essai — aucune différence de fonctionnalité, seulement une limite de temps.
Après l’essai
Une fois les 90 jours écoulés, les méthodes Pulse lèvent une PermissionError claire renvoyant vers l’activation plutôt que d’échouer silencieusement ou de se dégrader :
sp.scatter(x=x, y=y).push(idx, x, y)
# PermissionError: seraplot pulse: free trial (90 days) has ended.
# Activate a license with sp.pulse_activate(key) to keep using
# push()/serve()/record()/replay()/on_anomaly().
Activez avec la clé reçue après achat :
sp.pulse_activate("eyJjIjoi...ZTFhMTki.MEUCIQDx...")
sp.pulse_status()
# {'state': 'licensed', 'customer': '...', 'plan': 'pro', 'expires_at': '...'}
Une clé de licence est un jeton auto-suffisant signé cryptographiquement — l’activation fonctionne entièrement hors ligne, sans phone-home, sans télémétrie.
Les plans verrouillés à une machine (voir Solo ci-dessous) sont liés à un identifiant local plutôt que librement copiables d’un ordinateur à l’autre :
sp.pulse_machine_id()
# 'DESKTOP-ABC123-alice-...'
Envoyez cet identifiant lors de l’achat d’une clé verrouillée à une machine — il est embarqué dans la licence signée et vérifié à l’activation.
Formules
| Formule | Prix | Inclut | |
|---|---|---|---|
| Essai | Gratuit | Tout, 90 jours (3 mois), aucune carte requise. | Commencer |
| Solo | 10,97 €/poste/mois ou 109,70 €/poste/an | push/serve/record/replay/on_anomaly, verrouillé à un identifiant de machine (techniquement vérifié, pas seulement une politique). | Acheter mensuel · Acheter annuel |
| Team | 10,97 €/poste/mois ou 109,70 €/poste/an — même tarif que Solo | Solo, une fois par poste — une clé verrouillée machine par membre de l’équipe, plus support prioritaire. | choisissez postes et mois ci-dessous |
Payé via PayPal — cliquez un lien d’achat, payez sur la page de paiement hébergée par PayPal, et votre clé de licence est générée automatiquement dès que le paiement est confirmé : un webhook signé déclenche la génération, vérifié contre la signature de PayPal pour qu’il soit impossible de simuler un paiement. Préférence pour la crypto (USDC/Polygon, auto-hébergé) ou une vente manuelle ? Passez par le canal de support.
Ce prix est fixé pour de bon : chaque fonctionnalité payante que ce catalogue gagnera plus tard — Sera Firehose, Sera Secure, SeraReport, SeraStudio, ce qui sortira ensuite — rejoint le même prix Solo/Team ci-dessus sans coût supplémentaire. Il n’y a pas de palier supérieur caché derrière un mur de paiement.
Chaque clé s’active sur une seule machine — toujours. Team n’est pas une clé multi-postes partagée, c’est une clé équivalente à Solo par membre de l’équipe, chacune verrouillée à la machine de ce membre, facturées ensemble.
Saisissez n’importe quel nombre de postes et n’importe quel nombre de mois (1 à 36) — le lien facture le taux plein par poste et par mois multiplié par ce nombre de postes et de mois, sauf exactement 12 mois qui utilise le tarif annuel réduit indiqué ci-dessus (même tarif que vous tapiez 12 ici ou passiez par un forfait annuel ailleurs — jamais de prix caché ou différent). Même principe pour Solo : .../paypal/buy/solo/<mois>?seats=<postes>.
Renouveler une clé existante garde son identité client et son verrouillage machine, et repousse juste l’expiration — c’est en réalité un nouveau jeton signé sous le capot (une signature ne se modifie pas après coup), mais même client, même machine, même appel sp.pulse_activate() de votre côté. Prenez votre clé actuelle et placez-la dans https://sera-payment.onrender.com/paypal/renew/solo/monthly?key=<votre clé> (remplacez solo/monthly par votre vrai plan/période ; marche aussi pour team, une clé de poste à la fois).
Sera Secure
SecureDFrame/SecureDFrameBuilder/SeraKey sont les équivalents chiffrés en AES-256-GCM de SeraDFrame — les colonnes restent en texte chiffré en mémoire, déchiffrées seulement de façon transitoire pour une lecture ou un rendu de chart ponctuel. Ils font partie du même catalogue payant que les méthodes de streaming ci-dessus et partagent le même essai/licence — pas d’achat séparé, pas d’activation séparée :
import seraplot as sp
key = sp.SeraKey.generate()
frame = sp.SecureDFrame({"x": x_values, "y": y_values}, key.to_bytes())
chart_data = frame.to_chart_data("x", "y", key.to_bytes(), max_points=2000)
Comment c’est organisé
Pourquoi les équipes s’en servent
Un dump de process, un fichier core égaré, un débogueur attaché au mauvais moment — avec un SeraDFrame normal, c’est toute la colonne, en clair. Avec SecureDFrame, c’est du texte chiffré, un point c’est tout. La fenêtre en clair se limite exactement à une lecture : to_chart_data() déchiffre, le chart se rend, et il ne reste rien en mémoire qui puisse fuiter. Si vous affichez des dossiers patients, un historique de transactions, des données personnelles, ou tout ce que vous préféreriez ne pas avoir à expliquer à un responsable conformité après coup, c’est le changement d’une ligne (SeraDFrame → SecureDFrame) qui fait la différence entre « on a eu une fuite mémoire » et « on a eu un incident ».
Ça ne coûte rien de plus à essayer — c’est le même essai et la même licence que toutes les autres fonctionnalités Pulse de cette page, donc aucune décision d’achat séparée à prendre. Démarrez l’essai gratuit et changez un nom de classe.
SeraReport
export_pdf() convertit le SVG propre au chart directement en PDF vectoriel natif — sans navigateur headless, sans capture d’écran, juste une conversion SVG-vers-PDF directe. export_docx() fait pareil vers un document Word, le chart intégré en vraie image. Même catalogue, même essai/licence que le reste de cette page :
import seraplot as sp
chart = sp.bar("Ventes", labels=["Q1", "Q2", "Q3"], values=[120, 150, 90])
chart.export_pdf("ventes.pdf")
chart.export_docx("ventes.docx")
Nécessite que le chart ait un vrai contenu SVG — les charts assez grands pour dépasser leur seuil de rendu canvas natif se rendent via <canvas> plutôt que <svg> (la même limite qu’ont déjà export_svg()/export_png()). export_pdf_report(charts, path, titles=None) combine plusieurs charts en un seul PDF multi-page, une page chacun.
Les deux méthodes prennent un paramètre interactive, vrai par défaut : en plus du PDF/DOCX, un fichier .html compagnon est écrit à côté — la vraie page interactive du chart — et un lien cliquable est intégré dans le document (une annotation de lien /GoToR dans le PDF, un paragraphe hyperlien dans le DOCX) qui l’ouvre. Passez interactive=False pour un simple fichier statique sans compagnon :
chart.export_pdf("ventes.pdf", interactive=False)
sp.export_pdf_report(charts, "rapport_trimestriel.pdf", titles=titres, interactive=True)
Exemple complet
export_pdf() est une conversion 1:1 directe — la page PDF vectorielle est exactement le SVG du chart, rien n’est re-rendu ni rastérisé entre les deux. export_pdf_report() fait pareil pour tout un ensemble de charts d’un coup, une vraie page par chart. Le PDF ci-dessous a été généré par le script juste en dessous — six familles de charts différentes (bar, line, scatter, pie, boxplot, heatmap), un seul rapport multi-page, ouvrez-le directement ici :
Ouvrir dans un nouvel onglet si votre navigateur n'aperçoit pas les PDF en direct.
import seraplot as sp
bar = sp.bar("Ventes trimestrielles", labels=["Q1", "Q2", "Q3", "Q4"], values=[120, 150, 90, 175], color_hex=0x3B82F6).show_grid().despine()
line = sp.line("Utilisateurs actifs quotidiens", x_labels=["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"], values=[820, 932, 901, 934, 1290, 1330, 1320], color_hex=0x22C55E).show_grid().despine()
scatter = sp.scatter("Latence vs Charge", x=[10, 20, 30, 40, 50, 60, 70, 80], y=[12, 18, 22, 35, 40, 55, 61, 78], variant="regression").show_grid().despine()
pie = sp.pie("Sources de trafic", labels=["Organique", "Payant", "Référent", "Direct"], values=[45, 25, 15, 15])
boxplot = sp.boxplot("Temps de réponse par région", labels=["US"] * 6 + ["EU"] * 6 + ["APAC"] * 6, values=[120, 130, 125, 118, 140, 122, 200, 210, 195, 205, 220, 198, 90, 95, 88, 102, 97, 91])
heatmap = sp.heatmap("Activité hebdomadaire", labels=["Lun", "Mar", "Mer", "Jeu", "Ven"], col_labels=["8h", "12h", "16h", "20h"], values=[5, 9, 7, 3, 6, 12, 10, 4, 8, 15, 13, 7, 4, 8, 11, 5, 3, 7, 9, 2])
charts = [bar, line, scatter, pie, boxplot, heatmap]
titres = ["Ventes trimestrielles", "Utilisateurs actifs quotidiens", "Latence vs Charge", "Sources de trafic", "Temps de réponse par région", "Activité hebdomadaire"]
sp.export_pdf_report(charts, "rapport_trimestriel.pdf", titles=titres)
Sera Firehose
sp.firehose(...) est un chart construit pour une seule chose : absorber un flux très rapide de valeurs — capteurs, prix, latences de requêtes — sans que l’onglet du navigateur s’étouffe dessus. C’est un tampon circulaire de taille fixe, côté serveur comme navigateur : vous ne suivez jamais de position d’écriture, vous ne faites jamais grossir un tableau indéfiniment. Une fois le tampon plein, chaque nouvelle valeur écrase silencieusement la plus ancienne.
import seraplot as sp
chart = sp.firehose("CPU %", capacity=2_000, min_val=0.0, max_val=100.0)
chart.serve(port=8787)
for reading in flux_capteur():
chart.firehose_push([reading])
firehose_push() est push() avec les indices calculés pour vous — même limitation de débit, même détection d’anomalies via on_anomaly(), même record()/replay(), même diffusion .serve() sur les mêmes trames WebSocket binaires que tout autre chart natif diffuse déjà. Rien de nouveau côté transport ; ce qui est nouveau c’est le client : au lieu d’un <canvas> redessiné point par point sur le CPU, Firehose se rend via WebGL2 — un envoi de buffer GPU par frame d’animation, un seul appel drawArrays, aucun travail JavaScript par point quel que soit le nombre de valeurs arrivées depuis la dernière frame, dans tous les navigateurs actuels sans rien à installer en plus.
Le tampon circulaire lui-même est un type Rust générique et réellement réutilisable (RingBuffer<T>), pas un bricolage ponctuel : capacité fixe, aucune réallocation une fois construit, écrasement du plus ancien à chaque ajout. En build release il tient plus de 200 millions d’ajouts par seconde en pure gestion de tampon — le vrai plafond pour 100k+ valeurs/seconde, c’est le coût d’appel Python et le réseau, jamais le tampon.
Aperçu en direct
C’est le vrai chart, en train de vraiment se dessiner — pas une capture d’écran. Un petit générateur synthétique tourne entièrement dans votre navigateur, en appelant exactement la même fonction sp_apply_<id> dans laquelle firehose_push() streame :
Ouvrir en autonome pour le voir en pleine taille.
import random
import seraplot as sp
chart = sp.firehose("Flux capteur en direct", capacity=300, min_val=0.0, max_val=100.0, height=260)
chart.record("session.spls")
valeur = 50.0
for _ in range(1_200):
valeur = max(0.0, min(100.0, valeur + random.uniform(-2.5, 2.5)))
chart.firehose_push([valeur])
chart.stop_record()
chart.export_standalone("firehose-demo.html", "session.spls")
L’aperçu ci-dessus utilise un petit générateur en boucle plutôt qu’un enregistrement fini pour ne jamais tomber à court de données sur cette page, mais il appelle exactement la même fonction sp_apply_<id> dans les deux cas — export_standalone() est la vraie méthode, officielle, pour livrer une démo autonome comme celle-ci.
Sera Live
Un partage d’écran, c’est la fenêtre d’une seule personne, à la position de scroll d’une seule personne, décrite à voix haute à tous les autres qui plissent les yeux devant un flux qui rame. Sera Live transforme n’importe quel chart .serve() en vraie salle partagée : tout le monde qui ouvre le lien est dans le chart ensemble, en pleine résolution, sur son propre écran.
import seraplot as sp
chart = sp.bar("Taux d'erreur par région", labels=["us-east", "eu-west", "ap-south"], values=[12, 4, 31])
chart.record("incident.spls")
chart.serve(port=8787)
Envoyez ce lien et tous les curseurs de la salle bougent en direct, en temps réel, nommés et colorés par personne. Épinglez une note directement sur la barre qui explose et elle apparaît pour tout le monde instantanément — pas un commentaire trois scrolls plus bas dans un fil, une étiquette posée exactement là où est le problème. Entourez-la, soulignez-la, dessinez une flèche vers ce que tout le monde doit regarder, à main levée, dans la couleur qui fait passer le message. Cliquez sur « Replay together » et chaque onglet connecté traverse l’incident enregistré au même rythme, ensemble, au lieu que cinq personnes reconstituent la même chronologie de cinq façons différentes. Un clic bascule toute la salle dans un thème sombre pensé pour un écran mural ou une war room à 2h du matin, mémorisé par navigateur. Rejoignez avec quatre minutes de retard et vous n’avez rien raté — chaque épingle et chaque trait posés avant votre connexion vous sont rejoués dès que vous arrivez.
Rien de tout ça n’est un système à part greffé par-dessus : curseurs, épingles, dessins et rejeu voyagent tous sur le même WebSocket que .serve() ouvre déjà. Les épingles sont de vraies annotations bookmark(), pas une couche jetable — listez-les ensuite avec chart.bookmarks() comme d’habitude. Besoin de l’ancien comportement mono-spectateur, où rien concernant vous ou votre curseur ne quitte jamais votre machine ? collab=False désactive tout ça :
chart.serve(port=8787, collab=False)
Fermer un onglet retire ce curseur pour tout le monde immédiatement — une fenêtre de 15 secondes ne sert qu’à rattraper un vrai crash, jamais une fermeture normale. Les épingles et le rejeu partagent la même limitation de débit que le flux de données lui-même, donc un script errant — ou deux personnes qui cliquent Replay au même instant — ne peuvent pas inonder la salle. username/password gardent un lien privé, exactement comme pour le flux sous-jacent.
Sera Board
Un chart à la fois, ça suffit pour une seule métrique. Un vrai incident, jamais — taux d’erreur, latence, profondeur de file, marqueurs de déploiement, liés mais rarement de la même forme, et normalement éparpillés sur cinq onglets que personne ne peut voir en même temps. Sera Board pose tous les charts sur une seule toile infinie à la place : une barre latérale les liste tous, chacun dans son propre cadre qu’on déplace où on veut, et on zoome d’une vue commandement complète jusqu’à un seul chart plein écran.
serve_board() réunit Sera Board et Sera Live — la même salle partagée, les mêmes curseurs en direct, désormais sur toute une toile plutôt qu’un seul chart :
import seraplot as sp
erreurs = sp.bar("Taux d'erreur", labels=["us-east", "eu-west", "ap-south"], values=[12, 4, 31])
latence = sp.line("Latence p99 (ms)", x=list(range(20)), y=[120, 118, 125, 400, 410, 190, 130, 128, 126, 124, 122, 400, 405, 190, 128, 126, 124, 122, 120, 118])
file_attente = sp.scatter("Profondeur de file", x=list(range(30)), y=[3, 5, 4, 40, 38, 6, 5, 4, 3, 4] * 3)
sp.serve_board([erreurs, latence, file_attente], titles=["Erreurs", "Latence", "File"], session="2026-08-09", port=8787)
Envoyez ce lien à l’équipe. Épinglez un commentaire, déposez une capture d’écran directement depuis le bureau, ou dessinez sur un chart — ça se colle au cadre de ce chart, déplacez le cadre et l’annotation suit. Regroupez les charts qui racontent la même histoire et déplacez tout le bloc ensemble ; chaque mouvement est partagé en direct et sauvegardé, donc la disposition dans laquelle l’équipe laisse la salle est celle qu’elle retrouve la fois suivante. Survolez n’importe quel cadre et ses propres infobulles s’activent directement, sur place — la toile ne se dispute jamais la souris avec un chart.
Tout — épingles, dessins, images, disposition — vit dans une session nommée sur disque, restaurée automatiquement la prochaine fois que cette session est servie : fermez le serveur, rouvrez-le une semaine plus tard, la salle est exactement où elle était. Basculez toute la salle vers une autre session sauvegardée en un clic, en direct, sans recharger et sans nouveau lien — tout le monde connecté bascule ensemble. Besoin qu’un cadre précis streame de vraies données en direct plutôt qu’un instantané figé pendant que les autres restent statiques ? Pointez live_urls dessus :
erreurs.serve(port=8788)
sp.serve_board([erreurs, latence, file_attente], titles=["Erreurs", "Latence", "File"], live_urls=[f"http://127.0.0.1:8788", None, None], session="2026-08-09", port=8787)
Ce cadre reçoit un petit point vert qui pulse et streame pour de vrai, en continu ; les deux autres restent des instantanés statiques.
Pas besoin que ce soit en direct — juste un fichier à envoyer ? export_board_html() écrit la même toile en un seul .html autonome, sans serveur : même disposition glisser-zoomer, chaque chart garde son propre survol et ses infobulles dans son cadre.
sp.export_board_html([erreurs, latence, file_attente], "war_room.html", titles=["Erreurs", "Latence", "File"])
La présence (curseurs) est propre à toute la toile plutôt qu’à chaque chart — chaque chart intégré est son propre <iframe> isolé, donc un cadre purement statique ne peut pas streamer par lui-même ; c’est exactement à ça que sert live_urls. Une épingle supprimée de la toile disparaît de la vue en direct et de la session sauvegardée, mais pas de chart.bookmarks() si ce chart garde aussi ses propres repères persistants — la couche de la toile et l’enregistrement permanent d’un chart restent volontairement indépendants. La toile en direct et l’export statique tournent tous deux sur le même catalogue et la même licence que le reste de cette page.
SeraStudio
chart.export_gif(gif_path, spls_path) rend un enregistrement .spls (issu de record()) en GIF animé — une frame par mise à jour enregistrée, en ne dessinant un emplacement que pour les indices réellement poussés pendant l’enregistrement, pour qu’observer quelques valeurs évoluer au sein d’un chart bien plus grand produise quand même une animation lisible. Même catalogue, même essai/licence que le reste de cette page :
import seraplot as sp
chart = sp.bar("Capteurs", labels=[str(i) for i in range(600)], values=[10.0] * 600)
chart.record("session.spls")
chart.push([3], [14.5])
chart.push([3], [16.0])
chart.stop_record()
chart.export_gif("session.gif", "session.spls")
Quand l’enregistrement porte sa source de construction d’origine (vrai pour tout .spls enregistré depuis le format v4 avec source), chaque frame est rejouée via le vrai rendu de cette famille de chart — une heatmap themée montre sa vraie grille de couleurs, une barre variant="pictogram" montre de vraies icônes — plutôt qu’un substitut générique. Les enregistrements issus d’anciens fichiers .spls, ou des familles sans correspondance de rejeu, retombent automatiquement sur un rendu basé uniquement sur la forme des données poussées (une valeur par indice, une paire x/y, ou un vecteur à N valeurs) : les enregistrements bar/heatmap se rendent en barres de magnitude, les enregistrements scatter/ligne/bulle/aire se rendent en point mobile par indice, et les enregistrements vecteur à 4 valeurs se rendent en vrais chandeliers OHLC (vert/rouge pour hausse/baisse ; les autres largeurs de vecteur retombent sur des mini-barres groupées) — pour ne jamais échouer completement, même sans correspondance de rejeu. color=(r, g, b) personnalise la couleur de ce rendu de secours (ignorée dès qu’un rejeu fidèle à la famille rend la frame). sort_by_value=True reclasse les emplacements par valeur courante à chaque frame pour un effet « bar chart race », en compactant la frame sur les seules lignes en course et en emportant le vrai libellé de chaque ligne avec elle quand elle change de rang. chart.gif_frame_count(spls_path) consulte le nombre de frames d’un enregistrement sans rien rendre ; chart.gif_preview_frame(spls_path, frame_index, png_path) rend une seule frame en PNG. Alias : to_gif/save_gif pour export_gif, gif_length pour gif_frame_count.
Pour un vrai fichier vidéo plutôt qu’un GIF, chart.export_video(video_path, spls_path, format="mp4") rend la même visualisation fidèle à la famille quand elle est disponible, via un vrai codec vidéo (ffmpeg doit être installé et accessible sur le PATH) à un frame rate constant — les rafales de mises à jour rapides se compressent sur le dernier état, les silences répètent la dernière frame connue, donc la vitesse de lecture correspond toujours à ce qui s’est réellement passé :
chart.record("session.spls")
chart.push([3], [14.5])
chart.push([3], [16.0])
chart.stop_record()
chart.export_video("session.mp4", "session.spls", format="mp4", fps=30, sort_by_value=True)
format est un parmi "mp4" (H.264, largement compatible), "webm" (VP9, plus léger/natif web), "mov", "mkv", ou "avi" — avec un raccourci par format (export_mp4, export_webm, export_mov, export_mkv, export_avi), plus les alias to_video/save_video pour export_video lui-même.
export_gif() et export_video() (ainsi que gif_preview_frame()) acceptent aussi smooth=True : au lieu de sauter directement à chaque état enregistré, la géométrie des éléments correspondants est animée entre deux états enregistrés consécutifs — mis en correspondance par l’identité réelle de chaque ligne, pas son emplacement à l’écran, pour qu’une ligne en course garde sa propre animation même quand sort_by_value réassigne les emplacements autour d’elle. Les valeurs évoluent en douceur, et une ligne en course glisse visiblement devant les autres en les dépassant, plutôt que de se téléporter à son nouveau rang à la frame suivante. smooth_speed (défaut 1.0) ajuste le nombre d’étapes intermédiaires d’une transition — 2.0 est deux fois plus rapide avec moins d’étapes, 0.5 est deux fois plus lent avec plus d’étapes ; smooth_ease (défaut "linear") choisit la courbe de rythme parmi "linear", "ease_in", "ease_out", "ease_in_out" :
chart.export_video("session_smooth.mp4", "session.spls", format="mp4", fps=60, sort_by_value=True, smooth=True, smooth_speed=1.5, smooth_ease="ease_in_out")
smooth est optionnel et désactivé par défaut — il coûte un temps de rendu supplémentaire par transition (chaque étape interpolée rastérise quand même une frame complète), donc utile pour un export soigné destiné à une présentation, mais pas nécessaire pour un aperçu rapide. Il n’a aucun effet sans source de rejeu disponible (fichier .spls pré-v4, ou famille sans correspondance de rejeu) : ceux-là retombent toujours sur le rendu basé sur la forme des données, sans changement.
Pour un jeu de données connu et complet plutôt qu’un flux temps réel, reveal=True remplace entièrement la vraie ligne de temps de mises à jour : au lieu de rejouer les vrais événements de push de l’enregistrement, il révèle l’état final touché progressivement dans l’ordre des indices sur reveal_steps frames, comme si le chart se dessinait lui-même — une courbe ou un jeu de barres qui apparaît point par point plutôt que de sauter entre états enregistrés. Le rythme utilise le même smooth_tick_ms que celui déjà dérivé du fps de l’export, donc reveal_steps frames à un fps donné prennent reveal_steps / fps secondes, peu importe le vrai timing de l’enregistrement .spls sous-jacent :
chart.export_video("reveal.mp4", "session.spls", format="mp4", fps=30, reveal=True, reveal_steps=150)
breakpoint_at_ms et breakpoint_on_value maintiennent chacun la sortie sur une frame pendant breakpoint_pause_ms la première fois qu’une condition est atteinte — breakpoint_at_ms sur le temps écoulé en sortie, breakpoint_on_value (sous forme de paires (indice, seuil)) la première fois que la valeur décodée d’un indice suivi spécifique atteint ou dépasse un seuil. Chaque frame suivante est décalée en avant de la durée de la pause pour garder le reste de la ligne de temps intact ; chaque breakpoint se déclenche une seule fois. Les deux se combinent aussi bien avec reveal qu’avec la lecture normale/smooth, puisqu’ils opèrent sur la ligne de temps de frames déjà construite, quelle qu’elle soit :
chart.export_video(
"paused.mp4", "session.spls", format="mp4", fps=30,
reveal=True, reveal_steps=150,
breakpoint_at_ms=[2500], breakpoint_on_value=[(3, 90.0)], breakpoint_pause_ms=1200,
)
Vitrines vidéo
Sept exports réels, générés par les scripts affichés sous chacun — cliquez sur « Voir le code » pour lire le .py exact qui l’a produit.
Catalogue complet
Toutes les familles et variants de charts enregistrés, lus en direct depuis le même registre qui alimente la Vitrine — recherchez, puis cliquez sur « Voir le code » pour obtenir un extrait SeraStudio record() / push() / export_video() prêt à l’emploi, construit à partir des vrais paramètres de construction de ce variant. Toutes les familles ne supportent pas encore push() (voir les paragraphes SeraStudio ci-dessus) — l’extrait généré montre quand même la bonne forme à compléter.
Parcourir le catalogue complet
Référence des méthodes
Toujours synchronisé avec l’implémentation réelle. Utilisez les onglets ci-dessous pour aller directement à un mini-module (streaming, détection d’anomalies, alertes, repères, push distant, limitation de débit, historique/rapports, santé, licence, Sera Secure, SeraReport, SeraStudio) plutôt que de parcourir la liste complète.
SeraML
Description
API machine learning generee depuis les decorateurs Rust des modeles et fonctions.
Registre
Clustering
Description
Clustering functions registered in the ML bindings.
Description
Fonctions de clustering enregistrees dans les bindings ML.
K-Means
Description
API JSON fit-predict K-Means.
Reference API
KMeans Class
Signature
model = sp.KMeans(
k=3,
max_iter=300,
tol=1e-4,
mini_batch=False,
batch_size=1000,
n_init=10,
)
model.fit(x: list[list[float]]) -> None
model.fit_predict(x: list[list[float]]) -> list[int]
model.predict(x: list[list[float]]) -> list[int]
model.transform(x: list[list[float]]) -> list[list[float]]
model.labels_ -> list[int]
model.centroids_ -> list[list[float]]
model.inertia_ -> float
model.n_iter_ -> int
model.n_clusters -> int
Description
High-performance K-Means class for N-dimensional data with a scikit-learn-compatible API.
Constructor Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
k | int | 3 | Number of clusters |
max_iter | int | 300 | Maximum EM iterations |
tol | float | 1e-4 | Convergence tolerance on inertia delta |
mini_batch | bool | False | Force mini-batch mode |
batch_size | int | 1000 | Mini-batch sample size |
n_init | int | 10 | Number of times to run with different seeds (best inertia is kept) |
Methods
fit(x)
Runs K-Means on the N-D data. Populates labels_, centroids_, and inertia_.
| Argument | Type | Description |
|---|---|---|
x | list[list[float]] | Data matrix (rows = samples, cols = features) |
fit_predict(x) -> list[int]
Equivalent to fit(x) then returning labels_.
predict(x) -> list[int]
Assign new samples to the nearest centroid (does not refit).
transform(x) -> list[list[float]]
Return Euclidean distance from each sample to each centroid (shape: n_samples × k).
Attributes
| Attribute | Type | Description |
|---|---|---|
labels_ | list[int] | Cluster index per point (0-based) |
centroids_ | list[list[float]] | Final centroid coordinates (k × dims) |
inertia_ | float | Sum of squared distances to assigned centroids |
n_iter_ | int | Number of iterations run |
n_clusters | int | Effective number of clusters found |
k | int | Requested k |
Examples
Basic N-D clustering
import seraplot as sp
import random
random.seed(42)
centers = [(-2, -2, 0), (2, -2, 0), (0, 2, 1)]
data = [[cx + random.gauss(0, 0.4), cy + random.gauss(0, 0.4), cz + random.gauss(0, 0.3)]
for cx, cy, cz in centers for _ in range(300)]
model = sp.KMeans(k=3)
labels = model.fit_predict(data)
print(f"Clusters: {model.n_clusters}")
print(f"Inertia: {model.inertia_:.2f}")
print(f"Centroids: {model.centroids_}")
Combine class + chart
import seraplot as sp
import random
random.seed(0)
pts = [(random.gauss(cx, 0.3), random.gauss(cy, 0.3))
for cx, cy in [(0,0),(3,0),(1.5,2.5)] for _ in range(500)]
x, y = zip(*pts)
model = sp.KMeans(k=3)
labels = model.fit_predict([[xi, yi] for xi, yi in zip(x, y)])
# Build chart with known labels
chart = sp.kmeans(
title="K-Means Result",
x_values=list(x),
y_values=list(y),
k=3,
)
chart.show()
print(f"Inertia: {model.inertia_:.4f}")
Distance transform
import seraplot as sp
data = [[1.0, 2.0], [3.0, 4.0], [5.0, 6.0], [0.0, 0.0]]
model = sp.KMeans(k=2)
model.fit(data)
distances = model.transform(data)
for i, row in enumerate(distances):
print(f"Point {i}: distances to centroids = {[f'{d:.3f}' for d in row]}")
Algorithmic Functioning
K-Means minimises the total inertia — the sum of squared distances from each point to its assigned centroid:
K-Means++ initialisation
The first centroid $\mu_1$ is chosen uniformly at random. Each subsequent centroid $\mu_j$ is sampled with probability proportional to $D(x)^2$ — the squared distance to the nearest already-placed centroid. This reduces the expected inertia at convergence to $O(\log k)$ of optimal.
EM iterations
- Assignment: $c(x_i) = \underset{k}{\arg\min}\ |x_i - \mu_k|^2$
- Update: $\mu_k = \dfrac{1}{|C_k|}\displaystyle\sum_{x_i \in C_k} x_i$
Iterations run until inertia delta $< $ tol or max_iter is reached.
transform(x) returns the $n \times k$ matrix of Euclidean distances from each
sample to each centroid, useful for soft-assignment and feature engineering.
Description
Classe K-Means haute performance pour données N-dimensionnelles, compatible avec l’API scikit-learn. Passe automatiquement en mode mini-batch pour n > 100 000.
Constructeur
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
k | int | 3 | Nombre de clusters |
max_iter | int | 300 | Nombre maximum d’itérations |
tol | float | 1e-4 | Tolérance de convergence |
mini_batch | bool | False | Forcer le mode mini-batch |
batch_size | int | 1000 | Taille du mini-batch |
Méthodes
| Méthode | Description |
|---|---|
fit(x) | Ajuste le modèle |
fit_predict(x) | Ajuste et retourne les labels |
predict(x) | Prédit les clusters |
transform(x) | Distances aux centroïdes |
Attributs
| Attribut | Description |
|---|---|
labels_ | Labels par point |
centroids_ | Coordonnées des centroïdes |
inertia_ | Inertie finale |
n_iter_ | Nombre d’itérations |
Fonctionnement algorithmique
K-Means minimise l’inertie totale — la somme des carrés des distances de chaque point à son centroïde assigné :
Initialisation K-Means++
Le premier centroïde $\mu_1$ est choisi de façon uniforme aléatoire. Chaque centroïde suivant $\mu_j$ est échantillonné avec une probabilité proportionnelle à $D(x)^2$ — la distance au carré au centroïde le plus proche déjà placé. Cela réduit l’inertie attendue à la convergence à $O(\log k)$ de l’optimal.
Itérations EM
- Affectation : $c(x_i) = \underset{k}{\arg\min}\ |x_i - \mu_k|^2$
- Mise à jour : $\mu_k = \dfrac{1}{|C_k|}\displaystyle\sum_{x_i \in C_k} x_i$
Les itérations tournent jusqu’à ce que le delta d’inertie passe sous tol ou que
max_iter soit atteint.
transform(x) retourne la matrice $n \times k$ des distances euclidiennes de chaque
échantillon à chaque centroïde, utile pour l’affectation douce et l’ingénierie de
caractéristiques.
DBSCAN
Description
API JSON fit-predict DBSCAN.
Reference API
DBSCAN 3D Chart
Signature
sp.build_dbscan_chart_3d(
title: str,
x: list[float],
y: list[float],
z: list[float],
*,
eps: float = 0.5,
min_samples: int = 5,
width: int = 900,
height: int = 600,
x_label: str = "X",
y_label: str = "Y",
z_label: str = "Z",
bg_color: str = "#1a1a2e",
normalize: bool = False,
palette: list[int] | None = None,
) -> Chart
Aliases: sp.dbscan3d
Description
DBSCAN clustering in 3D — rendered via GPU WebGL. Each cluster is assigned a distinct color; noise points are grey.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
title | str | required | Chart title |
x | list[float] | required | X coordinates |
y | list[float] | required | Y coordinates |
z | list[float] | required | Z coordinates |
eps | float | 0.5 | Neighborhood radius |
min_samples | int | 5 | Core point threshold |
width | int | 900 | Canvas width |
height | int | 600 | Canvas height |
x_label | str | "X" | X-axis label |
y_label | str | "Y" | Y-axis label |
z_label | str | "Z" | Z-axis label |
bg_color | str | "#1a1a2e" | Background color |
normalize | bool | False | Normalize XYZ to [0, 1] |
palette | list[int] | None | None | Custom cluster colors |
Returns
Chart
Examples
3D clusters
import seraplot as sp
import random
def blob3d(cx, cy, cz, n=200, s=0.4):
return [(cx+random.gauss(0,s), cy+random.gauss(0,s), cz+random.gauss(0,s))
for _ in range(n)]
pts = blob3d(0,0,0) + blob3d(5,5,5) + blob3d(10,0,5)
x, y, z = zip(*pts)
chart = sp.build_dbscan_chart_3d(
"3D DBSCAN",
x_values=list(x), y_values=list(y), z_values=list(z),
eps=1.2,
min_samples=5,
)
const sp = require('seraplot');
import random
def blob3d(cx, cy, cz, {n: 200, s: 0.4}):
return [(cx+random.gauss(0,s), cy+random.gauss(0,s), cz+random.gauss(0,s))
for _ in range(n)]
const pts = blob3d(0,0,0) + blob3d(5,5,5) + blob3d(10,0,5)
x, y, z = zip(*pts)
const chart = sp.build_dbscan_chart_3d("3D DBSCAN",
list(x),
list(y),
{
z_values: list(z),
eps: 1.2,
min_samples: 5
})
import * as sp from 'seraplot';
import random
def blob3d(cx, cy, cz, {n: 200, s: 0.4}):
return [(cx+random.gauss(0,s), cy+random.gauss(0,s), cz+random.gauss(0,s))
for _ in range(n)]
const pts = blob3d(0,0,0) + blob3d(5,5,5) + blob3d(10,0,5)
x, y, z = zip(*pts)
const chart = sp.build_dbscan_chart_3d("3D DBSCAN",
list(x),
list(y),
{
z_values: list(z),
eps: 1.2,
min_samples: 5
})
▶ Live Preview
Algorithmic Functioning
DBSCAN groups points that lie in dense regions and marks isolated points as noise. It requires no prior specification of the number of clusters.
For a point $p$, its $\epsilon$-neighbourhood is:
- Core point: $|N_\epsilon(p)| \geq \text{min_samples}$
- Border point: reachable from a core point but not itself a core point
- Noise point: not reachable from any core point — assigned label $-1$
The 3D variant operates identically in $\mathbb{R}^3$ — the KD-tree extends to three dimensions with SIMD-accelerated Euclidean distance $|p - q| = \sqrt{\Delta x^2 + \Delta y^2 + \Delta z^2}$.
When normalize=True, each axis is scaled to $[0, 1]$ independently before clustering,
so that the scale of $z$ does not distort $\epsilon$.
Description
Clustering DBSCAN en 3D — rendu via WebGL GPU. Chaque cluster est coloré distinctement ; les points bruit sont gris.
Paramètres
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
title | str | requis | Titre du graphique |
x | list[float] | requis | Coordonnées X |
y | list[float] | requis | Coordonnées Y |
z | list[float] | requis | Coordonnées Z |
eps | float | 0.5 | Distance maximale de voisinage |
min_samples | int | 5 | Minimum de points pour une région dense |
normalize | bool | False | Normaliser les variables avant le clustering |
Fonctionnement algorithmique
DBSCAN regroupe les points situés dans des régions denses et marque les points isolés comme du bruit. Il ne nécessite pas de spécifier le nombre de clusters à l’avance.
Pour un point $p$, son $\epsilon$-voisinage est :
- Point cœur : $|N_\epsilon(p)| \geq \text{min_samples}$
- Point frontière : accessible depuis un point cœur, mais pas lui-même un point cœur
- Point bruit : non accessible depuis aucun point cœur — label $-1$
La variante 3D fonctionne identiquement dans $\mathbb{R}^3$ — le KD-tree s’étend à trois dimensions avec un calcul de distance euclidienne $|p - q| = \sqrt{\Delta x^2 + \Delta y^2 + \Delta z^2}$ accéléré par SIMD.
Avec normalize=True, chaque axe est normalisé dans $[0, 1]$ indépendamment avant le
clustering, de façon à ce que l’échelle de $z$ ne distorde pas $\epsilon$.
DBSCAN Class
API Reference
model = sp.DBSCAN(eps=0.5, min_samples=5)
model.fit(X) -> None
model.fit_predict(X) -> list[int]
model.labels_ -> list[int]
model.n_clusters_ -> int
model.n_noise_ -> int
Constructor Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
eps | float | 0.5 | Neighborhood radius threshold $\epsilon$ |
min_samples | int | 5 | Minimum points to form a dense core |
Methods
| Method | Signature | Returns | Description |
|---|---|---|---|
fit(X) | fit(X: list[list[float]]) | None | Fit DBSCAN on N-dimensional data, populates labels_, n_clusters_, n_noise_ |
fit_predict(X) | fit_predict(X: list[list[float]]) | list[int] | Fit and return cluster labels (convenience wrapper) |
Attributes
| Attribute | Type | Description |
|---|---|---|
labels_ | list[int] | Cluster label per point (-1 = noise) |
n_clusters_ | int | Number of identified clusters (noise not counted) |
n_noise_ | int | Count of noise points (label $-1$) |
Example
import seraplot as sp
import numpy as np
data = np.random.randn(100, 3)
model = sp.DBSCAN(eps=0.8, min_samples=5)
labels = model.fit_predict(data.tolist())
print(f"Clusters: {model.n_clusters_}, Noise: {model.n_noise_}")
print(f"Labels shape: {len(labels)}")
x, y, z = data[:, 0].tolist(), data[:, 1].tolist(), data[:, 2].tolist()
color_groups = [str(lbl) for lbl in labels]
chart = sp.build_dbscan_chart_3d(
f"DBSCAN ({model.n_clusters_} clusters)",
x, y, z,
eps=0.8, min_samples=5,
color_groups=color_groups
)
Algorithmic Functioning
DBSCAN (Density-Based Spatial Clustering of Applications with Noise) groups points in dense regions and marks isolated points as noise.
Core concepts — for point $p$:
- $\epsilon$-neighborhood: $N_\epsilon(p) = {q \in D : |p - q|_2 \leq \epsilon}$
- Core point: $|N_\epsilon(p)| \geq \text{min_samples}$
- Border point: not core, but within $\epsilon$ of a core point
- Noise point: not reachable from any core point → label $-1$
Algorithm:
-
For each unvisited point $p$:
- If $p$ is core, start a new cluster via BFS (expand through density-connected neighbors)
- Otherwise, mark as noise (or leave unvisited)
-
Clusters are maximal sets of density-connected points.
Implementation:
SeraPlot uses KD-tree for $O(\log n)$ radius queries and parallel BFS with SIMD distance acceleration. n_clusters_ counts only true clusters; noise points are excluded.
Complexity: $O(n \log n)$ average time; $O(n^2)$ worst case.
Référence API
model = sp.DBSCAN(eps=0.5, min_samples=5)
model.fit(X) -> None
model.fit_predict(X) -> list[int]
model.labels_ -> list[int]
model.n_clusters_ -> int
model.n_noise_ -> int
Paramètres du constructeur
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
eps | float | 0.5 | Rayon de voisinage $\epsilon$ |
min_samples | int | 5 | Points min pour former un cœur |
Méthodes
| Méthode | Signature | Retourne | Description |
|---|---|---|---|
fit(X) | fit(X: list[list[float]]) | None | Ajuste DBSCAN, remplit labels_, n_clusters_, n_noise_ |
fit_predict(X) | fit_predict(X: list[list[float]]) | list[int] | Ajuste et retourne labels |
Attributs
| Attribut | Type | Description |
|---|---|---|
labels_ | list[int] | Label de cluster par point (-1 = bruit) |
n_clusters_ | int | Nombre de clusters (bruit non compté) |
n_noise_ | int | Nombre de points bruit (label $-1$) |
Exemple
import seraplot as sp
import numpy as np
data = np.random.randn(100, 3)
model = sp.DBSCAN(eps=0.8, min_samples=5)
labels = model.fit_predict(data.tolist())
print(f"Clusters: {model.n_clusters_}, Bruit: {model.n_noise_}")
x, y, z = data[:, 0].tolist(), data[:, 1].tolist(), data[:, 2].tolist()
color_groups = [str(lbl) for lbl in labels]
chart = sp.build_dbscan_chart_3d(
f"DBSCAN ({model.n_clusters_} clusters)",
x, y, z,
eps=0.8, min_samples=5,
color_groups=color_groups
)
Fonctionnement algorithmique
DBSCAN groupe les points dans les régions denses et marque les points isolés comme bruit.
Concepts clés — pour un point $p$:
- $\epsilon$-voisinage: $N_\epsilon(p) = {q \in D : |p - q|_2 \leq \epsilon}$
- Point cœur: $|N_\epsilon(p)| \geq \text{min_samples}$
- Point frontière: non cœur, mais dans $\epsilon$ d’un point cœur
- Point bruit: non accessible depuis aucun point cœur → label $-1$
Algorithme:
-
Pour chaque point $p$ non visité:
- Si $p$ est cœur, démarrer cluster via BFS
- Sinon, marquer comme bruit
-
Les clusters sont ensembles maximaux de points densément connexes.
Implémentation:
SeraPlot utilise KD-tree pour $O(\log n)$ requêtes de rayon et BFS parallèle avec accélération SIMD. n_clusters_ ne compte que vrais clusters; bruit exclu.
Complexité: $O(n \log n)$ en moyenne; $O(n^2)$ pire cas.
Linear Models
Description
Linear estimators and classifiers registered in the ML bindings.
Description
Estimateurs et classifieurs lineaires enregistres dans les bindings ML.
LinearRegression
Description
Regression lineaire OLS depuis l implementation ML Rust.
Reference API
Ridge / RidgeClassifier
Description
APIs Ridge regression et Ridge classifier.
Reference API
Lasso
Description
API de regression lineaire regularisee L1.
Reference API
ElasticNet
Description
API de regression ElasticNet.
Reference API
LogisticRegression
Description
API du classifieur de regression logistique.
Reference API
SGDClassifier / SGDRegressor
Description
APIs classifieur et regresseur lineaires par gradient stochastique.
Reference API
Tree-Based Models
Description
Tree ensembles and CART-based models registered in the ML bindings.
Description
Ensembles d arbres et modeles bases CART enregistres dans les bindings ML.
DecisionTree
Description
APIs arbre de decision classifieur et regresseur.
Reference API
RandomForest
Description
APIs random forest classifieur et regresseur.
Reference API
GradientBoosting
Description
APIs gradient boosting classifieur et regresseur.
Reference API
AdaBoost
Description
APIs AdaBoost classifieur et regresseur.
Reference API
Neighbors
Description
Nearest-neighbor estimators registered in the ML bindings.
Description
Estimateurs par plus proches voisins enregistres dans les bindings ML.
KNN / NearestCentroid
Description
APIs classifieur, regresseur et centroide par voisins.
Reference API
Naive Bayes
Description
Naive Bayes estimators registered in the ML bindings.
Description
Estimateurs Naive Bayes enregistres dans les bindings ML.
Naive Bayes
Description
APIs Naive Bayes gaussien, multinomial et Bernoulli.
Reference API
SVM
Description
Linear SVM estimators registered in the ML bindings.
Description
Estimateurs SVM lineaires enregistres dans les bindings ML.
LinearSVC / LinearSVR
Description
APIs SVM lineaire classifieur et regresseur.
Reference API
Preprocessing
Description
Preprocessing transformers registered in Rust.
Description
Transformers de preprocessing enregistres en Rust.
Preprocessing
Description
APIs scalers et transformers exposees par les bindings preprocessing.
Reference API
Advanced Preprocessing
API Reference
Signatures
imp = sp.SimpleImputer(strategy="mean", fill_value=0.0)
poly = sp.PolynomialFeatures(degree=2, interaction_only=False, include_bias=True)
kbd = sp.KBinsDiscretizer(n_bins=5, strategy="quantile")
pt = sp.PowerTransformer(method="yeo-johnson")
qt = sp.QuantileTransformer(n_quantiles=1000, output_distribution="uniform")
ohe = sp.OneHotEncoder()
ord_ = sp.OrdinalEncoder()
est.fit(X)
Xt = est.transform(X) -> ndarray
Xt = est.fit_transform(X) -> ndarray
SimpleImputer
| Parameter | Type | Default | Description |
|---|---|---|---|
strategy | str | "mean" | "mean", "median", "most_frequent", "constant" |
fill_value | float | 0.0 | Value used when strategy is "constant" |
Attribute: statistics_ : list[float] — fitted per-column value used to fill missing entries (NaN/±inf).
PolynomialFeatures
| Parameter | Type | Default | Description |
|---|---|---|---|
degree | int | 2 | Maximum total degree |
interaction_only | bool | False | Drop pure powers (no $x_i^2$) |
include_bias | bool | True | Prepend a column of ones |
Attribute: n_features_out_ : int, powers_ : list[list[int]].
KBinsDiscretizer
| Parameter | Type | Default | Description |
|---|---|---|---|
n_bins | int | 5 | Bins per feature |
strategy | str | "quantile" | "uniform" or "quantile" |
Attribute: bin_edges_ : list[list[float]].
PowerTransformer
| Parameter | Type | Default | Description |
|---|---|---|---|
method | str | "yeo-johnson" | "yeo-johnson" (any sign) or "box-cox" (positive only) |
Attribute: lambdas_ : list[float]. Lambda is found by grid-searching $[-2, 2]$ and minimising variance after transform.
QuantileTransformer
| Parameter | Type | Default | Description |
|---|---|---|---|
n_quantiles | int | 1000 | Quantile knots |
output_distribution | str | "uniform" | "uniform" or "normal" |
Attribute: quantiles_ : list[list[float]].
OneHotEncoder / OrdinalEncoder
fit / transform accept list[list[Any]] of strings or numbers; categories are deduced per column.
Attribute: categories_ : list[list[Any]]. OneHotEncoder exposes n_features_out_.
Example — full preprocessing pipeline
import seraplot as sp
import numpy as np
rng = np.random.default_rng(0)
X = rng.normal(size=(500, 4))
X[10, 1] = np.nan
X[42, 3] = np.nan
imp = sp.SimpleImputer(strategy="median")
poly = sp.PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
kbd = sp.KBinsDiscretizer(n_bins=8, strategy="quantile")
pt = sp.PowerTransformer(method="yeo-johnson")
qt = sp.QuantileTransformer(n_quantiles=200, output_distribution="normal")
X1 = imp.fit_transform(X)
print("imputed :", X1.shape, "stats:", imp.statistics_)
X2 = poly.fit_transform(X1)
print("poly :", X2.shape, "n_out:", poly.n_features_out_)
X3 = kbd.fit_transform(X1)
print("discretised:", X3.shape)
X4 = pt.fit_transform(X1)
print("power :", X4.shape, "lambdas:", pt.lambdas_)
X5 = qt.fit_transform(X1)
print("quantile :", X5.shape, "mean ≈ 0:", X5.mean(0).round(2))
Example — categorical encoders
import seraplot as sp
rows = [["cat", "red"], ["dog", "red"], ["cat", "blue"], ["fish", "blue"]]
ohe = sp.OneHotEncoder()
print(ohe.fit_transform(rows))
print(ohe.categories_, ohe.n_features_out_)
oe = sp.OrdinalEncoder()
print(oe.fit_transform(rows))
print(oe.categories_)
Algorithmic Functioning
SimpleImputer
For each feature $j$, fit a statistic $\theta_j$ over the non-missing values ($\mathrm{NaN}$, $+\infty$, $-\infty$ are treated as missing):
transform replaces every missing entry with $\theta_j$.
PolynomialFeatures
Enumerates all monomials $\prod_j x_j^{a_j}$ with $\sum_j a_j \leq d$. With interaction_only=True, every $a_j \in {0, 1}$ (no pure powers). With include_bias=True, the constant 1 column is prepended.
KBinsDiscretizer
Computes per-feature bin edges:
- uniform : $[\min, \max]$ split into $K$ equal-width intervals.
- quantile : $[\min, q_{1/K}, q_{2/K}, \dots, \max]$ using sample quantiles.
transform returns the integer bin index in ${0, \dots, K-1}$.
PowerTransformer
Applies a parametric monotone transform to make data more Gaussian.
Yeo-Johnson (works with any sign):
Box-Cox (requires $y > 0$):
$\lambda^*$ is selected per feature by a grid search over $[-2, 2]$ minimising the variance of the transformed feature.
QuantileTransformer
Maps each feature to a uniform $[0, 1]$ distribution via its empirical CDF, then optionally re-maps to $\mathcal{N}(0, 1)$ via the inverse normal CDF (Beasley–Springer–Moro approximation).
Categorical encoders
OneHotEncoder builds the union of observed categories per column and emits one indicator per category. OrdinalEncoder assigns each category an integer index in fit-time order.
Référence API
Signatures
imp = sp.SimpleImputer(strategy="mean", fill_value=0.0)
poly = sp.PolynomialFeatures(degree=2, interaction_only=False, include_bias=True)
kbd = sp.KBinsDiscretizer(n_bins=5, strategy="quantile")
pt = sp.PowerTransformer(method="yeo-johnson")
qt = sp.QuantileTransformer(n_quantiles=1000, output_distribution="uniform")
ohe = sp.OneHotEncoder()
ord_ = sp.OrdinalEncoder()
est.fit(X)
Xt = est.transform(X) -> ndarray
Xt = est.fit_transform(X) -> ndarray
SimpleImputer
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
strategy | str | "mean" | "mean", "median", "most_frequent", "constant" |
fill_value | float | 0.0 | Valeur utilisée si stratégie "constant" |
Attribut : statistics_ : list[float] — valeur ajustée par colonne pour remplir les entrées manquantes (NaN/±inf).
PolynomialFeatures
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
degree | int | 2 | Degré total maximal |
interaction_only | bool | False | Supprime les puissances pures (pas de $x_i^2$) |
include_bias | bool | True | Ajoute une colonne de uns en tête |
Attribut : n_features_out_ : int, powers_ : list[list[int]].
KBinsDiscretizer
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
n_bins | int | 5 | Nombre de classes par feature |
strategy | str | "quantile" | "uniform" ou "quantile" |
Attribut : bin_edges_ : list[list[float]].
PowerTransformer
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
method | str | "yeo-johnson" | "yeo-johnson" (tout signe) ou "box-cox" (positif) |
Attribut : lambdas_ : list[float]. Lambda est trouvé par recherche sur grille $[-2, 2]$ minimisant la variance après transformation.
QuantileTransformer
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
n_quantiles | int | 1000 | Nœuds de quantiles |
output_distribution | str | "uniform" | "uniform" ou "normal" |
Attribut : quantiles_ : list[list[float]].
OneHotEncoder / OrdinalEncoder
fit / transform acceptent list[list[Any]] de chaînes ou de nombres ; les catégories sont déduites par colonne.
Attribut : categories_ : list[list[Any]]. OneHotEncoder expose n_features_out_.
Exemple — pipeline complet
import seraplot as sp
import numpy as np
rng = np.random.default_rng(0)
X = rng.normal(size=(500, 4))
X[10, 1] = np.nan
X[42, 3] = np.nan
imp = sp.SimpleImputer(strategy="median")
poly = sp.PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
kbd = sp.KBinsDiscretizer(n_bins=8, strategy="quantile")
pt = sp.PowerTransformer(method="yeo-johnson")
qt = sp.QuantileTransformer(n_quantiles=200, output_distribution="normal")
X1 = imp.fit_transform(X)
print("imputé :", X1.shape, "stats:", imp.statistics_)
X2 = poly.fit_transform(X1)
print("poly :", X2.shape, "n_out:", poly.n_features_out_)
X3 = kbd.fit_transform(X1)
print("discrétisé:", X3.shape)
X4 = pt.fit_transform(X1)
print("power :", X4.shape, "lambdas:", pt.lambdas_)
X5 = qt.fit_transform(X1)
print("quantile :", X5.shape, "moy ≈ 0 :", X5.mean(0).round(2))
Exemple — encodeurs catégoriels
import seraplot as sp
rows = [["cat", "red"], ["dog", "red"], ["cat", "blue"], ["fish", "blue"]]
ohe = sp.OneHotEncoder()
print(ohe.fit_transform(rows))
print(ohe.categories_, ohe.n_features_out_)
oe = sp.OrdinalEncoder()
print(oe.fit_transform(rows))
print(oe.categories_)
Fonctionnement algorithmique
SimpleImputer
Pour chaque feature $j$, ajuste une statistique $\theta_j$ sur les valeurs non manquantes ($\mathrm{NaN}$, $+\infty$, $-\infty$ sont considérés comme manquants) :
transform remplace toute entrée manquante par $\theta_j$.
PolynomialFeatures
Énumère tous les monômes $\prod_j x_j^{a_j}$ avec $\sum_j a_j \leq d$. Avec interaction_only=True, chaque $a_j \in {0, 1}$ (pas de puissances pures). Avec include_bias=True, la colonne constante 1 est ajoutée en tête.
KBinsDiscretizer
Calcule les bornes de classes par feature :
- uniform : $[\min, \max]$ découpé en $K$ intervalles équilarges.
- quantile : $[\min, q_{1/K}, q_{2/K}, \dots, \max]$ via les quantiles empiriques.
transform renvoie l’index de classe entier dans ${0, \dots, K-1}$.
PowerTransformer
Applique une transformation monotone paramétrique pour rendre les données plus gaussiennes.
Yeo-Johnson (tout signe) :
Box-Cox (requiert $y > 0$) :
$\lambda^*$ est sélectionné par feature via une recherche sur grille dans $[-2, 2]$ minimisant la variance de la feature transformée.
QuantileTransformer
Mappe chaque feature vers une distribution uniforme $[0, 1]$ via sa CDF empirique, puis optionnellement re-mappe vers $\mathcal{N}(0, 1)$ via la CDF normale inverse (approximation de Beasley–Springer–Moro).
Encodeurs catégoriels
OneHotEncoder construit l’union des catégories observées par colonne et émet un indicateur par catégorie. OrdinalEncoder attribue à chaque catégorie un index entier dans l’ordre du fit.
Decomposition
Description
Dimensionality-reduction models registered in Rust.
Description
Modeles de reduction de dimension enregistres en Rust.
PCA / TruncatedSVD
Description
APIs de decomposition exposees par les bindings ML.
Reference API
Model Selection
Description
Cross-validation, search and inspection utilities registered in ML.
Description
Utilitaires de validation croisee, recherche et inspection enregistres en ML.
GridSearchCV
Description
API JSON de recherche grille avec validation croisee.
Reference API
Cross-Validation
Description
APIs JSON K-Fold et validation croisee.
Reference API
Permutation Importance
Description
API JSON permutation importance.
Reference API
IsolationForest
Description
API de detection d anomalies Isolation Forest.
Reference API
Model Registry
Description
APIs JSON de persistance des modeles.
Reference API
PowerBI / Tableau Export
PowerBI & Tableau Export
Export predictions and feature matrices directly to PowerBI Push dataset JSON or Tableau TDS/CSV — native format, no extra tooling. / Export les prédictions et matrices de features en JSON PowerBI ou TDS/CSV Tableau.
Returns a JSON string representing a PowerBI Push dataset. Paste into the PowerBI REST API or save to .json.
Returns a Tableau Data Source XML (.tds). Defines all columns — open in Tableau Desktop to connect instantly.
Returns a CSV string with a header row. Includes feature columns, optional target and optional predictions.
Export predictions from a registry-loaded model payload and share reproducible exports alongside versioned models.
import seraplot as sp, numpy as np
X = np.random.randn(200, 3)
y = X[:, 0] * 2 + np.random.randn(200) * 0.1
model = sp.LinearRegression()
model.fit(X, y)
yhat = model.predict(X)
pbi_json = sp.export_powerbi(
name = "HousePrice",
table = "Predictions",
X = X,
y = list(y),
y_pred = list(yhat),
)
with open("powerbi_dataset.json", "w") as f:
f.write(pbi_json)
tds_xml = sp.export_tableau_tds(
name = "HousePrice",
X = X,
y = list(y),
y_pred = list(yhat),
)
with open("house_price.tds", "w") as f:
f.write(tds_xml)
csv_str = sp.export_tableau_csv(
name = "HousePrice",
X = X,
y = list(y),
y_pred = list(yhat),
)
with open("house_price.csv", "w") as f:
f.write(csv_str)
API Reference
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | — | Dataset name (appears in PowerBI) |
table | str | — | Table name inside the dataset |
X | list[list[float]] | — | Feature matrix, shape (n, p) |
y | list[float] | None | None | Target values (optional) |
y_pred | list[float] | None | None | Predicted values (optional) |
str — PowerBI Push dataset JSON| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | — | Data source name in Tableau |
X | list[list[float]] | — | Feature matrix |
y | list[float] | None | None | Target values (optional) |
y_pred | list[float] | None | None | Predicted values (optional) |
str — Tableau TDS XML| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | — | Used as comment in header row |
X | list[list[float]] | — | Feature matrix |
y | list[float] | None | None | Target values (optional) |
y_pred | list[float] | None | None | Predicted values (optional) |
str — CSV with header row (feat_0, feat_1, …, target, prediction)feat_0 … feat_{p-1}, target, prediction. All functions return strings — write them to disk or POST them to the relevant API.Référence API
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
name | str | — | Nom du dataset (visible dans PowerBI) |
table | str | — | Nom de la table dans le dataset |
X | list[list[float]] | — | Matrice de features, forme (n, p) |
y | list[float] | None | None | Valeurs cibles (optionnel) |
y_pred | list[float] | None | None | Prédictions (optionnel) |
str — JSON Push dataset PowerBI| Paramètre | Type | Défaut | Description |
|---|---|---|---|
name | str | — | Nom de la source de données dans Tableau |
X | list[list[float]] | — | Matrice de features |
y | list[float] | None | None | Valeurs cibles (optionnel) |
y_pred | list[float] | None | None | Prédictions (optionnel) |
str — XML Tableau TDS| Paramètre | Type | Défaut | Description |
|---|---|---|---|
name | str | — | Utilisé en commentaire dans la ligne d'en-tête |
X | list[list[float]] | — | Matrice de features |
y | list[float] | None | None | Valeurs cibles (optionnel) |
y_pred | list[float] | None | None | Prédictions (optionnel) |
str — CSV avec en-tête (feat_0, feat_1, …, target, prediction)feat_0 … feat_{p-1}, target, prediction. Toutes les fonctions retournent des chaînes de caractères — écrivez-les sur disque ou envoyez-les à l'API concernée via HTTP POST.GPU Backend
GPU Backend
Backend detection and selection for CUDA, Metal (Apple Silicon), ROCm and CPU. Auto-detection at runtime with zero configuration. / Détection et sélection du backend GPU/CPU — CUDA, Metal, ROCm détectés automatiquement.
pip install seraplot wheel ships CPU-only and uses rayon multi-threading. Real GPU kernels are available through opt-in feature flags compiled from source: cargo build --features cuda, --features metal, --features rocm. The detection and selection API works in both builds.FR — État actuel : La wheel par défaut
pip install seraplot est CPU only avec multi-threading rayon. Les vrais kernels GPU sont disponibles via des feature flags opt-in compilés depuis les sources : cargo build --features cuda, --features metal, --features rocm. L'API de détection et sélection fonctionne dans les deux builds.List all detected devices with backend, name, memory and availability flag.
Explicitly select a backend. Pass None for auto-selection (picks the first available non-CPU backend).
Returns the currently active backend name as a string.
CUDA: CUDA_PATH / CUDA_HOME env vars. Metal: macOS target. ROCm: /opt/rocm path probe.
import seraplot as sp
devices = sp.gpu_devices()
for d in devices:
avail = "available" if d["available"] else "detected / unavailable"
print(f" [{d['backend']}] {d['name']} {d['mem_mb']} MB — {avail}")
active = sp.gpu_active_backend()
print(f"\nActive backend: {active}")
sp.gpu_set_backend("cpu")
print(f"Forced CPU: {sp.gpu_active_backend()}")
sp.gpu_set_backend(None)
print(f"Auto-select: {sp.gpu_active_backend()}")
API Reference
| Function | Signature | Returns |
|---|---|---|
gpu_devices | () | list[dict] |
gpu_set_backend | (backend: str | None) | str — active backend |
gpu_active_backend | () | str |
| Field | Type | Description |
|---|---|---|
backend | str | "cpu", "cuda", "metal", "rocm" |
name | str | Human-readable device name |
mem_mb | int | Device memory in MB (0 for CPU) |
available | bool | True if the device can be used for computation |
| Value | Platform | Detection method |
|---|---|---|
"cpu" | All | Always available |
"cuda" | NVIDIA GPU | CUDA_PATH or CUDA_HOME env var set |
"metal" | Apple Silicon / macOS | cfg!(target_os = "macos") at compile time |
"rocm" | AMD GPU / Linux | /opt/rocm directory exists |
None | — | Auto-select: first non-CPU available backend |
Référence API
| Fonction | Signature | Retourne |
|---|---|---|
gpu_devices | () | list[dict] |
gpu_set_backend | (backend: str | None) | str — backend actif |
gpu_active_backend | () | str |
| Champ | Type | Description |
|---|---|---|
backend | str | "cpu", "cuda", "metal", "rocm" |
name | str | Nom lisible du périphérique |
mem_mb | int | Mémoire du périphérique en Mo (0 pour CPU) |
available | bool | True si le périphérique peut être utilisé |
| Valeur | Plateforme | Méthode de détection |
|---|---|---|
"cpu" | Toutes | Toujours disponible |
"cuda" | GPU NVIDIA | Variable d'env CUDA_PATH ou CUDA_HOME |
"metal" | Apple Silicon / macOS | cfg!(target_os = "macos") à la compilation |
"rocm" | GPU AMD / Linux | Dossier /opt/rocm présent |
None | — | Auto : premier backend non-CPU disponible |
Distributed & Cloud
Distributed Training & Cloud Planner
Ray-style scatter/gather primitives backed by rayon + auto-scaling planner for datasets >1M rows. Zero infra, pure Python. / Primitives scatter/gather style Ray via rayon + planificateur auto-scaling pour jeux de données >1M lignes.
Create a pool, scatter rows into shards, train per shard in parallel, then allreduce results back.
Given n_rows × n_cols and a memory budget, produces the optimal strategy: in_memory, chunked or streamed.
Runtime snapshot: CPU threads, active backend, OS, architecture, registry path.
Streaming CSV row count — constant memory regardless of file size. Useful before calling cloud_plan.
import seraplot as sp, numpy as np
n_rows = 10_000
X = np.random.randn(n_rows, 5)
y = X[:, 0] * 2 + np.random.randn(n_rows) * 0.1
wp = sp.WorkerPool(n_workers=0)
print(f"Pool: {wp.n_workers} workers")
handle = wp.scatter(n_rows)
shards = wp.shards(handle)
coefs = []
for shard in shards:
Xi = X[shard["start"]:shard["end"]]
yi = y[shard["start"]:shard["end"]]
model = sp.LinearRegression()
model.fit(Xi, yi)
coefs.append(model.coef_)
mean_coef = wp.allreduce_mean(coefs)
print(f"Aggregated coef: {mean_coef}")
wp.release(handle)
import seraplot as sp
plan = sp.cloud_plan(n_rows=5_000_000, n_cols=50, mem_budget_mb=4096)
print(f"Strategy : {plan['strategy']}")
print(f"Workers : {plan['recommended_workers']}")
print(f"Chunk rows : {plan['recommended_chunk_rows']}")
print(f"Chunks : {plan['n_chunks']}")
print(f"Est. seconds: {plan['estimated_seconds']:.1f}s")
resources = sp.cloud_resources()
print(f"\nCPU threads : {resources['cpu_threads']}")
print(f"Backend : {resources['backend']}")
print(f"OS : {resources['os']} / {resources['arch']}")
API Reference
wp = sp.WorkerPool(n_workers=0)
| Method / Attr | Signature | Returns | Description |
|---|---|---|---|
n_workers | property | int | Actual number of workers (resolved from n_workers=0 = rayon thread count) |
scatter | (n_rows) -> int | handle | Partition n_rows rows into shards, return opaque handle |
shards | (handle) -> list[dict] | list[dict] | Get shard list for handle: [{"id":0,"start":0,"end":625}, …] |
release | (handle) | None | Free memory associated with the scatter handle |
allreduce_mean | (vecs: list[list[float]]) -> list[float] | list[float] | Element-wise mean across all vectors (same length) |
allreduce_sum | (vecs: list[list[float]]) -> list[float] | list[float] | Element-wise sum across all vectors |
| Parameter | Type | Default | Description |
|---|---|---|---|
n_rows | int | — | Number of dataset rows |
n_cols | int | — | Number of feature columns |
mem_budget_mb | int | 2048 | Available RAM budget in MB |
dict with keys: n_rows, n_cols, bytes_total, mem_budget_mb, recommended_workers, recommended_chunk_rows, n_chunks, estimated_seconds, strategydict — cpu_threads (int), backend (str), os (str), arch (str), registry_dir (str), tasks_dir (str)| Parameter | Type | Default | Description |
|---|---|---|---|
path | str | — | Path to CSV file |
chunk_rows | int | 100000 | Internal read buffer size |
has_header | bool | True | Skip first line as header |
delimiter | str | "," | CSV delimiter |
int — number of data rows (header excluded if has_header=True)| Strategy | Condition | Description |
|---|---|---|
in_memory | Dataset fits in budget | Load all at once, use full parallel training |
chunked | Dataset > budget, <2× budget per chunk | Process in chunks, aggregate results per chunk |
streamed | Dataset >> budget | Stream rows one chunk at a time, online aggregation |
cloud_count_rows(path) on your CSV file, then pass the result to cloud_plan to get the optimal strategy before loading any data into memory.Référence API
wp = sp.WorkerPool(n_workers=0)
| Méthode / Attr | Signature | Retourne | Description |
|---|---|---|---|
n_workers | propriété | int | Nombre réel de workers (0 = nombre de threads rayon) |
scatter | (n_rows) -> int | handle | Partitionne n_rows lignes en shards, retourne un handle opaque |
shards | (handle) -> list[dict] | list[dict] | Récupère les shards : [{"id":0,"start":0,"end":625}, …] |
release | (handle) | None | Libère la mémoire associée au handle scatter |
allreduce_mean | (vecs) | list[float] | Moyenne élément par élément sur tous les vecteurs |
allreduce_sum | (vecs) | list[float] | Somme élément par élément sur tous les vecteurs |
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
n_rows | int | — | Nombre de lignes du jeu de données |
n_cols | int | — | Nombre de colonnes de features |
mem_budget_mb | int | 2048 | Budget RAM disponible en Mo |
dict avec les clés : n_rows, n_cols, bytes_total, mem_budget_mb, recommended_workers, recommended_chunk_rows, n_chunks, estimated_seconds, strategydict — cpu_threads (int), backend (str), os (str), arch (str), registry_dir (str), tasks_dir (str)| Paramètre | Type | Défaut | Description |
|---|---|---|---|
path | str | — | Chemin vers le fichier CSV |
chunk_rows | int | 100000 | Taille du buffer de lecture interne |
has_header | bool | True | Ignorer la première ligne (en-tête) |
delimiter | str | "," | Séparateur CSV |
int — nombre de lignes de données (en-tête exclu si has_header=True)| Stratégie | Condition | Description |
|---|---|---|
in_memory | Dataset tient dans le budget | Chargement complet, entraînement parallèle total |
chunked | Dataset > budget, <2× budget par chunk | Traitement en chunks, agrégation des résultats |
streamed | Dataset >> budget | Streaming ligne par ligne, agrégation en ligne |
cloud_count_rows(chemin) sur votre CSV, puis passez le résultat à cloud_plan pour obtenir la stratégie optimale avant de charger la moindre donnée en mémoire.Metrics
Description
APIs JSON de scores et courbes de metriques.
Reference API
Classification Metrics
API Reference
Signatures
sp.accuracy_score(y_true, y_pred) -> float
sp.balanced_accuracy_score(y_true, y_pred) -> float
sp.precision_score(y_true, y_pred, average="binary", pos_label=1) -> float
sp.recall_score(y_true, y_pred, average="binary", pos_label=1) -> float
sp.f1_score(y_true, y_pred, average="binary", pos_label=1) -> float
sp.fbeta_score(y_true, y_pred, beta=1.0, average="binary", pos_label=1) -> float
sp.jaccard_score(y_true, y_pred, pos_label=1) -> float
sp.matthews_corrcoef(y_true, y_pred) -> float
sp.cohen_kappa_score(y_true, y_pred) -> float
sp.hamming_loss(y_true, y_pred) -> float
sp.zero_one_loss(y_true, y_pred) -> float
sp.confusion_matrix(y_true, y_pred) -> list[list[int]]
sp.classification_report(y_true, y_pred) -> str
sp.log_loss(y_true, y_proba, n_classes, eps=1e-15) -> float
sp.binary_log_loss(y_true, y_proba, eps=1e-15) -> float
sp.brier_score_loss(y_true, y_proba) -> float
sp.hinge_loss(y_true, decision) -> float
sp.roc_curve(y_true, y_score, pos_label=1) -> (fpr, tpr, thresholds)
sp.roc_auc_score(y_true, y_score) -> float
sp.precision_recall_curve(y_true, y_score, pos_label=1) -> (precision, recall, thresholds)
sp.average_precision_score(y_true, y_score) -> float
Function summary
| Function | Domain | Output | Description |
|---|---|---|---|
accuracy_score | any | float | Fraction correct |
balanced_accuracy_score | any | float | Mean of per-class recall |
precision_score | binary / multiclass | float | TP / (TP+FP) |
recall_score | binary / multiclass | float | TP / (TP+FN) |
f1_score | binary / multiclass | float | Harmonic mean of P and R |
fbeta_score | binary / multiclass | float | F-beta with weight beta |
jaccard_score | binary | float | Intersection over Union |
matthews_corrcoef | binary | float | Phi coefficient (range $[-1,1]$) |
cohen_kappa_score | any | float | Agreement vs. chance |
hamming_loss | any | float | Fraction of wrong predictions |
zero_one_loss | any | float | $1 - \text{accuracy}$ |
log_loss | $K$-class proba | float | Cross-entropy |
binary_log_loss | binary proba | float | Cross-entropy (binary) |
brier_score_loss | binary proba | float | $(\hat{p} - y)^2$ averaged |
hinge_loss | $\pm 1$ labels | float | $\max(0, 1 - y \cdot s)$ averaged |
roc_curve | binary score | (fpr, tpr, thr) | ROC points |
roc_auc_score | binary score | float | Area under ROC |
precision_recall_curve | binary score | (p, r, thr) | PR points |
average_precision_score | binary score | float | Area under PR curve |
average accepts "binary", "macro", "weighted".
Example — full classification report
import seraplot as sp
import numpy as np
rng = np.random.default_rng(0)
y_true = rng.integers(0, 2, size=200).tolist()
y_score = rng.uniform(size=200).tolist()
y_pred = [1 if s >= 0.5 else 0 for s in y_score]
print("accuracy :", sp.accuracy_score(y_true, y_pred))
print("balanced_accuracy :", sp.balanced_accuracy_score(y_true, y_pred))
print("precision :", sp.precision_score(y_true, y_pred))
print("recall :", sp.recall_score(y_true, y_pred))
print("f1 :", sp.f1_score(y_true, y_pred))
print("f2 :", sp.fbeta_score(y_true, y_pred, beta=2.0))
print("matthews_corrcoef :", sp.matthews_corrcoef(y_true, y_pred))
print("cohen_kappa :", sp.cohen_kappa_score(y_true, y_pred))
print("jaccard :", sp.jaccard_score(y_true, y_pred))
print("hamming_loss :", sp.hamming_loss(y_true, y_pred))
print("zero_one_loss :", sp.zero_one_loss(y_true, y_pred))
print("brier :", sp.brier_score_loss(y_true, y_score))
print("binary_log_loss :", sp.binary_log_loss(y_true, y_score))
print("roc_auc :", sp.roc_auc_score(y_true, y_score))
print("average_precision :", sp.average_precision_score(y_true, y_score))
Example — ROC and PR curves
import seraplot as sp
fpr, tpr, thr = sp.roc_curve(y_true, y_score, pos_label=1)
sp.line(fpr, tpr, title=f"ROC (AUC={sp.roc_auc_score(y_true, y_score):.3f})")
prec, rec, thr = sp.precision_recall_curve(y_true, y_score, pos_label=1)
sp.line(rec, prec, title=f"PR (AP={sp.average_precision_score(y_true, y_score):.3f})")
Algorithmic Functioning
Accuracy — fraction of correct predictions:
Balanced accuracy — mean per-class recall, robust to class imbalance:
Matthews correlation coefficient (binary) — uses all four cells of the confusion matrix:
Cohen’s kappa — agreement adjusted for chance, with $p_o$ observed agreement and $p_e$ chance agreement:
F-beta generalises F1 by weighting recall $\beta$ times more than precision:
Jaccard score (binary) — intersection over union of positive predictions and labels:
Log loss (cross-entropy) for $K$ classes with predicted probabilities $p_{i,k}$:
Probabilities are clipped to $[\varepsilon, 1-\varepsilon]$ before the log to avoid $-\infty$.
Brier score — mean squared error between predicted probabilities and binary labels:
Hinge loss (margin loss) with labels in ${-1, +1}$ and decision values $s_i$:
ROC curve / AUC — sweep all thresholds of $s_i$, plotting FPR vs. TPR; AUC is the area under that curve, equal to the probability that a random positive scores higher than a random negative.
Precision-Recall curve / Average Precision — same sweep, plotting Precision vs. Recall; AP is computed as the step-area:
Référence API
Signatures
sp.accuracy_score(y_true, y_pred) -> float
sp.balanced_accuracy_score(y_true, y_pred) -> float
sp.precision_score(y_true, y_pred, average="binary", pos_label=1) -> float
sp.recall_score(y_true, y_pred, average="binary", pos_label=1) -> float
sp.f1_score(y_true, y_pred, average="binary", pos_label=1) -> float
sp.fbeta_score(y_true, y_pred, beta=1.0, average="binary", pos_label=1) -> float
sp.jaccard_score(y_true, y_pred, pos_label=1) -> float
sp.matthews_corrcoef(y_true, y_pred) -> float
sp.cohen_kappa_score(y_true, y_pred) -> float
sp.hamming_loss(y_true, y_pred) -> float
sp.zero_one_loss(y_true, y_pred) -> float
sp.confusion_matrix(y_true, y_pred) -> list[list[int]]
sp.classification_report(y_true, y_pred) -> str
sp.log_loss(y_true, y_proba, n_classes, eps=1e-15) -> float
sp.binary_log_loss(y_true, y_proba, eps=1e-15) -> float
sp.brier_score_loss(y_true, y_proba) -> float
sp.hinge_loss(y_true, decision) -> float
sp.roc_curve(y_true, y_score, pos_label=1) -> (fpr, tpr, thresholds)
sp.roc_auc_score(y_true, y_score) -> float
sp.precision_recall_curve(y_true, y_score, pos_label=1) -> (precision, recall, thresholds)
sp.average_precision_score(y_true, y_score) -> float
Résumé
| Fonction | Domaine | Sortie | Description |
|---|---|---|---|
accuracy_score | tout | float | Fraction correcte |
balanced_accuracy_score | tout | float | Moyenne du rappel par classe |
precision_score | binaire / multiclasse | float | $TP / (TP+FP)$ |
recall_score | binaire / multiclasse | float | $TP / (TP+FN)$ |
f1_score | binaire / multiclasse | float | Moyenne harmonique de P et R |
fbeta_score | binaire / multiclasse | float | F-bêta avec poids beta |
jaccard_score | binaire | float | Intersection sur union |
matthews_corrcoef | binaire | float | Coefficient phi (intervalle $[-1,1]$) |
cohen_kappa_score | tout | float | Accord corrigé du hasard |
hamming_loss | tout | float | Fraction d’erreurs |
zero_one_loss | tout | float | $1 - \text{accuracy}$ |
log_loss | proba $K$ classes | float | Entropie croisée |
binary_log_loss | proba binaire | float | Entropie croisée (binaire) |
brier_score_loss | proba binaire | float | $(\hat{p} - y)^2$ moyen |
hinge_loss | étiquettes $\pm 1$ | float | $\max(0, 1 - y \cdot s)$ moyen |
roc_curve | score binaire | (fpr, tpr, thr) | Points ROC |
roc_auc_score | score binaire | float | Aire sous ROC |
precision_recall_curve | score binaire | (p, r, thr) | Points PR |
average_precision_score | score binaire | float | Aire sous courbe PR |
average accepte "binary", "macro", "weighted".
Exemple — rapport de classification complet
import seraplot as sp
import numpy as np
rng = np.random.default_rng(0)
y_true = rng.integers(0, 2, size=200).tolist()
y_score = rng.uniform(size=200).tolist()
y_pred = [1 if s >= 0.5 else 0 for s in y_score]
print("accuracy :", sp.accuracy_score(y_true, y_pred))
print("balanced_accuracy :", sp.balanced_accuracy_score(y_true, y_pred))
print("precision :", sp.precision_score(y_true, y_pred))
print("recall :", sp.recall_score(y_true, y_pred))
print("f1 :", sp.f1_score(y_true, y_pred))
print("f2 :", sp.fbeta_score(y_true, y_pred, beta=2.0))
print("matthews_corrcoef :", sp.matthews_corrcoef(y_true, y_pred))
print("cohen_kappa :", sp.cohen_kappa_score(y_true, y_pred))
print("jaccard :", sp.jaccard_score(y_true, y_pred))
print("hamming_loss :", sp.hamming_loss(y_true, y_pred))
print("zero_one_loss :", sp.zero_one_loss(y_true, y_pred))
print("brier :", sp.brier_score_loss(y_true, y_score))
print("binary_log_loss :", sp.binary_log_loss(y_true, y_score))
print("roc_auc :", sp.roc_auc_score(y_true, y_score))
print("average_precision :", sp.average_precision_score(y_true, y_score))
Exemple — courbes ROC et PR
import seraplot as sp
fpr, tpr, thr = sp.roc_curve(y_true, y_score, pos_label=1)
sp.line(fpr, tpr, title=f"ROC (AUC={sp.roc_auc_score(y_true, y_score):.3f})")
prec, rec, thr = sp.precision_recall_curve(y_true, y_score, pos_label=1)
sp.line(rec, prec, title=f"PR (AP={sp.average_precision_score(y_true, y_score):.3f})")
Fonctionnement algorithmique
Précision (accuracy) — fraction des prédictions correctes :
Précision équilibrée — moyenne du rappel par classe, robuste au déséquilibre :
Coefficient de corrélation de Matthews (binaire) — utilise les quatre cases de la matrice de confusion :
Kappa de Cohen — accord corrigé du hasard, avec $p_o$ accord observé et $p_e$ accord aléatoire :
F-bêta généralise F1 en pondérant le rappel $\beta$ fois plus que la précision :
Score de Jaccard (binaire) — intersection sur union des positifs prédits et labellisés :
Log loss (entropie croisée) pour $K$ classes avec probabilités $p_{i,k}$ :
Les probabilités sont clampées à $[\varepsilon, 1-\varepsilon]$ avant le log pour éviter $-\infty$.
Score de Brier — erreur quadratique moyenne entre probabilités prédites et labels binaires :
Hinge loss (perte de marge) avec labels dans ${-1, +1}$ et valeurs de décision $s_i$ :
Courbe ROC / AUC — balayage de tous les seuils de $s_i$, traçant FPR vs. TPR ; l’AUC est l’aire sous la courbe, égale à la probabilité qu’un positif aléatoire ait un score supérieur à celui d’un négatif aléatoire.
Courbe Précision-Rappel / Average Precision — même balayage, traçant Précision vs. Rappel ; AP est l’aire en escalier :
Regression Metrics
API Reference
Signatures
sp.mean_squared_error(y_true, y_pred) -> float
sp.root_mean_squared_error(y_true, y_pred) -> float
sp.mean_absolute_error(y_true, y_pred) -> float
sp.median_absolute_error(y_true, y_pred) -> float
sp.r2_score(y_true, y_pred) -> float
sp.explained_variance_score(y_true, y_pred) -> float
sp.max_error(y_true, y_pred) -> float
sp.mean_absolute_percentage_error(y_true, y_pred) -> float
sp.mean_squared_log_error(y_true, y_pred) -> float
sp.root_mean_squared_log_error(y_true, y_pred) -> float
sp.mean_pinball_loss(y_true, y_pred, alpha=0.5) -> float
sp.d2_absolute_error_score(y_true, y_pred) -> float
Function summary
| Function | Output | Description |
|---|---|---|
mean_squared_error | float | Average squared error |
root_mean_squared_error | float | $\sqrt{\text{MSE}}$, in target units |
mean_absolute_error | float | Average absolute error |
median_absolute_error | float | Median of $ |
r2_score | float | Coefficient of determination |
explained_variance_score | float | Variance ratio (allows bias) |
max_error | float | Worst residual |
mean_absolute_percentage_error | float | MAPE, scale-free |
mean_squared_log_error | float | MSE in log space, requires $y, \hat{y} \geq 0$ |
root_mean_squared_log_error | float | $\sqrt{\text{MSLE}}$ |
mean_pinball_loss | float | Quantile loss (param alpha in $(0,1)$) |
d2_absolute_error_score | float | $R^2$ analogue using MAE |
Example
import seraplot as sp
y_true = [3.0, -0.5, 2.0, 7.0, 5.0, 4.5]
y_pred = [2.5, 0.0, 2.1, 7.8, 4.7, 4.6]
print("MSE :", sp.mean_squared_error(y_true, y_pred))
print("RMSE :", sp.root_mean_squared_error(y_true, y_pred))
print("MAE :", sp.mean_absolute_error(y_true, y_pred))
print("MedAE :", sp.median_absolute_error(y_true, y_pred))
print("R² :", sp.r2_score(y_true, y_pred))
print("EVS :", sp.explained_variance_score(y_true, y_pred))
print("MaxE :", sp.max_error(y_true, y_pred))
print("MAPE :", sp.mean_absolute_percentage_error(y_true, y_pred))
print("MSLE :", sp.mean_squared_log_error([1,2,3], [1.1,2.1,3.1]))
print("Q90 :", sp.mean_pinball_loss(y_true, y_pred, alpha=0.9))
print("D²-AE :", sp.d2_absolute_error_score(y_true, y_pred))
Algorithmic Functioning
MSE / RMSE / MAE — pointwise error aggregates:
Median absolute error — robust to outliers:
MAPE — scale-free, undefined when $y_i = 0$:
MSLE — penalises under-prediction more than over-prediction; requires non-negative values:
Pinball loss — asymmetric quantile loss; minimised by the $\alpha$-quantile predictor:
Explained variance allows for a constant bias:
$R^2$ vs. $D^2$-AE — both are “1 minus loss / loss-of-the-mean-predictor”, but using MSE for $R^2$ and MAE for $D^2$-AE:
with $\tilde{y}$ the median.
Référence API
Signatures
sp.mean_squared_error(y_true, y_pred) -> float
sp.root_mean_squared_error(y_true, y_pred) -> float
sp.mean_absolute_error(y_true, y_pred) -> float
sp.median_absolute_error(y_true, y_pred) -> float
sp.r2_score(y_true, y_pred) -> float
sp.explained_variance_score(y_true, y_pred) -> float
sp.max_error(y_true, y_pred) -> float
sp.mean_absolute_percentage_error(y_true, y_pred) -> float
sp.mean_squared_log_error(y_true, y_pred) -> float
sp.root_mean_squared_log_error(y_true, y_pred) -> float
sp.mean_pinball_loss(y_true, y_pred, alpha=0.5) -> float
sp.d2_absolute_error_score(y_true, y_pred) -> float
Résumé
| Fonction | Sortie | Description |
|---|---|---|
mean_squared_error | float | Erreur quadratique moyenne |
root_mean_squared_error | float | $\sqrt{\text{MSE}}$, dans l’unité cible |
mean_absolute_error | float | Erreur absolue moyenne |
median_absolute_error | float | Médiane de $ |
r2_score | float | Coefficient de détermination |
explained_variance_score | float | Ratio de variance (autorise un biais) |
max_error | float | Pire résidu |
mean_absolute_percentage_error | float | MAPE, sans échelle |
mean_squared_log_error | float | MSE en espace log, requiert $y, \hat{y} \geq 0$ |
root_mean_squared_log_error | float | $\sqrt{\text{MSLE}}$ |
mean_pinball_loss | float | Perte quantile (paramètre alpha dans $(0,1)$) |
d2_absolute_error_score | float | Analogue de $R^2$ avec MAE |
Exemple
import seraplot as sp
y_true = [3.0, -0.5, 2.0, 7.0, 5.0, 4.5]
y_pred = [2.5, 0.0, 2.1, 7.8, 4.7, 4.6]
print("MSE :", sp.mean_squared_error(y_true, y_pred))
print("RMSE :", sp.root_mean_squared_error(y_true, y_pred))
print("MAE :", sp.mean_absolute_error(y_true, y_pred))
print("MedAE :", sp.median_absolute_error(y_true, y_pred))
print("R² :", sp.r2_score(y_true, y_pred))
print("EVS :", sp.explained_variance_score(y_true, y_pred))
print("MaxE :", sp.max_error(y_true, y_pred))
print("MAPE :", sp.mean_absolute_percentage_error(y_true, y_pred))
print("MSLE :", sp.mean_squared_log_error([1,2,3], [1.1,2.1,3.1]))
print("Q90 :", sp.mean_pinball_loss(y_true, y_pred, alpha=0.9))
print("D²-AE :", sp.d2_absolute_error_score(y_true, y_pred))
Fonctionnement algorithmique
MSE / RMSE / MAE — agrégats d’erreur point par point :
Erreur absolue médiane — robuste aux outliers :
MAPE — sans échelle, indéfini quand $y_i = 0$ :
MSLE — pénalise davantage la sous-estimation que la sur-estimation ; requiert des valeurs positives :
Pinball loss — perte quantile asymétrique, minimisée par le prédicteur $\alpha$-quantile :
Variance expliquée autorise un biais constant :
$R^2$ vs. $D^2$-AE — tous deux « 1 moins perte / perte du prédicteur moyen », mais utilisant MSE pour $R^2$ et MAE pour $D^2$-AE :
avec $\tilde{y}$ la médiane.
Clustering Metrics
API Reference
Signatures
sp.silhouette_score(X, labels) -> float
sp.davies_bouldin_score(X, labels) -> float
sp.calinski_harabasz_score(X, labels) -> float
sp.adjusted_rand_score(labels_true, labels_pred) -> float
sp.normalized_mutual_info_score(labels_true, labels_pred) -> float
sp.fowlkes_mallows_score(labels_true, labels_pred) -> float
sp.homogeneity_score(labels_true, labels_pred) -> float
sp.completeness_score(labels_true, labels_pred) -> float
sp.v_measure_score(labels_true, labels_pred) -> float
Function summary
| Function | Type | Range | Best | Description |
|---|---|---|---|---|
silhouette_score | internal | $[-1, 1]$ | $\to 1$ | Mean silhouette over samples |
davies_bouldin_score | internal | $[0, \infty)$ | $\to 0$ | Mean cluster similarity ratio |
calinski_harabasz_score | internal | $[0, \infty)$ | $\to \infty$ | Variance ratio criterion |
adjusted_rand_score | external | $[-1, 1]$ | $\to 1$ | Rand index adjusted for chance |
normalized_mutual_info_score | external | $[0, 1]$ | $\to 1$ | MI normalised by mean entropy |
fowlkes_mallows_score | external | $[0, 1]$ | $\to 1$ | Geometric mean of pairwise P/R |
homogeneity_score | external | $[0, 1]$ | $\to 1$ | Each cluster contains one class |
completeness_score | external | $[0, 1]$ | $\to 1$ | Each class lies in one cluster |
v_measure_score | external | $[0, 1]$ | $\to 1$ | Harmonic mean of H and C |
X is a 2D ndarray (n_samples, n_features) (the silhouette is parallelised with rayon).
Example
import seraplot as sp
import numpy as np
rng = np.random.default_rng(0)
X = np.vstack([
rng.normal(loc=( 0, 0), scale=0.6, size=(100, 2)),
rng.normal(loc=( 5, 5), scale=0.6, size=(100, 2)),
rng.normal(loc=(-5, 5), scale=0.6, size=(100, 2)),
])
y_true = [0]*100 + [1]*100 + [2]*100
km = sp.KMeans(k=3, max_iter=100, tol=1e-4, mini_batch=False, batch_size=0, n_init=4)
labels = list(km.fit_predict(X))
print("silhouette :", sp.silhouette_score(X, labels))
print("davies_bouldin :", sp.davies_bouldin_score(X, labels))
print("calinski_harabasz :", sp.calinski_harabasz_score(X, labels))
print("ARI :", sp.adjusted_rand_score(y_true, labels))
print("NMI :", sp.normalized_mutual_info_score(y_true, labels))
print("FMI :", sp.fowlkes_mallows_score(y_true, labels))
print("homogeneity :", sp.homogeneity_score(y_true, labels))
print("completeness :", sp.completeness_score(y_true, labels))
print("v_measure :", sp.v_measure_score(y_true, labels))
Algorithmic Functioning
Silhouette — for each sample $i$, with $a_i$ the mean distance to the same-cluster points and $b_i$ the mean distance to the nearest other cluster:
Davies-Bouldin — for each cluster $k$ with intra-cluster dispersion $S_k$ and centroid distance $d_{kj}$:
Calinski-Harabasz — variance ratio between/within clusters, with $B$ between-cluster scatter and $W$ within-cluster scatter:
Adjusted Rand Index — Rand index corrected for chance by subtracting the expected value:
NMI — normalised mutual information:
Homogeneity / Completeness / V-measure — entropy-based duals:
Fowlkes-Mallows — geometric mean of pairwise precision and recall computed from TP/FP/FN of the pair confusion matrix:
Référence API
Signatures
sp.silhouette_score(X, labels) -> float
sp.davies_bouldin_score(X, labels) -> float
sp.calinski_harabasz_score(X, labels) -> float
sp.adjusted_rand_score(labels_true, labels_pred) -> float
sp.normalized_mutual_info_score(labels_true, labels_pred) -> float
sp.fowlkes_mallows_score(labels_true, labels_pred) -> float
sp.homogeneity_score(labels_true, labels_pred) -> float
sp.completeness_score(labels_true, labels_pred) -> float
sp.v_measure_score(labels_true, labels_pred) -> float
Résumé
| Fonction | Type | Plage | Idéal | Description |
|---|---|---|---|---|
silhouette_score | interne | $[-1, 1]$ | $\to 1$ | Silhouette moyenne |
davies_bouldin_score | interne | $[0, \infty)$ | $\to 0$ | Ratio de similarité moyen |
calinski_harabasz_score | interne | $[0, \infty)$ | $\to \infty$ | Critère de ratio de variance |
adjusted_rand_score | externe | $[-1, 1]$ | $\to 1$ | Index de Rand corrigé du hasard |
normalized_mutual_info_score | externe | $[0, 1]$ | $\to 1$ | MI normalisée par l’entropie |
fowlkes_mallows_score | externe | $[0, 1]$ | $\to 1$ | Moyenne géométrique de P/R par paires |
homogeneity_score | externe | $[0, 1]$ | $\to 1$ | Chaque cluster contient une classe |
completeness_score | externe | $[0, 1]$ | $\to 1$ | Chaque classe est dans un cluster |
v_measure_score | externe | $[0, 1]$ | $\to 1$ | Moyenne harmonique de H et C |
X est un ndarray 2D (n_samples, n_features) (la silhouette est parallélisée avec rayon).
Exemple
import seraplot as sp
import numpy as np
rng = np.random.default_rng(0)
X = np.vstack([
rng.normal(loc=( 0, 0), scale=0.6, size=(100, 2)),
rng.normal(loc=( 5, 5), scale=0.6, size=(100, 2)),
rng.normal(loc=(-5, 5), scale=0.6, size=(100, 2)),
])
y_true = [0]*100 + [1]*100 + [2]*100
km = sp.KMeans(k=3, max_iter=100, tol=1e-4, mini_batch=False, batch_size=0, n_init=4)
labels = list(km.fit_predict(X))
print("silhouette :", sp.silhouette_score(X, labels))
print("davies_bouldin :", sp.davies_bouldin_score(X, labels))
print("calinski_harabasz :", sp.calinski_harabasz_score(X, labels))
print("ARI :", sp.adjusted_rand_score(y_true, labels))
print("NMI :", sp.normalized_mutual_info_score(y_true, labels))
print("FMI :", sp.fowlkes_mallows_score(y_true, labels))
print("homogeneity :", sp.homogeneity_score(y_true, labels))
print("completeness :", sp.completeness_score(y_true, labels))
print("v_measure :", sp.v_measure_score(y_true, labels))
Fonctionnement algorithmique
Silhouette — pour chaque échantillon $i$, avec $a_i$ la distance moyenne aux points du même cluster et $b_i$ la distance moyenne au cluster voisin le plus proche :
Davies-Bouldin — pour chaque cluster $k$ avec dispersion intra $S_k$ et distance entre centroïdes $d_{kj}$ :
Calinski-Harabasz — ratio de variance inter/intra, avec $B$ dispersion inter-cluster et $W$ dispersion intra-cluster :
Index de Rand ajusté — Rand corrigé en soustrayant la valeur attendue :
NMI — information mutuelle normalisée :
Homogénéité / Complétude / V-mesure — duaux basés sur l’entropie :
Fowlkes-Mallows — moyenne géométrique de la précision et du rappel par paires, calculées à partir de TP/FP/FN de la matrice de confusion par paires :
train_test_split / StratifiedKFold
API Reference
Signature
X_train, X_test, y_train, y_test = sp.train_test_split(
X, y, test_size=0.2, random_state=None, stratify=False
)
kf = sp.StratifiedKFold(n_splits=5, shuffle=False, random_state=0)
for train_idx, test_idx in kf.split(X, y):
X_train, X_test = X[train_idx], X[test_idx]
y_train, y_test = y[train_idx], y[test_idx]
...
Parameters — train_test_split
| Parameter | Type | Default | Description |
|---|---|---|---|
X | ndarray (n, p) | — | Feature matrix |
y | list | ndarray | — | Target vector |
test_size | float | 0.2 | Fraction of samples to hold out |
random_state | int | None | None | Seed for reproducibility |
stratify | bool | False | Preserve class proportions in each split |
Constructor parameters — StratifiedKFold
| Parameter | Type | Default | Description |
|---|---|---|---|
n_splits | int | 5 | Number of folds $k$ |
shuffle | bool | False | Shuffle data before splitting |
random_state | int | None | None | Seed for reproducibility |
Returns — train_test_split
| Return value | Type | Description |
|---|---|---|
X_train | ndarray | Training features |
X_test | ndarray | Test features |
y_train | list | Training labels |
y_test | list | Test labels |
Example
import seraplot as sp
import numpy as np
X = np.random.randn(500, 6)
y = (X[:, 0] + X[:, 1] > 0).astype(int)
X_train, X_test, y_train, y_test = sp.train_test_split(
X, y, test_size=0.2, random_state=42, stratify=True
)
print(f"Train: {len(y_train)}, Test: {len(y_test)}")
kf = sp.StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
for fold, (tr, te) in enumerate(kf.split(X, y)):
clf = sp.LogisticRegression()
clf.fit(X[tr], np.array(y)[tr].tolist())
print(f"Fold {fold}: {clf.score(X[te], np.array(y)[te].tolist()):.4f}")
Algorithmic Functioning
train_test_split
Non-stratified split — randomly shuffle indices and cut at position $\lfloor n \cdot (1 - \texttt{test_size})\rfloor$:
where $\sigma$ is a random permutation seeded by random_state.
Stratified split — class proportions are preserved by splitting each class independently:
then combining and shuffling all per-class test/train sets. This ensures that rare classes are not accidentally excluded from the test set.
StratifiedKFold
Splits the dataset into $k$ non-overlapping folds whilst preserving class distributions in each fold.
Algorithm:
1. For each class $c$, collect its indices $\mathcal{I}_c = {i : y_i = c}$.
2. Optionally shuffle $\mathcal{I}_c$ with random_state.
3. Divide $\mathcal{I}_c$ into $k$ roughly equal sub-arrays of size $\lfloor|\mathcal{I}_c|/k\rfloor$ or $\lceil|\mathcal{I}_c|/k\rceil$.
4. For fold $f \in {0,\ldots,k-1}$: the test set is $\bigcup_c \mathcal{I}_c[f]$ and the train set is its complement.
The $f$-th fold test error estimate $\hat{e}_f$ gives the cross-validated score:
This estimate has lower variance than a single train/test split, especially for small datasets.
Référence API
Signature
X_train, X_test, y_train, y_test = sp.train_test_split(
X, y, test_size=0.2, random_state=None, stratify=False
)
kf = sp.StratifiedKFold(n_splits=5, shuffle=False, random_state=0)
for train_idx, test_idx in kf.split(X, y):
X_train, X_test = X[train_idx], X[test_idx]
y_train, y_test = y[train_idx], y[test_idx]
...
Paramètres — train_test_split
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
X | ndarray (n, p) | — | Matrice de features |
y | list | ndarray | — | Vecteur cible |
test_size | float | 0.2 | Fraction des échantillons à mettre de côté |
random_state | int | None | None | Graine pour la reproductibilité |
stratify | bool | False | Préserver les proportions de classes dans chaque partition |
Paramètres du constructeur — StratifiedKFold
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
n_splits | int | 5 | Nombre de plis $k$ |
shuffle | bool | True | Mélanger les données avant de diviser |
random_state | int | None | None | Graine pour la reproductibilité |
Valeurs de retour — train_test_split
| Valeur de retour | Type | Description |
|---|---|---|
X_train | ndarray | Features d’entraînement |
X_test | ndarray | Features de test |
y_train | list | Étiquettes d’entraînement |
y_test | list | Étiquettes de test |
Exemple
import seraplot as sp
import numpy as np
X = np.random.randn(500, 6)
y = (X[:, 0] + X[:, 1] > 0).astype(int)
X_train, X_test, y_train, y_test = sp.train_test_split(
X, y, test_size=0.2, random_state=42, stratify=True
)
print(f"Train : {len(y_train)}, Test : {len(y_test)}")
kf = sp.StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
for pli, (tr, te) in enumerate(kf.split(X, y)):
clf = sp.LogisticRegression()
clf.fit(X[tr], np.array(y)[tr].tolist())
print(f"Pli {pli} : {clf.score(X[te], np.array(y)[te].tolist()):.4f}")
Fonctionnement algorithmique
train_test_split
Division non stratifiée — mélange aléatoire des indices et coupe à la position $\lfloor n \cdot (1 - \texttt{test_size})\rfloor$ :
où $\sigma$ est une permutation aléatoire initialisée par random_state.
Division stratifiée — les proportions de classes sont préservées en divisant chaque classe indépendamment :
puis en combinant et mélangeant tous les ensembles test/train par classe. Cela garantit que les classes rares ne sont pas accidentellement exclues de l’ensemble de test.
StratifiedKFold
Divise le jeu de données en $k$ plis non-chevauchants tout en préservant les distributions de classes dans chaque pli.
Algorithme :
1. Pour chaque classe $c$, collecter ses indices $\mathcal{I}_c = {i : y_i = c}$.
2. Optionnellement mélanger $\mathcal{I}_c$ avec random_state.
3. Diviser $\mathcal{I}_c$ en $k$ sous-tableaux approximativement égaux de taille $\lfloor|\mathcal{I}_c|/k\rfloor$ ou $\lceil|\mathcal{I}_c|/k\rceil$.
4. Pour le pli $f \in {0,\ldots,k-1}$ : l’ensemble de test est $\bigcup_c \mathcal{I}_c[f]$ et l’ensemble d’entraînement est son complément.
L’estimation d’erreur du $f$-ième pli $\hat{e}_f$ donne le score de validation croisée :
Cette estimation a une variance plus faible qu’une seule division train/test, notamment pour les petits jeux de données.
SeraDFrame
SeraDFrame is a columnar, Rust-native dataframe — Vec<f64> / Vec<String>
/ Vec<bool> per column, no per-cell object boxing — covering the common
pandas surface: relational joins, group-by/aggregate, sorting, filtering,
dedup, describe/corr, and a builder pattern, plus native, lossless
conversion to and from pandas.DataFrame so it drops into an existing
pandas pipeline anywhere.
import seraplot as sp
df = sp.SeraDFrame.from_csv("events.csv")
by_region = df.groupby("region").agg({"cost": "sum", "latency_ms": "mean"})
top5 = by_region.sort_values("cost", ascending=False).head(5)
Every table below is generated at page load straight from the #[sera_doc(...)]
attributes on each method in v2/src/data/dframe/ — not hand-maintained, so
it cannot drift from what is actually implemented. SeraDFrame methods do
not currently carry aliases the way chart functions do — one canonical name
per method, matching the underlying pandas-shaped surface directly.
Construction & Interop
Reading & Attributes
Filtering & Masking
Shaping & Transform
Relational & Combine
GroupBy
Rolling & Expanding
Datetime
Stats & Reductions
String Methods
Sera Secure — encrypted DataFrames (commercial)
SecureDFrame is an AES-256-GCM encrypted counterpart to SeraDFrame —
columns stay ciphertext at rest, decrypted only transiently for a single
read or chart render. It’s a paid add-on, not part of the free/open-source
surface documented above: full description and method reference are on the
Sera Pulse pricing page, alongside the rest
of SeraPlot’s paid catalog.
SeraDFrame est un dataframe colonnaire, natif Rust — Vec<f64> /
Vec<String> / Vec<bool> par colonne, sans boxing objet par cellule —
couvrant la surface pandas courante : jointures relationnelles,
group-by/agrégation, tri, filtrage, dédoublonnage, describe/corr, un
patron builder, plus une conversion native et sans perte vers et depuis
pandas.DataFrame, pour s’insérer n’importe où dans un pipeline pandas
existant.
import seraplot as sp
df = sp.SeraDFrame.from_csv("events.csv")
by_region = df.groupby("region").agg({"cost": "sum", "latency_ms": "mean"})
top5 = by_region.sort_values("cost", ascending=False).head(5)
Chaque tableau ci-dessous est généré au chargement de la page directement
depuis les attributs #[sera_doc(...)] de chaque méthode dans
v2/src/data/dframe/ — pas maintenu à la main, donc impossible de dériver
de ce qui est réellement implémenté. Les méthodes SeraDFrame n’ont
actuellement pas d’alias comme les fonctions de graphique — un seul nom
canonique par méthode, reflétant directement la surface pandas sous-jacente.
Construction & Interopérabilité
Lecture & attributs
Filtrage & masques
Mise en forme & transformation
Relationnel & combinaison
GroupBy
Fenêtres glissantes
Dates & heures
Stats & réductions
Méthodes de chaînes
Sera Secure — DataFrames chiffrés (commercial)
SecureDFrame est un équivalent chiffré en AES-256-GCM de SeraDFrame — les
colonnes restent en texte chiffré au repos, déchiffrées seulement de façon
transitoire pour une lecture ou un rendu de chart ponctuel. C’est un
module payant, distinct de la surface gratuite/open-source documentée
ci-dessus : description complète et référence des méthodes sur la
page de tarification Sera Pulse, avec le
reste du catalogue payant de SeraPlot.
Table — Data Engineering
Table is a small, columnar data-shaping utility: relational joins,
group-by/aggregate, pivots, rolling windows and filters, all in Rust, with no
pandas dependency. Its purpose is narrow and deliberate — reshape one source
of truth into the exact inputs each chart function expects, so several
panels built from the same data stay consistent, instead of hand-rolling
loops per chart.
import seraplot as sp
t = sp.Table({
"region": ["North", "South", "North", "South"],
"product": ["Core", "Core", "Cloud", "Cloud"],
"revenue": [24.0, 18.0, 12.0, 9.0],
})
Columns are built from Python dict[str, list] — each value can be int,
float, str or bool; mixed-type columns coerce to string on read.
Reading
| Method | Effect |
|---|---|
columns() -> list[str] | Column names, in original order. |
nrows (getter) | Row count. |
column(name) -> list | Raw values (native Python types). |
column_f64(name) -> list[float] | Values coerced to float. |
column_str(name) -> list[str] | Values coerced to string. |
to_records() -> list[dict] | Row-oriented view, one dict per row. |
head(n) -> Table | First n rows. |
select(names) -> Table | A subset of columns. |
Filtering & sorting
| Method | Effect |
|---|---|
filter_eq(col, value) -> Table | Rows where col == value. |
filter_gt/lt/ge/le(col, value: float) -> Table | Numeric comparison filters. |
filter_in(col, values: list[str]) -> Table | Rows where col is one of values. |
sort_by(col, desc=False) -> Table | Sorted copy. |
top_n(col, n, desc=True) -> Table | Shortcut for sort_by(col, desc).head(n) — the “top 10” pattern. |
Relational & ETL operations
| Method | Effect |
|---|---|
join(other, on, how="inner") -> Table | Joins two tables on a key column. how="left" keeps unmatched left rows with zero-filled right columns. Colliding column names from other are prefixed right_. |
concat(other) -> Table | Vertical union of two tables; missing columns on either side are zero/empty-filled. |
with_column(name, op, left, right) -> Table | Adds a computed column. op is "add"/"sub"/"mul"/"div". right is either another column’s name or a constant. |
groupby_agg(group_col, value_col, agg="sum") -> Table | Groups by group_col, aggregates value_col. agg is "sum"/"mean"/"count"/"min"/"max"/"median". |
pivot(index_col, columns_col, values_col, agg="sum") -> Table | Reshapes long data to wide: one row per index_col value, one column per unique columns_col value. |
Time-series & stats prep
| Method | Effect |
|---|---|
rolling_mean(col, window) -> Table | Adds {col}_rolling{window}, a trailing moving average. |
cumsum(col) -> Table | Adds {col}_cumsum, the running total. |
pct_change(col) -> Table | Adds {col}_pct_change, row-over-row percent change. |
rank(col, desc=False) -> Table | Adds {col}_rank, 1-based rank. |
zscore(col) -> Table | Adds {col}_zscore, (x - mean) / std. |
describe() -> Table | One row per numeric column: count, mean, min, max, std. |
Every transform returns a new Table (chainable, no mutation):
monthly = (
sales.groupby_agg("month", "revenue", "sum")
.sort_by("month")
.rolling_mean("revenue", 3)
.cumsum("revenue")
)
Feeding charts directly
gb = table.to_grouped_bar("month", "product", "revenue", "sum")
bar = sp.grouped_bar(
"", labels=gb["category_labels"], values=gb["values"],
series_names=gb["series_names"],
)
to_grouped_bar(index_col, columns_col, values_col, agg="sum") pivots and
flattens in one call, returning a dict shaped exactly for
sp.grouped_bar(labels=, values=, series_names=) — the most common
table-to-chart handoff, done in one line instead of manual pivoting.
For any other chart, column_f64/column_str after a filter_*/sort_by/
groupby_agg chain gets you there just as directly.
Loading data
t = sp.Table.from_csv("sales.csv")
Columns are auto-typed: numeric if every value in the column parses as a float, string otherwise.
Table est un petit outil de mise en forme de données en colonnes :
jointures relationnelles, group-by/agrégation, pivots, fenêtres glissantes
et filtres, le tout en Rust, sans dépendance à pandas. Son rôle est étroit
et délibéré — remodeler une source de vérité unique vers les entrées exactes
qu’attend chaque fonction de chart, pour que plusieurs panneaux construits
depuis les mêmes données restent cohérents, au lieu d’écrire des boucles à
la main pour chacun.
import seraplot as sp
t = sp.Table({
"region": ["North", "South", "North", "South"],
"product": ["Core", "Core", "Cloud", "Cloud"],
"revenue": [24.0, 18.0, 12.0, 9.0],
})
Les colonnes se construisent depuis un dict[str, list] Python — chaque
valeur peut être int, float, str ou bool ; les colonnes de type
mixte sont converties en chaîne à la lecture.
Lecture
| Méthode | Effet |
|---|---|
columns() -> list[str] | Noms des colonnes, dans l’ordre d’origine. |
nrows (getter) | Nombre de lignes. |
column(name) -> list | Valeurs brutes (types Python natifs). |
column_f64(name) -> list[float] | Valeurs converties en float. |
column_str(name) -> list[str] | Valeurs converties en chaîne. |
to_records() -> list[dict] | Vue orientée ligne, un dict par ligne. |
head(n) -> Table | Les n premières lignes. |
select(names) -> Table | Un sous-ensemble de colonnes. |
Filtrage & tri
| Méthode | Effet |
|---|---|
filter_eq(col, value) -> Table | Lignes où col == value. |
filter_gt/lt/ge/le(col, value: float) -> Table | Filtres de comparaison numérique. |
filter_in(col, values: list[str]) -> Table | Lignes où col fait partie de values. |
sort_by(col, desc=False) -> Table | Copie triée. |
top_n(col, n, desc=True) -> Table | Raccourci pour sort_by(col, desc).head(n) — le motif “top 10”. |
Opérations relationnelles & ETL
| Méthode | Effet |
|---|---|
join(other, on, how="inner") -> Table | Joint deux tables sur une colonne clé. how="left" garde les lignes gauches sans correspondance avec des colonnes droites à zéro. Les noms de colonnes de other en collision sont préfixés right_. |
concat(other) -> Table | Union verticale de deux tables ; les colonnes manquantes d’un côté sont remplies à zéro/vide. |
with_column(name, op, left, right) -> Table | Ajoute une colonne calculée. op vaut "add"/"sub"/"mul"/"div". right est soit le nom d’une autre colonne, soit une constante. |
groupby_agg(group_col, value_col, agg="sum") -> Table | Groupe par group_col, agrège value_col. agg vaut "sum"/"mean"/"count"/"min"/"max"/"median". |
pivot(index_col, columns_col, values_col, agg="sum") -> Table | Passe du format long au format large : une ligne par valeur de index_col, une colonne par valeur unique de columns_col. |
Prépa séries temporelles & stats
| Méthode | Effet |
|---|---|
rolling_mean(col, window) -> Table | Ajoute {col}_rolling{window}, une moyenne mobile arrière. |
cumsum(col) -> Table | Ajoute {col}_cumsum, le total cumulé. |
pct_change(col) -> Table | Ajoute {col}_pct_change, la variation en % ligne à ligne. |
rank(col, desc=False) -> Table | Ajoute {col}_rank, le rang (base 1). |
zscore(col) -> Table | Ajoute {col}_zscore, (x - moyenne) / écart-type. |
describe() -> Table | Une ligne par colonne numérique : count, mean, min, max, std. |
Chaque transformation renvoie une nouvelle Table (chaînable, sans
mutation) :
monthly = (
sales.groupby_agg("month", "revenue", "sum")
.sort_by("month")
.rolling_mean("revenue", 3)
.cumsum("revenue")
)
Alimenter directement les charts
gb = table.to_grouped_bar("month", "product", "revenue", "sum")
bar = sp.grouped_bar(
"", labels=gb["category_labels"], values=gb["values"],
series_names=gb["series_names"],
)
to_grouped_bar(index_col, columns_col, values_col, agg="sum") pivote et
aplatit en un seul appel, renvoyant un dict au format exact pour
sp.grouped_bar(labels=, values=, series_names=) — la passerelle
table-vers-chart la plus courante, faite en une ligne plutôt qu’un pivot
manuel.
Pour tout autre chart, column_f64/column_str après une chaîne
filter_*/sort_by/groupby_agg y mène tout aussi directement.
Charger des données
t = sp.Table.from_csv("sales.csv")
Les colonnes sont typées automatiquement : numérique si chaque valeur de la colonne se parse en float, chaîne sinon.
VS Code Extension
Official SeraPlot extension for Visual Studio Code — live preview, theme studio, snippets and a chart gallery.
Marketplace: https://marketplace.visualstudio.com/items?itemName=feur25.seraplot-vscode
Install
From the command palette or terminal — no browser needed:
ext install feur25.seraplot-vscode
Or from the Extensions view Ctrl+Shift+X, search SeraPlot:
Open in VS Code Marketplace →seraplot.pythonPath. Works on Windows, macOS and Linux.
Download the .vsix from the GitHub releases page, then install via terminal:
code --install-extension seraplot-vscode-0.6.1.vsix
Or drag the .vsix file directly into the VS Code Extensions panel.
Commands
| ID | Title | Description |
|---|---|---|
seraplot.preview | SeraPlot: Live Preview | Render every sp.Chart of the current Python file in a side panel and refresh on save |
seraplot.themeStudio | SeraPlot: Open Theme Studio | Pick a palette + background, copy the generated sp.set_global_background(...) snippet |
seraplot.gallery | SeraPlot: Open Gallery | Browse all chart families with thumbnails and one-click code samples |
The Live Preview button also appears in the editor title bar for any .py file.
Snippets
| Prefix | Description |
|---|---|
seraplot-import | import seraplot as sp |
seraplot-bar | Minimal bar chart |
seraplot-scatter | Scatter chart |
seraplot-dashboard | 2x2 grid layout |
seraplot-automl | sp.auto_classify(...) skeleton |
seraplot-drift | sp.drift_detect(...) skeleton |
Settings
| Key | Default | Description |
|---|---|---|
seraplot.pythonPath | python | Python interpreter used to render previews |
seraplot.autoReload | true | Re-render on save |
Set seraplot.pythonPath to your project venv, e.g.
${workspaceFolder}/.venv/Scripts/python.exe on Windows or
${workspaceFolder}/.venv/bin/python on macOS / Linux.
How the preview works
- The active
.pyfile is executed viarunpy.run_pathin a child Python process usingseraplot.pythonPath. - Every
sp.Chartinstance found in the module globals is exported withsp.export_html(chart). - The HTML is concatenated and rendered inside a VS Code Webview panel.
- With
seraplot.autoReload = truethe panel re-runs automatically when the file is saved.
The preview is sandboxed in a Webview — no network access, no
eval. Charts are CSP-safe (seeconfig/csp.md).
Source
Repository: https://github.com/feur25/seraplot — folder seraplot-vscode/.
License: MIT.
Extension officielle SeraPlot pour Visual Studio Code — aperçu en direct, theme studio, snippets et galerie de graphiques.
Marketplace : https://marketplace.visualstudio.com/items?itemName=feur25.seraplot-vscode
Installation
Depuis la palette de commandes ou un terminal — sans navigateur :
ext install feur25.seraplot-vscode
Ou depuis la vue Extensions Ctrl+Shift+X, cherchez SeraPlot :
Ouvrir dans le Marketplace VS Code →seraplot.pythonPath. Fonctionne sur Windows, macOS et Linux.
Téléchargez le .vsix depuis la page des releases GitHub, puis installez via le terminal :
code --install-extension seraplot-vscode-0.6.1.vsix
Ou glissez-déposez le fichier .vsix directement dans le panneau Extensions de VS Code.
Commandes
| ID | Titre | Description |
|---|---|---|
seraplot.preview | SeraPlot: Live Preview | Affiche tous les sp.Chart du fichier Python courant dans un panneau et rafraîchit à la sauvegarde |
seraplot.themeStudio | SeraPlot: Open Theme Studio | Choisir une palette + un fond, copier le snippet sp.set_global_background(...) généré |
seraplot.gallery | SeraPlot: Open Gallery | Parcourir toutes les familles de graphiques avec aperçus et exemples de code en un clic |
Le bouton Live Preview apparaît également dans la barre de titre de l’éditeur pour tout fichier .py.
Snippets
| Préfixe | Description |
|---|---|
seraplot-import | import seraplot as sp |
seraplot-bar | Graphique en barres minimal |
seraplot-scatter | Nuage de points |
seraplot-dashboard | Grille 2×2 |
seraplot-automl | Squelette sp.auto_classify(...) |
seraplot-drift | Squelette sp.drift_detect(...) |
Paramètres
| Clé | Défaut | Description |
|---|---|---|
seraplot.pythonPath | python | Interpréteur Python utilisé pour les aperçus |
seraplot.autoReload | true | Re-rendu à chaque sauvegarde |
Pointer seraplot.pythonPath vers le venv du projet, par exemple
${workspaceFolder}/.venv/Scripts/python.exe sous Windows ou
${workspaceFolder}/.venv/bin/python sous macOS / Linux.
Fonctionnement de l’aperçu
- Le fichier
.pyactif est exécuté viarunpy.run_pathdans un processus Python enfant utilisantseraplot.pythonPath. - Chaque instance
sp.Charttrouvée dans les globales du module est exportée avecsp.export_html(chart). - Le HTML est concaténé et rendu dans un panneau Webview de VS Code.
- Avec
seraplot.autoReload = true, le panneau se relance automatiquement à chaque sauvegarde du fichier.
L’aperçu est sandboxé dans un Webview — pas d’accès réseau, pas d’
eval. Les graphiques sont CSP-safe (voirconfig/csp.md).
Code source
Dépôt : https://github.com/feur25/seraplot — dossier seraplot-vscode/.
Licence : MIT.
About & Support
A solo project
SeraPlot is built entirely on my own, on top of a day job. I regularly rework the core of the framework to keep improving it — not because I'm the best at low-level optimisation, but because I care about delivering a complete solution that originally answered my own needs and hopefully answers yours too.
Get in touch
If you'd like a specific mechanic, feature or chart type, don't hesitate to reach out — by email only. I can't promise I'll build everything, but I'll do as much as I can.
Support the project
I work on SeraPlot for free, on top of my day job. If it saves you time, a donation is very welcome — but you absolutely don't need to donate to send me a feature request.
Donate via PayPalThanks for using Sera ✨
Un projet en solo
SeraPlot est entièrement réalisé seul, en plus d'un travail à plein temps. Je retravaille régulièrement le corps du framework pour continuer à l'améliorer — pas parce que je suis le meilleur en optimisation bas-niveau, mais parce que je tiens à offrir une solution complète qui répondait au départ à mes propres besoins et qui répond, j'espère, aussi aux vôtres.
Me contacter
Si vous voulez une mécanique, une fonctionnalité ou un type de graphique particulier, n'hésitez pas — par mail exclusivement. Je ne dis pas que je serai en capacité de tout réaliser, mais je ferai le plus possible.
Soutenir le projet
Je travaille sur SeraPlot gratuitement, en plus de mon travail. Si cela vous fait gagner du temps, un don est le bienvenu — mais il n'y a aucun besoin de faire un don pour me faire une demande de fonctionnalité.
Soutenir via PayPalMerci d'utiliser Sera ✨