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/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