plotjs 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plotjs/__init__.py +6 -0
- plotjs/css.py +100 -0
- plotjs/data/__init__.py +3 -0
- plotjs/data/datasets.py +138 -0
- plotjs/data/iris.csv +151 -0
- plotjs/data/mtcars.csv +33 -0
- plotjs/data/titanic.csv +892 -0
- plotjs/javascript.py +23 -0
- plotjs/main.py +256 -0
- plotjs/static/d3.min.js +2 -0
- plotjs/static/default.css +40 -0
- plotjs/static/main.js +143 -0
- plotjs/static/template.html +163 -0
- plotjs/utils.py +30 -0
- plotjs-0.0.2.dist-info/METADATA +47 -0
- plotjs-0.0.2.dist-info/RECORD +19 -0
- plotjs-0.0.2.dist-info/WHEEL +5 -0
- plotjs-0.0.2.dist-info/licenses/LICENSE +21 -0
- plotjs-0.0.2.dist-info/top_level.txt +1 -0
plotjs/javascript.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
def from_file(javascript_file: str) -> str:
|
|
2
|
+
"""
|
|
3
|
+
Get raw javascript from a javascript file.
|
|
4
|
+
This function just reads the js from a given
|
|
5
|
+
file.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
javascript_file: Path to a js file.
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
A string of raw javascript.
|
|
12
|
+
|
|
13
|
+
Examples:
|
|
14
|
+
```python
|
|
15
|
+
from plotjs import javascript
|
|
16
|
+
|
|
17
|
+
javascript.from_file("path/to/style.js")
|
|
18
|
+
```
|
|
19
|
+
"""
|
|
20
|
+
with open(javascript_file, "r") as f:
|
|
21
|
+
js: str = f.read()
|
|
22
|
+
|
|
23
|
+
return js
|
plotjs/main.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from jinja2 import Environment, FileSystemLoader
|
|
4
|
+
|
|
5
|
+
from narwhals.typing import SeriesT
|
|
6
|
+
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
from matplotlib.figure import Figure
|
|
9
|
+
from matplotlib.axes import Axes
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import uuid
|
|
13
|
+
from typing import Literal, Text
|
|
14
|
+
import warnings
|
|
15
|
+
|
|
16
|
+
from .utils import _vector_to_list
|
|
17
|
+
|
|
18
|
+
if os.getcwd() == "/Users/josephbarbier/Desktop/plotjs":
|
|
19
|
+
# for debugging
|
|
20
|
+
TEMPLATE_DIR = f"{os.getcwd()}/plotjs/static"
|
|
21
|
+
else:
|
|
22
|
+
TEMPLATE_DIR: str = Path(__file__).parent / "static"
|
|
23
|
+
CSS_PATH: str = os.path.join(TEMPLATE_DIR, "default.css")
|
|
24
|
+
D3_PATH: str = os.path.join(TEMPLATE_DIR, "d3.min.js")
|
|
25
|
+
JS_PATH: str = os.path.join(TEMPLATE_DIR, "main.js")
|
|
26
|
+
|
|
27
|
+
env: Environment = Environment(loader=FileSystemLoader(str(TEMPLATE_DIR)))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MagicPlot:
|
|
31
|
+
"""
|
|
32
|
+
Class to convert static matplotlib plots to interactive charts.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
*,
|
|
38
|
+
fig: Figure | None = None,
|
|
39
|
+
**savefig_kws: dict,
|
|
40
|
+
):
|
|
41
|
+
"""
|
|
42
|
+
Initiate an `MagicPlot` instance to convert matplotlib
|
|
43
|
+
figures to interactive charts.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
fig: An optional matplotlib figure. If None, uses `plt.gcf()`.
|
|
47
|
+
savefig_kws: Additional keyword arguments passed to `plt.savefig()`.
|
|
48
|
+
"""
|
|
49
|
+
svg_path: Literal["user_plot.svg"] = "user_plot.svg"
|
|
50
|
+
|
|
51
|
+
if fig is None:
|
|
52
|
+
fig: Figure = plt.gcf()
|
|
53
|
+
self.fig = fig
|
|
54
|
+
self.fig.savefig(svg_path, **savefig_kws)
|
|
55
|
+
axes: Axes = self.fig.get_axes()
|
|
56
|
+
if len(axes) == 0:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"No Axes found in Figure. Make sure your graph is not empty."
|
|
59
|
+
)
|
|
60
|
+
elif len(axes) > 1:
|
|
61
|
+
warnings.warn(
|
|
62
|
+
"Figure with multiple Axes is not supported yet.", category=UserWarning
|
|
63
|
+
)
|
|
64
|
+
self.ax = axes[0]
|
|
65
|
+
self.legend_handles, self.legend_handles_labels = (
|
|
66
|
+
self.ax.get_legend_handles_labels()
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
self.additional_css = ""
|
|
70
|
+
self.additional_javascript = ""
|
|
71
|
+
self.svg_content = Path(svg_path).read_text()
|
|
72
|
+
self.template = env.get_template("template.html")
|
|
73
|
+
|
|
74
|
+
with open(CSS_PATH) as f:
|
|
75
|
+
self.default_css = f.read()
|
|
76
|
+
|
|
77
|
+
with open(D3_PATH) as f:
|
|
78
|
+
self.d3js = f.read()
|
|
79
|
+
|
|
80
|
+
with open(JS_PATH) as f:
|
|
81
|
+
self.js = f.read()
|
|
82
|
+
|
|
83
|
+
def add_tooltip(
|
|
84
|
+
self,
|
|
85
|
+
*,
|
|
86
|
+
labels: list | tuple | np.ndarray | SeriesT | None = None,
|
|
87
|
+
groups: list | tuple | np.ndarray | SeriesT | None = None,
|
|
88
|
+
tooltip_x_shift: int = 10,
|
|
89
|
+
tooltip_y_shift: int = -10,
|
|
90
|
+
) -> "MagicPlot":
|
|
91
|
+
"""
|
|
92
|
+
Add a tooltip to the interactive plot. You can set either
|
|
93
|
+
just `labels`, just `groups`, both or none.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
labels: An iterable containing the labels for the tooltip.
|
|
97
|
+
It corresponds to the text that will appear on hover.
|
|
98
|
+
groups: An iterable containing the group for tooltip. It
|
|
99
|
+
corresponds to how to 'group' the tooltip. The easiest
|
|
100
|
+
way to understand this argument is to check the examples
|
|
101
|
+
below. Also note that the use of this argument is required
|
|
102
|
+
to 'connect' the legend with plot elements.
|
|
103
|
+
tooltip_x_shift: Number of pixels to shift the tooltip from
|
|
104
|
+
the cursor, on the x axis.
|
|
105
|
+
tooltip_y_shift: Number of pixels to shift the tooltip from
|
|
106
|
+
the cursor, on the y axis.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
self: Returns the instance to allow method chaining.
|
|
110
|
+
|
|
111
|
+
Examples:
|
|
112
|
+
```python
|
|
113
|
+
MagicPlot(...).add_tooltip(
|
|
114
|
+
labels=["S&P500", "CAC40", "Sunflower"],
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
MagicPlot(...).add_tooltip(
|
|
120
|
+
labels=["S&P500", "CAC40", "Sunflower"],
|
|
121
|
+
columns=["S&P500", "CAC40", "Sunflower"],
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
"""
|
|
125
|
+
self.tooltip_x_shift = tooltip_x_shift
|
|
126
|
+
self.tooltip_y_shift = tooltip_y_shift
|
|
127
|
+
|
|
128
|
+
if labels is None:
|
|
129
|
+
self.tooltip_labels = []
|
|
130
|
+
else:
|
|
131
|
+
self.tooltip_labels = _vector_to_list(labels)
|
|
132
|
+
self.tooltip_labels.extend(self.legend_handles_labels)
|
|
133
|
+
if groups is None:
|
|
134
|
+
self.tooltip_groups = list(range(len(self.tooltip_labels)))
|
|
135
|
+
else:
|
|
136
|
+
self.tooltip_groups = _vector_to_list(groups)
|
|
137
|
+
self.tooltip_groups.extend(self.legend_handles_labels)
|
|
138
|
+
|
|
139
|
+
return self
|
|
140
|
+
|
|
141
|
+
def _set_plot_data_json(self):
|
|
142
|
+
if not hasattr(self, "tooltip_labels"):
|
|
143
|
+
self.add_tooltip()
|
|
144
|
+
|
|
145
|
+
self.plot_data_json = {
|
|
146
|
+
"tooltip_labels": self.tooltip_labels,
|
|
147
|
+
"tooltip_groups": self.tooltip_groups,
|
|
148
|
+
"tooltip_x_shift": self.tooltip_x_shift,
|
|
149
|
+
"tooltip_y_shift": self.tooltip_y_shift,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
def _set_html(self):
|
|
153
|
+
self._set_plot_data_json()
|
|
154
|
+
self.html: Text = self.template.render(
|
|
155
|
+
uuid=str(uuid.uuid4()),
|
|
156
|
+
default_css=self.default_css,
|
|
157
|
+
additional_css=self.additional_css,
|
|
158
|
+
additional_javascript=self.additional_javascript,
|
|
159
|
+
svg=self.svg_content,
|
|
160
|
+
plot_data_json=self.plot_data_json,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def add_css(self, css_content: str) -> "MagicPlot":
|
|
164
|
+
"""
|
|
165
|
+
Add CSS to the final HTML output. This function allows you to override
|
|
166
|
+
default styles or add custom CSS rules.
|
|
167
|
+
|
|
168
|
+
See the [CSS guide](../guides/css/index.md) for more info on how to work with CSS.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
css_content: CSS rules to apply, as a string.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
self: Returns the instance to allow method chaining.
|
|
175
|
+
|
|
176
|
+
Examples:
|
|
177
|
+
```python
|
|
178
|
+
MagicPlot(...).add_css('.tooltip {"color": "red";}')
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from plotjs import css
|
|
183
|
+
|
|
184
|
+
MagicPlot(...).add_css(css.from_file("path/to/style.css"))
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from plotjs import css
|
|
189
|
+
|
|
190
|
+
MagicPlot(...).add_css(css.from_dict({".tooltip": {"color": "red";}}))
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from plotjs import css
|
|
195
|
+
|
|
196
|
+
MagicPlot(...).add_css(
|
|
197
|
+
css.from_dict({".tooltip": {"color": "red";}}),
|
|
198
|
+
).add_css(
|
|
199
|
+
css.from_dict({".tooltip": {"background": "blue";}}),
|
|
200
|
+
)
|
|
201
|
+
```
|
|
202
|
+
"""
|
|
203
|
+
self.additional_css += css_content
|
|
204
|
+
return self
|
|
205
|
+
|
|
206
|
+
def add_javascript(self, javascript_content: str) -> "MagicPlot":
|
|
207
|
+
"""
|
|
208
|
+
Add custom JavaScript to the final HTML output. This function allows
|
|
209
|
+
users to enhance interactivity, define custom behaviors, or extend
|
|
210
|
+
the existing chart logic.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
javascript_content: JavaScript code to include, as a string.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
self: Returns the instance to allow method chaining.
|
|
217
|
+
|
|
218
|
+
Examples:
|
|
219
|
+
```python
|
|
220
|
+
MagicPlot(...).add_javascript("console.log('Custom JS loaded!');")
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from plotjs import javascript
|
|
225
|
+
|
|
226
|
+
custom_js = javascript.from_file("script.js")
|
|
227
|
+
MagicPlot(...).add_javascript(custom_js)
|
|
228
|
+
```
|
|
229
|
+
"""
|
|
230
|
+
self.additional_javascript += javascript_content
|
|
231
|
+
return self
|
|
232
|
+
|
|
233
|
+
def save(self, file_path: str) -> "MagicPlot":
|
|
234
|
+
"""
|
|
235
|
+
Save the interactive matplotlib plots to an HTML file.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
file_path: Where to save the HTML file. If the ".html"
|
|
239
|
+
extension is missing, it's added.
|
|
240
|
+
|
|
241
|
+
Examples:
|
|
242
|
+
```python
|
|
243
|
+
MagicPlot(...).save("index.html")
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
MagicPlot(...).save("path/to/my_chart.html")
|
|
248
|
+
```
|
|
249
|
+
"""
|
|
250
|
+
self._set_html()
|
|
251
|
+
if not file_path.endswith(".html"):
|
|
252
|
+
file_path += ".html"
|
|
253
|
+
with open(file_path, "w") as f:
|
|
254
|
+
f.write(self.html)
|
|
255
|
+
|
|
256
|
+
return self
|