Skip to content

Commit 0323ab0

Browse files
Merge pull request #31 from y-sunflower/develop
support for multiple axes, among others
2 parents 3e156a5 + be6153e commit 0323ab0

29 files changed

Lines changed: 5990 additions & 3679 deletions

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
![](./coverage-badge.svg)
22

3-
# `plotjs`: bridge between static matplotlib and interactive storytelling
3+
# `plotjs`: Turn static matplotlib charts into interactive web visualizations
44

5-
`plotjs` is a proof of concept, inspired by [mpld3](https://github.com/mpld3/mpld3), to make matplotlib plots interactive (for the browser) with minimum user inputs.
5+
<img src="https://github.com/JosephBARBIERDARNAL/static/blob/main/python-libs/plotjs/image.png?raw=true" alt="plotjs logo" align="right" width="150px"/>
66

7-
The goal is also to give users a large customization power.
7+
`plotjs` is a Python package that transform matplotlib plots into interactive charts with minimum user inputs. You can:
8+
9+
- control tooltip labels and grouping
10+
- add CSS
11+
- add JavaScript
12+
- and many more
813

914
> Consider that the project is still **very unstable**.
1015

docs/developers/how-it-works.md

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
1-
Transforming a static plot into something that you can interact with can't be done (unfortunately) just by saying "make this interactive".
1+
Transforming a static plot into something interactive can't be done (unfortunately) just by saying "make this interactive."
22

3-
But that does not mean we have to do mystic things to make it to work, because, **yes** that's perfectly possible without weird hacking stuff.
3+
But that doesn't mean we have to do mystic things to make it work, because **yes**, that's perfectly possible without weird hacking stuff.
44

55
## Overview
66

7-
There are 2 ways to tackle this problem:
7+
There are two ways to tackle this problem:
88

9-
- Take a matplotlib Figure (instance containing all plot elements) and convert it to a more common format such as json. We call this **serialization**. Then, with this json file, we recreate the figure with an interactive tool such as D3.js (that's what [mpld3](https://github.com/mpld3/mpld3) does btw!).
10-
- Use native matplotlib figure output format (especially SVG) and parse this instead (that's what `plotjs` does).
9+
- Take a matplotlib Figure (an instance containing all plot elements) and convert it to a more common format such as JSON. We call this **serialization**. Then, with this JSON file, we recreate the figure with an interactive tool such as D3.js (that's what [mpld3](https://github.com/mpld3/mpld3) does, by the way!).
10+
- Use the native matplotlib figure output format (especially SVG) and parse this instead (that's what `plotjs` does).
1111

12-
The second option is **much simpler** (well, it depends) because we don't have to
12+
The second option is **much simpler** (well, it depends), because we don't have to:
1313

14-
- translate the figure to JSON (which can be painfully complex if you want to handle all egde cases and make it robust).
15-
- recreate the chart: browsers can display SVG perfectly.
14+
- translate the figure to JSON (which can be painfully complex if you want to handle all edge cases and make it robust),
15+
- recreate the chart (browsers can display SVG perfectly).
1616

17-
But it means that we don't have full control over how the plot is structured (from the browser point of view). We need to find a way to parse this SVG.
17+
But it means we don't have full control over how the plot is structured (from the browser's point of view). We need to find a way to parse this SVG.
1818

1919
## Parsing SVG
2020

21-
For the moment, we just take user's matplotlib figure and save it as SVG. This is just:
21+
For the moment, we just take the user's matplotlib figure and save it as SVG. This is just:
2222

2323
```python
2424
fig.savefig("plot.svg")
2525
```
2626

27-
Now let's say the Figure contains a scatterplot we want to add a tooltip: when someone passes his mouse over a point, it displays a label.
27+
Now, let's say the figure contains a scatter plot and we want to add a tooltip: when someone hovers their mouse over a point, it displays a label.
2828

29-
The **core problem to solve** is: "how do I know what elements from the SVG are points"?
29+
The **core problem to solve** is: "how do I know what elements from the SVG are points?"
3030

3131
If we're able to find a solution to this, then we're able to do pretty much **anything we want**.
3232

33-
The thing is that, there's nothing in the SVG output file that tells us "this element is a point from the scatter plot". **Even worse**: we don't even know if that's a scatter plot or something completly unrelated like a choropleth map.
33+
The thing is, there's nothing in the SVG output file that tells us "this element is a point from the scatter plot." **Even worse**, we don't even know if it's a scatter plot or something completely unrelated, like a choropleth map.
3434

3535
For example, here is a polygon of a choropleth map:
3636

@@ -91,47 +91,47 @@ If you pay close attention, you'll see potential patterns in the structure of ce
9191

9292
That's exactly what we'll use to determine what kind of plot elements we have.
9393

94-
> Note: determining the kind of plot elements could have been done (partially) from the Python side, but this felt easier to me to do it from the JavaScript side.
94+
> Note: determining the kind of plot elements could have been done (partially) from the Python side, but this felt easier to me to do from the JavaScript side.
9595
96-
The next step is to understand matplotlib underlying objects (called [artists](https://matplotlib.org/stable/users/explain/artists/artist_intro.html){target="\_blank"}) and how that translate to SVG.
96+
The next step is to understand matplotlib's underlying objects (called [artists](https://matplotlib.org/stable/users/explain/artists/artist_intro.html){target="\_blank"}) and how that translates to SVG.
9797

98-
## TLDR: artists in matplotlib
98+
## TL;DR: Artists in matplotlib
9999

100-
In matplotlib, artists are all the visual elements you see on a plot. There is the `Artist` base class, and all others artists inherits from this class.
100+
In matplotlib, artists are all the visual elements you see on a plot. There is the `Artist` base class, and all other artists inherit from this class.
101101

102102
For example:
103103

104-
- the `scatter()` function returns a `PathCollection` object, a subclass of `Artist`.
105-
- the `plot()` function returns a `Line2D` object, a subclass of `Artist`.
106-
- and so on
104+
- the `scatter()` function returns a `PathCollection` object, a subclass of `Artist`,
105+
- the `plot()` function returns a `Line2D` object, a subclass of `Artist`,
106+
- and so on.
107107

108108
## Selecting artists from SVG
109109

110110
In the SVG output of `savefig("plot.svg")`, we can find some info about what object was used.
111111

112-
For example, all `PathCollection` object looks like `<g id="PathCollection_1">`, `<g id="PathCollection_2">`. And since `PathCollection` is just one or multiple points, we can easily know how many scatter plots there are.
112+
For example, all `PathCollection` objects look like `<g id="PathCollection_1">`, `<g id="PathCollection_2">`. And since `PathCollection` is just one or multiple points, we can easily know how many scatter plots there are.
113113

114-
For lines, there are represented by `Line2D`. In the SVG, they look like `<g id="line2d_1">`, `<g id="line2d_2">`, etc. With this, we can easily detect that there are lines the chart.
114+
For lines, they are represented by `Line2D`. In the SVG, they look like `<g id="line2d_1">`, `<g id="line2d_2">`, etc. With this, we can easily detect that there are lines in the chart.
115115

116-
But there is a major issue here: not all `PathCollection` are relevant, same for `Line2D`, and so on.
116+
But there's a major issue here: not all `PathCollection` elements are relevant, same for `Line2D`, and so on.
117117

118-
By relevant I mean that we want to add interactivity to them. For example, what elements here are considered to be a `Line2D`:
118+
By "relevant," I mean those we want to add interactivity to. For example, what elements here are considered to be a `Line2D`?
119119

120120
![](../img/how-it-works-1.png)
121121

122-
At first, I thought there was 3: one for each main line. But in practice, it's much more:
122+
At first, I thought there were three: one for each main line. But in practice, it's much more:
123123

124124
![](../img/how-it-works-2.png)
125125

126-
What that means is that we can't select all `Line2D` and give them an hover effect, for instance. We need to find a way to discriminate relevant lines (the 3 big ones) and the other ones.
126+
What that means is that we can't just select all `Line2D` elements and give them a hover effect, for instance. We need to find a way to discriminate relevant lines (the three big ones) from the other ones.
127127

128128
## Filtering artists from SVG
129129

130-
This section might not be up to date to the latest version, but it'll give you the idea of how `plotjs` detect what is a "core" plot elements, and what is not.
130+
This section might not be up to date with the latest version, but it'll give you an idea of how `plotjs` detects what is a "core" plot element and what is not.
131131

132-
It's mostly consist of handling edge cases here, and is very different depending on the plot element (`Line2D`, `PathCollection`, etc).
132+
It mostly consists of handling edge cases here, and is very different depending on the plot element (`Line2D`, `PathCollection`, etc.).
133133

134-
For example, in order to select only "core" `Line2D` (the 3 colored ones in the previous image), we do:
134+
For example, in order to select only "core" `Line2D` elements (the three colored ones in the previous image), we do:
135135

136136
```javascript
137137
const lines = svg.selectAll('g[id^="line2d"] path').filter(function () {
@@ -150,8 +150,8 @@ const lines = svg.selectAll('g[id^="line2d"] path').filter(function () {
150150

151151
The idea is basically:
152152

153-
- select all `Line2D` with the first line
154-
- filter to remove non-wanted `Line2D`
153+
- select all `Line2D` elements with the first line,
154+
- filter to remove non-wanted `Line2D` elements.
155155

156156
This gives us a `lines` variable that only contains the lines of interest!
157157

docs/guides/css/index.md

Lines changed: 49 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,85 @@
1-
With `plotjs`, you can add your own CSS for advanced plot customization. Let's see how it works.
1+
With `plotjs`, you can add your own CSS for advanced plot customization. Here's how it works.
22

3-
## Understanding CSS
3+
## What is CSS?
44

5-
CSS requires 2 things:
5+
CSS has two main components:
66

7-
- a selector: which elements should receive the style
8-
- a list of key-value pairs, where keys are the attribute we want to set and value the value for this attribute.
7+
- **Selectors**: which elements the style applies to
8+
- **Rules**: a set of `key: value` pairs defining the style
99

10-
A minimalist CSS code would be:
10+
A basic CSS rule looks like this:
1111

12-
```CSS
12+
```css
1313
.tooltip {
14-
background: red;
15-
color: blue;
14+
background: red;
15+
color: blue;
1616
}
1717
```
1818

19-
Here we basically say _"To all objects of the class `tooltip`, set the `background` to `red` and the text `color` to `blue`."_
19+
This means: _"For all elements with class `tooltip`, set the background to red and the text color to blue."_
2020

21-
Now let's add this CSS to our plot to change the tooltip:
21+
## Applying CSS to a plot
22+
23+
You can directly apply a CSS string to your plot:
2224

2325
```python
2426
(
2527
MagicPlot()
2628
.add_tooltip(labels=df["tooltip"])
27-
.add_css(".tooltip {background: red; color: blue;}")
29+
.add_css(".tooltip { background: red; color: blue; }")
2830
)
2931
```
3032

3133
<iframe width="800" height="600" src="../../iframes/css.html" style="border:none;"></iframe>
3234

33-
> Note that this does not require any indentation, contrary to Python. We can write CSS with a single line of code.
34-
35-
## Pass CSS as a Python dictionnary
35+
> CSS doesn’t require indentation: one-liners work fine.
3636
37-
Being able to pass CSS inside a string is convenient, but not very readable when you want to pass a lot of CSS.
37+
## Using a Python dictionary
3838

39-
One option that you can use is to define your CSS via dictionnary. For this we need to import the css module from `plotjs`.
39+
For better readability and reusability, you can define CSS as a dictionary using `plotjs.css.from_dict()`:
4040

4141
```python
4242
from plotjs import css
4343

4444
(
4545
MagicPlot()
4646
.add_tooltip(labels=df["tooltip"])
47-
.add_css(css.from_dict({".tooltip": {"background": "red", "color": "blue"}}))
47+
.add_css(css.from_dict({
48+
".tooltip": {
49+
"background": "red",
50+
"color": "blue"
51+
}
52+
}))
4853
)
4954
```
5055

51-
Since `add_css()` returns the instance itself, you can do method chaining:
56+
Method chaining also works if you want to split styles:
5257

5358
```python
5459
(
5560
MagicPlot()
56-
.add_tooltip(
57-
labels=df["tooltip"],
58-
)
61+
.add_tooltip(labels=df["tooltip"])
5962
.add_css(css.from_dict({".tooltip": {"color": "blue"}}))
6063
.add_css(css.from_dict({".tooltip": {"background": "red"}}))
6164
)
6265
```
6366

64-
## Pass a CSS file
67+
## Loading CSS from a file
6568

66-
Finally, if your CSS is in a CSS file, you can use `css.from_file()`. Assuming your CSS file looks like this:
69+
If your CSS is stored in a `.css` file like:
6770

68-
```CSS
71+
```css
6972
.tooltip {
7073
background: pink;
7174
color: yellow;
7275
}
7376
```
7477

75-
We now do:
78+
You can load it with:
7679

7780
```python
81+
from plotjs import css
82+
7883
(
7984
MagicPlot()
8085
.add_tooltip(labels=df["tooltip"])
@@ -84,29 +89,33 @@ We now do:
8489

8590
<iframe width="800" height="600" src="../../iframes/css-2.html" style="border:none;"></iframe>
8691

87-
## Elements to select
92+
## Selectable elements
8893

89-
In order to apply CSS or [JavaScript](../javascript/index.md), you need to select elements from the DOM[^1]. You can find most of them using the [inspector](../troubleshooting/index.md) of your browser. All the common ones are defined below:
94+
To style or add interactivity, you need to select elements using the DOM[^1]. These are the most common selectors:
9095

91-
#### Plot elements
96+
### Plot elements
9297

93-
- `.point`: all points from a scatter plot
94-
- `.line`: all lines from a line chart
95-
- `.area`: all areas from an area chart
96-
- `.bar`: all bars from a bar chart
97-
- `.plot-element`: all previous elements (points, lines, areas and bars)
98+
- `.point`: scatter plot points
99+
- `.line`: line chart lines
100+
- `.area`: area chart fills
101+
- `.bar`: bar chart bars
102+
- `.plot-element`: all of the above
98103

99-
For all of those previous elements, you can add `.hovered` or `.not-hovered` (e.g, `.point.not-hovered`) to, respectively, select currently hovered and not hovered elements.
104+
You can combine with `.hovered` or `.not-hovered`, e.g., `.point.hovered`.
100105

101-
#### Misc
106+
### Misc
102107

103-
- `.tooltip`: the tooltip displayed when hovering elements
104-
- `svg`: the entire SVG containing the chart
108+
- `.tooltip`: tooltip shown on hover
109+
- `svg`: the entire SVG element
105110

106111
???+ question
107112

108-
Something's missing? Please [tell me](https://github.com/y-sunflower/plotjs/issues) about it by opening a new issue!
113+
Something missing? Please [open an issue](https://github.com/y-sunflower/plotjs/issues)!
114+
115+
## Default CSS
116+
117+
You can find the default CSS applied by plotjs [here](https://github.com/y-sunflower/plotjs/blob/main/plotjs/static/default.css)
109118

110119
## Appendix
111120

112-
[^1]: The DOM (Document Object Model) is a tree-like structure that represents all the elements of a web page, allowing JavaScript to read, change, and interact with them. Think of it as a live map of the webpage that your code can explore and update in real time.
121+
[^1]: The DOM (Document Object Model) is like a tree structure representing your webpage. JavaScript and CSS use it to select, modify, and interact with elements dynamically.

docs/guides/customization/customization.py

Whitespace-only changes.

docs/guides/customization/index.md

Whitespace-only changes.

docs/guides/javascript/index.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
Under the hood, JavaScript is what is used to make the charts interactive. But `plotjs` allows anyone to add some more JavaScript for a finer control of what is happening and basically do whatever you want!
1+
Under the hood, JavaScript is what is used to make the charts interactive. But `plotjs` allows anyone to add some more JavaScript for finer control of what is happening and basically do whatever you want!
22

33
## Basic example
44

5-
Try to click on one of the point in the chart!
5+
Try to click on one of the points in the chart!
66

77
```python
88
import matplotlib.pyplot as plt
@@ -43,10 +43,10 @@ d3.selectAll(".point").on("click", () =>
4343
);
4444
```
4545

46-
Here what it does:
46+
Here’s what it does:
4747

48-
- select all points (e.g, from the scatter plot)
49-
- sets that when clicking one of the point
48+
- selects all points (e.g., from the scatter plot)
49+
- sets that when clicking one of the points
5050
- it displays a message
5151

5252
## Loading JavaScript from file
@@ -61,7 +61,7 @@ MagicPlot(fig=fig).add_javascript(
6161
)
6262
```
6363

64-
This allows you to write JavaScript in a separate file so that you can have a code formatter (prettier, etc), code completion, syntax highlighting, and so on. This is what is recommended to do if you're writing a significant amount of code.
64+
This allows you to write JavaScript in a separate file so that you can have a code formatter (prettier, etc.), code completion, syntax highlighting, and so on. This is what is recommended to do if you're writing a significant amount of code.
6565

6666
## Advanced usage
6767

@@ -140,9 +140,9 @@ In order to apply [CSS](../css/index.md) or JavaScript, you need to select eleme
140140
- `.line`: all lines from a line chart
141141
- `.area`: all areas from an area chart
142142
- `.bar`: all bars from a bar chart
143-
- `.plot-element`: all previous elements (points, lines, areas and bars)
143+
- `.plot-element`: all previous elements (points, lines, areas, and bars)
144144

145-
For all of those previous elements, you can add `.hovered` or `.not-hovered` (e.g, `.point.not-hovered`) to, respectively, select currently hovered and not hovered elements.
145+
For all of those previous elements, you can add `.hovered` or `.not-hovered` (e.g., `.point.not-hovered`) to, respectively, select currently hovered and not-hovered elements.
146146

147147
#### Misc
148148

docs/guides/troubleshooting/index.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
Since `plotjs` does many things via JavaScript (e.g, in your browser when you open your html file), you may easily encounter "silent" errors.
1+
Since `plotjs` does many things via JavaScript (e.g., in your browser when you open your HTML file), you may easily encounter "silent" errors.
22

3-
In practice you will run you Python and everything will seems fine, but that does not mean what you'll see in the output is what you expected. There may multiple reasons for this. Here I'll explain common things that can happen, and how to debug them.
3+
In practice, you will run your Python and everything will seem fine, but that does not mean what you'll see in the output is what you expected. There may be multiple reasons for this. Here I'll explain common things that can happen, and how to debug them.
44

55
## Developer tools
66

77
Your browser has a thing called developer tools. It allows you to view many things, but here we're mostly interested in its "console" section.
88

9-
The console displays all the messages, including error messages, that the web page encountered at some point. Many of them are not necessarly interesting and are standard messages, but some of them might come from `plotjs` doing something wrong.
9+
The console displays all the messages, including error messages, that the web page encountered at some point. Many of them are not necessarily interesting and are standard messages, but some of them might come from `plotjs` doing something wrong.
1010

1111
How to open the developer tools is browser-specific, but there's likely a shortcut to make it convenient. For instance, on macOS + Firefox I use ++option+cmd+i++.
1212

1313
## Debug `plotjs`
1414

1515
### Workflow
1616

17-
Since currently `plotjs` can't (yet) really be displayed in tools like Jupyter notebooks, marimo, etc, you have to open the output html file in your browser.
17+
Since currently `plotjs` can't (yet) really be displayed in tools like Jupyter notebooks, marimo, etc., you have to open the output HTML file in your browser.
1818

19-
In order to have a comfortable workflow, it's recommend to have [`live-server`](https://www.npmjs.com/package/live-server) installed on your machine for automatic reload on file changes. Assuming you name your html file `mychart.html`, you'll just have to run `live-server mychart.html` and it'll open your plot in your default browser. Every time `mychart.html` is updated, it'll refresh the page. This makes debugging and iterating much faster and easier.
19+
In order to have a comfortable workflow, it's recommended to have [`live-server`](https://www.npmjs.com/package/live-server) installed on your machine for automatic reload on file changes. Assuming you name your HTML file `mychart.html`, you'll just have to run `live-server mychart.html` and it'll open your plot in your default browser. Every time `mychart.html` is updated, it'll refresh the page. This makes debugging and iterating much faster and easier.
2020

2121
### Debugging
2222

0 commit comments

Comments
 (0)