From cf396c2ad6844e19478daaf1c10f6ecb500bb63d Mon Sep 17 00:00:00 2001
From: Barbier--Darnal Joseph
Date: Thu, 26 Mar 2026 14:48:25 +0100
Subject: [PATCH 01/16] format pyproject.toml
---
pyproject.toml | 111 ++++++++++++++++++++++++++-----------------------
1 file changed, 60 insertions(+), 51 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 5ebdca4..aeca375 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,75 +1,84 @@
[project]
name = "plotjs"
-description = "Turn static matplotlib charts into interactive web visualizations"
version = "0.0.9"
+description = "Turn static matplotlib charts into interactive web visualizations"
+readme = "README.md"
+requires-python = ">=3.10"
license = "MIT"
license-files = ["LICENSE"]
-keywords = ["matplotlib", "interactive", "javascript", "web", "css", "d3", "mpld3", "plotnine"]
authors = [
- { name="Joseph Barbier", email="joseph.barbierdarnal@mail.com" },
+ { name = "Joseph Barbier", email = "joseph.barbierdarnal@mail.com" },
+]
+keywords = [
+ "css",
+ "d3",
+ "interactive",
+ "javascript",
+ "matplotlib",
+ "mpld3",
+ "plotnine",
+ "web"
]
-readme = "README.md"
-requires-python = ">=3.10"
classifiers = [
- "Programming Language :: Python :: 3",
- "Operating System :: OS Independent",
- "Development Status :: 3 - Alpha"
+ "Development Status :: 3 - Alpha",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 3"
]
dependencies = [
- "jinja2>=3.0.0",
- "matplotlib>=3.10.0",
- "narwhals>=2.0.0",
+ "jinja2>=3.0.0",
+ "matplotlib>=3.10.0",
+ "narwhals>=2.0.0",
+]
+
+[project.urls]
+Documentation = "https://y-sunflower.github.io/plotjs/"
+Homepage = "https://y-sunflower.github.io/plotjs/"
+Issues = "https://github.com/y-sunflower/plotjs/issues"
+Repository = "https://github.com/y-sunflower/plotjs"
+
+[dependency-groups]
+dev = [
+ "coverage>=7.9.1",
+ "drawarrow>=0.1.0",
+ "genbadge[coverage]>=1.1.2",
+ "highlight-text>=0.2",
+ "ipywidgets>=8.1.7",
+ "jupyter>=1.1.1",
+ "mkdocs-material>=9.6.9",
+ "mkdocstrings-python>=1.16.5",
+ "morethemes>=0.4.0",
+ "nbclient>=0.10.2",
+ "nbformat>=5.10.4",
+ "pandas>=2.3.1",
+ "playwright>=1.40.0",
+ "plotnine>=0.13.6",
+ "polars>=1.31.0",
+ "prek>=0.3.5",
+ "pyfonts>=1.0.0",
+ "pypalettes>=0.1.4",
+ "pytest>=8.3.5",
+ "ruff>=0.11.13",
+ "seaborn>=0.13.2",
+ "ty>=0.0.1a16",
+ "zensical>=0.0.27",
]
[build-system]
requires = [
- "setuptools",
- "setuptools-scm",
+ "setuptools",
+ "setuptools-scm",
]
build-backend = "setuptools.build_meta"
+[tool.ruff]
+extend-exclude = ["docs/index.qmd"]
+
[tool.setuptools]
packages = ["plotjs"]
-[tool.uv.sources]
-plotjs = { workspace = true }
-
-[dependency-groups]
-dev = [
- "pytest>=8.3.5",
- "ruff>=0.11.13",
- "mkdocs-material>=9.6.9",
- "mkdocstrings-python>=1.16.5",
- "coverage>=7.9.1",
- "genbadge[coverage]>=1.1.2",
- "pandas>=2.3.1",
- "pypalettes>=0.1.4",
- "pyfonts>=1.0.0",
- "morethemes>=0.4.0",
- "plotnine>=0.13.6",
- "ty>=0.0.1a16",
- "polars>=1.31.0",
- "nbformat>=5.10.4",
- "nbclient>=0.10.2",
- "jupyter>=1.1.1",
- "ipywidgets>=8.1.7",
- "highlight-text>=0.2",
- "drawarrow>=0.1.0",
- "playwright>=1.40.0",
- "prek>=0.3.5",
- "seaborn>=0.13.2",
- "zensical>=0.0.27",
-]
-
-[project.urls]
-Homepage = "https://y-sunflower.github.io/plotjs/"
-Issues = "https://github.com/y-sunflower/plotjs/issues"
-Documentation = "https://y-sunflower.github.io/plotjs/"
-Repository = "https://github.com/y-sunflower/plotjs"
-
[tool.ty.src]
include = ["plotjs"]
exclude = ["tests", "sandbox"]
-[tool.ruff]
-extend-exclude = ["docs/index.qmd"]
+[tool.uv.sources]
+plotjs = { workspace = true }
From 9c10be1fb925d66b6a0ae13554988d0543f7adc1 Mon Sep 17 00:00:00 2001
From: Barbier--Darnal Joseph
Date: Thu, 26 Mar 2026 15:03:57 +0100
Subject: [PATCH 02/16] add tests for area chart with a legend
---
tests/test-python/test_plotjs.py | 36 ++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/tests/test-python/test_plotjs.py b/tests/test-python/test_plotjs.py
index 5ef595a..429ea4f 100644
--- a/tests/test-python/test_plotjs.py
+++ b/tests/test-python/test_plotjs.py
@@ -1,4 +1,5 @@
from plotjs import PlotJS, data
+import numpy as np
import matplotlib.pyplot as plt
import os
import tempfile
@@ -407,3 +408,38 @@ def test_init_restores_svg_rcparams_if_savefig_fails():
assert plt.rcParams["svg.id"] == old_svg_id
plt.close(fig)
+
+
+def test_fill_between_with_legend():
+ x = np.arange(10)
+ y1 = np.array([2, 3, 4, 3, 5, 6, 5, 7, 6, 8])
+ y2 = np.array([1, 2, 2, 3, 3, 4, 4, 5, 5, 6])
+
+ fig, ax = plt.subplots()
+
+ ax.fill_between(x, y1, label="Series A")
+ ax.fill_between(x, y2, label="Series B")
+
+ ax.spines[["top", "right"]].set_visible(False)
+ ax.legend()
+
+ plotjs = PlotJS(fig, _debug=True).add_tooltip(
+ labels=["Series A", "Series B"],
+ groups=["Series A", "Series B"],
+ on="area",
+ )
+
+ assert len(plotjs._axes) == 1
+ assert plotjs._tooltip_labels == ["Series A", "Series B", "Series A", "Series B"]
+ assert plotjs._tooltip_groups == ["Series A", "Series B", "Series A", "Series B"]
+
+ assert len(plotjs._legend_handles) == 2
+ assert plotjs._legend_handles_labels == ["Series A", "Series B"]
+ assert plotjs._axes_tooltip == {
+ "axes_1": {
+ "tooltip_labels": ["Series A", "Series B", "Series A", "Series B"],
+ "tooltip_groups": ["Series A", "Series B", "Series A", "Series B"],
+ "hover_nearest": "false",
+ "on": ["area"],
+ }
+ }
From 056bfdb247ca1a71ec7ba74d1c95eb74a9a61af1 Mon Sep 17 00:00:00 2001
From: Barbier--Darnal Joseph
Date: Thu, 26 Mar 2026 15:10:58 +0100
Subject: [PATCH 03/16] fix string quote in d3js example
---
plotjs/plotjs.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plotjs/plotjs.py b/plotjs/plotjs.py
index 0d105dc..a0787c6 100644
--- a/plotjs/plotjs.py
+++ b/plotjs/plotjs.py
@@ -341,7 +341,7 @@ def add_d3js(self, version: int = 7) -> "PlotJS":
(
PlotJS()
.add_d3js()
- .add_javascript("d3.selectAll(".point").on("click", () => alert("I wish cookies were 0 calories..."));"
+ .add_javascript("d3.selectAll('.point').on('click', () => alert('I wish cookies were 0 calories...'));"
)
)
```
From 571a7613447ca35752434d041af48a494e3ebf07 Mon Sep 17 00:00:00 2001
From: Barbier--Darnal Joseph
Date: Thu, 26 Mar 2026 15:31:27 +0100
Subject: [PATCH 04/16] add tests and example for histogram
---
docs/iframes/bug.html | 2 +-
docs/iframes/quickstart.html | 2 +-
docs/iframes/quickstart10.html | 2 +-
docs/iframes/quickstart11.html | 2 +-
docs/iframes/quickstart12.html | 1031 ++++++++++++++++++++
docs/iframes/quickstart2.html | 2 +-
docs/iframes/quickstart3.html | 2 +-
docs/iframes/quickstart4.html | 2 +-
docs/iframes/quickstart5.html | 1548 +++++++++++++++---------------
docs/iframes/quickstart6.html | 2 +-
docs/iframes/quickstart7.html | 2 +-
docs/iframes/quickstart8.html | 2 +-
docs/iframes/quickstart9.html | 2 +-
docs/index.md | 27 +-
docs/index.qmd | 27 +-
tests/test-python/test_plotjs.py | 50 +
16 files changed, 1935 insertions(+), 770 deletions(-)
create mode 100644 docs/iframes/quickstart12.html
diff --git a/docs/iframes/bug.html b/docs/iframes/bug.html
index 65014ce..128ad24 100644
--- a/docs/iframes/bug.html
+++ b/docs/iframes/bug.html
@@ -61,7 +61,7 @@
- 2026-03-15T20:57:15.050060
+ 2026-03-26T15:26:20.757980
image/svg+xml
diff --git a/docs/iframes/quickstart.html b/docs/iframes/quickstart.html
index 8ae12fe..c8ff6ad 100644
--- a/docs/iframes/quickstart.html
+++ b/docs/iframes/quickstart.html
@@ -61,7 +61,7 @@
- 2026-03-15T20:57:13.886877
+ 2026-03-26T15:26:19.436768
image/svg+xml
diff --git a/docs/iframes/quickstart10.html b/docs/iframes/quickstart10.html
index 7860a77..2e7a90c 100644
--- a/docs/iframes/quickstart10.html
+++ b/docs/iframes/quickstart10.html
@@ -61,7 +61,7 @@
- 2026-03-15T20:57:14.708540
+ 2026-03-26T15:26:20.396567
image/svg+xml
diff --git a/docs/iframes/quickstart11.html b/docs/iframes/quickstart11.html
index 4600c67..26d2e26 100644
--- a/docs/iframes/quickstart11.html
+++ b/docs/iframes/quickstart11.html
@@ -61,7 +61,7 @@
- 2026-03-15T20:57:14.836735
+ 2026-03-26T15:26:20.533903
image/svg+xml
diff --git a/docs/iframes/quickstart12.html b/docs/iframes/quickstart12.html
new file mode 100644
index 0000000..4a3f531
--- /dev/null
+++ b/docs/iframes/quickstart12.html
@@ -0,0 +1,1031 @@
+
+
+
+
+ Made with plotjs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2026-03-26T15:26:19.772224
+ image/svg+xml
+
+
+ Matplotlib v3.10.3, https://matplotlib.org/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/iframes/quickstart2.html b/docs/iframes/quickstart2.html
index 166ffd4..23e523e 100644
--- a/docs/iframes/quickstart2.html
+++ b/docs/iframes/quickstart2.html
@@ -61,7 +61,7 @@
- 2026-03-15T20:57:13.909929
+ 2026-03-26T15:26:19.461958
image/svg+xml
diff --git a/docs/iframes/quickstart3.html b/docs/iframes/quickstart3.html
index efd64bf..8dac4ca 100644
--- a/docs/iframes/quickstart3.html
+++ b/docs/iframes/quickstart3.html
@@ -61,7 +61,7 @@
- 2026-03-15T20:57:13.929348
+ 2026-03-26T15:26:19.482519
image/svg+xml
diff --git a/docs/iframes/quickstart4.html b/docs/iframes/quickstart4.html
index ac933f9..606980a 100644
--- a/docs/iframes/quickstart4.html
+++ b/docs/iframes/quickstart4.html
@@ -61,7 +61,7 @@
- 2026-03-15T20:57:13.954602
+ 2026-03-26T15:26:19.509885
image/svg+xml
diff --git a/docs/iframes/quickstart5.html b/docs/iframes/quickstart5.html
index deb522a..5063974 100644
--- a/docs/iframes/quickstart5.html
+++ b/docs/iframes/quickstart5.html
@@ -61,7 +61,7 @@
- 2026-03-15T20:57:13.979453
+ 2026-03-26T15:26:19.536201
image/svg+xml
@@ -343,12 +343,12 @@
" style="stroke: #000000; stroke-width: 0.8"/>
-
+
-
-
+
+
-
+
@@ -367,41 +367,40 @@
-
+
-
-
-
-
-
+
+
+
-
+
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
@@ -409,759 +408,794 @@
-
+
-
-
-
+
+
+
-
-
-
diff --git a/docs/iframes/quickstart6.html b/docs/iframes/quickstart6.html
index bd9c5c0..050a303 100644
--- a/docs/iframes/quickstart6.html
+++ b/docs/iframes/quickstart6.html
@@ -61,7 +61,7 @@
- 2026-03-15T20:57:14.119874
+ 2026-03-26T15:26:19.685815
image/svg+xml
diff --git a/docs/iframes/quickstart7.html b/docs/iframes/quickstart7.html
index a645be7..c35589d 100644
--- a/docs/iframes/quickstart7.html
+++ b/docs/iframes/quickstart7.html
@@ -61,7 +61,7 @@
- 2026-03-15T20:57:14.198616
+ 2026-03-26T15:26:19.857726
image/svg+xml
diff --git a/docs/iframes/quickstart8.html b/docs/iframes/quickstart8.html
index 535bce9..6265461 100644
--- a/docs/iframes/quickstart8.html
+++ b/docs/iframes/quickstart8.html
@@ -61,7 +61,7 @@
- 2026-03-15T20:57:14.344118
+ 2026-03-26T15:26:20.014295
image/svg+xml
diff --git a/docs/iframes/quickstart9.html b/docs/iframes/quickstart9.html
index 1d85252..7b52b8b 100644
--- a/docs/iframes/quickstart9.html
+++ b/docs/iframes/quickstart9.html
@@ -61,7 +61,7 @@
- 2026-03-15T20:57:14.503003
+ 2026-03-26T15:26:20.183536
image/svg+xml
diff --git a/docs/index.md b/docs/index.md
index 27e7a7f..da8a8fa 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -13,7 +13,7 @@ interactive charts with minimum user inputs. You can:
???+ warning
- `plotjs` is in very early stage: expect regular breaking changes.
+ Consider that `plotjs` is still unstable.
## Get started
@@ -227,6 +227,31 @@ ax.barh(
+### Histogram
+
+``` python
+import matplotlib.pyplot as plt
+import numpy as np
+
+from plotjs import PlotJS
+
+x = np.random.normal(loc=0.0, scale=1.0, size=100)
+
+fig, ax = plt.subplots()
+counts, bins, _ = ax.hist(x, color="#2a9d8f")
+
+labels = [
+ f"Lower bound: {lo:.2f} Upper bound:{hi:.2f} n: {int(n)}"
+ for lo, hi, n in zip(bins[:-1], bins[1:], counts)
+]
+
+PlotJS(fig=fig).add_tooltip(labels=labels).save("iframes/quickstart12.html")
+```
+
+
+
### Connect legend and plot elements:
- Scatter plot
diff --git a/docs/index.qmd b/docs/index.qmd
index 8ac3f99..6bb69aa 100644
--- a/docs/index.qmd
+++ b/docs/index.qmd
@@ -9,8 +9,10 @@ execute:
```{python}
# | include: false
import matplotlib.pyplot as plt
+import numpy as np
plt.rcParams["figure.dpi"] = 300
+np.random.seed(0)
```
@@ -25,7 +27,7 @@ plt.rcParams["figure.dpi"] = 300
???+ warning
- `plotjs` is in very early stage: expect regular breaking changes.
+ Consider that `plotjs` is still unstable.
## Get started
@@ -213,6 +215,29 @@ ax.barh(
+### Histogram
+
+```{python}
+import matplotlib.pyplot as plt
+import numpy as np
+
+from plotjs import PlotJS
+
+x = np.random.normal(loc=0.0, scale=1.0, size=100)
+
+fig, ax = plt.subplots()
+counts, bins, _ = ax.hist(x, color="#2a9d8f")
+
+labels = [
+ f"Lower bound: {lo:.2f} Upper bound:{hi:.2f} n: {int(n)}"
+ for lo, hi, n in zip(bins[:-1], bins[1:], counts)
+]
+
+PlotJS(fig=fig).add_tooltip(labels=labels).save("iframes/quickstart12.html")
+```
+
+
+
### Connect legend and plot elements:
- Scatter plot
diff --git a/tests/test-python/test_plotjs.py b/tests/test-python/test_plotjs.py
index 429ea4f..80a88cf 100644
--- a/tests/test-python/test_plotjs.py
+++ b/tests/test-python/test_plotjs.py
@@ -443,3 +443,53 @@ def test_fill_between_with_legend():
"on": ["area"],
}
}
+
+
+def test_histogram_with_custom_labels():
+ np.random.seed(0)
+ x = np.random.normal(loc=0.0, scale=1.0, size=100)
+
+ fig, ax = plt.subplots()
+ counts, bins, _ = ax.hist(x, color="#2a9d8f")
+
+ labels = [
+ f"Lower bound: {lo:.2f} Upper bound:{hi:.2f} n: {int(n)}"
+ for lo, hi, n in zip(bins[:-1], bins[1:], counts)
+ ]
+
+ plotjs = PlotJS(fig=fig).add_tooltip(labels=labels)
+
+ assert len(plotjs._axes) == 1
+ assert plotjs._tooltip_labels == [
+ "Lower bound: -2.55 Upper bound:-2.07 n: 1",
+ "Lower bound: -2.07 Upper bound:-1.59 n: 5",
+ "Lower bound: -1.59 Upper bound:-1.11 n: 7",
+ "Lower bound: -1.11 Upper bound:-0.62 n: 13",
+ "Lower bound: -0.62 Upper bound:-0.14 n: 17",
+ "Lower bound: -0.14 Upper bound:0.34 n: 18",
+ "Lower bound: 0.34 Upper bound:0.82 n: 16",
+ "Lower bound: 0.82 Upper bound:1.31 n: 11",
+ "Lower bound: 1.31 Upper bound:1.79 n: 7",
+ "Lower bound: 1.79 Upper bound:2.27 n: 5",
+ ]
+ assert plotjs._tooltip_groups == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+
+ assert plotjs._axes_tooltip == {
+ "axes_1": {
+ "tooltip_labels": [
+ "Lower bound: -2.55 Upper bound:-2.07 n: 1",
+ "Lower bound: -2.07 Upper bound:-1.59 n: 5",
+ "Lower bound: -1.59 Upper bound:-1.11 n: 7",
+ "Lower bound: -1.11 Upper bound:-0.62 n: 13",
+ "Lower bound: -0.62 Upper bound:-0.14 n: 17",
+ "Lower bound: -0.14 Upper bound:0.34 n: 18",
+ "Lower bound: 0.34 Upper bound:0.82 n: 16",
+ "Lower bound: 0.82 Upper bound:1.31 n: 11",
+ "Lower bound: 1.31 Upper bound:1.79 n: 7",
+ "Lower bound: 1.79 Upper bound:2.27 n: 5",
+ ],
+ "tooltip_groups": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
+ "hover_nearest": "false",
+ "on": None,
+ }
+ }
From 7c9ac76f917f19cc17f69db1f600071171f5428f Mon Sep 17 00:00:00 2001
From: Barbier--Darnal Joseph
Date: Thu, 26 Mar 2026 16:22:02 +0100
Subject: [PATCH 05/16] init pie element parser
---
docs/index.qmd | 2 --
plotjs/plotjs.py | 7 +++---
plotjs/static/plotparser.js | 17 +++++++++++++
plotjs/static/template.html | 25 +++++++++++++++++--
tests/test-python/test_plotjs.py | 43 ++++++++++++++++++++++++++++++++
5 files changed, 87 insertions(+), 7 deletions(-)
diff --git a/docs/index.qmd b/docs/index.qmd
index 6bb69aa..2f49726 100644
--- a/docs/index.qmd
+++ b/docs/index.qmd
@@ -276,8 +276,6 @@ import matplotlib.pyplot as plt
import numpy as np
from plotjs import PlotJS
-np.random.seed(0)
-
length = 500
walk1 = np.cumsum(np.random.choice([-1, 1], size=length))
walk2 = np.cumsum(np.random.choice([-1, 1], size=length))
diff --git a/plotjs/plotjs.py b/plotjs/plotjs.py
index a0787c6..efa2eb1 100644
--- a/plotjs/plotjs.py
+++ b/plotjs/plotjs.py
@@ -118,8 +118,8 @@ def add_tooltip(
hover_nearest: When `True`, hover the nearest plot element.
on: Which plot elements to apply interactivity to. Can be a
single element type or a list. Valid values are "point",
- "line", "bar", "area" (plurals like "points" also accepted).
- If `None` (default), applies to all element types.
+ "line", "bar", "area", "pie (plurals like "points" also
+ accepted). If `None` (default), applies to all element types.
ax: A matplotlib Axes. If `None` (default), uses first Axes.
Returns:
@@ -164,12 +164,13 @@ def add_tooltip(
self._tooltip_y_shift = tooltip_y_shift
# Normalize and validate the `on` parameter
- valid_elements = {"point", "line", "bar", "area"}
+ valid_elements = {"point", "line", "bar", "area", "pie"}
plural_to_singular = {
"points": "point",
"lines": "line",
"bars": "bar",
"areas": "area",
+ "pies": "pie",
}
if on is None:
diff --git a/plotjs/static/plotparser.js b/plotjs/static/plotparser.js
index 0502872..c259b5d 100755
--- a/plotjs/static/plotparser.js
+++ b/plotjs/static/plotparser.js
@@ -229,6 +229,23 @@ export default class PlotSVGParser {
return points;
}
+ /**
+ * Find pie elements (`patch` paths) inside a given axes.
+ *
+ * @param {Selection} svg - Selection of the SVG element.
+ * @param {string} axes_class - ID of the axes group.
+ * @returns {Selection} Selection of pie elements.
+ */
+ findPies(svg, axes_class) {
+ // select all of Patch elements within the specific axes
+ const pies = svg.selectAll(`g#${axes_class} g[id^="patch_"] path`);
+
+ pies.attr("class", "pie plot-element");
+
+ console.log(`Found ${pies.size()} "pie" element`);
+ return pies;
+ }
+
/**
* Find line elements (`line2d` paths) inside a given axes,
* excluding axis grid lines.
diff --git a/plotjs/static/template.html b/plotjs/static/template.html
index ec071c0..4313eb3 100644
--- a/plotjs/static/template.html
+++ b/plotjs/static/template.html
@@ -77,6 +77,9 @@
const lines = shouldProcess("line")
? plotParser.findLines(plotParser.svg, axes_class)
: new Selection([]);
+ const pies = shouldProcess("pie")
+ ? plotParser.findPies(plotParser.svg, axes_class)
+ : new Selection([]);
const bars = shouldProcess("bar")
? plotParser.findBars(plotParser.svg, axes_class)
: new Selection([]);
@@ -92,9 +95,13 @@
: new Selection([]);
const totalElements =
- lines.size() + bars.size() + points.size() + areas.size();
+ lines.size() +
+ bars.size() +
+ points.size() +
+ areas.size() +
+ pies.size();
console.log(
- `PlotJS: Total elements: ${totalElements} (${lines.size()} lines, ${bars.size()} bars, ${points.size()} points, ${areas.size()} areas)`,
+ `PlotJS: Total elements: ${totalElements} (${lines.size()} lines, ${bars.size()} bars, ${points.size()} points, ${areas.size()} areas, ${pies.size()} pies)`,
);
if (points.size() > 0) {
@@ -125,6 +132,20 @@
);
}
+ if (pies.size() > 0) {
+ plotParser.setHoverEffect(
+ pies,
+ axes_class,
+ tooltip_labels,
+ tooltip_groups,
+ show_tooltip,
+ hover_nearest,
+ );
+ console.log(
+ `PlotJS: Hover effects attached to ${pies.size()} pies`,
+ );
+ }
+
if (bars.size() > 0) {
plotParser.setHoverEffect(
bars,
diff --git a/tests/test-python/test_plotjs.py b/tests/test-python/test_plotjs.py
index 80a88cf..c798fd9 100644
--- a/tests/test-python/test_plotjs.py
+++ b/tests/test-python/test_plotjs.py
@@ -493,3 +493,46 @@ def test_histogram_with_custom_labels():
"on": None,
}
}
+
+
+def test_pie_chart():
+ labels = ["A", "B", "C", "D"]
+ sizes = [15, 10, 25, 10]
+
+ fig, ax = plt.subplots()
+ ax.pie(sizes, labels=labels, autopct="%1.1f%%")
+
+ tooltip = [f"{lab} (n = {size})" for lab, size in zip(labels, sizes)]
+
+ plotjs = PlotJS(fig, _debug=True).add_tooltip(labels=tooltip)
+
+ assert len(plotjs._axes) == 1
+ assert plotjs._tooltip_labels == [
+ "A (n = 15)",
+ "B (n = 10)",
+ "C (n = 25)",
+ "D (n = 10)",
+ "A",
+ "B",
+ "C",
+ "D",
+ ]
+ assert plotjs._tooltip_groups == [0, 1, 2, 3, 4, 5, 6, 7]
+
+ assert plotjs._axes_tooltip == {
+ "axes_1": {
+ "tooltip_labels": [
+ "A (n = 15)",
+ "B (n = 10)",
+ "C (n = 25)",
+ "D (n = 10)",
+ "A",
+ "B",
+ "C",
+ "D",
+ ],
+ "tooltip_groups": [0, 1, 2, 3, 4, 5, 6, 7],
+ "hover_nearest": "false",
+ "on": None,
+ }
+ }
From 089f47ac04294e97d36b18637a3e9d9ecce9fd03 Mon Sep 17 00:00:00 2001
From: Barbier--Darnal Joseph
Date: Thu, 26 Mar 2026 16:32:21 +0100
Subject: [PATCH 06/16] update pie elements parser with precise filter
---
plotjs/static/plotparser.js | 20 +++++-
tests/test-javascript/ParserSelectors.test.js | 62 +++++++++++++++++++
2 files changed, 80 insertions(+), 2 deletions(-)
diff --git a/plotjs/static/plotparser.js b/plotjs/static/plotparser.js
index c259b5d..aeb4d8c 100755
--- a/plotjs/static/plotparser.js
+++ b/plotjs/static/plotparser.js
@@ -237,8 +237,24 @@ export default class PlotSVGParser {
* @returns {Selection} Selection of pie elements.
*/
findPies(svg, axes_class) {
- // select all of Patch elements within the specific axes
- const pies = svg.selectAll(`g#${axes_class} g[id^="patch_"] path`);
+ // Matplotlib also uses patch_* groups for axes backgrounds and spines.
+ // Pie wedges are the patch paths with a non-empty fill and curve commands.
+ const parser = this;
+ const pies = svg
+ .selectAll(`g#${axes_class} g[id^="patch_"] path`)
+ .filter(function () {
+ const element = this;
+ const clipPath = element.getAttribute("clip-path");
+ const normalizedFill = parser.getFillValue(element);
+ const pathData = element.getAttribute("d") ?? "";
+
+ return (
+ !clipPath &&
+ normalizedFill !== "" &&
+ normalizedFill !== "none" &&
+ /[CQAST]/.test(pathData)
+ );
+ });
pies.attr("class", "pie plot-element");
diff --git a/tests/test-javascript/ParserSelectors.test.js b/tests/test-javascript/ParserSelectors.test.js
index 85e2050..6436a45 100644
--- a/tests/test-javascript/ParserSelectors.test.js
+++ b/tests/test-javascript/ParserSelectors.test.js
@@ -201,6 +201,68 @@ describe("findPoints", () => {
});
});
+describe("findPies", () => {
+ test("should find pie slice patch paths", () => {
+ const dom = new JSDOM(`
+
+
+
+
+
+
+
+
+ `);
+
+ const svg = dom.window.document.querySelector("svg");
+ const parser = new PlotSVGParser(svg, null, 0, 0);
+ const pies = parser.findPies(parser.svg, "axes_1");
+
+ expect(pies.size()).toBe(2);
+ pies.each(function () {
+ expect(this.getAttribute("class")).toBe("pie plot-element");
+ });
+ });
+
+ test("should ignore scatter plot patch paths", () => {
+ const dom = new JSDOM(`
+
+
+
+
+
+
+
+
+
+
+
+ `);
+
+ const svg = dom.window.document.querySelector("svg");
+ const parser = new PlotSVGParser(svg, null, 0, 0);
+ const pies = parser.findPies(parser.svg, "axes_1");
+
+ expect(pies.size()).toBe(0);
+ expect(pies.empty()).toBe(true);
+ });
+});
+
describe("findLines", () => {
test("should find line2d path elements", () => {
const dom = new JSDOM(`
From b2972fce681b0d7a6e509620220a308dec161a3b Mon Sep 17 00:00:00 2001
From: Barbier--Darnal Joseph
Date: Thu, 26 Mar 2026 16:34:34 +0100
Subject: [PATCH 07/16] document new pie elements parser
---
docs/developers/svg-parser-reference.md | 101 +++++++++++++++---------
1 file changed, 64 insertions(+), 37 deletions(-)
diff --git a/docs/developers/svg-parser-reference.md b/docs/developers/svg-parser-reference.md
index 9ebc6fe..2b51d4a 100644
--- a/docs/developers/svg-parser-reference.md
+++ b/docs/developers/svg-parser-reference.md
@@ -27,6 +27,9 @@ Provides basic DOM manipulation methods for working with SVG elements.
Handles both <use> and <path> fallback cases,
and assigns data-group attributes based on tooltip groups.
+findPies(svg, axes_class) ⇒ Selection
+Find pie elements (patch paths) inside a given axes.
+
findLines(svg, axes_class) ⇒ Selection
Find line elements (line2d paths) inside a given axes,
excluding axis grid lines.
@@ -50,6 +53,7 @@ Can highlight nearest element (if enabled) or hovered element directly.
## Selection
+
Lightweight Selection wrapper that mimics d3-selection's chainable API.
Provides basic DOM manipulation methods for working with SVG elements.
@@ -57,56 +61,61 @@ Provides basic DOM manipulation methods for working with SVG elements.
## select(selector) ⇒ [Selection](#Selection)
+
Create a Selection from a DOM element or selector string.
**Kind**: global function
**Returns**: [Selection](#Selection) - New Selection instance
-| Param | Type | Description |
-| --- | --- | --- |
+| Param | Type | Description |
+| -------- | ------------------------------------------- | ---------------------------------- |
| selector | string \| Element | CSS selector string or DOM element |
## getPointerPosition(event, svgElement) ⇒ Array.<number>
+
Get mouse position relative to an SVG element.
**Kind**: global function
**Returns**: Array.<number> - [x, y] coordinates relative to the SVG
-| Param | Type | Description |
-| --- | --- | --- |
-| event | MouseEvent | The mouse event |
+| Param | Type | Description |
+| ---------- | ------------------------------------------------------------ | ---------------------------- |
+| event | MouseEvent | The mouse event |
| svgElement | Element \| [Selection](#Selection) | The SVG element or Selection |
## getFillValue(element) ⇒ string
+
Extract the raw fill value from an SVG element.
**Kind**: global function
**Returns**: string - Normalized fill value, or empty string if absent.
-| Param | Type | Description |
-| --- | --- | --- |
+| Param | Type | Description |
+| ------- | -------------------- | ----------------------- |
| element | Element | SVG element to inspect. |
## findBars(svg, axes_class) ⇒ [Selection](#Selection)
+
Find bar elements (`patch` groups with clipping) inside a given axes.
**Kind**: global function
**Returns**: [Selection](#Selection) - Selection of bar elements.
-| Param | Type | Description |
-| --- | --- | --- |
-| svg | [Selection](#Selection) | Selection of the SVG element. |
-| axes_class | string | ID of the axes group (e.g. "axes_1"). |
+| Param | Type | Description |
+| ---------- | ------------------------------------ | ------------------------------------- |
+| svg | [Selection](#Selection) | Selection of the SVG element. |
+| axes_class | string | ID of the axes group (e.g. "axes_1"). |
## findPoints(svg, axes_class, tooltip_groups) ⇒ [Selection](#Selection)
+
Find scatter plot points inside a given axes.
Handles both `` and `` fallback cases,
and assigns `data-group` attributes based on tooltip groups.
@@ -114,29 +123,45 @@ and assigns `data-group` attributes based on tooltip groups.
**Kind**: global function
**Returns**: [Selection](#Selection) - Selection of point elements.
-| Param | Type | Description |
-| --- | --- | --- |
-| svg | [Selection](#Selection) | Selection of the SVG element. |
-| axes_class | string | ID of the axes group (e.g. "axes_1"). |
-| tooltip_groups | Array.<string> | Group identifiers for tooltips, parallel to points. |
+| Param | Type | Description |
+| -------------- | ------------------------------------ | --------------------------------------------------- |
+| svg | [Selection](#Selection) | Selection of the SVG element. |
+| axes_class | string | ID of the axes group (e.g. "axes_1"). |
+| tooltip_groups | Array.<string> | Group identifiers for tooltips, parallel to points. |
+
+
+
+## findPies(svg, axes_class) ⇒ [Selection](#Selection)
+
+Find pie elements (`patch` paths) inside a given axes.
+
+**Kind**: global function
+**Returns**: [Selection](#Selection) - Selection of pie elements.
+
+| Param | Type | Description |
+| ---------- | ------------------------------------ | ----------------------------- |
+| svg | [Selection](#Selection) | Selection of the SVG element. |
+| axes_class | string | ID of the axes group. |
## findLines(svg, axes_class) ⇒ [Selection](#Selection)
+
Find line elements (`line2d` paths) inside a given axes,
excluding axis grid lines.
**Kind**: global function
**Returns**: [Selection](#Selection) - Selection of line elements.
-| Param | Type | Description |
-| --- | --- | --- |
-| svg | [Selection](#Selection) | Selection of the SVG element. |
-| axes_class | string | ID of the axes group. |
+| Param | Type | Description |
+| ---------- | ------------------------------------ | ----------------------------- |
+| svg | [Selection](#Selection) | Selection of the SVG element. |
+| axes_class | string | ID of the axes group. |
## findAreas(svg, axes_class) ⇒ [Selection](#Selection)
+
Find filled area elements (`FillBetweenPolyCollection` paths) inside a given axes.
Also includes legend swatches whose fill matches the plotted areas so legend hover
can target the same series as the chart area.
@@ -144,14 +169,15 @@ can target the same series as the chart area.
**Kind**: global function
**Returns**: [Selection](#Selection) - Selection of area elements.
-| Param | Type | Description |
-| --- | --- | --- |
-| svg | [Selection](#Selection) | Selection of the SVG element. |
-| axes_class | string | ID of the axes group. |
+| Param | Type | Description |
+| ---------- | ------------------------------------ | ----------------------------- |
+| svg | [Selection](#Selection) | Selection of the SVG element. |
+| axes_class | string | ID of the axes group. |
## nearestElementFromMouse(mouseX, mouseY, elements) ⇒ Element \| null
+
Compute the nearest element to the mouse cursor from a set of elements.
Uses bounding box centers for distance.
This function is used when the `hover_nearest` argument is true.
@@ -159,25 +185,26 @@ This function is used when the `hover_nearest` argument is true.
**Kind**: global function
**Returns**: Element \| null - The nearest DOM element or `null`.
-| Param | Type | Description |
-| --- | --- | --- |
-| mouseX | number | X coordinate of the mouse relative to SVG. |
-| mouseY | number | Y coordinate of the mouse relative to SVG. |
-| elements | [Selection](#Selection) | Selection of candidate elements. |
+| Param | Type | Description |
+| -------- | ------------------------------------ | ------------------------------------------ |
+| mouseX | number | X coordinate of the mouse relative to SVG. |
+| mouseY | number | Y coordinate of the mouse relative to SVG. |
+| elements | [Selection](#Selection) | Selection of candidate elements. |
## setHoverEffect(plot_element, axes_class, tooltip_labels, tooltip_groups, show_tooltip, hover_nearest)
+
Attach hover interaction and tooltip display to plot elements.
Can highlight nearest element (if enabled) or hovered element directly.
**Kind**: global function
-| Param | Type | Description |
-| --- | --- | --- |
-| plot_element | [Selection](#Selection) | Selection of plot elements (points, lines, etc.). |
-| axes_class | string | ID of the axes group. |
-| tooltip_labels | Array.<string> | Tooltip labels for each element. |
-| tooltip_groups | Array.<string> | Group identifiers for each element. |
-| show_tooltip | "block" \| "none" | Whether to display tooltips. |
-| hover_nearest | boolean | If true, highlight nearest element instead of hovered one. |
+| Param | Type | Description |
+| -------------- | --------------------------------------------------------------- | ---------------------------------------------------------- |
+| plot_element | [Selection](#Selection) | Selection of plot elements (points, lines, etc.). |
+| axes_class | string | ID of the axes group. |
+| tooltip_labels | Array.<string> | Tooltip labels for each element. |
+| tooltip_groups | Array.<string> | Group identifiers for each element. |
+| show_tooltip | "block" \| "none" | Whether to display tooltips. |
+| hover_nearest | boolean | If true, highlight nearest element instead of hovered one. |
From 89153e83749dbed902593808580071984ce6abd3 Mon Sep 17 00:00:00 2001
From: Barbier--Darnal Joseph
Date: Thu, 26 Mar 2026 16:35:30 +0100
Subject: [PATCH 08/16] document everything
---
docs/developers/svg-parser-reference.md | 93 +-
docs/gallery/index.md | 24 +-
docs/iframes/CSS-2.html | 60 +-
docs/iframes/CSS.html | 60 +-
docs/iframes/area-natural-disasters.html | 60 +-
docs/iframes/bug.html | 352 +-
docs/iframes/javascript.html | 60 +-
docs/iframes/javascript2.html | 60 +-
docs/iframes/quickstart.html | 60 +-
docs/iframes/quickstart10.html | 60 +-
docs/iframes/quickstart11.html | 60 +-
docs/iframes/quickstart12.html | 60 +-
docs/iframes/quickstart2.html | 60 +-
docs/iframes/quickstart3.html | 60 +-
docs/iframes/quickstart4.html | 60 +-
docs/iframes/quickstart5.html | 60 +-
docs/iframes/quickstart6.html | 60 +-
docs/iframes/quickstart7.html | 60 +-
docs/iframes/quickstart8.html | 1677 +-
docs/iframes/quickstart9.html | 60 +-
docs/iframes/random-walk-1.html | 74809 ++++++++++-----------
docs/index.md | 2 -
22 files changed, 39450 insertions(+), 38467 deletions(-)
diff --git a/docs/developers/svg-parser-reference.md b/docs/developers/svg-parser-reference.md
index 2b51d4a..6a7277f 100644
--- a/docs/developers/svg-parser-reference.md
+++ b/docs/developers/svg-parser-reference.md
@@ -53,7 +53,6 @@ Can highlight nearest element (if enabled) or hovered element directly.
## Selection
-
Lightweight Selection wrapper that mimics d3-selection's chainable API.
Provides basic DOM manipulation methods for working with SVG elements.
@@ -61,61 +60,56 @@ Provides basic DOM manipulation methods for working with SVG elements.
## select(selector) ⇒ [Selection](#Selection)
-
Create a Selection from a DOM element or selector string.
**Kind**: global function
**Returns**: [Selection](#Selection) - New Selection instance
-| Param | Type | Description |
-| -------- | ------------------------------------------- | ---------------------------------- |
+| Param | Type | Description |
+| --- | --- | --- |
| selector | string \| Element | CSS selector string or DOM element |
## getPointerPosition(event, svgElement) ⇒ Array.<number>
-
Get mouse position relative to an SVG element.
**Kind**: global function
**Returns**: Array.<number> - [x, y] coordinates relative to the SVG
-| Param | Type | Description |
-| ---------- | ------------------------------------------------------------ | ---------------------------- |
-| event | MouseEvent | The mouse event |
+| Param | Type | Description |
+| --- | --- | --- |
+| event | MouseEvent | The mouse event |
| svgElement | Element \| [Selection](#Selection) | The SVG element or Selection |
## getFillValue(element) ⇒ string
-
Extract the raw fill value from an SVG element.
**Kind**: global function
**Returns**: string - Normalized fill value, or empty string if absent.
-| Param | Type | Description |
-| ------- | -------------------- | ----------------------- |
+| Param | Type | Description |
+| --- | --- | --- |
| element | Element | SVG element to inspect. |
## findBars(svg, axes_class) ⇒ [Selection](#Selection)
-
Find bar elements (`patch` groups with clipping) inside a given axes.
**Kind**: global function
**Returns**: [Selection](#Selection) - Selection of bar elements.
-| Param | Type | Description |
-| ---------- | ------------------------------------ | ------------------------------------- |
-| svg | [Selection](#Selection) | Selection of the SVG element. |
-| axes_class | string | ID of the axes group (e.g. "axes_1"). |
+| Param | Type | Description |
+| --- | --- | --- |
+| svg | [Selection](#Selection) | Selection of the SVG element. |
+| axes_class | string | ID of the axes group (e.g. "axes_1"). |
## findPoints(svg, axes_class, tooltip_groups) ⇒ [Selection](#Selection)
-
Find scatter plot points inside a given axes.
Handles both `` and `` fallback cases,
and assigns `data-group` attributes based on tooltip groups.
@@ -123,45 +117,42 @@ and assigns `data-group` attributes based on tooltip groups.
**Kind**: global function
**Returns**: [Selection](#Selection) - Selection of point elements.
-| Param | Type | Description |
-| -------------- | ------------------------------------ | --------------------------------------------------- |
-| svg | [Selection](#Selection) | Selection of the SVG element. |
-| axes_class | string | ID of the axes group (e.g. "axes_1"). |
-| tooltip_groups | Array.<string> | Group identifiers for tooltips, parallel to points. |
+| Param | Type | Description |
+| --- | --- | --- |
+| svg | [Selection](#Selection) | Selection of the SVG element. |
+| axes_class | string | ID of the axes group (e.g. "axes_1"). |
+| tooltip_groups | Array.<string> | Group identifiers for tooltips, parallel to points. |
## findPies(svg, axes_class) ⇒ [Selection](#Selection)
-
Find pie elements (`patch` paths) inside a given axes.
**Kind**: global function
**Returns**: [Selection](#Selection) - Selection of pie elements.
-| Param | Type | Description |
-| ---------- | ------------------------------------ | ----------------------------- |
-| svg | [Selection](#Selection) | Selection of the SVG element. |
-| axes_class | string | ID of the axes group. |
+| Param | Type | Description |
+| --- | --- | --- |
+| svg | [Selection](#Selection) | Selection of the SVG element. |
+| axes_class | string | ID of the axes group. |
## findLines(svg, axes_class) ⇒ [Selection](#Selection)
-
Find line elements (`line2d` paths) inside a given axes,
excluding axis grid lines.
**Kind**: global function
**Returns**: [Selection](#Selection) - Selection of line elements.
-| Param | Type | Description |
-| ---------- | ------------------------------------ | ----------------------------- |
-| svg | [Selection](#Selection) | Selection of the SVG element. |
-| axes_class | string | ID of the axes group. |
+| Param | Type | Description |
+| --- | --- | --- |
+| svg | [Selection](#Selection) | Selection of the SVG element. |
+| axes_class | string | ID of the axes group. |
## findAreas(svg, axes_class) ⇒ [Selection](#Selection)
-
Find filled area elements (`FillBetweenPolyCollection` paths) inside a given axes.
Also includes legend swatches whose fill matches the plotted areas so legend hover
can target the same series as the chart area.
@@ -169,15 +160,14 @@ can target the same series as the chart area.
**Kind**: global function
**Returns**: [Selection](#Selection) - Selection of area elements.
-| Param | Type | Description |
-| ---------- | ------------------------------------ | ----------------------------- |
-| svg | [Selection](#Selection) | Selection of the SVG element. |
-| axes_class | string | ID of the axes group. |
+| Param | Type | Description |
+| --- | --- | --- |
+| svg | [Selection](#Selection) | Selection of the SVG element. |
+| axes_class | string | ID of the axes group. |
## nearestElementFromMouse(mouseX, mouseY, elements) ⇒ Element \| null
-
Compute the nearest element to the mouse cursor from a set of elements.
Uses bounding box centers for distance.
This function is used when the `hover_nearest` argument is true.
@@ -185,26 +175,25 @@ This function is used when the `hover_nearest` argument is true.
**Kind**: global function
**Returns**: Element \| null - The nearest DOM element or `null`.
-| Param | Type | Description |
-| -------- | ------------------------------------ | ------------------------------------------ |
-| mouseX | number | X coordinate of the mouse relative to SVG. |
-| mouseY | number | Y coordinate of the mouse relative to SVG. |
-| elements | [Selection](#Selection) | Selection of candidate elements. |
+| Param | Type | Description |
+| --- | --- | --- |
+| mouseX | number | X coordinate of the mouse relative to SVG. |
+| mouseY | number | Y coordinate of the mouse relative to SVG. |
+| elements | [Selection](#Selection) | Selection of candidate elements. |
## setHoverEffect(plot_element, axes_class, tooltip_labels, tooltip_groups, show_tooltip, hover_nearest)
-
Attach hover interaction and tooltip display to plot elements.
Can highlight nearest element (if enabled) or hovered element directly.
**Kind**: global function
-| Param | Type | Description |
-| -------------- | --------------------------------------------------------------- | ---------------------------------------------------------- |
-| plot_element | [Selection](#Selection) | Selection of plot elements (points, lines, etc.). |
-| axes_class | string | ID of the axes group. |
-| tooltip_labels | Array.<string> | Tooltip labels for each element. |
-| tooltip_groups | Array.<string> | Group identifiers for each element. |
-| show_tooltip | "block" \| "none" | Whether to display tooltips. |
-| hover_nearest | boolean | If true, highlight nearest element instead of hovered one. |
+| Param | Type | Description |
+| --- | --- | --- |
+| plot_element | [Selection](#Selection) | Selection of plot elements (points, lines, etc.). |
+| axes_class | string | ID of the axes group. |
+| tooltip_labels | Array.<string> | Tooltip labels for each element. |
+| tooltip_groups | Array.<string> | Group identifiers for each element. |
+| show_tooltip | "block" \| "none" | Whether to display tooltips. |
+| hover_nearest | boolean | If true, highlight nearest element instead of hovered one. |
diff --git a/docs/gallery/index.md b/docs/gallery/index.md
index de90d31..fadfdb1 100644
--- a/docs/gallery/index.md
+++ b/docs/gallery/index.md
@@ -54,11 +54,11 @@ This page contains all the `plotjs` examples from this website.