ml-time-graph 1.0.0 → 1.0.5
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.
- package/API_DESIGN.md +1211 -0
- package/README.de.md +12 -0
- package/README.md +12 -0
- package/dist/analyze/index.d.ts +272 -46
- package/dist/analyze/index.js +11 -570
- package/dist/index.d.ts +346 -9
- package/dist/index.js +57 -3420
- package/dist/interaction/index.d.ts +1 -1
- package/dist/interaction/index.js +2 -435
- package/dist/internals.d.ts +20 -5
- package/dist/internals.js +40 -2455
- package/dist/{layout-Sc5UkC0r.d.ts → layout-BOYtrsZa.d.ts} +1 -1
- package/dist/{scale-Cbr0KpPz.d.ts → scale-BRE_QhbZ.d.ts} +38 -15
- package/package.json +65 -80
- package/dist/aggregated_subtypes-DZNZyFTX.d.ts +0 -43
- /package/{MKT_AGGREGATE.md → docs/MKT_AGGREGATE.md} +0 -0
package/API_DESIGN.md
ADDED
|
@@ -0,0 +1,1211 @@
|
|
|
1
|
+
# MLTimeGraph — API-Referenz (v1.0.0)
|
|
2
|
+
|
|
3
|
+
*Stand: v1.0.0 — erste npm-Veröffentlichung nach dem Cleanup. Eine API-Form, keine `@deprecated`-Pfade.*
|
|
4
|
+
|
|
5
|
+
Dieses Dokument ist die **API-Referenz für die aktuelle Library-Form**: nested
|
|
6
|
+
`SeriesStyle`-System, strukturiertes `FillSpec`-Modell (inkl. `outer`-Gate für
|
|
7
|
+
klassifizierte Zonen), Statistik-Overlays (`movingMkt` / `movingAverage` /
|
|
8
|
+
`stdDevBand` / `limits`), First-Class-Achsen, `GapsConfig` mit Auto-Detect +
|
|
9
|
+
`bridge_line`-Mode, Theme-Runtime, sowie die Subpath-Module
|
|
10
|
+
`ml-time-graph/analyze` und `ml-time-graph/interaction`.
|
|
11
|
+
|
|
12
|
+
Praktische Beispiele leben in [USAGE.md](USAGE.md) und in der Demo-Galerie
|
|
13
|
+
unter [`demos/`](demos/). Die Migrations-Historie ist als **Appendix** am
|
|
14
|
+
Ende dieses Dokuments — als Begründungs-Kontext für Designentscheidungen.
|
|
15
|
+
|
|
16
|
+
## Inhalt
|
|
17
|
+
|
|
18
|
+
1. [Ziel der Library](#1-ziel-der-library)
|
|
19
|
+
2. [Design-Prinzipien](#2-design-prinzipien)
|
|
20
|
+
3. [Daten-Vertrag](#3-daten-vertrag)
|
|
21
|
+
4. [Style-System (Serie)](#4-style-system-serie)
|
|
22
|
+
5. [Fill-Modell](#5-fill-modell)
|
|
23
|
+
6. [Achsen + Grid](#6-achsen--grid)
|
|
24
|
+
7. [Gaps](#7-gaps)
|
|
25
|
+
8. [Thresholds](#8-thresholds)
|
|
26
|
+
9. [Annotationen (Marker, Highlights, Overlays, Bands)](#9-annotationen-marker-highlights-overlays-bands)
|
|
27
|
+
10. [Statistik-Overlays (Moving Avg / MKT / Limits)](#10-statistik-overlays-moving-avg--mkt--limits)
|
|
28
|
+
11. [Theme + Patterns (Modul-Struktur)](#11-theme--patterns-modul-struktur)
|
|
29
|
+
12. [MLTimeGraphOptions — Top-Level-Form](#12-mltimegraphoptions--top-level-form)
|
|
30
|
+
|
|
31
|
+
**Appendix:**
|
|
32
|
+
|
|
33
|
+
A. [Migrations-Historie (v0.2 → v0.3 → Cleanup)](#a-migrations-historie-v02--v03--cleanup)
|
|
34
|
+
B. [Polish-Backlog (post-v0.3)](#b-polish-backlog-post-v03)
|
|
35
|
+
C. [Aufgelöste Design-Entscheidungen](#c-aufgelöste-design-entscheidungen)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 1. Ziel der Library
|
|
40
|
+
|
|
41
|
+
> **MLTimeGraph visualisiert Sensor-Messwerte über die Zeit.**
|
|
42
|
+
>
|
|
43
|
+
> - Mehrere Serien pro Chart, optional zwei Y-Achsen (links / rechts).
|
|
44
|
+
> - Beliebige Kombination aus **Linie, Fläche, Punkten, Step** pro Serie —
|
|
45
|
+
> inkl. mehrerer übereinandergelegter Linien (z. B. Step + Catmull-Smooth)
|
|
46
|
+
> für dieselben Daten.
|
|
47
|
+
> - Schwerpunkt: **Datenanalyse mit reichen Annotationen** (Marker, Highlights,
|
|
48
|
+
> Pfeile/Labels/Boxen in Datenkoordinaten, horizontale Annotation-Bänder).
|
|
49
|
+
> - **Statistik-Overlays** auf Roh-Daten: Moving Average, Moving Mean Kinetic
|
|
50
|
+
> Temperature, Standardabweichungs-Band, Limit-Überschreitungen.
|
|
51
|
+
> - **Hatching auf jeder gefüllten Fläche** (Fill-to-Threshold, Zonen-Fill,
|
|
52
|
+
> Min/Max/Avg-Fill, Gap-Fill, Threshold-Halbebene, Annotation-Band-Item) —
|
|
53
|
+
> 11 vorgefertigte Muster siehe [Demo Hatch](demos/hatch.html).
|
|
54
|
+
> - Renderer-agnostisch (`DrawCommand[]` → `Renderer.render()`).
|
|
55
|
+
> - Keine Runtime-Dependencies. MIT mit Attribution.
|
|
56
|
+
|
|
57
|
+
**Begleit-Module** (im selben Repo, eigene Subpath-Exports):
|
|
58
|
+
|
|
59
|
+
- `ml-time-graph/analyze` — Analyse-Helfer (Aggregation, Downsampling,
|
|
60
|
+
Gap-Detection, MKT, StdDev, Limit-Statistik, Geometrie-Splits).
|
|
61
|
+
Kandidat für späteren Ausgliederungs-Schnitt zu `mlcanalyze` (separate
|
|
62
|
+
Library), sobald (a) das API stabil ist und (b) ein zweiter Konsument
|
|
63
|
+
auftaucht. Bis dahin one-repo.
|
|
64
|
+
- `ml-time-graph/interaction` — optionale Interaktions-Helfer (Zoom,
|
|
65
|
+
Minimap, Tooltip). Die Chart selbst bleibt rein darstellend; wer
|
|
66
|
+
interagieren will, importiert diese Helfer dazu oder baut eigene.
|
|
67
|
+
|
|
68
|
+
Was die Library **nicht** ist: kein Universal-Chart, kein Finanzchart (kein
|
|
69
|
+
OHLC).
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## 2. Design-Prinzipien
|
|
75
|
+
|
|
76
|
+
Diese Prinzipien sind verbindlich für alle Entscheidungen unten.
|
|
77
|
+
|
|
78
|
+
| # | Prinzip | Begründung |
|
|
79
|
+
|---|---------|------------|
|
|
80
|
+
| **P1** | **Eine Sache, ein Konzept.** Nur eine API für "fülle Fläche", nur eine für "Linie zeichnen", nur eine für "Gap konfigurieren". | Reduziert die zu lernende Oberfläche, verhindert Spec-Drift. |
|
|
81
|
+
| **P2** | **Daten sind Daten.** Das Datenmodell trägt nur, was zum Zeichnen des **Werts an einer Zeit** nötig ist. Statistik, Limits, Domain-Kontext sind separate Layer. | Wiederverwendbarkeit, Testbarkeit, klare Verantwortung. |
|
|
82
|
+
| **P3** | **Verschachtelte Styles statt flacher Properties.** `style.line.color` ist auffindbarer als `color` neben `lineWidth` neben `shadowColor`. | Bessere IDE-Completion, weniger "wo war das nochmal?". |
|
|
83
|
+
| **P4** | **Defaults im Theme, nicht im Aufruf.** Wer keinen Style angibt, kriegt sinnvolle Werte aus dem Theme. Theme ist mutierbar pro Chart. | Konsistenz, schnelle Restyling. |
|
|
84
|
+
| **P5** | **Strikte TypeScript-Typen.** Diskriminierte Unions wo immer möglich (`kind:` als Diskriminator), keine `any`, keine offenen `Record`-Typen für interne API. | Compiler-Sicherheit, weniger Laufzeitfehler. |
|
|
85
|
+
| **P6** | **JSDoc auf jedem öffentlichen Member.** Was, optional Default, Beispiel. | Hover-Doku in jedem IDE. |
|
|
86
|
+
| **P7** | **Max 250 Zeilen pro Datei.** | Lesbarkeit (Projekt-Style-Guide). |
|
|
87
|
+
| **P8** | **Separable Begleit-Module.** Analyse + Interaktion sind eigenständige Unterverzeichnisse mit eigenem Barrel und eigenem Subpath-Export. Beide sind reine Add-ons — die Chart selbst funktioniert ohne sie. | Klarer Render-Kern, optionale Schwere, späterer Ausgliederungs-Schnitt ohne API-Bruch möglich. |
|
|
88
|
+
| **P9** | **Kompositions statt Aufzählungs-Typen.** „Welche Darstellungsart" ist keine Enum-Frage (`seriesType: 'line' \| 'area' \| …`), sondern eine Frage *welche Style-Sub-Objekte gesetzt sind*. Linie + Fläche + Punkte gleichzeitig = einfach alle drei setzen. | Aus Anwendersicht naheliegender, beliebige Kombinationen ohne neue Enum-Werte, Renderer-Pipeline klar (jedes Style-Stück = ein Layer). |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 3. Daten-Vertrag
|
|
93
|
+
|
|
94
|
+
### 3.1 Roh-Daten
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
/** Ein Mess-Sample. `value: null` markiert eine Lücke — die Linie bricht dort. */
|
|
98
|
+
export interface DataPoint {
|
|
99
|
+
/** Zeitstempel in ms seit Epoch. */
|
|
100
|
+
time: number;
|
|
101
|
+
/** Mess-Wert. `null` = Gap. */
|
|
102
|
+
value: number | null;
|
|
103
|
+
/** Optionaler punktbezogener Annotationstext (z. B. Event-Marker). */
|
|
104
|
+
annotation?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Synthetischer Punkt — keine echte Messung. Wird automatisch von der
|
|
107
|
+
* Library auf interpoliert eingefügten Punkten gesetzt (Threshold-Schnitte,
|
|
108
|
+
* Gap-Ränder), kann aber auch manuell verwendet werden, wenn man eigene
|
|
109
|
+
* berechnete Werte einbringt (z. B. eine Trendlinie auf zwischengeschobenen
|
|
110
|
+
* Stützstellen).
|
|
111
|
+
*
|
|
112
|
+
* Renderer behandeln `synthetic: true` so:
|
|
113
|
+
* - **Marker-Renderer**: kein Punkt-Marker (auch bei `style.markers.type !== 'none'`).
|
|
114
|
+
* - **Tooltip**: nicht hit-testbar — der Tooltip snapt nur auf echte Messungen.
|
|
115
|
+
* - **Werte-Tabelle**: ausgeblendet (kein "Phantom-Eintrag" in der Values-Tab).
|
|
116
|
+
*
|
|
117
|
+
* Default `undefined` = echte Messung.
|
|
118
|
+
*/
|
|
119
|
+
synthetic?: boolean;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Neu** gegenüber heute: das optionale `synthetic`-Flag. Begründung: der
|
|
124
|
+
`SeriesProcessor` setzt heute schon Interpolations-Punkte an Threshold-Schnitten
|
|
125
|
+
und Gap-Rändern, damit Zonen-Fills exakt an der Schwelle stoppen. Diese
|
|
126
|
+
Punkte würden bei aktivierten Markern fälschlich als "Datenpunkt" auftauchen.
|
|
127
|
+
Das Flag erlaubt es, sie sauber zu ignorieren — und gleichzeitig macht es das
|
|
128
|
+
Konzept "berechneter / eingefügter Punkt" für Konsumenten verfügbar.
|
|
129
|
+
|
|
130
|
+
Daten-Modell-Invariante: **`synthetic` betrifft nur das Rendering, nie die
|
|
131
|
+
Skalen-Berechnung** (ein synthetischer Punkt zählt für Min/Max der Y-Achse).
|
|
132
|
+
|
|
133
|
+
### 3.2 Aggregat (Slim Core + spezialisierte Untertypen)
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
/** Aggregierter Slot — min/max/avg/count über ein Zeitfenster. */
|
|
137
|
+
export interface AggregatedPoint {
|
|
138
|
+
/** Slot-Start in ms. */
|
|
139
|
+
time: number;
|
|
140
|
+
/** Minimum im Slot. `null` = kein Sample im Slot (Gap). */
|
|
141
|
+
min: number | null;
|
|
142
|
+
/** Maximum im Slot. `null` = Gap. */
|
|
143
|
+
max: number | null;
|
|
144
|
+
/** Durchschnitt im Slot. `null` = Gap. */
|
|
145
|
+
avg: number | null;
|
|
146
|
+
/** Anzahl der Roh-Samples im Slot (0 = Gap). */
|
|
147
|
+
count: number;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Vom heutigen Stand entfernt:** `mkt`, `deltaMkt`, `stdDev`, `minutesAboveHigh`,
|
|
152
|
+
`minutesBelowLow`. Die wandern in **spezialisierte Aggregat-Typen** — Interfaces
|
|
153
|
+
die `AggregatedPoint` strukturell erweitern:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
/** Rolling Mean Kinetic Temperature je Zeitslot. */
|
|
157
|
+
export interface MktPoint extends AggregatedPoint {
|
|
158
|
+
/** MKT-Wert (USP <1079.2>) für diesen Slot. `null` = Gap. */
|
|
159
|
+
mkt: number | null;
|
|
160
|
+
/** Delta zum vorherigen Slot (optional). */
|
|
161
|
+
deltaMkt?: number | null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Aggregat mit Standardabweichung. */
|
|
165
|
+
export interface StdDevPoint extends AggregatedPoint {
|
|
166
|
+
/** Standardabweichung der Samples im Slot. `null` = Gap. */
|
|
167
|
+
stdDev: number | null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Aggregat mit Limit-Überschreitungs-Statistik. */
|
|
171
|
+
export interface LimitStatsPoint extends AggregatedPoint {
|
|
172
|
+
/** Anzahl Minuten oberhalb des oberen Limits im Slot. */
|
|
173
|
+
minutesAboveHigh?: number | null;
|
|
174
|
+
/** Anzahl Minuten unterhalb des unteren Limits im Slot. */
|
|
175
|
+
minutesBelowLow?: number | null;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Warum strukturell statt diskriminiert** (siehe Q1-Entscheidung in Appendix D):
|
|
180
|
+
|
|
181
|
+
- Strukturell heisst: `MktPoint[]` ist ein gültiger `AggregatedPoint[]` —
|
|
182
|
+
jedes Standard-Aggregat-Rendering (Band, Min/Max/Avg) funktioniert automatisch.
|
|
183
|
+
- Spezialisierte Views/Overlays können auf die zusätzlichen Felder zugreifen,
|
|
184
|
+
indem sie auf den Untertyp casten oder per `'mkt' in point`-Guard fragen.
|
|
185
|
+
- Kein extra `kind`-Diskriminator, weniger Boilerplate für Konsumenten.
|
|
186
|
+
|
|
187
|
+
**Zwei klare Wege** für Statistik-Werte:
|
|
188
|
+
|
|
189
|
+
| Use case | Was tun |
|
|
190
|
+
|----------|---------|
|
|
191
|
+
| Ich habe **Roh-Daten** und will im Chart eine Trend-/Stat-Linie obendrauf | `overlays: [{ kind: 'movingAverage', window, … }]` — Library rechnet, siehe §10 |
|
|
192
|
+
| Ich habe **fertig berechnete MKT-Slots** und will sie als primäre Daten zeigen | `series: [{ data: { points: mktSlots /* MktPoint[] */ }, view: 'minmaxavg' }]` — Library nimmt die Werte wie sie sind |
|
|
193
|
+
|
|
194
|
+
Beides funktioniert nebeneinander; die Library zwingt dich nicht in eine Form.
|
|
195
|
+
|
|
196
|
+
### 3.3 Eingabe-Pfade — wie man Daten reingibt
|
|
197
|
+
|
|
198
|
+
Flach, strukturell:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
/**
|
|
202
|
+
* Ein Punkt-Array. Strukturell erkannt:
|
|
203
|
+
* - hat `value` → Roh-Datenpunkt(e) → DataPoint[]
|
|
204
|
+
* - hat `min/max/avg` → Aggregat → AggregatedPoint[] (oder spezialisierter Untertyp)
|
|
205
|
+
*/
|
|
206
|
+
export type SeriesPoints = DataPoint[] | AggregatedPoint[];
|
|
207
|
+
|
|
208
|
+
export interface Series {
|
|
209
|
+
name: string;
|
|
210
|
+
/** Die Daten — strukturell erkannt. */
|
|
211
|
+
points: SeriesPoints;
|
|
212
|
+
style?: SeriesStyle;
|
|
213
|
+
// …
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Konsumiert wird das so:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
new MLTimeGraph({
|
|
221
|
+
series: [
|
|
222
|
+
{
|
|
223
|
+
name: 'Temperature',
|
|
224
|
+
points: rawTemperaturePoints, // DataPoint[]
|
|
225
|
+
style: { /* … */ },
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: 'Temperature (hourly avg)',
|
|
229
|
+
points: aggregateBySlot(rawTemperaturePoints, 'hourly'), // AggregatedPoint[]
|
|
230
|
+
view: 'minmaxavg',
|
|
231
|
+
style: { /* … */ },
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: 'MKT (rolling 7d)',
|
|
235
|
+
points: rollingMkt(rawTemperaturePoints, 7 * 24 * 3_600_000), // MktPoint[]
|
|
236
|
+
style: { line: { color: '#7c3aed' } },
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Die Library erkennt den Punkt-Typ strukturell (`'min' in points[0]` →
|
|
243
|
+
Aggregat, sonst Roh). Spezialisierte Aggregat-Typen wie `MktPoint` werden
|
|
244
|
+
für Standard-Rendering wie `AggregatedPoint` behandelt — Views/Overlays
|
|
245
|
+
können bei Bedarf auf die Extra-Felder zugreifen.
|
|
246
|
+
|
|
247
|
+
**Implikation für Phase 4** (siehe Appendix A): `TimeSeries` und `AggregatedSeries`
|
|
248
|
+
gehen weg, eine einzige `Series`-Form bleibt — bis dahin laufen die alten
|
|
249
|
+
Aliase parallel.
|
|
250
|
+
|
|
251
|
+
### 3.4 Helfer
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
import { aggregateBySlot, downsample, detectGaps, parseSeries } from 'ml-time-graph';
|
|
255
|
+
|
|
256
|
+
aggregateBySlot(raw, 'hourly'); // DataPoint[] → AggregatedPoint[]
|
|
257
|
+
downsample(raw, 2000); // LTTB
|
|
258
|
+
detectGaps(raw, minGapMs); // automatische Gap-Detektion
|
|
259
|
+
parseSeries(json); // JSON → DataPoint[]
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Diese sind unverändert und bleiben das einzige offizielle Helfer-Set.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## 4. Style-System (Serie)
|
|
267
|
+
|
|
268
|
+
### 4.1 Series-Definition
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
export interface Series {
|
|
272
|
+
/** Anzeige-Name (Legende, Tooltip). */
|
|
273
|
+
name: string;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Die Mess-Daten. Strukturell erkannt:
|
|
277
|
+
* - Roh: `DataPoint[]` (Punkt hat `value`)
|
|
278
|
+
* - Aggregat: `AggregatedPoint[]` oder Untertyp (`MktPoint[]`, `StdDevPoint[]`, …)
|
|
279
|
+
* (Punkt hat `min`/`max`/`avg`)
|
|
280
|
+
*/
|
|
281
|
+
points: SeriesPoints;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Visuelles Styling. Welche Sub-Objekte gesetzt sind, entscheidet was
|
|
285
|
+
* dargestellt wird: `line` → Linie, `fill` → Fläche, `markers` → Punkte.
|
|
286
|
+
* Beliebige Kombination. Alle weglassen = unsichtbar (kein gültiger Zustand).
|
|
287
|
+
* Defaults aus dem Theme.
|
|
288
|
+
*/
|
|
289
|
+
style?: SeriesStyle;
|
|
290
|
+
|
|
291
|
+
/** Welche Y-Achse: 0 = links, 1 = rechts. Default 0. */
|
|
292
|
+
axis?: 0 | 1;
|
|
293
|
+
|
|
294
|
+
/** Optionale Enum-Map (numerischer Wert → Label) für kategoriale Sensoren. */
|
|
295
|
+
enumMap?: Record<number, string>;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Wie eine aggregierte Serie dargestellt wird. Nur bei Aggregat-Punkten.
|
|
299
|
+
* 'band' = gefülltes Rechteck min→max pro Slot, optional + avg-Linie.
|
|
300
|
+
* 'minmaxavg' = drei Linien (min, max, avg) + optionale Fills.
|
|
301
|
+
* Default: 'minmaxavg'.
|
|
302
|
+
*/
|
|
303
|
+
view?: 'band' | 'minmaxavg';
|
|
304
|
+
|
|
305
|
+
/** Optionale Statistik-Overlays (siehe §10). */
|
|
306
|
+
overlays?: SeriesOverlay[];
|
|
307
|
+
|
|
308
|
+
/** Stabile id für DOM-Selektoren. Auto-generiert, falls leer. */
|
|
309
|
+
id?: string;
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Kein `seriesType`-Enum.** Stattdessen Komposition durch Style-Sub-Objekte
|
|
314
|
+
(Prinzip P9):
|
|
315
|
+
|
|
316
|
+
| Was du willst | Style |
|
|
317
|
+
|---------------|-------|
|
|
318
|
+
| Nur Linie | `{ line: { color: '#4285f4' } }` |
|
|
319
|
+
| Nur Fläche (Area) | `{ line: false, fill: '#4285f422' }` |
|
|
320
|
+
| Nur Punkte | `{ line: false, markers: { type: 'circle' } }` |
|
|
321
|
+
| Linie + Fläche | `{ line: { color: '#4285f4' }, fill: '#4285f422' }` |
|
|
322
|
+
| Linie + Punkte | `{ line: {...}, markers: { type: 'circle' } }` |
|
|
323
|
+
| Linie + Fläche + Punkte | alle drei Sub-Objekte setzen |
|
|
324
|
+
| Step + Catmull-Smooth gleichzeitig | `line: [{ shape: 'step', color: '#999', width: 1 }, { shape: 'line', smoothing: true, color: '#3b82f6' }]` |
|
|
325
|
+
|
|
326
|
+
`line: LineStyle[]` legt mehrere Linien durch dieselben Punkte — Reihenfolge =
|
|
327
|
+
Z-Reihenfolge. Saubere Lösung für „Schritt-Daten plus geglättete Trendlinie"
|
|
328
|
+
ohne die Daten zu duplizieren.
|
|
329
|
+
|
|
330
|
+
### 4.2 `SeriesStyle`
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
export interface SeriesStyle {
|
|
334
|
+
/**
|
|
335
|
+
* Linien-Stil. Drei Formen:
|
|
336
|
+
* LineStyle → eine Linie
|
|
337
|
+
* LineStyle[] → mehrere übereinander (z. B. Step + Catmull-Smooth)
|
|
338
|
+
* false → keine Linie zeichnen
|
|
339
|
+
* Default: eine Linie mit Theme-Defaults.
|
|
340
|
+
*/
|
|
341
|
+
line?: LineStyle | LineStyle[] | false;
|
|
342
|
+
|
|
343
|
+
/** Flächen-Stil (siehe §5). */
|
|
344
|
+
fill?: FillSpec;
|
|
345
|
+
|
|
346
|
+
/** Punkt-Marker auf den Samples. */
|
|
347
|
+
markers?: MarkerStyle;
|
|
348
|
+
|
|
349
|
+
/** Drop-Shadow für Linie und Punkte. */
|
|
350
|
+
shadow?: ShadowStyle;
|
|
351
|
+
|
|
352
|
+
/** Pro-Serie Override für Gap-Darstellung (siehe §7). */
|
|
353
|
+
gap?: GapStyle;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export interface LineStyle {
|
|
357
|
+
/** Stroke-Farbe (Hex / rgba). */
|
|
358
|
+
color?: string;
|
|
359
|
+
/** Strichbreite in px. Default Theme-`strokeWidth`. */
|
|
360
|
+
width?: number;
|
|
361
|
+
/** Strich-Muster. Default 'solid'. */
|
|
362
|
+
style?: LineVariant;
|
|
363
|
+
/** Catmull-Rom-Glättung. Default false. */
|
|
364
|
+
smoothing?: boolean;
|
|
365
|
+
/** Linienform — 'line' (Default) oder 'step' (Treppe). */
|
|
366
|
+
shape?: 'line' | 'step';
|
|
367
|
+
/** Max. Zeitabstand (ms) zwischen Punkten, bevor die Linie bricht. 0 = aus. */
|
|
368
|
+
gapThreshold?: number;
|
|
369
|
+
/** Deckkraft 0..1 (z. B. um eine darunterliegende Step-Linie scheinen zu lassen). */
|
|
370
|
+
opacity?: number;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Alle Strich-Varianten (Quelle: theme.ts). */
|
|
374
|
+
export type LineVariant =
|
|
375
|
+
| 'solid'
|
|
376
|
+
| 'dotted'
|
|
377
|
+
| 'sparse-dots'
|
|
378
|
+
| 'dashed'
|
|
379
|
+
| 'long-dash'
|
|
380
|
+
| 'dense-dash'
|
|
381
|
+
| 'dash-dot'
|
|
382
|
+
| 'dash-dot-dot'
|
|
383
|
+
| 'loose-dash';
|
|
384
|
+
|
|
385
|
+
export interface MarkerStyle {
|
|
386
|
+
/** Marker-Shape (Default 'none'). */
|
|
387
|
+
type?: PointStyleType;
|
|
388
|
+
/** Größe (Radius oder Halb-Breite) in px. */
|
|
389
|
+
size?: number;
|
|
390
|
+
/** Outline-Farbe. */
|
|
391
|
+
stroke?: string;
|
|
392
|
+
/** Füll-Farbe (für gefüllte Shapes). */
|
|
393
|
+
fill?: string;
|
|
394
|
+
/** Strichbreite des Outlines. */
|
|
395
|
+
strokeWidth?: number;
|
|
396
|
+
/** Marker nur zeigen, wenn die Serie ≤ n Samples hat (Anti-Clutter). */
|
|
397
|
+
threshold?: number;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** Alle Marker-Shapes (Quelle: theme.ts — 13 Varianten). */
|
|
401
|
+
export type PointStyleType =
|
|
402
|
+
| 'none'
|
|
403
|
+
| 'circle' // gefüllter Kreis
|
|
404
|
+
| 'cross' // zwei diagonale Linien
|
|
405
|
+
| 'square' // gefülltes Quadrat
|
|
406
|
+
| 'diamond' // gedrehtes Quadrat
|
|
407
|
+
| 'triangle' // Dreieck (Spitze oben)
|
|
408
|
+
| 'triangle-down' // Dreieck (Spitze unten)
|
|
409
|
+
| 'star' // 5-zackiger Stern
|
|
410
|
+
| 'arrow' // Chevron nach oben
|
|
411
|
+
| 'plus' // Plus-Zeichen
|
|
412
|
+
| 'hexagon' // Sechseck
|
|
413
|
+
| 'hourglass' // Sanduhr-Form
|
|
414
|
+
| 'line-horizontal'; // horizontaler Strich
|
|
415
|
+
|
|
416
|
+
export interface ShadowStyle {
|
|
417
|
+
color?: string;
|
|
418
|
+
blur?: number;
|
|
419
|
+
offsetX?: number;
|
|
420
|
+
offsetY?: number;
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Entfernt** gegenüber heute:
|
|
425
|
+
|
|
426
|
+
- `dashed: boolean` (nur noch `line.style = 'dashed'`).
|
|
427
|
+
- Separate `lineWidth`, `smoothing`, `pointStyle`, `pointSize`, `shadowColor/Blur/OffsetX/OffsetY` auf der Serie — alle in `style.{line,markers,shadow}` umgezogen.
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## 5. Fill-Modell
|
|
432
|
+
|
|
433
|
+
**Das zentrale Aufräum-Stück.** Eine Fill-Beschreibung, mehrere Geometrie-Ziele.
|
|
434
|
+
|
|
435
|
+
### 5.1 `FillStyle` — die Form
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
/**
|
|
439
|
+
* Eine Füllung. Drei Formen, klar diskriminiert:
|
|
440
|
+
*
|
|
441
|
+
* string // einfache Farbe ('#ef444433' oder 'rgba(…)')
|
|
442
|
+
* { color: string } // explizite Farbe, sonst gleich
|
|
443
|
+
* { color: string, hatch: HatchVariant, // Farbe + Hatch (mit eigener Linien-Farbe)
|
|
444
|
+
* hatchColor?: string, hatchWidth?: number }
|
|
445
|
+
*
|
|
446
|
+
* Wo gefüllt wird, beschreibt `region` (siehe 6.2) — nicht `FillStyle` selbst.
|
|
447
|
+
*/
|
|
448
|
+
export type FillStyle =
|
|
449
|
+
| string
|
|
450
|
+
| { color: string; hatch?: HatchVariant; hatchColor?: string; hatchWidth?: number };
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Das bildet exakt deine Anforderung ab: *"farbe, oder farbe+hatching(+farbe)"*.
|
|
454
|
+
|
|
455
|
+
### 5.2 Wo gefüllt wird — `FillRegion`
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
/**
|
|
459
|
+
* Eine Füllungs-Region. `from` und `to` definieren das Y-Intervall;
|
|
460
|
+
* der X-Bereich ist immer die Serie.
|
|
461
|
+
*/
|
|
462
|
+
export interface FillRegion {
|
|
463
|
+
/** Untere Grenze. Default 'chartBottom'. */
|
|
464
|
+
from?: FillBound;
|
|
465
|
+
/** Obere Grenze. Default 'series' (die Linie selbst). */
|
|
466
|
+
to?: FillBound;
|
|
467
|
+
/** Die Füllung. */
|
|
468
|
+
fill: FillStyle;
|
|
469
|
+
/** Nur an dieser Seite des Übergangs zwischen `from`/`to` füllen. */
|
|
470
|
+
side?: 'above' | 'below';
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export type FillBound =
|
|
474
|
+
| 'series' // die Linie selbst
|
|
475
|
+
| 'chartTop' | 'chartBottom' // Plot-Kanten
|
|
476
|
+
| { threshold: string } // benannter Threshold (siehe §8)
|
|
477
|
+
| { value: number }; // fester Wert
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### 5.3 Wie `SeriesStyle.fill` aussieht
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
export type FillSpec =
|
|
484
|
+
// Häufiger Fall: durchgehende Füllung vom Plot-Boden bis zur Linie
|
|
485
|
+
| FillStyle
|
|
486
|
+
// Erweitert: eine oder mehrere Regionen
|
|
487
|
+
| { regions: FillRegion[] };
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### 5.4 Beispiele — die häufigen Fälle
|
|
491
|
+
|
|
492
|
+
Alle drei Threshold-bezogenen Anwendungsfälle, die im Hatch-Demo vorkommen,
|
|
493
|
+
plus die Brücke zur heutigen API:
|
|
494
|
+
|
|
495
|
+
**A. „Werte oberhalb eines Werts hervorheben"** — der Klassiker.
|
|
496
|
+
|
|
497
|
+
```ts
|
|
498
|
+
thresholds: [{ name: 'warn', value: 22 }],
|
|
499
|
+
series: [{
|
|
500
|
+
// …
|
|
501
|
+
style: {
|
|
502
|
+
line: { color: '#ef4444' },
|
|
503
|
+
fill: { regions: [{
|
|
504
|
+
from: { threshold: 'warn' },
|
|
505
|
+
to: 'series', // bis hoch zur Linie
|
|
506
|
+
side: 'above', // nur wo die Linie über 'warn' liegt
|
|
507
|
+
fill: { color: '#ef444433', hatch: 'crosshatch' },
|
|
508
|
+
}] },
|
|
509
|
+
},
|
|
510
|
+
}]
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**B. „Bereich zwischen zwei Thresholds einfärben"** — z. B. den "OK"-Korridor
|
|
514
|
+
zwischen low und high markieren, oder umgekehrt einen Warnbereich.
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
thresholds: [
|
|
518
|
+
{ name: 'low', value: 18 },
|
|
519
|
+
{ name: 'high', value: 26 },
|
|
520
|
+
],
|
|
521
|
+
series: [{
|
|
522
|
+
// …
|
|
523
|
+
style: {
|
|
524
|
+
line: { color: '#3b82f6' },
|
|
525
|
+
fill: { regions: [{
|
|
526
|
+
from: { threshold: 'low' },
|
|
527
|
+
to: { threshold: 'high' },
|
|
528
|
+
fill: { color: '#22c55e22', hatch: 'dots' },
|
|
529
|
+
}] },
|
|
530
|
+
},
|
|
531
|
+
}]
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Mit `from` und `to` als Threshold-Referenzen ist der Korridor zwischen zwei
|
|
535
|
+
benannten Schwellen eine Zeile.
|
|
536
|
+
|
|
537
|
+
**C. „Zonen-Fill (mehrere Bereiche, je eigene Farbe + Hatch)"** — der
|
|
538
|
+
Multi-Zone-Fall aus dem Hatch-Demo.
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
thresholds: [
|
|
542
|
+
{ name: 'cold', value: 10 },
|
|
543
|
+
{ name: 'norm', value: 18 },
|
|
544
|
+
{ name: 'hot', value: 24 },
|
|
545
|
+
],
|
|
546
|
+
series: [{
|
|
547
|
+
// …
|
|
548
|
+
style: {
|
|
549
|
+
line: { color: '#2c0a0a', smoothing: true },
|
|
550
|
+
fill: { regions: [
|
|
551
|
+
{ to: { threshold: 'cold' }, fill: { color: '#60a5fa33', hatch: 'classic-diagonal' } },
|
|
552
|
+
{ from: { threshold: 'cold' }, to: { threshold: 'norm' }, fill: { color: '#22c55e33', hatch: 'crosshatch' } },
|
|
553
|
+
{ from: { threshold: 'norm' }, to: { threshold: 'hot' }, fill: { color: '#fbbf2433', hatch: 'dots' } },
|
|
554
|
+
{ from: { threshold: 'hot' }, fill: { color: '#ef444433', hatch: 'waves' } },
|
|
555
|
+
] },
|
|
556
|
+
},
|
|
557
|
+
}]
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Mit dem Helper (§11, Q9 entschieden: Haupt-Barrel):
|
|
561
|
+
|
|
562
|
+
```ts
|
|
563
|
+
import { fillBetweenThresholds } from 'ml-time-graph';
|
|
564
|
+
|
|
565
|
+
style: {
|
|
566
|
+
fill: fillBetweenThresholds({
|
|
567
|
+
thresholds: ['cold', 'norm', 'hot'],
|
|
568
|
+
colors: ['#60a5fa33', '#22c55e33', '#fbbf2433', '#ef444433'],
|
|
569
|
+
hatches: ['classic-diagonal', 'crosshatch', 'dots', 'waves'],
|
|
570
|
+
}),
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
Ein Helper kann den Threshold-Zonen-Fall kürzer schreiben:
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
import { fillBetweenThresholds } from 'ml-time-graph';
|
|
578
|
+
|
|
579
|
+
fill: fillBetweenThresholds({
|
|
580
|
+
thresholds: ['warn', 'crit'],
|
|
581
|
+
colors: ['#22c55e22', '#fbbf2433', '#ef444433'],
|
|
582
|
+
hatches: [undefined, 'dots', 'crosshatch'],
|
|
583
|
+
}),
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
→ liefert eine `{ regions: [...] }`-Spec zurück. Die Library selbst kennt nur
|
|
587
|
+
das `regions`-Konzept; `fillBetweenThresholds` ist syntaktischer Zucker.
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## 6. Achsen + Grid
|
|
592
|
+
|
|
593
|
+
`axisLabels` und `grid` werden zu einem zusammenhängenden `axes`-Objekt.
|
|
594
|
+
|
|
595
|
+
```ts
|
|
596
|
+
export interface MLTimeGraphOptions {
|
|
597
|
+
axes?: {
|
|
598
|
+
x?: XAxisConfig;
|
|
599
|
+
left?: YAxisConfig;
|
|
600
|
+
right?: YAxisConfig;
|
|
601
|
+
};
|
|
602
|
+
// …
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export interface YAxisConfig {
|
|
606
|
+
/** Achsen-Beschriftung (rotiert neben der Achse). */
|
|
607
|
+
label?: string;
|
|
608
|
+
/** Override für den Wertebereich. Default: automatisch. */
|
|
609
|
+
domain?: [number, number] | 'auto';
|
|
610
|
+
/** Wert-Formatter für Tick-Labels. */
|
|
611
|
+
format?: (value: number) => string;
|
|
612
|
+
/** Tick-Konfiguration. */
|
|
613
|
+
ticks?: TickConfig;
|
|
614
|
+
/** Grid-Linien-Stil. */
|
|
615
|
+
grid?: GridStyle;
|
|
616
|
+
/** Achsen-Linie + Tick-Stroke-Stil. */
|
|
617
|
+
axis?: AxisStyle;
|
|
618
|
+
/** Beschriftungs-Stil. */
|
|
619
|
+
labels?: { color?: string; fontSize?: number };
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
export interface XAxisConfig {
|
|
623
|
+
label?: string;
|
|
624
|
+
/** Tick-Formatter — bekommt das Date. Default: i18n via `locale`. */
|
|
625
|
+
format?: (d: Date) => string;
|
|
626
|
+
ticks?: TickConfig;
|
|
627
|
+
grid?: GridStyle;
|
|
628
|
+
axis?: AxisStyle;
|
|
629
|
+
labels?: { color?: string; fontSize?: number };
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export interface TickConfig {
|
|
633
|
+
/** Zielanzahl der Major-Ticks. */
|
|
634
|
+
major?: number;
|
|
635
|
+
/** Wie viele Minor-Ticks zwischen zwei Major-Ticks (Default 0 = aus). */
|
|
636
|
+
minor?: number;
|
|
637
|
+
/** Tick-Länge in px. */
|
|
638
|
+
size?: number;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export interface GridStyle {
|
|
642
|
+
/** Major-Grid: durchgehende Linien an den Major-Ticks. */
|
|
643
|
+
major?: { color?: string; width?: number; style?: LineVariant; opacity?: number } | false;
|
|
644
|
+
/** Minor-Grid (Default: keiner). */
|
|
645
|
+
minor?: { color?: string; width?: number; style?: LineVariant; opacity?: number } | false;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export interface AxisStyle {
|
|
649
|
+
/** Farbe der Achsen-Linie + Ticks. */
|
|
650
|
+
color?: string;
|
|
651
|
+
/** Strichbreite. */
|
|
652
|
+
width?: number;
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
Damit fällt das alte `axisLabels` weg, und `grid: GridOptions` wird zu
|
|
657
|
+
`axes.{x|left|right}.grid`.
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
## 7. Gaps
|
|
662
|
+
|
|
663
|
+
Gap-Logik konsolidiert zu **einem** Pfad mit klarer Hierarchie:
|
|
664
|
+
|
|
665
|
+
1. **Default**: Punkte mit `value: null` brechen die Linie automatisch.
|
|
666
|
+
2. **Auto-Detect** (`gaps: { autoDetect, minGapMs }`) erzeugt Gap-Regionen aus
|
|
667
|
+
den Zeitabständen.
|
|
668
|
+
3. **Manuelle Gaps** (`gaps: { regions: [{ startTime, endTime, label, … }] }`)
|
|
669
|
+
werden zusätzlich gezeichnet.
|
|
670
|
+
4. **Per-Serie-Style** (`series.style.gap`) übersteuert das chart-weite Aussehen
|
|
671
|
+
nur für diese Serie.
|
|
672
|
+
|
|
673
|
+
```ts
|
|
674
|
+
export interface GapsConfig {
|
|
675
|
+
/** Automatisch erkennen anhand des Zeitabstands. Default true. */
|
|
676
|
+
autoDetect?: boolean;
|
|
677
|
+
/** Min. Zeitabstand für Auto-Detect in ms. Default 60000. */
|
|
678
|
+
minGapMs?: number;
|
|
679
|
+
/** Manuell deklarierte Gap-Regionen. */
|
|
680
|
+
regions?: GapRegion[];
|
|
681
|
+
/** Default-Style für alle Gaps. */
|
|
682
|
+
style?: GapStyle;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
export interface GapRegion {
|
|
686
|
+
startTime: number;
|
|
687
|
+
endTime: number;
|
|
688
|
+
label?: string;
|
|
689
|
+
/** Override des Default-Styles. */
|
|
690
|
+
style?: GapStyle;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
export interface GapStyle {
|
|
694
|
+
/** Wie der Gap-Bereich dargestellt wird. */
|
|
695
|
+
display?: 'empty' | 'dashed_border' | 'filled';
|
|
696
|
+
/** Hintergrund-Fill (nur bei display='filled'). */
|
|
697
|
+
fill?: FillStyle;
|
|
698
|
+
/** Opacity. Default 0.15. */
|
|
699
|
+
opacity?: number;
|
|
700
|
+
/** Label-Konfiguration. */
|
|
701
|
+
label?: GapLabel;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
export interface GapLabel {
|
|
705
|
+
/** Vertikale Position. Default 'middle'. */
|
|
706
|
+
baseline?: 'above' | 'middle' | 'below';
|
|
707
|
+
/** Rotation in Grad. Default 0. */
|
|
708
|
+
rotate?: number;
|
|
709
|
+
/** Schriftfarbe. */
|
|
710
|
+
color?: string;
|
|
711
|
+
/** Schriftgrösse. */
|
|
712
|
+
fontSize?: number;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Auf der Serie: nur Override-Slot
|
|
716
|
+
export interface SeriesStyle {
|
|
717
|
+
// …
|
|
718
|
+
gap?: GapStyle;
|
|
719
|
+
}
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
Die heutigen 6 `gap*`-Properties auf `TimeSeries` (`gapFill`, `gapFillOpacity`,
|
|
723
|
+
`gapLabel`, `gapLabelBaseline`, `gapLabelRotate`, `gapFontFill`) kollabieren zu
|
|
724
|
+
**einem** `style.gap`-Objekt mit den klar benennbaren Sub-Feldern oben.
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
|
|
728
|
+
## 8. Thresholds
|
|
729
|
+
|
|
730
|
+
`Threshold` bleibt im Konzept gleich (benannter Wert, referenzierbar), aber
|
|
731
|
+
der Fill wird ans neue `FillStyle` angeglichen.
|
|
732
|
+
|
|
733
|
+
```ts
|
|
734
|
+
export interface Threshold {
|
|
735
|
+
/** Eindeutiger Name, mit dem Serien diese Grenze referenzieren. */
|
|
736
|
+
name: string;
|
|
737
|
+
/** Wert. */
|
|
738
|
+
value: number;
|
|
739
|
+
/** Zugehörige Y-Achse. Default 0. */
|
|
740
|
+
axis?: 0 | 1;
|
|
741
|
+
/** Linie-Stil (`false` = keine Linie zeichnen, nur als Referenz). */
|
|
742
|
+
line?: LineStyle | false;
|
|
743
|
+
/** Halb-Ebene füllen. */
|
|
744
|
+
halfPlane?: { side: 'above' | 'below'; fill: FillStyle; opacity?: number };
|
|
745
|
+
/** Label. */
|
|
746
|
+
label?: ThresholdLabelConfig | false;
|
|
747
|
+
/** Drop-Shadow. */
|
|
748
|
+
shadow?: ShadowStyle;
|
|
749
|
+
id?: string;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export interface ThresholdLabelConfig {
|
|
753
|
+
text?: string;
|
|
754
|
+
position?: 'left' | 'right' | 'above' | 'below' | 'center';
|
|
755
|
+
rotate?: number;
|
|
756
|
+
baseline?: 'top' | 'middle' | 'bottom';
|
|
757
|
+
color?: string;
|
|
758
|
+
fontSize?: number;
|
|
759
|
+
}
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
Auf der Serie:
|
|
763
|
+
|
|
764
|
+
```ts
|
|
765
|
+
export interface Series {
|
|
766
|
+
// …
|
|
767
|
+
/** Linie pro Threshold-Zone einfärben. */
|
|
768
|
+
colorByThresholds?: string[];
|
|
769
|
+
}
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
---
|
|
773
|
+
|
|
774
|
+
## 9. Annotationen (Marker, Highlights, Overlays, Bands)
|
|
775
|
+
|
|
776
|
+
Bleibt im Wesentlichen wie heute — die API ist hier bereits sauber. Kleinere
|
|
777
|
+
Anpassungen für Konsistenz:
|
|
778
|
+
|
|
779
|
+
### 9.1 `Marker`
|
|
780
|
+
|
|
781
|
+
```ts
|
|
782
|
+
export interface Marker {
|
|
783
|
+
time: number;
|
|
784
|
+
value?: number;
|
|
785
|
+
label?: string;
|
|
786
|
+
/** Stil-Block (statt flacher color/pointStyle/…-Felder). */
|
|
787
|
+
style?: { color?: string; point?: PointStyleType; size?: number; line?: 'full' | 'to-value' | 'to-top' };
|
|
788
|
+
/** Ziel-Serie für 'to-value' / 'to-top'. */
|
|
789
|
+
seriesIndex?: number;
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
### 9.2 `Highlight` — unverändert
|
|
794
|
+
|
|
795
|
+
`Highlight` ist bereits in Ordnung (inkl. `labelPosition: 'above' | 'top' | …`).
|
|
796
|
+
|
|
797
|
+
### 9.3 Freie Overlays — `Annotation`
|
|
798
|
+
|
|
799
|
+
`Annotation` (`line | arrow | rect | point | label` in Datenkoordinaten) bleibt
|
|
800
|
+
strukturell gleich. `rect` kriegt — wie heute — optional `hatch`.
|
|
801
|
+
|
|
802
|
+
### 9.4 Annotation-Bands (horizontale Streifen)
|
|
803
|
+
|
|
804
|
+
`AnnotationBandConfig` ist bereits sauber (`name`, `height`, `spacing`, `items`).
|
|
805
|
+
Nur die Item-Properties werden ans neue `FillStyle` angepasst:
|
|
806
|
+
|
|
807
|
+
```ts
|
|
808
|
+
export interface AnnotationBandItem {
|
|
809
|
+
startTime: number;
|
|
810
|
+
endTime: number;
|
|
811
|
+
fill?: FillStyle; // statt fill + hatch separat
|
|
812
|
+
label?: { text: string; baseline?: 'top' | 'middle' | 'bottom'; color?: string; fontSize?: number };
|
|
813
|
+
stroke?: { color: string; width: number };
|
|
814
|
+
}
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
---
|
|
818
|
+
|
|
819
|
+
## 10. Statistik-Overlays (Moving Avg / MKT / StdDev)
|
|
820
|
+
|
|
821
|
+
Statt Stat-Werte ins Datenmodell einzubacken (`AggregatedPoint.mkt`,
|
|
822
|
+
`AggregatedPoint.stdDev`), werden sie als **Overlays** deklariert und vom
|
|
823
|
+
`SeriesProcessor` zur Render-Zeit aus den Roh-/Aggregat-Daten berechnet.
|
|
824
|
+
|
|
825
|
+
```ts
|
|
826
|
+
export interface Series {
|
|
827
|
+
// …
|
|
828
|
+
overlays?: SeriesOverlay[];
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
export type SeriesOverlay =
|
|
832
|
+
| { kind: 'movingAverage'; window: number; type?: 'simple' | 'exponential'; style?: SeriesStyle }
|
|
833
|
+
| { kind: 'movingMkt'; window: number; activationEnergy?: number; style?: SeriesStyle }
|
|
834
|
+
| { kind: 'stdDevBand'; multiplier?: number; style?: SeriesStyle }
|
|
835
|
+
| { kind: 'limits'; low?: number; high?: number; style?: SeriesStyle };
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
Beispiel:
|
|
839
|
+
|
|
840
|
+
```ts
|
|
841
|
+
const tempSeries: Series = {
|
|
842
|
+
name: 'Temperature',
|
|
843
|
+
data: { kind: 'raw', points: rawTemp },
|
|
844
|
+
style: { line: { color: '#4285f4' } },
|
|
845
|
+
overlays: [
|
|
846
|
+
{ kind: 'movingAverage', window: 60_000, style: { line: { color: '#fbbf24', style: 'dashed' } } },
|
|
847
|
+
{ kind: 'stdDevBand', multiplier: 2, style: { fill: '#4285f422' } },
|
|
848
|
+
],
|
|
849
|
+
};
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
Statt:
|
|
853
|
+
|
|
854
|
+
```ts
|
|
855
|
+
// heute — Stats müssten vorgängig in AggregatedPoint.stdDev gerechnet sein
|
|
856
|
+
// und werden dann implizit irgendwo gerendert
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
Vorteil: die Roh-Daten bleiben unberührt, Overlays sind komponierbar,
|
|
860
|
+
sie haben ihren eigenen Style-Block und tauchen automatisch in der Legende auf.
|
|
861
|
+
|
|
862
|
+
**Bestehende + neue Helfer wandern alle in `src/analyze/`** (Subpath-Export
|
|
863
|
+
`ml-time-graph/analyze`, siehe §11). Dort liegen sie als pur funktionale
|
|
864
|
+
Module — `(input, opts) => result` — ohne Render-Abhängigkeit, damit sie
|
|
865
|
+
isoliert testbar sind und später als `mlcanalyze` ausziehen können.
|
|
866
|
+
|
|
867
|
+
| Datei | Inhalt |
|
|
868
|
+
|-------|--------|
|
|
869
|
+
| `src/analyze/aggregator.ts` | `aggregateBySlot`, `downsample` (LTTB), `detectGaps` — heute in `src/data/aggregator.ts` |
|
|
870
|
+
| `src/analyze/processor.ts` | Interpolation, `getRuns`, `splitByBoundaries`, `splitByThreshold` — heute in `src/data/series_processor.ts` |
|
|
871
|
+
| `src/analyze/moving_avg.ts` | Simple + Exponential — heute in `src/statistics/moving_avg.ts` |
|
|
872
|
+
| `src/analyze/stats.ts` | `StatsAggregator.compute()` — heute in `src/statistics/stats.ts` |
|
|
873
|
+
| `src/analyze/mkt.ts` | **NEU.** Mean Kinetic Temperature mit `activationEnergy` (Default 83144, USP <1079.2>) |
|
|
874
|
+
| `src/analyze/std_dev.ts` | **NEU.** Standardabweichungs-Band |
|
|
875
|
+
| `src/analyze/limits.ts` | **NEU.** Aus `{ low, high }` → "Minuten ausserhalb"-Statistik (bisher `AggregatedPoint.minutesAboveHigh/Below`) |
|
|
876
|
+
| `src/analyze/index.ts` | Barrel-Export — die öffentliche `ml-time-graph/analyze`-Oberfläche |
|
|
877
|
+
|
|
878
|
+
`src/statistics/stats_overlay.ts` (der Render-Layer für Stats) **bleibt** unter
|
|
879
|
+
`src/series/` — das ist die Render-Seite, nicht Analyse.
|
|
880
|
+
|
|
881
|
+
Beispiel-Import:
|
|
882
|
+
|
|
883
|
+
```ts
|
|
884
|
+
import { aggregateBySlot, mkt, stdDev } from 'ml-time-graph/analyze';
|
|
885
|
+
// Daten transformieren BEVOR sie an die Chart gehen, falls man die
|
|
886
|
+
// Berechnung nicht der Chart überlassen will:
|
|
887
|
+
const hourly = aggregateBySlot(raw, 'hourly');
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
Die Chart kann beides: entweder bereits berechnete Werte zeigen, oder die
|
|
891
|
+
Berechnung intern per `overlays:` selbst anstoßen.
|
|
892
|
+
|
|
893
|
+
---
|
|
894
|
+
|
|
895
|
+
## 11. Modul-Struktur (Render-Kern + Begleit-Module)
|
|
896
|
+
|
|
897
|
+
```
|
|
898
|
+
src/
|
|
899
|
+
├── MLTimeGraph.ts // Facade
|
|
900
|
+
├── options.ts // MLTimeGraphOptions, LegendOptions, …
|
|
901
|
+
├── index.ts // public barrel: ml-time-graph
|
|
902
|
+
│
|
|
903
|
+
├── core/ // Skalen, Layout, Geometrie-Basics
|
|
904
|
+
│ ├── layout.ts
|
|
905
|
+
│ ├── scale.ts
|
|
906
|
+
│ └── clip.ts
|
|
907
|
+
│
|
|
908
|
+
├── theme/ // ⟵ aufgespalten aus theme.ts (542 LoC)
|
|
909
|
+
│ ├── defaults.ts // theme-Objekt (Farben, Default-Sizes)
|
|
910
|
+
│ └── palette.ts // Farb-Palette
|
|
911
|
+
│
|
|
912
|
+
├── patterns/ // SVG-Pattern-Generatoren
|
|
913
|
+
│ ├── hatch.ts // HatchVariant + getHatch()
|
|
914
|
+
│ ├── line.ts // LineVariant + getLineStyle()
|
|
915
|
+
│ └── points.ts // PointStyleType-Geometrie (Marker-Pfade)
|
|
916
|
+
│
|
|
917
|
+
├── style/ // Style-Typen (öffentlich)
|
|
918
|
+
│ └── types.ts // LineStyle, FillStyle, FillRegion, MarkerStyle, ShadowStyle, SeriesStyle
|
|
919
|
+
│
|
|
920
|
+
├── data/ // reines Daten-Modell + Parsing
|
|
921
|
+
│ ├── types.ts // DataPoint, AggregatedPoint, Series, Threshold, Annotation, …
|
|
922
|
+
│ └── parser.ts // parseSeries, parseAggregated, …
|
|
923
|
+
│
|
|
924
|
+
├── axis/ // Zeit-/Wert-Achsen
|
|
925
|
+
│ ├── time_axis.ts
|
|
926
|
+
│ ├── value_axis.ts
|
|
927
|
+
│ └── enum_axis.ts
|
|
928
|
+
│
|
|
929
|
+
├── renderer/ // DrawCommand-Renderer
|
|
930
|
+
│ ├── renderer.ts // abstract
|
|
931
|
+
│ ├── svg_renderer.ts // DOM-freier SVG-String
|
|
932
|
+
│ ├── command_builder.ts
|
|
933
|
+
│ ├── grid_renderer.ts
|
|
934
|
+
│ ├── legend_renderer.ts
|
|
935
|
+
│ └── series_renderer.ts // Series → DrawCommand[]
|
|
936
|
+
│
|
|
937
|
+
├── series/ // Per-Serie-Geometrie & Render-Layer
|
|
938
|
+
│ ├── line_series.ts
|
|
939
|
+
│ ├── step_series.ts
|
|
940
|
+
│ ├── band_series.ts
|
|
941
|
+
│ ├── minmaxavg_series.ts
|
|
942
|
+
│ ├── zoned_line_series.ts // Multi-Zone-Coloring + Region-Fills
|
|
943
|
+
│ ├── threshold_renderer.ts
|
|
944
|
+
│ ├── gap_renderer.ts
|
|
945
|
+
│ ├── annotation_band.ts
|
|
946
|
+
│ └── stats_overlay.ts // ⟵ aus statistics/ hierher; ist Render-Layer
|
|
947
|
+
│
|
|
948
|
+
├── annotation/ // Marker / Highlights / Annotations
|
|
949
|
+
│ ├── marker.ts
|
|
950
|
+
│ ├── highlight.ts
|
|
951
|
+
│ └── annotation_renderer.ts
|
|
952
|
+
│
|
|
953
|
+
├── analyze/ // ⟵ Begleit-Modul, Subpath-Export ml-time-graph/analyze
|
|
954
|
+
│ ├── index.ts // Barrel
|
|
955
|
+
│ ├── aggregator.ts // aggregateBySlot, downsample, detectGaps
|
|
956
|
+
│ ├── processor.ts // Interpolation, getRuns, splitByBoundaries
|
|
957
|
+
│ ├── moving_avg.ts
|
|
958
|
+
│ ├── stats.ts // StatsAggregator
|
|
959
|
+
│ ├── mkt.ts // NEU
|
|
960
|
+
│ ├── std_dev.ts // NEU
|
|
961
|
+
│ └── limits.ts // NEU
|
|
962
|
+
│
|
|
963
|
+
└── interaction/ // ⟵ Begleit-Modul, Subpath-Export ml-time-graph/interaction
|
|
964
|
+
├── index.ts // Barrel
|
|
965
|
+
├── zoom.ts
|
|
966
|
+
├── minimap.ts
|
|
967
|
+
└── tooltip.ts
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
Jede Datei < 250 Zeilen (Projekt-Style-Guide). `interaction/` ist heute schon
|
|
971
|
+
ein eigenes Verzeichnis — bekommt nur das Barrel + Subpath-Export.
|
|
972
|
+
|
|
973
|
+
### Subpath-Exports in `package.json`
|
|
974
|
+
|
|
975
|
+
```json
|
|
976
|
+
{
|
|
977
|
+
"exports": {
|
|
978
|
+
".": {
|
|
979
|
+
"import": "./dist/index.js",
|
|
980
|
+
"require": "./dist/index.cjs",
|
|
981
|
+
"types": "./dist/index.d.ts"
|
|
982
|
+
},
|
|
983
|
+
"./analyze": {
|
|
984
|
+
"import": "./dist/analyze.js",
|
|
985
|
+
"require": "./dist/analyze.cjs",
|
|
986
|
+
"types": "./dist/analyze.d.ts"
|
|
987
|
+
},
|
|
988
|
+
"./interaction": {
|
|
989
|
+
"import": "./dist/interaction.js",
|
|
990
|
+
"require": "./dist/interaction.cjs",
|
|
991
|
+
"types": "./dist/interaction.d.ts"
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
Konsumenten importieren je nach Bedarf:
|
|
998
|
+
|
|
999
|
+
```ts
|
|
1000
|
+
import { MLTimeGraph, SVGRenderer } from 'ml-time-graph'; // Render-Kern
|
|
1001
|
+
import { aggregateBySlot, mkt, stdDev } from 'ml-time-graph/analyze';
|
|
1002
|
+
import { Zoom, Minimap, Tooltip } from 'ml-time-graph/interaction';
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
Wer nur die Chart braucht, holt sich nicht die Analyse-Helfer in den Bundle.
|
|
1006
|
+
|
|
1007
|
+
### Theme — global default + per-Chart-Override
|
|
1008
|
+
|
|
1009
|
+
Zwei Ebenen:
|
|
1010
|
+
|
|
1011
|
+
```ts
|
|
1012
|
+
import { setDefaultTheme, getDefaultTheme, MLTimeGraph } from 'ml-time-graph';
|
|
1013
|
+
|
|
1014
|
+
// 1. Globales Default einmal setzen (z. B. beim App-Start) — optional.
|
|
1015
|
+
setDefaultTheme({
|
|
1016
|
+
stroke: '#0f172a',
|
|
1017
|
+
gridStroke: '#f1f5f9',
|
|
1018
|
+
palette: ['#3b82f6', '#ef4444', '#22c55e', '#f59e0b'],
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
// 2. Per-Chart-Override — wird auf das globale Default gemergt.
|
|
1022
|
+
new MLTimeGraph({
|
|
1023
|
+
theme: { stroke: '#7c3aed' }, // nur stroke wird übersteuert
|
|
1024
|
+
// …
|
|
1025
|
+
});
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
**Resolutions-Reihenfolge** beim Chart-Konstruktor:
|
|
1029
|
+
|
|
1030
|
+
```
|
|
1031
|
+
resolvedTheme = { ...BUILTIN_DEFAULTS, ...globalDefault, ...options.theme }
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
Wenn nirgends gesetzt: nur `BUILTIN_DEFAULTS` (die Werte aus
|
|
1035
|
+
`src/theme/defaults.ts`). Wenn nur global gesetzt: alle Charts kriegen den
|
|
1036
|
+
globalen Look. Per-Chart-Override schlägt beides.
|
|
1037
|
+
|
|
1038
|
+
```ts
|
|
1039
|
+
// API:
|
|
1040
|
+
export function setDefaultTheme(partial: Partial<Theme>): void;
|
|
1041
|
+
export function getDefaultTheme(): Readonly<Theme>;
|
|
1042
|
+
export function resetDefaultTheme(): void; // zurück auf BUILTIN_DEFAULTS
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
**Vorsicht** (im Doc dokumentieren!): das globale Default ist ein
|
|
1046
|
+
**Module-Level-Side-Effect**. Konsequenzen:
|
|
1047
|
+
|
|
1048
|
+
- **SSR**: in einer Node.js-Server-Umgebung mit mehreren parallelen Renders
|
|
1049
|
+
würde `setDefaultTheme` aus Request A auch Request B beeinflussen. Empfehlung
|
|
1050
|
+
für SSR: Theme **per-Chart** übergeben, `setDefaultTheme` nur in
|
|
1051
|
+
Browser-/CLI-Kontexten benutzen.
|
|
1052
|
+
- **Tests**: nach Tests, die `setDefaultTheme` setzen, `resetDefaultTheme()`
|
|
1053
|
+
in `afterEach` aufrufen, damit Test-Reihenfolge keine Rolle spielt.
|
|
1054
|
+
|
|
1055
|
+
Diese Caveats stehen so auch in der JSDoc von `setDefaultTheme`, damit
|
|
1056
|
+
TypeScript-Hover sie zeigt.
|
|
1057
|
+
|
|
1058
|
+
### Späterer Ausgliederungs-Schnitt
|
|
1059
|
+
|
|
1060
|
+
Wenn (a) das Analyse-API stabil ist und (b) ein zweiter Konsument auftaucht,
|
|
1061
|
+
kann `src/analyze/` 1:1 zu einem separaten Package `mlcanalyze` (oder
|
|
1062
|
+
`@mlc/analyze`) auf einem eigenen Repo ausziehen. `ml-time-graph` würde dann
|
|
1063
|
+
`mlcanalyze` als (Peer-)Dependency listen, oder die `overlays:`-Berechnung
|
|
1064
|
+
würde komplett vom Konsumenten kommen. Solange das nicht ansteht — bleibt
|
|
1065
|
+
einrepoig.
|
|
1066
|
+
|
|
1067
|
+
---
|
|
1068
|
+
|
|
1069
|
+
## 12. MLTimeGraphOptions — Top-Level-Form
|
|
1070
|
+
|
|
1071
|
+
Das Ziel-Top-Level-API:
|
|
1072
|
+
|
|
1073
|
+
```ts
|
|
1074
|
+
export interface MLTimeGraphOptions {
|
|
1075
|
+
// — Geometrie
|
|
1076
|
+
width?: number;
|
|
1077
|
+
height?: number;
|
|
1078
|
+
margin?: Margin;
|
|
1079
|
+
|
|
1080
|
+
// — Renderer (DI)
|
|
1081
|
+
renderer?: Renderer;
|
|
1082
|
+
|
|
1083
|
+
// — Daten
|
|
1084
|
+
series: Series[];
|
|
1085
|
+
|
|
1086
|
+
// — Achsen + Grid
|
|
1087
|
+
axes?: { x?: XAxisConfig; left?: YAxisConfig; right?: YAxisConfig };
|
|
1088
|
+
|
|
1089
|
+
// — Hintergrund-Layer (in Z-Reihenfolge: thresholds → highlights → gaps → series → annotations)
|
|
1090
|
+
thresholds?: Threshold[];
|
|
1091
|
+
highlights?: Highlight[];
|
|
1092
|
+
gaps?: GapsConfig;
|
|
1093
|
+
|
|
1094
|
+
// — Vordergrund-Annotationen
|
|
1095
|
+
markers?: Marker[];
|
|
1096
|
+
annotations?: Annotation[];
|
|
1097
|
+
annotationBands?: AnnotationBandConfig[];
|
|
1098
|
+
|
|
1099
|
+
// — Theme + Locale
|
|
1100
|
+
theme?: Partial<typeof theme>;
|
|
1101
|
+
locale?: string;
|
|
1102
|
+
|
|
1103
|
+
// — Legende
|
|
1104
|
+
legend?: LegendOptions;
|
|
1105
|
+
}
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
`AxisLabels`, `GridOptions` (flach), `GapConfig` (alt) entfallen — ihre Inhalte
|
|
1109
|
+
sind jetzt strukturiert unter `axes` / `gaps`.
|
|
1110
|
+
|
|
1111
|
+
---
|
|
1112
|
+
|
|
1113
|
+
## A. Migrations-Historie (v0.2 → v0.3 → Cleanup)
|
|
1114
|
+
|
|
1115
|
+
Die heutige API entstand in vier Phasen über mehrere Branches. Detail-Logs
|
|
1116
|
+
stehen in [`cleanup_progress.md`](docs/cleanup_progress.md) und in den
|
|
1117
|
+
Git-Commit-Messages; hier nur die Eckpunkte als Begründungs-Kontext.
|
|
1118
|
+
|
|
1119
|
+
**Phase 1 — Interner Cleanup (kein API-Bruch).** `theme.ts` (542 LoC) in vier
|
|
1120
|
+
fokussierte Module aufgespalten. Analyse-Helfer aus `src/data/` und
|
|
1121
|
+
`src/statistics/` nach `src/analyze/` umgezogen, Subpath-Export
|
|
1122
|
+
`ml-time-graph/analyze` eingeführt. `src/interaction/` analog mit eigenem
|
|
1123
|
+
Barrel + Subpath. Tote Code-Blöcke entfernt, leere Files gelöscht. Alle 211
|
|
1124
|
+
Tests blieben grün, kein Aufrufer brach.
|
|
1125
|
+
|
|
1126
|
+
**Phase 2 — Style-System additiv eingeführt.** `SeriesStyle` (nested:
|
|
1127
|
+
`line`/`fill`/`markers`/`shadow`/`gap`) als alternative Form zu den ~30 flachen
|
|
1128
|
+
Style-Feldern auf `TimeSeries`. `FillSpec` / `FillRegion` als strukturiertes
|
|
1129
|
+
Fill-Modell (heute auch für legacy `fillToThreshold(s)` der zugrundeliegende
|
|
1130
|
+
Render-Pfad). `axes: AxesConfig` als First-Class-Block. `GapsConfig` als
|
|
1131
|
+
Union-Form für `gaps`. `setDefaultTheme` / `getDefaultTheme` / `resetDefaultTheme`
|
|
1132
|
+
für globale Theme-Defaults. Spezialisierte Aggregat-Typen `MktPoint` /
|
|
1133
|
+
`StdDevPoint` / `LimitStatsPoint`. `synthetic?: boolean` auf `DataPoint`.
|
|
1134
|
+
`SeriesOverlay`-Union (Typ-only, Rendering folgt). `LineVariant` + 13
|
|
1135
|
+
Marker-Shapes vollständig im SVG-Renderer verdrahtet. ~80 % der flachen
|
|
1136
|
+
Style-Felder auf den Series-Interfaces mit `@deprecated`-Migrations-Hinweisen
|
|
1137
|
+
markiert. Alle 211 Phase-1-Tests + 58 neue (= 269) grün.
|
|
1138
|
+
|
|
1139
|
+
**Phase 3 — Statistik-Overlays + Schließen der Wiring-Lücken.** Drei
|
|
1140
|
+
pur-funktionale Compute-Helfer in `src/analyze/`: `mkt`/`rollingMkt` (USP
|
|
1141
|
+
<1079.2>), `stdDev`/`sampleStdDev`/`rollingStdDev`, `computeLimitExcursions`.
|
|
1142
|
+
`SeriesOverlay`-Rendering verdrahtet (`movingAverage`, `movingMkt`, `limits`;
|
|
1143
|
+
`stdDevBand` blieb no-op). Alle weiteren Style-Sub-Objekte (`style.line[]`,
|
|
1144
|
+
`style.markers`, `style.shadow`, `style.gap`, `axes.*.format` / `ticks`,
|
|
1145
|
+
`axes.*.domain`) im Renderer verdrahtet. Legacy `fillToThreshold(s)` per
|
|
1146
|
+
Resolver-Mapping auf den `FillSpecRenderer` umgelenkt — heute ein einziger
|
|
1147
|
+
Fill-Render-Pfad. `GapsConfig.autoDetect` aktiv. 49 neue Tests (= 318).
|
|
1148
|
+
|
|
1149
|
+
**Phase 4 — Stabilisierung 0.3.0.** `USAGE.md` durchgängig auf die neue
|
|
1150
|
+
API umgeschrieben. `VERSION` + `package.json` von `0.2.0` → `0.3.0`. Die
|
|
1151
|
+
legacy flachen Felder blieben noch mit `@deprecated`-Hinweis erhalten.
|
|
1152
|
+
|
|
1153
|
+
**Phase 5 — Hart-Removal-Bruch (durchgeführt 2026-06-01).** Alle
|
|
1154
|
+
`@deprecated`-Pfade entfernt:
|
|
1155
|
+
|
|
1156
|
+
- Flache Style-Felder auf `TimeSeries` (`color`, `lineWidth`, `dashed`,
|
|
1157
|
+
`smoothing`, `shadow*`, `pointStyle/Size/Threshold`, `gapThreshold`,
|
|
1158
|
+
`gapFill*`, `gapLabel*`, `gapFontFill`) gelöscht — alles über
|
|
1159
|
+
`style.{line,fill,markers,shadow,gap}`.
|
|
1160
|
+
- `fillToThreshold` / `fillToThresholds` / `fillByThresholds*` entfernt
|
|
1161
|
+
— Konsumenten nutzen `style.fill.regions[]` oder den
|
|
1162
|
+
`fillBetweenThresholds()`-Helper.
|
|
1163
|
+
- `AggregatedSeries` flache `color` / `smoothing` / `lineWidth` entfernt.
|
|
1164
|
+
- `axisLabels` / `grid`-Top-Level entfernt — alles über
|
|
1165
|
+
`axes.{x,left,right}`.
|
|
1166
|
+
- `AggregatedPoint`-Extras (`mkt`, `deltaMkt`, `stdDev`,
|
|
1167
|
+
`minutesAboveHigh/BelowLow`) auf die spezialisierten Untertypen
|
|
1168
|
+
(`MktPoint`, `StdDevPoint`, `LimitStatsPoint`, `StatsAggregatedPoint`)
|
|
1169
|
+
umgezogen. Interne Konsumenten (`aggregator`, `heatmap`,
|
|
1170
|
+
`model/apotheken`) auf `StatsAggregatedPoint` umgetypt.
|
|
1171
|
+
- 8 Renderer-Klassen → freie Funktionen (`renderMarkers`,
|
|
1172
|
+
`renderHighlights`, `renderAnnotations`, `renderGrid`,
|
|
1173
|
+
`renderThresholds`, `renderLegend`, `renderGaps`, plus die 6
|
|
1174
|
+
Funktionen aus dem ehemaligen `SeriesRenderer`-Static-Container).
|
|
1175
|
+
- `src/style/types.ts` (244 LoC Monolith) in vier konzept-fokussierte
|
|
1176
|
+
Files aufgespalten (`enums.ts`, `series.ts`, `fill.ts`, `gap.ts`).
|
|
1177
|
+
- `#config`-Pattern auf alle restlichen Series-/Axis-Klassen statt
|
|
1178
|
+
Feld-für-Feld-Kopie.
|
|
1179
|
+
- Demo-Migration: 9 Demo-Files auf nested-Style umgestellt.
|
|
1180
|
+
|
|
1181
|
+
Resultat: **0 × `@deprecated`** in `src/`, `demos/src/`, `tests/`. Eine
|
|
1182
|
+
API-Form überall. 340 Tests grün.
|
|
1183
|
+
|
|
1184
|
+
## B. Polish-Backlog (post-v0.3)
|
|
1185
|
+
|
|
1186
|
+
Liste der Polish-Ideen, die im Backlog (`.mlcai/BACKLOG.md`) leben:
|
|
1187
|
+
|
|
1188
|
+
- `mount()`-One-Liner-Helper — landed (2026-06-01).
|
|
1189
|
+
- Tooltip-Helper mit Multi-Series-Callback (`I-20260601-01`).
|
|
1190
|
+
- `validate`-Option für klarere Fehler bei kaputten Options (`I-20260601-02`).
|
|
1191
|
+
- Public-vs-Internal-Type-Split via Subpath-Exports (`I-20260601-03`).
|
|
1192
|
+
- CSS-Class-basiertes Theming statt SVG-Attribute (`I-20260601-04`).
|
|
1193
|
+
- Journey-Format auf alle Demo-HTMLs ausrollen (`T-20260601-01`).
|
|
1194
|
+
|
|
1195
|
+
## C. Aufgelöste Design-Entscheidungen
|
|
1196
|
+
|
|
1197
|
+
Während des Cleanups gefällte Entscheidungen, jeweils mit Datum + Kurz-Begründung:
|
|
1198
|
+
|
|
1199
|
+
| # | Frage | Entscheidung |
|
|
1200
|
+
|---|-------|--------------|
|
|
1201
|
+
| Q1 | `Series.data`: diskriminiert oder flach? | **flach** — `points: DataPoint[] \| AggregatedPoint[]`, strukturell erkannt. Spezialisierte Aggregate erweitern `AggregatedPoint` strukturell. |
|
|
1202
|
+
| Q2 | `TimeSeries`/`AggregatedSeries` als Alias erhalten? | **ja**, als `@deprecated`-Type-Aliase bis Phase 5. |
|
|
1203
|
+
| Q3 | Wie wird die Darstellungsart ausgewählt? | **kein `seriesType`-Enum** — Komposition durch Style-Sub-Objekte (`line` da → Linie, `fill` → Fläche, `markers` → Punkte). `line: LineStyle[]` für Multi-Line-Stack. |
|
|
1204
|
+
| Q4 | `fillBetweenThresholds()` als offizieller Helper? | **ja**, im Haupt-Barrel. |
|
|
1205
|
+
| Q5 | Theme per-Chart oder global? | **beides** — `options.theme` per Chart + `setDefaultTheme(partial)` global. Resolution: `BUILTIN_DEFAULTS` ← `globalDefault` ← `options.theme`. SSR-/Test-Caveats in JSDoc. |
|
|
1206
|
+
| Q6 | `interaction/` (Zoom/Minimap/Tooltip): bleiben oder raus? | **bleibt** als Begleit-Modul mit eigenem Barrel + Subpath-Export `ml-time-graph/interaction`. |
|
|
1207
|
+
| Q7 | JSDoc-Tiefe? | **Top-Ebene** ist Pflicht; Property-Doku ergänzt der Konsument bei Bedarf. |
|
|
1208
|
+
| Q8 | Major-Bump direkt nach Phase 4? | **nein**, erst Stabilisierungs-Phase (`0.3.x`/`0.5.x`), dann `1.0`. |
|
|
1209
|
+
| Q9 | Wo lebt `fillBetweenThresholds()`? | **Haupt-Barrel** (`ml-time-graph`). Style-Konstruktion ist Konsumenten-API, kein Analyse-Schritt. |
|
|
1210
|
+
| Q10 | Bei `line: LineStyle[]`: gleiche oder eigene Daten pro Sub-Linie? | **gleiche Daten**. Eigene Daten für Trendlinien gehen über `overlays: [{ kind: 'movingAverage', … }]`. |
|
|
1211
|
+
| Q11 | Wie mit künstlich eingefügten Punkten (Threshold-Schnitte, Gap-Ränder)? | **`DataPoint.synthetic?: boolean`** — wird vom internen `SeriesProcessor` automatisch gesetzt, kann auch manuell verwendet werden. Renderer-Konsequenz: kein Marker, nicht hit-testbar, nicht in Werte-Tabelle. Skalen zählen den Punkt mit. |
|