uplot-python 0.0.1__py3-none-any.whl → 1.0.0__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.
- uplot/__init__.py +7 -3
- uplot/generate_html.py +42 -149
- uplot/plot.py +12 -23
- uplot/plot2.py +161 -0
- uplot/utils.py +37 -0
- uplot/{write_tmpfile.py → write_html_tempfile.py} +3 -3
- {uplot_python-0.0.1.dist-info → uplot_python-1.0.0.dist-info}/METADATA +51 -2
- uplot_python-1.0.0.dist-info/RECORD +16 -0
- uplot_python-0.0.1.dist-info/RECORD +0 -14
- /uplot/{uplot → static}/__init__.py +0 -0
- /uplot/{uplot → static}/uPlot.iife.js +0 -0
- /uplot/{uplot → static}/uPlot.min.css +0 -0
- /uplot/{uplot → static}/uPlot.mousewheel.js +0 -0
- {uplot_python-0.0.1.dist-info → uplot_python-1.0.0.dist-info}/LICENSE +0 -0
- {uplot_python-0.0.1.dist-info → uplot_python-1.0.0.dist-info}/WHEEL +0 -0
uplot/__init__.py
CHANGED
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
# Copyright 2022 Stéphane Caron
|
|
6
6
|
# Copyright 2023-2024 Inria
|
|
7
7
|
|
|
8
|
-
"""
|
|
8
|
+
"""Python wrapper for μPlot time series."""
|
|
9
9
|
|
|
10
|
-
__version__ = "0.0
|
|
10
|
+
__version__ = "1.0.0"
|
|
11
11
|
|
|
12
12
|
from .plot import plot
|
|
13
|
+
from .plot2 import plot2
|
|
13
14
|
|
|
14
|
-
__all__ = [
|
|
15
|
+
__all__ = [
|
|
16
|
+
"plot",
|
|
17
|
+
"plot2",
|
|
18
|
+
]
|
uplot/generate_html.py
CHANGED
|
@@ -7,73 +7,50 @@
|
|
|
7
7
|
|
|
8
8
|
"""Generate an HTML page containing the output plot."""
|
|
9
9
|
|
|
10
|
+
import json
|
|
11
|
+
from datetime import datetime
|
|
10
12
|
from importlib import resources
|
|
11
|
-
from
|
|
12
|
-
from typing import Dict, Iterable, List
|
|
13
|
+
from typing import List
|
|
13
14
|
|
|
14
15
|
import numpy as np
|
|
15
|
-
from numpy.typing import NDArray
|
|
16
16
|
|
|
17
|
-
from .
|
|
17
|
+
from .utils import array2string, js
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def __escape_null(series: Iterable) -> str:
|
|
25
|
-
"""Escape undefined values in a series.
|
|
20
|
+
def generate_html(opts: dict, data: List[np.ndarray], resize: bool) -> str:
|
|
21
|
+
"""Generate plot in an HTML page.
|
|
26
22
|
|
|
27
23
|
Args:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
String representation of the series.
|
|
32
|
-
"""
|
|
33
|
-
return (
|
|
34
|
-
"["
|
|
35
|
-
+ ", ".join(
|
|
36
|
-
map(
|
|
37
|
-
lambda x: (
|
|
38
|
-
str(int(x))
|
|
39
|
-
if isinstance(x, bool)
|
|
40
|
-
else (
|
|
41
|
-
str(x)
|
|
42
|
-
if isinstance(x, (int, float)) and not isnan(x)
|
|
43
|
-
else x if isinstance(x, str) else "null"
|
|
44
|
-
)
|
|
45
|
-
),
|
|
46
|
-
series,
|
|
47
|
-
)
|
|
48
|
-
)
|
|
49
|
-
+ "]"
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def generate_html(
|
|
54
|
-
opts: dict,
|
|
55
|
-
data: List[Iterable[float, int]],
|
|
56
|
-
) -> str:
|
|
57
|
-
"""Generate plot in an HTML page.
|
|
24
|
+
opts: uPlot option dictionary.
|
|
25
|
+
data: List of NumPy arrays, one for each series in the plot.
|
|
58
26
|
|
|
59
27
|
Returns:
|
|
60
28
|
HTML contents of the page.
|
|
61
29
|
"""
|
|
62
|
-
with resources.path("
|
|
30
|
+
with resources.path("uplot.static", "uPlot.min.css") as path:
|
|
63
31
|
uplot_min_css = path
|
|
64
|
-
with resources.path("
|
|
32
|
+
with resources.path("uplot.static", "uPlot.iife.js") as path:
|
|
65
33
|
uplot_iife_js = path
|
|
66
|
-
with resources.path("
|
|
34
|
+
with resources.path("uplot.static", "uPlot.mousewheel.js") as path:
|
|
67
35
|
uplot_mwheel_js = path
|
|
68
36
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
37
|
+
date = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
38
|
+
title = opts.get("title", f"Plot from {date}")
|
|
39
|
+
|
|
40
|
+
data_string = ""
|
|
41
|
+
for array in data:
|
|
42
|
+
data_string += f"""
|
|
43
|
+
{array2string(array)},"""
|
|
44
|
+
|
|
45
|
+
if "class" not in opts:
|
|
46
|
+
opts["class"] = "uplot-chart"
|
|
47
|
+
if resize:
|
|
48
|
+
opts["width"] = js("window.innerWidth - 20")
|
|
49
|
+
opts["height"] = js("window.innerHeight - 150")
|
|
50
|
+
opts_string = json.dumps(opts)
|
|
51
|
+
opts_string = opts_string.replace('"<script>', "")
|
|
52
|
+
opts_string = opts_string.replace('</script>"', "")
|
|
53
|
+
|
|
77
54
|
html = f"""<!DOCTYPE html>
|
|
78
55
|
<html lang="en">
|
|
79
56
|
<head>
|
|
@@ -82,7 +59,7 @@ def generate_html(
|
|
|
82
59
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
83
60
|
<link rel="stylesheet" href="{uplot_min_css}">
|
|
84
61
|
<style>
|
|
85
|
-
div.
|
|
62
|
+
div.uplot-chart {{
|
|
86
63
|
background-color: white;
|
|
87
64
|
padding: 10px 0px; // appear in Right Click -> Take Screenshot
|
|
88
65
|
}}
|
|
@@ -94,26 +71,22 @@ def generate_html(
|
|
|
94
71
|
<script>
|
|
95
72
|
const {{ linear, stepped, bars, spline, spline2 }} = uPlot.paths;
|
|
96
73
|
|
|
97
|
-
let data = [
|
|
98
|
-
for label in data.keys():
|
|
99
|
-
html += f"""
|
|
100
|
-
{__escape_null(data[label])},"""
|
|
101
|
-
html += """
|
|
74
|
+
let data = [{data_string}
|
|
102
75
|
];
|
|
103
76
|
|
|
104
|
-
const lineInterpolations = {
|
|
77
|
+
const lineInterpolations = {{
|
|
105
78
|
linear: 0,
|
|
106
79
|
stepAfter: 1,
|
|
107
80
|
stepBefore: 2,
|
|
108
81
|
spline: 3,
|
|
109
|
-
};
|
|
82
|
+
}};
|
|
110
83
|
|
|
111
|
-
const _stepBefore = stepped({align: -1});
|
|
112
|
-
const _stepAfter = stepped({align: 1});
|
|
84
|
+
const _stepBefore = stepped({{align: -1}});
|
|
85
|
+
const _stepAfter = stepped({{align: 1}});
|
|
113
86
|
const _linear = linear();
|
|
114
87
|
const _spline = spline();
|
|
115
88
|
|
|
116
|
-
function paths(u, seriesIdx, idx0, idx1, extendGap, buildClip) {
|
|
89
|
+
function paths(u, seriesIdx, idx0, idx1, extendGap, buildClip) {{
|
|
117
90
|
let s = u.series[seriesIdx];
|
|
118
91
|
let interp = s.lineInterpolation;
|
|
119
92
|
|
|
@@ -128,100 +101,20 @@ def generate_html(
|
|
|
128
101
|
return renderer(
|
|
129
102
|
u, seriesIdx, idx0, idx1, extendGap, buildClip
|
|
130
103
|
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
let opts = {"""
|
|
134
|
-
html += f"""
|
|
135
|
-
title: "{title}","""
|
|
136
|
-
html += """
|
|
137
|
-
id: "chart1",
|
|
138
|
-
class: "my-chart",
|
|
139
|
-
width: window.innerWidth - 20,
|
|
140
|
-
height: window.innerHeight - 150,
|
|
141
|
-
cursor: {
|
|
142
|
-
drag: {
|
|
143
|
-
x: true,
|
|
144
|
-
y: true,
|
|
145
|
-
uni: 50,
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
plugins: [
|
|
149
|
-
wheelZoomPlugin({factor: 0.75})
|
|
150
|
-
],"""
|
|
151
|
-
html += f"""
|
|
152
|
-
scales: {{
|
|
153
|
-
x: {{
|
|
154
|
-
time: {"true" if timestamped else "false"},
|
|
155
|
-
}},
|
|
156
|
-
}},
|
|
157
|
-
series: ["""
|
|
158
|
-
html += f"""
|
|
159
|
-
{{
|
|
160
|
-
value: (self, rawValue) => Number.parseFloat(rawValue -
|
|
161
|
-
{times[0]}).toPrecision(4),
|
|
162
|
-
}},"""
|
|
163
|
-
for label in labels:
|
|
164
|
-
html += f"""
|
|
165
|
-
{{
|
|
166
|
-
// initial toggled state (optional)
|
|
167
|
-
show: true,
|
|
168
|
-
spanGaps: false,
|
|
169
|
-
|
|
170
|
-
// in-legend display
|
|
171
|
-
label: "{label}","""
|
|
172
|
-
if label in right_labels:
|
|
173
|
-
html += f"""
|
|
174
|
-
value: (self, rawValue) =>
|
|
175
|
-
Number.parseFloat(rawValue).toPrecision(2) +
|
|
176
|
-
"{right_axis_label}",
|
|
177
|
-
scale: "{right_axis_unit}","""
|
|
178
|
-
else: # label in left_labels
|
|
179
|
-
html += f"""
|
|
180
|
-
value: (self, rawValue) =>
|
|
181
|
-
Number.parseFloat(rawValue).toPrecision(2) +
|
|
182
|
-
"{left_axis_label}","""
|
|
183
|
-
html += f"""
|
|
184
|
-
// series style
|
|
185
|
-
stroke: "{color_picker.get_next_color()}",
|
|
186
|
-
width: 2 / devicePixelRatio,
|
|
187
|
-
lineInterpolation: lineInterpolations.stepAfter,
|
|
188
|
-
paths,
|
|
189
|
-
}},"""
|
|
190
|
-
html += """
|
|
191
|
-
],
|
|
192
|
-
axes: [
|
|
193
|
-
{},
|
|
194
|
-
{"""
|
|
195
|
-
html += f"""
|
|
196
|
-
size: {60 + 10 * len({left_axis_label})},
|
|
197
|
-
values: (u, vals, space) => vals.map(
|
|
198
|
-
v => v + "{left_axis_label}"
|
|
199
|
-
),"""
|
|
200
|
-
html += """
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
side: 1,"""
|
|
204
|
-
html += f"""
|
|
205
|
-
scale: "{right_axis_unit}",
|
|
206
|
-
size: {60 + 10 * len({right_axis_label})},
|
|
207
|
-
values: (u, vals, space) => vals.map(
|
|
208
|
-
v => v + "{right_axis_label}"
|
|
209
|
-
),"""
|
|
210
|
-
html += """
|
|
211
|
-
grid: {show: false},
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
};
|
|
104
|
+
}}
|
|
215
105
|
|
|
216
|
-
let
|
|
106
|
+
let opts = {opts_string};
|
|
107
|
+
let uplot = new uPlot(opts, data, document.body);"""
|
|
108
|
+
if resize:
|
|
109
|
+
html += """
|
|
217
110
|
|
|
218
|
-
// resize with window
|
|
219
111
|
window.addEventListener("resize", e => {
|
|
220
112
|
uplot.setSize({
|
|
221
113
|
width: window.innerWidth - 20,
|
|
222
114
|
height: window.innerHeight - 150,
|
|
223
115
|
});
|
|
224
|
-
});
|
|
116
|
+
});"""
|
|
117
|
+
html += """
|
|
225
118
|
</script>
|
|
226
119
|
</body>
|
|
227
120
|
</html>"""
|
uplot/plot.py
CHANGED
|
@@ -4,33 +4,22 @@
|
|
|
4
4
|
# SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
# Copyright 2024 Inria
|
|
6
6
|
|
|
7
|
-
"""Main
|
|
7
|
+
"""Main plot function."""
|
|
8
8
|
|
|
9
|
-
import logging
|
|
10
|
-
import sys
|
|
11
9
|
import webbrowser
|
|
12
|
-
from typing import
|
|
13
|
-
|
|
14
|
-
import numpy as np
|
|
15
|
-
from numpy.typing import NDArray
|
|
10
|
+
from typing import Iterable, List
|
|
16
11
|
|
|
17
12
|
from .generate_html import generate_html
|
|
18
|
-
from .
|
|
13
|
+
from .write_html_tempfile import write_html_tempfile
|
|
14
|
+
|
|
19
15
|
|
|
16
|
+
def plot(opts: dict, data: List[Iterable]) -> None:
|
|
17
|
+
"""Plot function with the same API as uPlot's `plot`.
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
times,
|
|
28
|
-
left_series,
|
|
29
|
-
right_series,
|
|
30
|
-
title,
|
|
31
|
-
left_axis_unit,
|
|
32
|
-
right_axis_unit,
|
|
33
|
-
timestamped=self.__time is not None,
|
|
34
|
-
)
|
|
35
|
-
filename = write_tmpfile(html)
|
|
19
|
+
Args:
|
|
20
|
+
opts: Options dictionary, as expected by uPlot.
|
|
21
|
+
data: List of series, as expected by uPlot.
|
|
22
|
+
"""
|
|
23
|
+
html = generate_html(opts, data, resize=False)
|
|
24
|
+
filename = write_html_tempfile(html)
|
|
36
25
|
webbrowser.open_new_tab(filename)
|
uplot/plot2.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
# Copyright 2024 Inria
|
|
6
|
+
|
|
7
|
+
"""Additional plot function."""
|
|
8
|
+
|
|
9
|
+
import webbrowser
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from .color_picker import ColorPicker
|
|
15
|
+
from .exceptions import UplotException
|
|
16
|
+
from .generate_html import generate_html
|
|
17
|
+
from .utils import js
|
|
18
|
+
from .write_html_tempfile import write_html_tempfile
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def prepare_data(x, left, right):
|
|
22
|
+
if isinstance(left, np.ndarray) and left.ndim == 1:
|
|
23
|
+
left = [left]
|
|
24
|
+
data = [x, *left]
|
|
25
|
+
if right:
|
|
26
|
+
data.extend(right)
|
|
27
|
+
return data
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def add_default_options(opts: dict) -> None:
|
|
31
|
+
if "cursor" not in opts:
|
|
32
|
+
opts["cursor"] = {
|
|
33
|
+
"drag": {
|
|
34
|
+
"x": True,
|
|
35
|
+
"y": True,
|
|
36
|
+
"uni": 50,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if "plugins" not in opts:
|
|
40
|
+
opts["plugins"] = [
|
|
41
|
+
js("wheelZoomPlugin({factor: 0.75})"),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def add_series(
|
|
46
|
+
opts: dict,
|
|
47
|
+
data: List[np.ndarray],
|
|
48
|
+
nb_left: int,
|
|
49
|
+
left_labels: Optional[List[str]],
|
|
50
|
+
right_labels: Optional[List[str]],
|
|
51
|
+
) -> None:
|
|
52
|
+
if left_labels is not None and len(left_labels) < nb_left:
|
|
53
|
+
raise UplotException(
|
|
54
|
+
f"Not enough labels in left_labels ({len(left_labels)}) "
|
|
55
|
+
f"to label all {nb_left} left-axis series"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
opts["series"] = [{}]
|
|
59
|
+
color_picker = ColorPicker()
|
|
60
|
+
for i, series in enumerate(data[1:]):
|
|
61
|
+
new_series = {
|
|
62
|
+
"show": True,
|
|
63
|
+
"spanGaps": False,
|
|
64
|
+
"stroke": color_picker.get_next_color(),
|
|
65
|
+
"width": js("2 / devicePixelRatio"),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
def find_in_lists(
|
|
69
|
+
i: int,
|
|
70
|
+
left_list: Optional[List[str]],
|
|
71
|
+
right_list: Optional[List[str]],
|
|
72
|
+
) -> Optional[str]:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
if left_labels is not None and i < nb_left:
|
|
76
|
+
new_series["label"] = left_labels[i]
|
|
77
|
+
if i >= nb_left:
|
|
78
|
+
if right_labels is not None:
|
|
79
|
+
new_series["label"] = right_labels[i - nb_left]
|
|
80
|
+
new_series["scale"] = "right_axis"
|
|
81
|
+
|
|
82
|
+
# scale = find_in_lists(i, left_scales, right_scales)
|
|
83
|
+
# if scale is not None:
|
|
84
|
+
# new_series["scale"] = scale
|
|
85
|
+
|
|
86
|
+
new_series["value"] = js(
|
|
87
|
+
"(self, rawValue) => Number.parseFloat(rawValue).toPrecision(2)"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Last, we hack the wrapper to append `paths` after "lineInterpolation"
|
|
91
|
+
new_series["lineInterpolation"] = js(
|
|
92
|
+
"lineInterpolations.stepAfter, paths"
|
|
93
|
+
)
|
|
94
|
+
opts["series"].append(new_series)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def add_axes(opts: dict) -> None:
|
|
98
|
+
opts["axes"] = [
|
|
99
|
+
{},
|
|
100
|
+
{
|
|
101
|
+
"size": 60,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"side": 1,
|
|
105
|
+
"scale": "right_axis",
|
|
106
|
+
"size": 60,
|
|
107
|
+
"grid": {"show": False},
|
|
108
|
+
},
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def plot2(
|
|
113
|
+
x: np.ndarray,
|
|
114
|
+
left: List[np.ndarray],
|
|
115
|
+
right: Optional[List[np.ndarray]] = None,
|
|
116
|
+
resize: bool = True,
|
|
117
|
+
title: Optional[str] = None,
|
|
118
|
+
timestamped: bool = False,
|
|
119
|
+
width: Optional[int] = None,
|
|
120
|
+
height: Optional[int] = None,
|
|
121
|
+
left_labels: Optional[List[str]] = None,
|
|
122
|
+
right_labels: Optional[List[str]] = None,
|
|
123
|
+
**kwargs,
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Plot function with additional defaults and parameters.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
x: Values for the x-axis.
|
|
129
|
+
left: Values for the left y-axis.
|
|
130
|
+
right: Values for the (optional) right y-axis.
|
|
131
|
+
resize: If set (default), scale plot to page width and height.
|
|
132
|
+
title: Plot title.
|
|
133
|
+
timestamped: If set, x-axis values are treated as timestamps.
|
|
134
|
+
width: Plot width in pixels.
|
|
135
|
+
height: Plot height in pixels.
|
|
136
|
+
kwargs: Other keyword arguments are forward to uPlot as options.
|
|
137
|
+
"""
|
|
138
|
+
data = prepare_data(x, left, right)
|
|
139
|
+
|
|
140
|
+
# Prepare options
|
|
141
|
+
opts = kwargs.copy()
|
|
142
|
+
add_default_options(opts)
|
|
143
|
+
if "id" not in opts:
|
|
144
|
+
opts["id"] = "chart1"
|
|
145
|
+
if title is not None:
|
|
146
|
+
opts["title"] = title
|
|
147
|
+
if timestamped is not None:
|
|
148
|
+
opts["scales"] = {"x": {"time": timestamped}}
|
|
149
|
+
if width is not None:
|
|
150
|
+
opts["width"] = width
|
|
151
|
+
if height is not None:
|
|
152
|
+
opts["height"] = height
|
|
153
|
+
if "series" not in opts:
|
|
154
|
+
add_series(opts, data, len(left), left_labels, right_labels)
|
|
155
|
+
if "axes" not in opts:
|
|
156
|
+
add_axes(opts)
|
|
157
|
+
|
|
158
|
+
# Generate and open plot
|
|
159
|
+
html = generate_html(opts, data, resize=resize)
|
|
160
|
+
filename = write_html_tempfile(html)
|
|
161
|
+
webbrowser.open_new_tab(filename)
|
uplot/utils.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
# Copyright 2024 Inria
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def array2string(array: np.ndarray) -> str:
|
|
11
|
+
"""Get string representation of a NumPy array suitable for uPlot.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
array: NumPy array to convert to JavaScript.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
String representation of the array.
|
|
18
|
+
"""
|
|
19
|
+
array_str = np.array2string(
|
|
20
|
+
array,
|
|
21
|
+
precision=64,
|
|
22
|
+
separator=",",
|
|
23
|
+
threshold=np.inf,
|
|
24
|
+
)
|
|
25
|
+
return array_str.replace("nan", "null")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def js(code: str) -> str:
|
|
29
|
+
"""Wrap a code string so that it is processed as output JavaScript.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
code: Code string to wrap.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Wrapped string.
|
|
36
|
+
"""
|
|
37
|
+
return f"<script>{code}</script>"
|
|
@@ -11,14 +11,14 @@ import tempfile
|
|
|
11
11
|
from datetime import datetime
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def
|
|
15
|
-
"""Write
|
|
14
|
+
def write_html_tempfile(html: str) -> str:
|
|
15
|
+
"""Write string to an HTML file.
|
|
16
16
|
|
|
17
17
|
Args:
|
|
18
18
|
html: HTML content.
|
|
19
19
|
|
|
20
20
|
Returns:
|
|
21
|
-
Name of the output
|
|
21
|
+
Name of the output temporary file.
|
|
22
22
|
"""
|
|
23
23
|
filename: str = ""
|
|
24
24
|
with tempfile.NamedTemporaryFile(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: uplot-python
|
|
3
|
-
Version: 0.0
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python wrapper for μPlot time series.
|
|
5
5
|
Keywords: json,time,series,plot
|
|
6
6
|
Author-email: Stéphane Caron <stephane.caron@normalesup.org>
|
|
7
7
|
Maintainer-email: Stéphane Caron <stephane.caron@normalesup.org>
|
|
@@ -29,3 +29,52 @@ Project-URL: Tracker, https://github.com/stephane-caron/uplot-python/issues
|
|
|
29
29
|
|
|
30
30
|
Python wrapper for [μPlot](https://github.com/leeoniya/uPlot) 📈
|
|
31
31
|
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### From PyPI
|
|
35
|
+
|
|
36
|
+
```console
|
|
37
|
+
pip install uplot-python
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
The `plot` function has the same API as µPlot's `uPlot.plot`:
|
|
43
|
+
|
|
44
|
+
```py
|
|
45
|
+
import numpy as np
|
|
46
|
+
import uplot
|
|
47
|
+
|
|
48
|
+
t = np.linspace(0.0, 1.0, 10)
|
|
49
|
+
data = [t, np.exp(0.42 * t)]
|
|
50
|
+
opts = {
|
|
51
|
+
"width": 1920,
|
|
52
|
+
"height": 600,
|
|
53
|
+
"title": "Example with plot",
|
|
54
|
+
"series": [{}, { "stroke": "red", }, ],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
uplot.plot(opts, data)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
For convenience, the library also provides a `plot2` function with additional defaults aimed at time series and line plots, for an experience closer to `matplotlib.pyplot.plot`:
|
|
61
|
+
|
|
62
|
+
```py
|
|
63
|
+
import numpy as np
|
|
64
|
+
import uplot
|
|
65
|
+
|
|
66
|
+
t = np.linspace(0.0, 1.0, 10)
|
|
67
|
+
uplot.plot2(
|
|
68
|
+
t,
|
|
69
|
+
[np.exp(0.1 * t), np.exp(-10.0 * t), np.cos(t)],
|
|
70
|
+
title="Example with plot2",
|
|
71
|
+
left_labels=["exp(A t)", "exp(-B t)", "cos(t)"],
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## See also
|
|
76
|
+
|
|
77
|
+
- [µPlot](https://github.com/leeoniya/uPlot): A small (~45 KB min), fast chart for time series, lines, areas, ohlc & bars.
|
|
78
|
+
- [Matplotlib](https://matplotlib.org/stable/): Comprehensive library for creating static, animated, and interactive visualizations.
|
|
79
|
+
- [matplotlive](https://github.com/stephane-caron/matplotlive): Stream live plots to a Matplotlib figure.
|
|
80
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
uplot/__init__.py,sha256=cNNmW1r6MVagQ85Otp-Ms55B63KoVTutayVYDDt4Q40,306
|
|
2
|
+
uplot/color_picker.py,sha256=tBcc4t1gN2spSBqmy7gY827GvpzPE2u8N8-6xUMv-YY,962
|
|
3
|
+
uplot/exceptions.py,sha256=iMQrMn8yzcU95-g0vL1Lg03M5FB8vo4ckJpZXEb0TyE,248
|
|
4
|
+
uplot/generate_html.py,sha256=CiwaJZtIShSNAufidH74OCzdJB-7lrm9-f4MDi_AR60,3698
|
|
5
|
+
uplot/plot.py,sha256=GugDtmx2nqcxmwZ20LEbaX06lX0Swvncb9-136tTXqM,654
|
|
6
|
+
uplot/plot2.py,sha256=PmEsGyBEFD0vvvfvD5LYah-hV2Lp48saviqhkGqtWDE,4504
|
|
7
|
+
uplot/utils.py,sha256=0cuQ8XWcDCm0hFvhtUHtt2OeLWf6ujwfSvKokTxuiCQ,775
|
|
8
|
+
uplot/write_html_tempfile.py,sha256=gYbp-btAItW4CX1Wjt2ufhcptdlA0ckFK-xFXA92tGw,722
|
|
9
|
+
uplot/static/__init__.py,sha256=1mykIjCDK2TC5EfbjCTNxfOtJx3P-hIFgbHQEtBfM_o,71
|
|
10
|
+
uplot/static/uPlot.iife.js,sha256=ZVvIqjN4Hochxa9rWfFpnAPJvalRqnSmq1GKwgzEMm0,120184
|
|
11
|
+
uplot/static/uPlot.min.css,sha256=PxIc1KmXtjTgeoh2WIUAGOR1wm8L3dh0EJqNOCwZ3oc,1839
|
|
12
|
+
uplot/static/uPlot.mousewheel.js,sha256=p2CcCwxxafvNqXsxw-zrqUUogxZ1URZhxWK59B0-tgw,4543
|
|
13
|
+
uplot_python-1.0.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
14
|
+
uplot_python-1.0.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
15
|
+
uplot_python-1.0.0.dist-info/METADATA,sha256=b9XQy0hZ17t7ks58s_wIYSOGBDqGwNJHGSIEHAOAh4g,2537
|
|
16
|
+
uplot_python-1.0.0.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
uplot/__init__.py,sha256=ltGGn3R24SRcO2DqcSLi8SQRCRDpO11k4h_FAzmeBWg,257
|
|
2
|
-
uplot/color_picker.py,sha256=tBcc4t1gN2spSBqmy7gY827GvpzPE2u8N8-6xUMv-YY,962
|
|
3
|
-
uplot/exceptions.py,sha256=iMQrMn8yzcU95-g0vL1Lg03M5FB8vo4ckJpZXEb0TyE,248
|
|
4
|
-
uplot/generate_html.py,sha256=WJRV592DDT38VfghO03wLslptYko7brYm4_bZnoR7iM,7182
|
|
5
|
-
uplot/plot.py,sha256=Fw2iIcH5QB_56WBJxUvef5cA4tI5MDTwir1lphlLrdw,771
|
|
6
|
-
uplot/write_tmpfile.py,sha256=6M-GiNyHAV2vVD1H7SHjFITVFrRtwFgHGwcDRkxeeCA,714
|
|
7
|
-
uplot/uplot/__init__.py,sha256=1mykIjCDK2TC5EfbjCTNxfOtJx3P-hIFgbHQEtBfM_o,71
|
|
8
|
-
uplot/uplot/uPlot.iife.js,sha256=ZVvIqjN4Hochxa9rWfFpnAPJvalRqnSmq1GKwgzEMm0,120184
|
|
9
|
-
uplot/uplot/uPlot.min.css,sha256=PxIc1KmXtjTgeoh2WIUAGOR1wm8L3dh0EJqNOCwZ3oc,1839
|
|
10
|
-
uplot/uplot/uPlot.mousewheel.js,sha256=p2CcCwxxafvNqXsxw-zrqUUogxZ1URZhxWK59B0-tgw,4543
|
|
11
|
-
uplot_python-0.0.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
12
|
-
uplot_python-0.0.1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
13
|
-
uplot_python-0.0.1.dist-info/METADATA,sha256=haZ0NwZTVvTdz6_2rXO91WJhb3irB-iAfqy29MK_oBQ,1342
|
|
14
|
-
uplot_python-0.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|