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 CHANGED
@@ -5,10 +5,14 @@
5
5
  # Copyright 2022 Stéphane Caron
6
6
  # Copyright 2023-2024 Inria
7
7
 
8
- """Plot Python iterables with µPlot."""
8
+ """Python wrapper for μPlot time series."""
9
9
 
10
- __version__ = "0.0.1"
10
+ __version__ = "1.0.0"
11
11
 
12
12
  from .plot import plot
13
+ from .plot2 import plot2
13
14
 
14
- __all__ = ["plot"]
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 math import isnan
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 .color_picker import ColorPicker
17
+ from .utils import array2string, js
18
18
 
19
19
 
20
- def __ensure_floats(series: Iterable) -> List[float]:
21
- return [float(x) for x in series]
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
- series: Series to filter.
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("foxplot.uplot", "uPlot.min.css") as path:
30
+ with resources.path("uplot.static", "uPlot.min.css") as path:
63
31
  uplot_min_css = path
64
- with resources.path("foxplot.uplot", "uPlot.iife.js") as path:
32
+ with resources.path("uplot.static", "uPlot.iife.js") as path:
65
33
  uplot_iife_js = path
66
- with resources.path("foxplot.uplot", "uPlot.mousewheel.js") as path:
34
+ with resources.path("uplot.static", "uPlot.mousewheel.js") as path:
67
35
  uplot_mwheel_js = path
68
36
 
69
- color_picker = ColorPicker()
70
- right_axis_label = f" {right_axis_unit}" if right_axis_unit else ""
71
- left_labels = list(left_axis.keys())
72
- right_labels = list(right_axis.keys())
73
- labels = left_labels + [r for r in right_labels if r not in left_labels]
74
- series_from_label = {}
75
- series_from_label.update(left_axis)
76
- series_from_label.update(right_axis)
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.my-chart {{
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 uplot = new uPlot(opts, data, document.body);
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 class to manipulate dictionary-series data."""
7
+ """Main plot function."""
8
8
 
9
- import logging
10
- import sys
11
9
  import webbrowser
12
- from typing import BinaryIO, Dict, List, Optional, TextIO, Union
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 .write_tmpfile import write_tmpfile
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
- def plot(
22
- self,
23
- opts: dict,
24
- data: List[Iterable[float, int]],
25
- ) -> None:
26
- html = generate_html(
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 write_tmpfile(html: str) -> str:
15
- """Write output page.
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 file (a temporary file).
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.1
4
- Summary: Plot Python iterables with µPlot.
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