youplot 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.
youplot/__init__.py 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
@@ -0,0 +1,123 @@
1
+ """
2
+ Basic example — weather station telemetry over a 24-hour period.
3
+ Two synced charts via up.combine() / the + operator.
4
+
5
+ Run from the youplot parent directory:
6
+ python -m youplot.examples.basic_line
7
+ """
8
+
9
+ import sys, os
10
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
11
+
12
+ import math, time
13
+ import youplot as up
14
+
15
+
16
+ def make_data(n=1440, seed=7):
17
+ base_ts = int(time.time()) - 86400
18
+ ts_ms = [(base_ts + i * 60) * 1000 for i in range(n)]
19
+
20
+ def noise(scale, offset=0):
21
+ import random; random.seed(seed + offset)
22
+ return [random.gauss(0, scale) for _ in range(n)]
23
+
24
+ def diurnal(lo, hi, phase=0):
25
+ return [lo + (hi-lo)*0.5*(1-math.cos(2*math.pi*(i/n+phase))) for i in range(n)]
26
+
27
+ temp_c = [round(diurnal(14,31)[i] + noise(0.3,1)[i], 1) for i in range(n)]
28
+ feels = [round(temp_c[i] - 2.5 + 0.4*math.sin(i/120) + noise(1.5,2)[i], 1) for i in range(n)]
29
+ humidity = [round(max(10, min(100, diurnal(80,35,0.5)[i] + noise(2,3)[i])), 1) for i in range(n)]
30
+ dew_point = [round(temp_c[i] - (100-humidity[i])/5 + noise(0.6,4)[i], 1) for i in range(n)]
31
+ pressure = [round(1012 + 6*math.sin(2*math.pi*i/n) + 3*math.cos(2*math.pi*i/(n*0.4)) + noise(0.4,5)[i], 1) for i in range(n)]
32
+ wind_spd = [round(max(0, 8 + 6*math.sin(i/180+1) + abs(noise(2,6)[i])), 1) for i in range(n)]
33
+
34
+ day_frac = lambda i: (i%n)/n
35
+ solar = [max(0, round(900*math.sin(math.pi*max(0,(day_frac(i)-0.25))/0.5)**1.2 + noise(15,7)[i], 0))
36
+ if 0.25 <= day_frac(i) <= 0.75 else 0 for i in range(n)]
37
+ uv = [round(max(0, min(11, solar[i]/80 + noise(0.2,8)[i])), 1) for i in range(n)]
38
+ pm25 = [round(max(0, 12
39
+ + 20*math.exp(-((i-n*0.30)**2)/(2*(n*0.04)**2))
40
+ + 15*math.exp(-((i-n*0.75)**2)/(2*(n*0.04)**2))
41
+ + abs(noise(3,9)[i])), 1) for i in range(n)]
42
+ rain = [0.0]*n
43
+ sc = int(n*0.58)
44
+ for i in range(n):
45
+ d = i - sc
46
+ if abs(d) < 60:
47
+ rain[i] = round(max(0, 8*math.exp(-(d**2)/800) + noise(0.3,10)[i]), 2)
48
+
49
+ return ts_ms, temp_c, feels, humidity, dew_point, pressure, wind_spd, solar, uv, pm25, rain
50
+
51
+
52
+ def main():
53
+ ts_ms, temp_c, feels, humidity, dew_point, pressure, wind_spd, solar, uv, pm25, rain = make_data()
54
+ n = len(ts_ms)
55
+
56
+ # ── Chart 1: Temperature, Humidity, Wind, Pressure ───────────────────────
57
+ fig1 = up.Figure(
58
+ title="Temperature · Humidity · Wind · Pressure",
59
+ subtitle="Hover to sync crosshair · drag to zoom · vertical drag zooms Y · annotate to pin notes",
60
+ theme="light", height=300,
61
+ y_label="Temp °C / Humidity % / Wind km·h⁻¹",
62
+ y_right_label="Pressure hPa",
63
+ zoom=True, legend=True,
64
+ )
65
+ fig1.line(ts_ms, temp_c, label="Temp °C", color="#f97316", width=2.5, hover_unit=" °C")
66
+ fig1.line(ts_ms, feels, label="Feels Like °C", color="#fb923c", width=1.5, dash=True, hover_unit=" °C")
67
+ fig1.line(ts_ms, humidity, label="Humidity %", color="#38bdf8", width=2.0, fill=True, fill_opacity=0.07, hover_unit="%")
68
+ fig1.line(ts_ms, dew_point, label="Dew Point °C", color="#0ea5e9", width=1.5, dash=True, hover_unit=" °C")
69
+ fig1.line(ts_ms, wind_spd, label="Wind km/h", color="#a3e635", width=1.5, hover_unit=" km/h")
70
+ # fig1.line(ts_ms, pressure, label="Pressure hPa", color="#8b5cf6", width=1.5, axis="right", hover_unit=" hPa")
71
+
72
+ fig1.band(y_lo=25, y_hi=35, label="Heat stress", color="#f97316", opacity=0.07)
73
+ fig1.band(y_lo=70, y_hi=100, label="High humidity", color="#38bdf8", opacity=0.06)
74
+
75
+ fig1.region(x_start=ts_ms[0], x_end=ts_ms[int(n*0.25)], color="#6366f1", opacity=0.04)
76
+ fig1.region(x_start=ts_ms[int(n*0.88)], x_end=ts_ms[-1], color="#6366f1", opacity=0.04)
77
+
78
+ fig1.vline(x=ts_ms[int(n*0.25)], label="Sunrise", color="#f97316")
79
+ fig1.vline(x=ts_ms[int(n*0.75)], label="Sunset", color="#8b5cf6")
80
+ fig1.hline(y=25, label="Heat threshold", color="#f97316", dash=True)
81
+ fig1.hline(y=60, label="High humidity", color="#38bdf8", dash=True)
82
+
83
+ fig1.tag(x_start=ts_ms[int(n*0.28)], x_end=ts_ms[int(n*0.40)],
84
+ label="Morning Rush", color="#f43f5e", removable=False)
85
+ fig1.tag(x_start=ts_ms[int(n*0.72)], x_end=ts_ms[int(n*0.80)],
86
+ label="Evening Peak", color="#8b5cf6", removable=True)
87
+
88
+ # Code-defined annotation pins
89
+ # fig1.pin(ts_ms[int(n*0.25)], label="Sunrise crossover", y_frac=0.15, color="#f97316")
90
+ # fig1.pin(ts_ms[int(n*0.52)], label="Temp peak 31°C", y_frac=0.05, color="#f43f5e")
91
+
92
+ # ── Chart 2: Solar, Air Quality, Rain ────────────────────────────────────
93
+ fig2 = up.Figure(
94
+ title="Solar · Air Quality · Rain",
95
+ subtitle="Crosshair synced with chart above",
96
+ theme="light", height=300,
97
+ y_label="Solar W/m² / UV / PM2.5 µg/m³",
98
+ y_right_label="Rain mm/h",
99
+ zoom=True, legend=True,
100
+ )
101
+ fig2.line(ts_ms, solar, label="Solar W/m²", color="#facc15", width=2.0, fill=True, fill_opacity=0.08, hover_unit=" W/m²")
102
+ fig2.line(ts_ms, uv, label="UV Index", color="#fbbf24", width=1.5, dash=True)
103
+ fig2.line(ts_ms, pm25, label="PM2.5 µg/m³", color="#f43f5e", width=1.5, hover_unit=" µg/m³")
104
+ fig2.line(ts_ms, rain, label="Rain mm/h", color="#06b6d4", width=1.5, fill=True, fill_opacity=0.15,
105
+ axis="right", hover_unit=" mm/h")
106
+
107
+ fig2.band(y_lo=35, y_hi=150, label="PM2.5 Unhealthy", color="#f43f5e", opacity=0.05)
108
+ fig2.vline(x=ts_ms[int(n*0.58)], label="Peak Rain", color="#06b6d4", dash=True)
109
+ fig2.hline(y=35, label="PM2.5 Moderate", color="#f43f5e", dash=True)
110
+ fig2.tag(x_start=ts_ms[int(n*0.55)], x_end=ts_ms[int(n*0.62)],
111
+ label="Afternoon Shower", color="#06b6d4", removable=False)
112
+
113
+
114
+
115
+ dash = up.combine(fig1, fig2, title="Weather Station — 24h Overview")
116
+
117
+ out = dash.save("/tmp/youplot_weather_demo.html")
118
+ print(f"✓ Saved to {out}")
119
+ dash.show()
120
+
121
+
122
+ if __name__ == "__main__":
123
+ main()