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.
Files changed (35) hide show
  1. youplot-1.0.0/LICENSE +21 -0
  2. youplot-1.0.0/MANIFEST.in +3 -0
  3. youplot-1.0.0/PKG-INFO +224 -0
  4. youplot-1.0.0/README.md +199 -0
  5. youplot-1.0.0/pyproject.toml +37 -0
  6. youplot-1.0.0/setup.cfg +4 -0
  7. youplot-1.0.0/youplot/__init__.py +37 -0
  8. youplot-1.0.0/youplot/colors/__init__.py +0 -0
  9. youplot-1.0.0/youplot/colors/palette.py +106 -0
  10. youplot-1.0.0/youplot/examples/basic_line.py +123 -0
  11. youplot-1.0.0/youplot/figure.py +518 -0
  12. youplot-1.0.0/youplot/options/__init__.py +0 -0
  13. youplot-1.0.0/youplot/options/annotations.py +60 -0
  14. youplot-1.0.0/youplot/options/axes.py +22 -0
  15. youplot-1.0.0/youplot/render/css.py +578 -0
  16. youplot-1.0.0/youplot/render/html.py +320 -0
  17. youplot-1.0.0/youplot/render/js.py +1080 -0
  18. youplot-1.0.0/youplot/render/scatter_js.py +195 -0
  19. youplot-1.0.0/youplot/render/serializer.py +242 -0
  20. youplot-1.0.0/youplot/series/__init__.py +2 -0
  21. youplot-1.0.0/youplot/series/line.py +70 -0
  22. youplot-1.0.0/youplot/series/scatter.py +76 -0
  23. youplot-1.0.0/youplot/themes/__init__.py +0 -0
  24. youplot-1.0.0/youplot/themes/base.py +105 -0
  25. youplot-1.0.0/youplot/utils/__init__.py +0 -0
  26. youplot-1.0.0/youplot/utils/browser.py +22 -0
  27. youplot-1.0.0/youplot/utils/data.py +150 -0
  28. youplot-1.0.0/youplot/vendor/__init__.py +12 -0
  29. youplot-1.0.0/youplot/vendor/__pycache__/__init__.cpython-311.pyc +0 -0
  30. youplot-1.0.0/youplot/vendor/uplot.iife.min.js +2 -0
  31. youplot-1.0.0/youplot/vendor/uplot.min.css +1 -0
  32. youplot-1.0.0/youplot.egg-info/PKG-INFO +224 -0
  33. youplot-1.0.0/youplot.egg-info/SOURCES.txt +33 -0
  34. youplot-1.0.0/youplot.egg-info/dependency_links.txt +1 -0
  35. 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.
@@ -0,0 +1,3 @@
1
+ recursive-include youplot/vendor *
2
+ include README.md
3
+ include LICENSE
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
@@ -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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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