youplot 1.0.0__tar.gz
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.
- youplot-1.0.0/LICENSE +21 -0
- youplot-1.0.0/MANIFEST.in +3 -0
- youplot-1.0.0/PKG-INFO +224 -0
- youplot-1.0.0/README.md +199 -0
- youplot-1.0.0/pyproject.toml +37 -0
- youplot-1.0.0/setup.cfg +4 -0
- youplot-1.0.0/youplot/__init__.py +37 -0
- youplot-1.0.0/youplot/colors/__init__.py +0 -0
- youplot-1.0.0/youplot/colors/palette.py +106 -0
- youplot-1.0.0/youplot/examples/basic_line.py +123 -0
- youplot-1.0.0/youplot/figure.py +518 -0
- youplot-1.0.0/youplot/options/__init__.py +0 -0
- youplot-1.0.0/youplot/options/annotations.py +60 -0
- youplot-1.0.0/youplot/options/axes.py +22 -0
- youplot-1.0.0/youplot/render/css.py +578 -0
- youplot-1.0.0/youplot/render/html.py +320 -0
- youplot-1.0.0/youplot/render/js.py +1080 -0
- youplot-1.0.0/youplot/render/scatter_js.py +195 -0
- youplot-1.0.0/youplot/render/serializer.py +242 -0
- youplot-1.0.0/youplot/series/__init__.py +2 -0
- youplot-1.0.0/youplot/series/line.py +70 -0
- youplot-1.0.0/youplot/series/scatter.py +76 -0
- youplot-1.0.0/youplot/themes/__init__.py +0 -0
- youplot-1.0.0/youplot/themes/base.py +105 -0
- youplot-1.0.0/youplot/utils/__init__.py +0 -0
- youplot-1.0.0/youplot/utils/browser.py +22 -0
- youplot-1.0.0/youplot/utils/data.py +150 -0
- youplot-1.0.0/youplot/vendor/__init__.py +12 -0
- youplot-1.0.0/youplot/vendor/__pycache__/__init__.cpython-311.pyc +0 -0
- youplot-1.0.0/youplot/vendor/uplot.iife.min.js +2 -0
- youplot-1.0.0/youplot/vendor/uplot.min.css +1 -0
- youplot-1.0.0/youplot.egg-info/PKG-INFO +224 -0
- youplot-1.0.0/youplot.egg-info/SOURCES.txt +33 -0
- youplot-1.0.0/youplot.egg-info/dependency_links.txt +1 -0
- youplot-1.0.0/youplot.egg-info/top_level.txt +1 -0
youplot-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 youplot contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
youplot-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: youplot
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Extremely fast, lightweight timeseries charts for Python — powered by uPlot
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/YOUR_USERNAME/youplot
|
|
7
|
+
Project-URL: Documentation, https://github.com/YOUR_USERNAME/youplot#readme
|
|
8
|
+
Project-URL: Repository, https://github.com/YOUR_USERNAME/youplot
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/YOUR_USERNAME/youplot/issues
|
|
10
|
+
Keywords: charts,timeseries,visualization,uplot,html,interactive,plotting
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# youplot
|
|
27
|
+
|
|
28
|
+
**Extremely fast, lightweight timeseries charts for Python — powered by [uPlot](https://github.com/leeoniya/uPlot).**
|
|
29
|
+
|
|
30
|
+
Write Python. Get a fully self-contained, interactive HTML file. No server. No viewer dependencies.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
pip install youplot
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Requires Python 3.10+. No runtime dependencies.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Benchmark (Apple M2 Pro, Python 3.11, 3 series, median of 3 runs)
|
|
41
|
+
|
|
42
|
+
| Library | 1K pts | 10K pts | 100K pts | 500K pts | Output |
|
|
43
|
+
|------------|--------|---------|----------|----------|-----------------|
|
|
44
|
+
| **youplot** | **2ms** | **18ms** | **176ms** | **892ms** | Interactive HTML |
|
|
45
|
+
| Plotly | 16ms | 81ms | 676ms | 3.36s | Interactive HTML |
|
|
46
|
+
| Bokeh | 27ms | 100ms | 835ms | 4.14s | Interactive HTML |
|
|
47
|
+
| Matplotlib | 95ms | 125ms | 355ms | 926ms | Static PNG † |
|
|
48
|
+
|
|
49
|
+
† Matplotlib produces a static raster image with no interactivity.
|
|
50
|
+
At 500K points: **3.8× faster than Plotly, 4.6× faster than Bokeh.**
|
|
51
|
+
youplot's bundled JS runtime is **50 KB**; Plotly's is 3.5 MB.
|
|
52
|
+
|
|
53
|
+
Run `python benchmark.py` to reproduce.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Quickstart
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import youplot as fp
|
|
61
|
+
|
|
62
|
+
# Unix milliseconds on the x axis
|
|
63
|
+
ts_ms = [1_700_000_000_000 + i * 1000 for i in range(86_400)]
|
|
64
|
+
|
|
65
|
+
fig = fp.Figure(title="24h Temperature", zoom=True, legend=True)
|
|
66
|
+
fig.line(ts_ms, temp, label="°C", color="#f97316", fill=True)
|
|
67
|
+
fig.band(y_lo=18, y_hi=24, label="Comfort", color="#10b981")
|
|
68
|
+
fig.vline(x=ts_ms[3600], label="Event", color="#dc2626")
|
|
69
|
+
fig.save("temperature.html") # open in any browser, no install
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Multi-chart dashboard with crosshair sync
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
fig1 = fp.Figure(title="Engine", height=380, zoom=True)
|
|
76
|
+
fig1.line(ts_ms, coolant, label="Coolant °C", color="#ef4444")
|
|
77
|
+
fig1.line(ts_ms, oil, label="Oil °C", color="#f97316", dash=True)
|
|
78
|
+
fig1.tag(x_start=fault_start, x_end=fault_end, label="Fault window")
|
|
79
|
+
fig1.pin(ts_ms[peak_idx], label="Peak: 127°C", y_frac=0.1)
|
|
80
|
+
|
|
81
|
+
fig2 = fp.Figure(title="RPM & Fuel", height=280, zoom=True)
|
|
82
|
+
fig2.line(ts_ms, rpm, label="RPM", color="#6366f1")
|
|
83
|
+
fig2.line(ts_ms, fuel_psi, label="Fuel PSI", color="#16a34a")
|
|
84
|
+
|
|
85
|
+
# Hover on one → cursor syncs on both, each shows its own tooltip
|
|
86
|
+
dash = fig1 + fig2 # or fp.combine(fig1, fig2)
|
|
87
|
+
dash.save("engine.html")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## API
|
|
93
|
+
|
|
94
|
+
### Figure
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
fig = fp.Figure(
|
|
98
|
+
title = "My Chart",
|
|
99
|
+
subtitle = "Optional subtitle",
|
|
100
|
+
theme = "light", # "light" | "dark"
|
|
101
|
+
height = 400,
|
|
102
|
+
width = None, # None = 100% container width
|
|
103
|
+
y_label = "Signal",
|
|
104
|
+
zoom = True, # toolbar: zoom/tag/measure/annotate/export
|
|
105
|
+
legend = True,
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
All methods return `self` for chaining.
|
|
110
|
+
|
|
111
|
+
#### fig.line()
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
fig.line(x, y, label="", color=None, width=2.0, dash=False,
|
|
115
|
+
fill=False, fill_opacity=0.15, points=False, step=False,
|
|
116
|
+
hover_unit="", hover_format="")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### fig.scatter()
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
fig.scatter(x, y, label="", color=None, size=6.0,
|
|
123
|
+
size_by=None, color_by=None, shape="circle",
|
|
124
|
+
trendline=False, jitter_x=0.0, jitter_y=0.0)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### fig.band()
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
fig.band(y_lo=18, y_hi=24, label="Comfort", color="#10b981", opacity=0.12)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### fig.vline() / fig.hline()
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
fig.vline(x=ts_ms[fault_idx], label="Fault", color="#dc2626")
|
|
137
|
+
fig.hline(y=100, label="Max safe", color="#f97316", dash=True)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### fig.region()
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
fig.region(x_start=ts_ms[0], x_end=ts_ms[3600], label="Night",
|
|
144
|
+
color="indigo", opacity=0.06)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### fig.tag()
|
|
148
|
+
|
|
149
|
+
Named region with a coloured header band and clickable bubble below the chart.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
fig.tag(x_start=ts_ms[fault_start], x_end=ts_ms[fault_end],
|
|
153
|
+
label="Fault window", color="#f43f5e", removable=True)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### fig.pin()
|
|
157
|
+
|
|
158
|
+
Annotation pin on the canvas + sticky-note card below the chart.
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
fig.pin(x=ts_ms[peak_idx], label="Peak: 127°C", y_frac=0.1, color="")
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`y_frac` sets vertical position: 0 = top of plot, 1 = bottom.
|
|
165
|
+
Leave `color=""` to auto-cycle through the annotation palette.
|
|
166
|
+
|
|
167
|
+
#### Output
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
fig.save("out.html") # write to disk, returns path
|
|
171
|
+
fig.show() # save to /tmp, open in browser
|
|
172
|
+
html = fig.to_html() # return HTML string
|
|
173
|
+
frag = fig.to_fragment() # fragment (no <head>/<body>) for embedding
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Dashboard / combine
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
dash = fig1 + fig2 + fig3 # operator
|
|
180
|
+
dash = fp.combine(fig1, fig2, fig3, title="...") # function
|
|
181
|
+
dash = fp.Dashboard(title="...").add(fig1).add(fig2) # builder
|
|
182
|
+
|
|
183
|
+
dash.save("dashboard.html")
|
|
184
|
+
dash.show()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Colors
|
|
188
|
+
|
|
189
|
+
- Hex: `"#f43f5e"`
|
|
190
|
+
- Named: `"rose"` `"indigo"` `"emerald"` `"orange"` `"violet"` `"cyan"` `"amber"` `"pink"` `"lime"` `"teal"`
|
|
191
|
+
- `None` — auto-cycles per Figure
|
|
192
|
+
|
|
193
|
+
### Themes
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
fp.Figure(theme="light") # default
|
|
197
|
+
fp.Figure(theme="dark")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## UI Tools (when zoom=True)
|
|
203
|
+
|
|
204
|
+
| Tool | How to use |
|
|
205
|
+
|-----------|-----------|
|
|
206
|
+
| **Zoom** | Left-drag X to zoom. Vertical drag zooms Y. Double-click resets. |
|
|
207
|
+
| **Tag** | Drag to name a region. Click bubble to navigate. |
|
|
208
|
+
| **Measure** | Click anchor, move to see ΔX/ΔY live. |
|
|
209
|
+
| **Annotate** | Click to drop a pin. |
|
|
210
|
+
| **Export** | Download current state (tags + annotations) as HTML. |
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Built on uPlot
|
|
215
|
+
|
|
216
|
+
youplot is a Python API layer over [uPlot](https://github.com/leeoniya/uPlot) by Leon Sorokin.
|
|
217
|
+
uPlot renders 1M points in <35ms, ships at 50KB with zero dependencies.
|
|
218
|
+
All rendering performance comes from uPlot. Please ⭐ the repository.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT © youplot contributors
|
youplot-1.0.0/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# youplot
|
|
2
|
+
|
|
3
|
+
**Extremely fast, lightweight timeseries charts for Python — powered by [uPlot](https://github.com/leeoniya/uPlot).**
|
|
4
|
+
|
|
5
|
+
Write Python. Get a fully self-contained, interactive HTML file. No server. No viewer dependencies.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pip install youplot
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Python 3.10+. No runtime dependencies.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Benchmark (Apple M2 Pro, Python 3.11, 3 series, median of 3 runs)
|
|
16
|
+
|
|
17
|
+
| Library | 1K pts | 10K pts | 100K pts | 500K pts | Output |
|
|
18
|
+
|------------|--------|---------|----------|----------|-----------------|
|
|
19
|
+
| **youplot** | **2ms** | **18ms** | **176ms** | **892ms** | Interactive HTML |
|
|
20
|
+
| Plotly | 16ms | 81ms | 676ms | 3.36s | Interactive HTML |
|
|
21
|
+
| Bokeh | 27ms | 100ms | 835ms | 4.14s | Interactive HTML |
|
|
22
|
+
| Matplotlib | 95ms | 125ms | 355ms | 926ms | Static PNG † |
|
|
23
|
+
|
|
24
|
+
† Matplotlib produces a static raster image with no interactivity.
|
|
25
|
+
At 500K points: **3.8× faster than Plotly, 4.6× faster than Bokeh.**
|
|
26
|
+
youplot's bundled JS runtime is **50 KB**; Plotly's is 3.5 MB.
|
|
27
|
+
|
|
28
|
+
Run `python benchmark.py` to reproduce.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quickstart
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import youplot as fp
|
|
36
|
+
|
|
37
|
+
# Unix milliseconds on the x axis
|
|
38
|
+
ts_ms = [1_700_000_000_000 + i * 1000 for i in range(86_400)]
|
|
39
|
+
|
|
40
|
+
fig = fp.Figure(title="24h Temperature", zoom=True, legend=True)
|
|
41
|
+
fig.line(ts_ms, temp, label="°C", color="#f97316", fill=True)
|
|
42
|
+
fig.band(y_lo=18, y_hi=24, label="Comfort", color="#10b981")
|
|
43
|
+
fig.vline(x=ts_ms[3600], label="Event", color="#dc2626")
|
|
44
|
+
fig.save("temperature.html") # open in any browser, no install
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Multi-chart dashboard with crosshair sync
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
fig1 = fp.Figure(title="Engine", height=380, zoom=True)
|
|
51
|
+
fig1.line(ts_ms, coolant, label="Coolant °C", color="#ef4444")
|
|
52
|
+
fig1.line(ts_ms, oil, label="Oil °C", color="#f97316", dash=True)
|
|
53
|
+
fig1.tag(x_start=fault_start, x_end=fault_end, label="Fault window")
|
|
54
|
+
fig1.pin(ts_ms[peak_idx], label="Peak: 127°C", y_frac=0.1)
|
|
55
|
+
|
|
56
|
+
fig2 = fp.Figure(title="RPM & Fuel", height=280, zoom=True)
|
|
57
|
+
fig2.line(ts_ms, rpm, label="RPM", color="#6366f1")
|
|
58
|
+
fig2.line(ts_ms, fuel_psi, label="Fuel PSI", color="#16a34a")
|
|
59
|
+
|
|
60
|
+
# Hover on one → cursor syncs on both, each shows its own tooltip
|
|
61
|
+
dash = fig1 + fig2 # or fp.combine(fig1, fig2)
|
|
62
|
+
dash.save("engine.html")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
### Figure
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
fig = fp.Figure(
|
|
73
|
+
title = "My Chart",
|
|
74
|
+
subtitle = "Optional subtitle",
|
|
75
|
+
theme = "light", # "light" | "dark"
|
|
76
|
+
height = 400,
|
|
77
|
+
width = None, # None = 100% container width
|
|
78
|
+
y_label = "Signal",
|
|
79
|
+
zoom = True, # toolbar: zoom/tag/measure/annotate/export
|
|
80
|
+
legend = True,
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
All methods return `self` for chaining.
|
|
85
|
+
|
|
86
|
+
#### fig.line()
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
fig.line(x, y, label="", color=None, width=2.0, dash=False,
|
|
90
|
+
fill=False, fill_opacity=0.15, points=False, step=False,
|
|
91
|
+
hover_unit="", hover_format="")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### fig.scatter()
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
fig.scatter(x, y, label="", color=None, size=6.0,
|
|
98
|
+
size_by=None, color_by=None, shape="circle",
|
|
99
|
+
trendline=False, jitter_x=0.0, jitter_y=0.0)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### fig.band()
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
fig.band(y_lo=18, y_hi=24, label="Comfort", color="#10b981", opacity=0.12)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### fig.vline() / fig.hline()
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
fig.vline(x=ts_ms[fault_idx], label="Fault", color="#dc2626")
|
|
112
|
+
fig.hline(y=100, label="Max safe", color="#f97316", dash=True)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### fig.region()
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
fig.region(x_start=ts_ms[0], x_end=ts_ms[3600], label="Night",
|
|
119
|
+
color="indigo", opacity=0.06)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### fig.tag()
|
|
123
|
+
|
|
124
|
+
Named region with a coloured header band and clickable bubble below the chart.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
fig.tag(x_start=ts_ms[fault_start], x_end=ts_ms[fault_end],
|
|
128
|
+
label="Fault window", color="#f43f5e", removable=True)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### fig.pin()
|
|
132
|
+
|
|
133
|
+
Annotation pin on the canvas + sticky-note card below the chart.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
fig.pin(x=ts_ms[peak_idx], label="Peak: 127°C", y_frac=0.1, color="")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`y_frac` sets vertical position: 0 = top of plot, 1 = bottom.
|
|
140
|
+
Leave `color=""` to auto-cycle through the annotation palette.
|
|
141
|
+
|
|
142
|
+
#### Output
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
fig.save("out.html") # write to disk, returns path
|
|
146
|
+
fig.show() # save to /tmp, open in browser
|
|
147
|
+
html = fig.to_html() # return HTML string
|
|
148
|
+
frag = fig.to_fragment() # fragment (no <head>/<body>) for embedding
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Dashboard / combine
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
dash = fig1 + fig2 + fig3 # operator
|
|
155
|
+
dash = fp.combine(fig1, fig2, fig3, title="...") # function
|
|
156
|
+
dash = fp.Dashboard(title="...").add(fig1).add(fig2) # builder
|
|
157
|
+
|
|
158
|
+
dash.save("dashboard.html")
|
|
159
|
+
dash.show()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Colors
|
|
163
|
+
|
|
164
|
+
- Hex: `"#f43f5e"`
|
|
165
|
+
- Named: `"rose"` `"indigo"` `"emerald"` `"orange"` `"violet"` `"cyan"` `"amber"` `"pink"` `"lime"` `"teal"`
|
|
166
|
+
- `None` — auto-cycles per Figure
|
|
167
|
+
|
|
168
|
+
### Themes
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
fp.Figure(theme="light") # default
|
|
172
|
+
fp.Figure(theme="dark")
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## UI Tools (when zoom=True)
|
|
178
|
+
|
|
179
|
+
| Tool | How to use |
|
|
180
|
+
|-----------|-----------|
|
|
181
|
+
| **Zoom** | Left-drag X to zoom. Vertical drag zooms Y. Double-click resets. |
|
|
182
|
+
| **Tag** | Drag to name a region. Click bubble to navigate. |
|
|
183
|
+
| **Measure** | Click anchor, move to see ΔX/ΔY live. |
|
|
184
|
+
| **Annotate** | Click to drop a pin. |
|
|
185
|
+
| **Export** | Download current state (tags + annotations) as HTML. |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Built on uPlot
|
|
190
|
+
|
|
191
|
+
youplot is a Python API layer over [uPlot](https://github.com/leeoniya/uPlot) by Leon Sorokin.
|
|
192
|
+
uPlot renders 1M points in <35ms, ships at 50KB with zero dependencies.
|
|
193
|
+
All rendering performance comes from uPlot. Please ⭐ the repository.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT © youplot contributors
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "youplot"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Extremely fast, lightweight timeseries charts for Python — powered by uPlot"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
keywords = ["charts", "timeseries", "visualization", "uplot", "html", "interactive", "plotting"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/YOUR_USERNAME/youplot"
|
|
28
|
+
Documentation = "https://github.com/YOUR_USERNAME/youplot#readme"
|
|
29
|
+
Repository = "https://github.com/YOUR_USERNAME/youplot"
|
|
30
|
+
"Bug Tracker" = "https://github.com/YOUR_USERNAME/youplot/issues"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["."]
|
|
34
|
+
include = ["youplot*"]
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.package-data]
|
|
37
|
+
youplot = ["vendor/*.js", "vendor/*.css"]
|
youplot-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
youplot — Extremely fast, lightweight timeseries charts for Python.
|
|
3
|
+
Powered by uPlot (https://github.com/leeoniya/uPlot).
|
|
4
|
+
|
|
5
|
+
Quick start::
|
|
6
|
+
|
|
7
|
+
import youplot as fp
|
|
8
|
+
|
|
9
|
+
fig1 = fp.Figure(title="Temperature", zoom=True)
|
|
10
|
+
fig1.line(ts_ms, temp, label="°C", color="#f97316", fill=True)
|
|
11
|
+
fig1.band(y_lo=18, y_hi=24, label="Comfort zone", color="#10b981")
|
|
12
|
+
fig1.tag(x_start=ts_ms[0], x_end=ts_ms[3600], label="Night")
|
|
13
|
+
fig1.pin(ts_ms[peak], label="Peak: 38°C", y_frac=0.1)
|
|
14
|
+
|
|
15
|
+
fig2 = fp.Figure(title="Humidity", zoom=True)
|
|
16
|
+
fig2.line(ts_ms, humidity, label="%", color="#6366f1")
|
|
17
|
+
|
|
18
|
+
# Combine → synced crosshair, one HTML file
|
|
19
|
+
dash = fig1 + fig2 # or fp.combine(fig1, fig2)
|
|
20
|
+
dash.save("dashboard.html")
|
|
21
|
+
dash.show()
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from youplot.figure import Figure, Dashboard, combine
|
|
25
|
+
from youplot.options.annotations import VLine, HLine, Region, Band, Pin
|
|
26
|
+
from youplot.colors.palette import resolve as resolve_color, NAMED as COLORS
|
|
27
|
+
from youplot.themes.base import LIGHT, DARK
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"Figure", "Dashboard", "combine",
|
|
31
|
+
"VLine", "HLine", "Region", "Band", "Pin",
|
|
32
|
+
"resolve_color", "COLORS", "LIGHT", "DARK",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
__version__ = "1.0.0"
|
|
36
|
+
__author__ = "youplot contributors"
|
|
37
|
+
__license__ = "MIT"
|
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Color palette for youplot.
|
|
3
|
+
Named colors resolve to hex. Default cycle ensures series never clash.
|
|
4
|
+
Inspired by Tailwind + Observable's categorical palette.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# ── Named color map ────────────────────────────────────────────────────────────
|
|
8
|
+
# Tailwind-derived but tuned for data viz (slightly more saturated)
|
|
9
|
+
|
|
10
|
+
NAMED = {
|
|
11
|
+
# Blues / purples
|
|
12
|
+
"indigo": "#6366f1",
|
|
13
|
+
"violet": "#7c3aed",
|
|
14
|
+
"purple": "#9333ea",
|
|
15
|
+
"blue": "#3b82f6",
|
|
16
|
+
"sky": "#0ea5e9",
|
|
17
|
+
"cyan": "#06b6d4",
|
|
18
|
+
|
|
19
|
+
# Greens
|
|
20
|
+
"emerald": "#10b981",
|
|
21
|
+
"green": "#22c55e",
|
|
22
|
+
"teal": "#14b8a6",
|
|
23
|
+
|
|
24
|
+
# Warm
|
|
25
|
+
"amber": "#f59e0b",
|
|
26
|
+
"orange": "#f97316",
|
|
27
|
+
"yellow": "#eab308",
|
|
28
|
+
|
|
29
|
+
# Reds / pinks
|
|
30
|
+
"rose": "#f43f5e",
|
|
31
|
+
"red": "#ef4444",
|
|
32
|
+
"pink": "#ec4899",
|
|
33
|
+
|
|
34
|
+
# Neutrals
|
|
35
|
+
"slate": "#64748b",
|
|
36
|
+
"gray": "#6b7280",
|
|
37
|
+
"zinc": "#71717a",
|
|
38
|
+
"white": "#ffffff",
|
|
39
|
+
"black": "#000000",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# ── Default series cycle ────────────────────────────────────────────────────────
|
|
43
|
+
# Ordered for max visual separation. Observable-inspired but warmer.
|
|
44
|
+
|
|
45
|
+
DEFAULT_CYCLE = [
|
|
46
|
+
"#6366f1", # indigo
|
|
47
|
+
"#f59e0b", # amber
|
|
48
|
+
"#10b981", # emerald
|
|
49
|
+
"#f43f5e", # rose
|
|
50
|
+
"#0ea5e9", # sky
|
|
51
|
+
"#9333ea", # purple
|
|
52
|
+
"#f97316", # orange
|
|
53
|
+
"#14b8a6", # teal
|
|
54
|
+
"#ec4899", # pink
|
|
55
|
+
"#3b82f6", # blue
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
# Dark theme variants — slightly lighter/more saturated for dark bg legibility
|
|
59
|
+
DEFAULT_CYCLE_DARK = [
|
|
60
|
+
"#818cf8", # indigo-400
|
|
61
|
+
"#fbbf24", # amber-400
|
|
62
|
+
"#34d399", # emerald-400
|
|
63
|
+
"#fb7185", # rose-400
|
|
64
|
+
"#38bdf8", # sky-400
|
|
65
|
+
"#c084fc", # purple-400
|
|
66
|
+
"#fb923c", # orange-400
|
|
67
|
+
"#2dd4bf", # teal-400
|
|
68
|
+
"#f472b6", # pink-400
|
|
69
|
+
"#60a5fa", # blue-400
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def resolve(color: str | None, dark: bool = False) -> str:
|
|
74
|
+
"""
|
|
75
|
+
Resolve a color name or passthrough a hex/rgb string.
|
|
76
|
+
|
|
77
|
+
resolve("indigo") → "#6366f1"
|
|
78
|
+
resolve("#ff0000") → "#ff0000"
|
|
79
|
+
resolve("rgb(0,0,0)") → "rgb(0,0,0)"
|
|
80
|
+
resolve(None) → raises ValueError
|
|
81
|
+
"""
|
|
82
|
+
if color is None:
|
|
83
|
+
raise ValueError("Color cannot be None — use next_color() to get default")
|
|
84
|
+
if color in NAMED:
|
|
85
|
+
return NAMED[color]
|
|
86
|
+
if color.startswith("#") or color.startswith("rgb"):
|
|
87
|
+
return color
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"Unknown color '{color}'. Use a named color ({', '.join(NAMED)}) or a hex string."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ColorCycle:
|
|
94
|
+
"""Stateful iterator over the default color cycle. One per Figure."""
|
|
95
|
+
|
|
96
|
+
def __init__(self, dark: bool = False):
|
|
97
|
+
self._cycle = DEFAULT_CYCLE_DARK if dark else DEFAULT_CYCLE
|
|
98
|
+
self._idx = 0
|
|
99
|
+
|
|
100
|
+
def next(self) -> str:
|
|
101
|
+
color = self._cycle[self._idx % len(self._cycle)]
|
|
102
|
+
self._idx += 1
|
|
103
|
+
return color
|
|
104
|
+
|
|
105
|
+
def reset(self):
|
|
106
|
+
self._idx = 0
|