ml-time-graph 1.1.0 → 1.2.0

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/README.md CHANGED
@@ -86,8 +86,8 @@ Need server-side rendering of the exact same charts? There is a 1:1 Go port of t
86
86
 
87
87
  Both the TypeScript and Go libraries:
88
88
  - Process the **exact same JSON configuration schema**.
89
- - Generate **identical SVG structures and layout calculations**.
90
- - Support **multiple Y-axes** on both left and right sides (each additional axis is automatically offset horizontally by `50px` to prevent overlaps).
89
+ - Support **multiple stacked Y-axes** on both left and right sides (with configurable pixel `offset` and automatic step offsets).
90
+ - Ensure the **time axis baseline spans the entire plot area** with exact parity between TypeScript and Go.
91
91
  - Are continuously tested for rendering parity using shared test fixtures.
92
92
 
93
93
  ---
package/USAGE.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  *[← README](README.md) · [Deutsch](README.de.md)*
4
4
 
5
+ **Contents:** [Install](#install) · [Quick start](#quick-start) · [Series](#series) ·
6
+ [Thresholds](#thresholds-incident-analysis) · [Fill regions](#fill-regions-the-structured-way) ·
7
+ [Legend](#legend) · [Axes](#axes) · [Localization](#localization-i18n) · [Gaps](#gaps) ·
8
+ [Statistic overlays](#statistic-overlays) · [Aggregation](#aggregation--helpers) ·
9
+ [Annotations](#annotations-custom-overlays) · [Interaction & tooltips](#interaction--tooltips) ·
10
+ [Server-side](#server-side-rendering-node) · [Package exports](#package-exports) · [Renderer](#renderer)
11
+
5
12
  ## Install
6
13
 
7
14
  ```bash
@@ -91,7 +98,7 @@ underneath, add `markers` for points, set `style.line: false` for points only.
91
98
  'dense-dash' | 'dash-dot' | 'dash-dot-dot' | 'loose-dash'`.
92
99
 
93
100
  A point with `value: null` (or a slot with `min/max/avg: null`) marks a **gap** — the
94
- line breaks there. Two y-axes are supported via `yAxisIndex: 0 | 1` on the series.
101
+ line breaks there. Multiple y-axes are supported via `yAxisIndex: 0 | 1 | 2 | ...` on the series.
95
102
 
96
103
  ## Thresholds (incident analysis)
97
104
 
@@ -180,7 +187,7 @@ new MLTimeGraph({
180
187
  label: 'Time',
181
188
  domain: 'auto', // or [tMin, tMax]
182
189
  format: (d) => d.toISOString().slice(0, 10),
183
- axis: { color: '#7c3aed' }, // baseline + ticks
190
+ axis: { color: '#7c3aed' }, // baseline + ticks (spans full chart width)
184
191
  },
185
192
  left: {
186
193
  label: 'Temperature (°C)',
@@ -195,6 +202,48 @@ new MLTimeGraph({
195
202
  });
196
203
  ```
197
204
 
205
+ ### Multi-Axis Layout (N Stacked Y-Axes)
206
+
207
+ To support 3 or more vertical axes (or arbitrary left/right positioning), define `axes.y: YAxisConfig[]` and assign series using `yAxisIndex`:
208
+
209
+ ```ts
210
+ new MLTimeGraph({
211
+ margin: { top: 30, right: 180, bottom: 50, left: 100 },
212
+ series: [
213
+ { name: 'RPM', yAxisIndex: 0, data: rpmData },
214
+ { name: 'Temp', yAxisIndex: 1, data: tempData },
215
+ { name: 'Vibration', yAxisIndex: 2, data: vibData },
216
+ ],
217
+ axes: {
218
+ x: { label: 'Time of Day' },
219
+ y: [
220
+ { position: 'left', label: 'Spindle RPM' },
221
+ { position: 'right', offset: 0, label: 'Temp (°C)' },
222
+ { position: 'right', offset: 60, label: 'Vibration (mm/s)' }, // explicit 60px offset
223
+ ],
224
+ },
225
+ });
226
+ ```
227
+
228
+ - **`position: 'left' | 'right'`**: Selects which side of the plot area the axis is anchored to.
229
+ - **`offset?: number`**: Sets an explicit pixel offset from the chart boundary. If omitted, sequential axes step outwards by 50px automatically.
230
+ - **Full Baseline Range**: The horizontal time-axis baseline always spans the complete chart plot area (`chartX` to `chartX + chartWidth`), even if ticks do not extend to the boundary.
231
+
232
+ ### Coordinate Projections & Inversion
233
+
234
+ `MLTimeGraph` provides mathematical projection methods for building interactive tools (tooltips, scrubbers, crosshairs, zoom windows):
235
+
236
+ ```ts
237
+ chart.renderCommands(); // initializes scale mappings
238
+
239
+ // Data -> Pixel (with yAxisIndex)
240
+ const { x, y } = chart.project(timestampMs, value, yAxisIndex);
241
+
242
+ // Pixel -> Data
243
+ const time = chart.invertTime(pixelX);
244
+ const val = chart.invertValue(pixelY, yAxisIndex);
245
+ ```
246
+
198
247
  ## Localization (i18n)
199
248
 
200
249
  The time axis is **locale-aware**: tick labels are formatted with
@@ -359,12 +408,72 @@ chart.renderCommands();
359
408
  const { x, y } = chart.project(time, value, axis); // data → pixel
360
409
  ```
361
410
 
362
- ## Interaction
411
+ ## Interaction & tooltips
412
+
413
+ ### Tooltips — `attachTooltip` (built-in)
414
+
415
+ One line wires a DOM tooltip that snaps to the nearest sample per series, draws a
416
+ "pick" ring on each matched point, and **flips across the cursor to stay inside
417
+ the chart** (right/below in the left/top half, left/above past 50%):
418
+
419
+ ```ts
420
+ import { mount, attachTooltip } from 'ml-time-graph';
421
+
422
+ const chart = mount('#chart', { series });
423
+ const detach = attachTooltip(document.querySelector('#chart')!, chart, {
424
+ format: (samples) =>
425
+ `<div>${new Date(samples[0].time).toLocaleString()}</div>` +
426
+ samples.map((s) => `<div>${s.series.name}: ${s.value}</div>`).join(''),
427
+ });
428
+ // …later:
429
+ detach(); // removes the listeners + pick markers
430
+ ```
431
+
432
+ `attachTooltip(target, chart, options?)` returns a cleanup function. The `target`
433
+ must already contain the rendered `<svg>` (mount / render first). Each hover
434
+ yields one `TooltipSample` per series — `{ seriesIndex, series, time, value, x, y }`.
435
+
436
+ Options: `format` (HTML; default = time header + `name: value` rows),
437
+ `snapRadius` (px, default ∞), `className` (default `mlc-tooltip`), and
438
+ `showPicks` / `pickRadius` / `picksClassName` for the markers. Style the box via
439
+ the `.mlc-tooltip` CSS class — **don't set `transform`**; placement (incl. the
440
+ flip) is computed in JS.
441
+
442
+ ### Zoom / pan
443
+
444
+ Zoom, pan and minimap are **not** baked into the chart. Map pixels back to data
445
+ with `chart.invertTime(px)` / `chart.invertValue(py, axisIndex)` and wire the
446
+ interaction in your app, or pull the optional `Zoom` / `Minimap` helpers from the
447
+ [`ml-time-graph/interaction`](#package-exports) subpath. See the demo gallery's
448
+ "Fridge" (range slider) and "Interaction" pages.
449
+
450
+ ## Server-side rendering (Node)
451
+
452
+ `SVGRenderer` needs no DOM, so the same code runs in Node — set explicit
453
+ `width`/`height` (there's no element to measure) and write the string out:
454
+
455
+ ```ts
456
+ import { MLTimeGraph, SVGRenderer } from 'ml-time-graph';
457
+ import { writeFileSync } from 'node:fs';
458
+
459
+ const chart = new MLTimeGraph({ width: 800, height: 400, series });
460
+ const { content } = new SVGRenderer().render(chart.renderCommands());
461
+ writeFileSync('chart.svg', content);
462
+ ```
463
+
464
+ Useful for report generation, PDF embedding (via a headless browser), and email
465
+ attachments. For a Node-free backend, the Go port
466
+ [`go-time-graph`](https://gitlab.com/mlc0911/mlctimegraph/-/tree/main/go-time-graph)
467
+ renders the same JSON config to identical SVG.
468
+
469
+ ## Package exports
363
470
 
364
- Zoom/pan and tooltips are intentionally **not** baked into the chart. Map pixels to
365
- data with `chart.invertTime(px)` / `chart.invertValue(py, axisIndex)` and build the
366
- interaction in your appsee the demo gallery's "Fridge" (range slider) and
367
- "Interaction" (tooltip) pages.
471
+ | Import | Contents |
472
+ | :--- | :--- |
473
+ | `ml-time-graph` | The rendering API `mount`, `MLTimeGraph`, `SVGRenderer`, `attachTooltip`, and all option/data types. |
474
+ | `ml-time-graph/analyze` | Statistics companion — `aggregateBySlot`, `downsample`, `detectGaps`, `mkt` / `rollingMkt`, `stdDev`, `StatsAggregator`, … (see [Aggregation](#aggregation--helpers)). |
475
+ | `ml-time-graph/interaction` | Optional interaction primitives — `Zoom`, `Minimap`, `Tooltip` (lower-level than `attachTooltip`). |
476
+ | `ml-time-graph/internals` | Building blocks for **custom renderers** — the abstract `Renderer`, free render-functions, scales, axis classes, the `DrawCommand` model. |
368
477
 
369
478
  ## Renderer
370
479
 
@@ -147,7 +147,7 @@ declare function aggregateBySlot(data: DataPoint[], mode?: "hourly" | "daily" |
147
147
  declare function createAggr(time: number, values: number[]): AggregatedPoint;
148
148
  /**
149
149
  * Detect gaps in time-series data (autoDetect).
150
- * Gibt alle Zeitenrücken zurück wo das Zeitintervall > minGapMs ist.
150
+ * Returns all time gaps where the interval is > minGapMs.
151
151
  */
152
152
  declare function detectGaps(data: DataPoint[], minGapMs?: number): DetectedGap[];
153
153
  interface LongTermInsight {
@@ -350,6 +350,13 @@ interface NamedSeries {
350
350
  /** Time-ordered samples. `value: null` is treated as a gap and ignored. */
351
351
  data: DataPoint[];
352
352
  }
353
+
354
+ /*!
355
+ * MLTimeGraph — Copyright (c) 2026 Michael Lechner
356
+ * MIT with Attribution: free use incl. commercial requires visible credit to
357
+ * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
358
+ */
359
+
353
360
  /**
354
361
  * Per-timestamp summary of a multi-sensor recording.
355
362
  * `delta = max - min` is the room's "spatial spread" at that instant.
@@ -382,6 +389,13 @@ interface SpatialPoint {
382
389
  * silently skipped for that point.
383
390
  */
384
391
  declare function spatialDelta(sensors: NamedSeries[]): SpatialPoint[];
392
+
393
+ /*!
394
+ * MLTimeGraph — Copyright (c) 2026 Michael Lechner
395
+ * MIT with Attribution: free use incl. commercial requires visible credit to
396
+ * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
397
+ */
398
+
385
399
  /** Aggregate statistics for one sensor across the full observation window. */
386
400
  interface SensorStat {
387
401
  name: string;
@@ -1,4 +1,4 @@
1
- var H=class{static interpolateDataPoint(n,i,t){return {time:n.time+t*(i.time-n.time),value:(n.value??0)+t*((i.value??0)-(n.value??0))}}static interpolateAggregatedPoint(n,i,t){let u=(e,l)=>e!==null&&l!==null?e+t*(l-e):null;return {time:n.time+t*(i.time-n.time),min:u(n.min,i.min),max:u(n.max,i.max),avg:u(n.avg,i.avg),count:Math.round(n.count+t*(i.count-n.count))}}static getRuns(n,i,t=0){let u=[...n].sort((r,o)=>r.time-o.time),e=[],l=[],a=null;for(let r of u){let o=i(r),m=t>0&&a&&r.time-a.time>t;(o||m)&&l.length&&(e.push(l),l=[]),o||l.push(r),a=r;}return l.length&&e.push(l),e}static splitByBoundaries(n,i,t,u){if(n.length===0)return [];if(i.length===0)return [{data:n,zoneIndex:0}];let e=[...i].sort((o,m)=>o-m),l=[],a=o=>{let m=0;for(let s=0;s<e.length&&o>=e[s];s++)m=s+1;return m},r=[n[0]];for(let o=1;o<n.length;o++){let m=n[o-1],s=n[o],h=t(m),g=t(s),d;g>h?d=e.filter(c=>c>h&&c<=g):g<h?d=e.filter(c=>c>=g&&c<h).reverse():d=[];for(let c of d){let p=(c-h)/(g-h),x=u(m,s,p);r.push(x),l.push({data:r,zoneIndex:a((h+c)/2)}),r=[x];}r.push(s);}if(r.length>0){let o=t(r[0]),m=t(r[r.length-1]);l.push({data:r,zoneIndex:a((o+m)/2)});}return l}static splitByThreshold(n,i,t,u){let e=this.splitByBoundaries(n,[i],t,u),l={above:[],below:[]};for(let a of e)a.zoneIndex===0?l.below.push(a.data):l.above.push(a.data);return l}},L={pharma_cold:{limitLow:2,limitHigh:8,activationEnergy:83144},pharma_ambient:{limitLow:15,limitHigh:25,activationEnergy:83144},blood:{limitLow:2,limitHigh:6,activationEnergy:83144},food_chilled:{limitLow:0,limitHigh:4},frozen:{limitLow:-40,limitHigh:-18}},T=8.314462618,w=273.15;function N(n,i,t,u=1){let e=i.filter(f=>f.value!==null&&f.value!==void 0&&!isNaN(f.value)).map(f=>f.value);if(!e.length)return {time:n,min:null,max:null,avg:null,mkt:null,deltaMkt:null,stdDev:null,minutesAboveHigh:t?0:null,minutesBelowLow:t?0:null,count:0};let l=Math.min(...e),a=Math.max(...e),r=e.reduce((f,v)=>f+v,0)/e.length,o=t?0:null,m=t?0:null;if(t)for(let f=0;f<i.length;f++){let v=i[f];if(v.value!==null&&v.value!==void 0){let b=i[f+1],M=(b?b.time-v.time:0)/(1e3*60);v.value>t.limitHigh&&(o+=M),v.value<t.limitLow&&(m+=M);}}let s=0,h=0,g=(t?.activationEnergy??83144)/T;for(let f of e){s+=Math.pow(f-r,2);let v=f+w;h+=Math.exp(-g/v);}let d=s/e.length,c=Math.sqrt(d),p=null,x=null;if(e.length>=u){let f=h/e.length,v=g/-Math.log(f);p=Number((v-w).toFixed(2)),x=Number((p-r).toFixed(2));}return {time:n,min:Number(l.toFixed(2)),max:Number(a.toFixed(2)),avg:Number(r.toFixed(2)),mkt:p,deltaMkt:x,stdDev:Number(c.toFixed(2)),minutesAboveHigh:o!==null?Number(o.toFixed(1)):null,minutesBelowLow:m!==null?Number(m.toFixed(1)):null,count:e.length}}function D(n,i="hourly",t,u,e=1){if(!n.length)return [];let l=i==="hourly"?36e5:i==="daily"?864e5:t??3600*1e3,a=[...n].sort((s,h)=>s.time-h.time),r=[],o=a[0].time,m=[];for(let s of a){for(;s.time-o>=l;)r.push(N(o,m,u,e)),o+=l,m=[];m.push(s);}return r.push(N(o,m,u,e)),r}function A(n,i){let t=Math.min(...i),u=Math.max(...i),e=i.reduce((l,a)=>l+a,0)/i.length;return {time:n,min:t,max:u,avg:e,count:i.length}}function B(n,i=6e4){if(n.length<2)return [];let t=[...n].sort((e,l)=>e.time-l.time),u=[];for(let e=1;e<t.length;e++)t[e].time-t[e-1].time>i&&u.push({startTime:t[e-1].time,endTime:t[e].time});return u}function I(n){let i=[],t=0,u=0,e=0,l=0;for(let a of n)a.count>0&&(e++,a.minutesAboveHigh&&(t+=a.minutesAboveHigh),a.stdDev&&a.stdDev>u&&(u=a.stdDev),a.deltaMkt&&a.deltaMkt>2&&l++);return e===0?[]:(t>120&&i.push({type:"critical",message:`Kritische Gesamtbelastung: Das Produkt war im gesamten Zeitraum insgesamt ${Math.round(t)} Minuten zu warm.`,metric:"Total Excursion Time"}),l>e*.15&&i.push({type:"warning",message:"H\xE4ufige thermische Schocks erkannt. Die kinetische Temperatur weicht oft stark vom Schnitt ab (m\xF6gliche regelm\xE4\xDFige T\xFCr\xF6ffnungen).",metric:"Delta MKT Instability"}),u>4&&i.push({type:"warning",message:`Hohe Instabilit\xE4t gemessen. Die maximale Standardabweichung lag bei ${u}\xB0C. Das K\xFChlsystem regelt unsauber.`,metric:"Standard Deviation Peak"}),i)}function z(n,i){if(n.length<=i)return n;let t=[],u=Math.ceil(n.length/i);t.push(n[0]);for(let e=1;e<n.length;e+=u)t.push(n[Math.min(e,n.length-1)]);return t}function C(n,i="hourly",t){let u={limitLow:t.low,limitHigh:t.high,activationEnergy:t.activationEnergy};return D(n,i,t.interval,u,t.minCountForMkt??1)}var P=class{static compute(n){let i=n.map(r=>r.value).filter(r=>r!==null).sort((r,o)=>r-o);if(i.length===0)return {min:NaN,max:NaN,avg:NaN,mean:NaN,median:NaN,stdDev:NaN,count:0};let t=i.length,u=i.reduce((r,o)=>r+o,0)/t,e=t%2===1?i[Math.floor(t/2)]:(i[t/2-1]+i[t/2])/2,l=i.reduce((r,o)=>r+Math.pow(o-u,2),0)/t,a=Math.sqrt(l);return {min:i[0],max:i[t-1],avg:u,mean:u,median:e,stdDev:a,count:t}}static computeInRange(n,i,t){let u=n.filter(e=>e.time>=i&&e.time<=t);return this.compute(u)}};function _(n,i){let t=typeof n=="number"?n:n.mean,u=typeof n=="number"?i??NaN:n.stdDev;return !Number.isFinite(t)||!Number.isFinite(u)||t===0?null:Math.abs(u/t)*100}var R=83144;function F(n,i=83144){let t=i/8.314,u=0,e=0;for(let a of n)a!==null&&(u+=Math.exp(-t/(a+273.15)),e++);if(e===0)return null;let l=u/e;return t/-Math.log(l)-273.15}function q(n,i,t=83144){let u=new Array(n.length),e=0;for(let l=0;l<n.length;l++){let a=n[l].time,r=a-i;for(;e<l&&n[e].time<r;)e++;let o=n.slice(e,l+1).map(s=>s.value),m=F(o,t);u[l]={time:a,value:m,synthetic:true};}return u}function k(n){let i=0,t=0;for(let l of n)l!==null&&(i+=l,t++);if(t===0)return null;let u=i/t,e=0;for(let l of n){if(l===null)continue;let a=l-u;e+=a*a;}return Math.sqrt(e/t)}function S(n){let i=0,t=0;for(let l of n)l!==null&&(i+=l,t++);if(t<2)return null;let u=i/t,e=0;for(let l of n){if(l===null)continue;let a=l-u;e+=a*a;}return Math.sqrt(e/(t-1))}function K(n,i,t=false){let u=new Array(n.length),e=0,l=t?S:k;for(let a=0;a<n.length;a++){let r=n[a].time,o=r-i;for(;e<a&&n[e].time<o;)e++;let m=n.slice(e,a+1).map(h=>h.value),s=l(m);u[a]={time:r,value:s,synthetic:true};}return u}function G(n,i){let{high:t,low:u}=i,e={msAboveHigh:0,msBelowLow:0,globalMax:null,globalMin:null,excursions:[]},l=null;for(let a=0;a<n.length;a++){let r=n[a];if(r.value===null){l&&(e.excursions.push(l),l=null);continue}(e.globalMax===null||r.value>e.globalMax)&&(e.globalMax=r.value),(e.globalMin===null||r.value<e.globalMin)&&(e.globalMin=r.value);let o=t!==void 0&&r.value>t,m=u!==void 0&&r.value<u,s=o?"above":m?"below":null,h=n[a-1];if(h&&h.value!==null&&s!==null){let g=r.time-h.time;s==="above"?e.msAboveHigh+=g:e.msBelowLow+=g;}if(s===null){l&&(e.excursions.push(l),l=null);continue}l&&l.side===s?(l.endTime=r.time,l.durationMs=l.endTime-l.startTime,(s==="above"&&r.value>l.extremum||s==="below"&&r.value<l.extremum)&&(l.extremum=r.value)):(l&&e.excursions.push(l),l={startTime:r.time,endTime:r.time,side:s,extremum:r.value,durationMs:0});}return l&&e.excursions.push(l),e}function O(n){if(n.length===0)return [];let i=new Map;for(let u of n)for(let e of u.data){if(e.value===null)continue;let l=i.get(e.time);l||(l=[],i.set(e.time,l)),l.push({name:u.name,value:e.value});}let t=[];for(let u of [...i.keys()].sort((e,l)=>e-l)){let e=i.get(u);if(e.length===0){t.push({time:u,min:null,max:null,delta:null,minSensor:null,maxSensor:null});continue}let l=e[0],a=e[0];for(let r=1;r<e.length;r++)e[r].value<l.value&&(l=e[r]),e[r].value>a.value&&(a=e[r]);t.push({time:u,min:l.value,max:a.value,delta:e.length<2?null:a.value-l.value,minSensor:l.name,maxSensor:a.name});}return t}function U(n){let i=[];for(let e of n){let l=0,a=0,r=1/0,o=-1/0;for(let m of e.data)m.value!==null&&(l+=m.value,a++,m.value<r&&(r=m.value),m.value>o&&(o=m.value));a!==0&&i.push({name:e.name,mean:l/a,min:r,max:o,count:a});}if(i.length===0)return {sensors:[],hottest:null,coldest:null,meanDelta:null};let t=i[0],u=i[0];for(let e=1;e<i.length;e++)i[e].mean>t.mean&&(t=i[e]),i[e].mean<u.mean&&(u=i[e]);return {sensors:i,hottest:t,coldest:u,meanDelta:t.mean-u.mean}}function y(n){let i=0,t=0,u=0,e=0,l=0;for(let m of n)m.value!==null&&(i++,t+=m.time,u+=m.value,e+=m.time*m.value,l+=m.time*m.time);if(i<2)return null;let a=i*l-t*t;if(a===0)return null;let r=(i*e-t*u)/a,o=(u-r*t)/i;return {slope:r,intercept:o,n:i}}function E(n,i){if(n.length===0)return null;let t=n[n.length-1].time,u=t-i,e=n.filter(l=>l.time>=u&&l.time<=t);return y(e)}function V(n,i){let t=[],u=0;for(let e=0;e<n.length;e++){let l=n[e].time,a=l-i;for(;u<n.length&&n[u].time<a;)u++;let r=y(n.slice(u,e+1));t.push({time:l,fit:r});}return t}function W(n,i,t={}){let u=t.lookbackMs??9e5,e=t.side??"either";if(n.length===0)return null;let l=n[n.length-1];if(l.value===null)return null;let a=E(n,u);if(!a||a.slope===0)return null;let r=a.slope>0&&l.value<i,o=a.slope<0&&l.value>i;if(e==="above"&&!r||e==="below"&&!o||e==="either"&&!r&&!o)return null;let m=(i-a.intercept)/a.slope,s=m-l.time;return s<=0?null:{msUntil:s,eta:m,fit:a,currentValue:l.value}}function Y(n,i={}){let t=i.refTempCelsius??121.11,u=i.zValueKelvin??10,e=0;for(let l=0;l<n.length-1;l++){let a=n[l],r=n[l+1];if(a.value===null||r.value===null)continue;let o=(r.time-a.time)/6e4;if(o<=0)continue;let m=Math.pow(10,(a.value-t)/u),s=Math.pow(10,(r.value-t)/u);e+=(m+s)/2*o;}return e}/*!
1
+ var H=class{static interpolateDataPoint(n,i,t){return {time:n.time+t*(i.time-n.time),value:(n.value??0)+t*((i.value??0)-(n.value??0))}}static interpolateAggregatedPoint(n,i,t){let u=(e,l)=>e!==null&&l!==null?e+t*(l-e):null;return {time:n.time+t*(i.time-n.time),min:u(n.min,i.min),max:u(n.max,i.max),avg:u(n.avg,i.avg),count:Math.round(n.count+t*(i.count-n.count))}}static getRuns(n,i,t=0){let u=[...n].sort((r,o)=>r.time-o.time),e=[],l=[],a=null;for(let r of u){let o=i(r),m=t>0&&a&&r.time-a.time>t;(o||m)&&l.length&&(e.push(l),l=[]),o||l.push(r),a=r;}return l.length&&e.push(l),e}static splitByBoundaries(n,i,t,u){if(n.length===0)return [];if(i.length===0)return [{data:n,zoneIndex:0}];let e=[...i].sort((o,m)=>o-m),l=[],a=o=>{let m=0;for(let s=0;s<e.length&&o>=e[s];s++)m=s+1;return m},r=[n[0]];for(let o=1;o<n.length;o++){let m=n[o-1],s=n[o],h=t(m),g=t(s),d;g>h?d=e.filter(c=>c>h&&c<=g):g<h?d=e.filter(c=>c>=g&&c<h).reverse():d=[];for(let c of d){let p=(c-h)/(g-h),x=u(m,s,p);r.push(x),l.push({data:r,zoneIndex:a((h+c)/2)}),r=[x];}r.push(s);}if(r.length>0){let o=t(r[0]),m=t(r[r.length-1]);l.push({data:r,zoneIndex:a((o+m)/2)});}return l}static splitByThreshold(n,i,t,u){let e=this.splitByBoundaries(n,[i],t,u),l={above:[],below:[]};for(let a of e)a.zoneIndex===0?l.below.push(a.data):l.above.push(a.data);return l}},S={pharma_cold:{limitLow:2,limitHigh:8,activationEnergy:83144},pharma_ambient:{limitLow:15,limitHigh:25,activationEnergy:83144},blood:{limitLow:2,limitHigh:6,activationEnergy:83144},food_chilled:{limitLow:0,limitHigh:4},frozen:{limitLow:-40,limitHigh:-18}},T=8.314462618,w=273.15;function y(n,i,t,u=1){let e=i.filter(f=>f.value!==null&&f.value!==void 0&&!isNaN(f.value)).map(f=>f.value);if(!e.length)return {time:n,min:null,max:null,avg:null,mkt:null,deltaMkt:null,stdDev:null,minutesAboveHigh:t?0:null,minutesBelowLow:t?0:null,count:0};let l=Math.min(...e),a=Math.max(...e),r=e.reduce((f,v)=>f+v,0)/e.length,o=t?0:null,m=t?0:null;if(t)for(let f=0;f<i.length;f++){let v=i[f];if(v.value!==null&&v.value!==void 0){let b=i[f+1],M=(b?b.time-v.time:0)/(1e3*60);v.value>t.limitHigh&&(o+=M),v.value<t.limitLow&&(m+=M);}}let s=0,h=0,g=(t?.activationEnergy??83144)/T;for(let f of e){s+=Math.pow(f-r,2);let v=f+w;h+=Math.exp(-g/v);}let d=s/e.length,c=Math.sqrt(d),p=null,x=null;if(e.length>=u){let f=h/e.length,v=g/-Math.log(f);p=Number((v-w).toFixed(2)),x=Number((p-r).toFixed(2));}return {time:n,min:Number(l.toFixed(2)),max:Number(a.toFixed(2)),avg:Number(r.toFixed(2)),mkt:p,deltaMkt:x,stdDev:Number(c.toFixed(2)),minutesAboveHigh:o!==null?Number(o.toFixed(1)):null,minutesBelowLow:m!==null?Number(m.toFixed(1)):null,count:e.length}}function D(n,i="hourly",t,u,e=1){if(!n.length)return [];let l=i==="hourly"?36e5:i==="daily"?864e5:t??3600*1e3,a=[...n].sort((s,h)=>s.time-h.time),r=[],o=a[0].time,m=[];for(let s of a){for(;s.time-o>=l;)r.push(y(o,m,u,e)),o+=l,m=[];m.push(s);}return r.push(y(o,m,u,e)),r}function E(n,i){let t=Math.min(...i),u=Math.max(...i),e=i.reduce((l,a)=>l+a,0)/i.length;return {time:n,min:t,max:u,avg:e,count:i.length}}function B(n,i=6e4){if(n.length<2)return [];let t=[...n].sort((e,l)=>e.time-l.time),u=[];for(let e=1;e<t.length;e++)t[e].time-t[e-1].time>i&&u.push({startTime:t[e-1].time,endTime:t[e].time});return u}function z(n){let i=[],t=0,u=0,e=0,l=0;for(let a of n)a.count>0&&(e++,a.minutesAboveHigh&&(t+=a.minutesAboveHigh),a.stdDev&&a.stdDev>u&&(u=a.stdDev),a.deltaMkt&&a.deltaMkt>2&&l++);return e===0?[]:(t>120&&i.push({type:"critical",message:`Kritische Gesamtbelastung: Das Produkt war im gesamten Zeitraum insgesamt ${Math.round(t)} Minuten zu warm.`,metric:"Total Excursion Time"}),l>e*.15&&i.push({type:"warning",message:"Frequent thermal shocks detected. The kinetic temperature deviates strongly from the average (possible regular door openings).",metric:"Delta MKT Instability"}),u>4&&i.push({type:"warning",message:`High instability measured. Maximum standard deviation was ${u}\xB0C. The cooling system controls imprecisely.`,metric:"Standard Deviation Peak"}),i)}function I(n,i){if(n.length<=i)return n;let t=[],u=Math.ceil(n.length/i);t.push(n[0]);for(let e=1;e<n.length;e+=u)t.push(n[Math.min(e,n.length-1)]);return t}function C(n,i="hourly",t){let u={limitLow:t.low,limitHigh:t.high,activationEnergy:t.activationEnergy};return D(n,i,t.interval,u,t.minCountForMkt??1)}var P=class{static compute(n){let i=n.map(r=>r.value).filter(r=>r!==null).sort((r,o)=>r-o);if(i.length===0)return {min:NaN,max:NaN,avg:NaN,mean:NaN,median:NaN,stdDev:NaN,count:0};let t=i.length,u=i.reduce((r,o)=>r+o,0)/t,e=t%2===1?i[Math.floor(t/2)]:(i[t/2-1]+i[t/2])/2,l=i.reduce((r,o)=>r+Math.pow(o-u,2),0)/t,a=Math.sqrt(l);return {min:i[0],max:i[t-1],avg:u,mean:u,median:e,stdDev:a,count:t}}static computeInRange(n,i,t){let u=n.filter(e=>e.time>=i&&e.time<=t);return this.compute(u)}};function _(n,i){let t=typeof n=="number"?n:n.mean,u=typeof n=="number"?i??NaN:n.stdDev;return !Number.isFinite(t)||!Number.isFinite(u)||t===0?null:Math.abs(u/t)*100}var q=83144;function k(n,i=83144){let t=i/8.314,u=0,e=0;for(let a of n)a!==null&&(u+=Math.exp(-t/(a+273.15)),e++);if(e===0)return null;let l=u/e;return t/-Math.log(l)-273.15}function R(n,i,t=83144){let u=new Array(n.length),e=0;for(let l=0;l<n.length;l++){let a=n[l].time,r=a-i;for(;e<l&&n[e].time<r;)e++;let o=n.slice(e,l+1).map(s=>s.value),m=k(o,t);u[l]={time:a,value:m,synthetic:true};}return u}function F(n){let i=0,t=0;for(let l of n)l!==null&&(i+=l,t++);if(t===0)return null;let u=i/t,e=0;for(let l of n){if(l===null)continue;let a=l-u;e+=a*a;}return Math.sqrt(e/t)}function L(n){let i=0,t=0;for(let l of n)l!==null&&(i+=l,t++);if(t<2)return null;let u=i/t,e=0;for(let l of n){if(l===null)continue;let a=l-u;e+=a*a;}return Math.sqrt(e/(t-1))}function G(n,i,t=false){let u=new Array(n.length),e=0,l=t?L:F;for(let a=0;a<n.length;a++){let r=n[a].time,o=r-i;for(;e<a&&n[e].time<o;)e++;let m=n.slice(e,a+1).map(h=>h.value),s=l(m);u[a]={time:r,value:s,synthetic:true};}return u}function K(n,i){let{high:t,low:u}=i,e={msAboveHigh:0,msBelowLow:0,globalMax:null,globalMin:null,excursions:[]},l=null;for(let a=0;a<n.length;a++){let r=n[a];if(r.value===null){l&&(e.excursions.push(l),l=null);continue}(e.globalMax===null||r.value>e.globalMax)&&(e.globalMax=r.value),(e.globalMin===null||r.value<e.globalMin)&&(e.globalMin=r.value);let o=t!==void 0&&r.value>t,m=u!==void 0&&r.value<u,s=o?"above":m?"below":null,h=n[a-1];if(h&&h.value!==null&&s!==null){let g=r.time-h.time;s==="above"?e.msAboveHigh+=g:e.msBelowLow+=g;}if(s===null){l&&(e.excursions.push(l),l=null);continue}l&&l.side===s?(l.endTime=r.time,l.durationMs=l.endTime-l.startTime,(s==="above"&&r.value>l.extremum||s==="below"&&r.value<l.extremum)&&(l.extremum=r.value)):(l&&e.excursions.push(l),l={startTime:r.time,endTime:r.time,side:s,extremum:r.value,durationMs:0});}return l&&e.excursions.push(l),e}function O(n){if(n.length===0)return [];let i=new Map;for(let u of n)for(let e of u.data){if(e.value===null)continue;let l=i.get(e.time);l||(l=[],i.set(e.time,l)),l.push({name:u.name,value:e.value});}let t=[];for(let u of [...i.keys()].sort((e,l)=>e-l)){let e=i.get(u);if(e.length===0){t.push({time:u,min:null,max:null,delta:null,minSensor:null,maxSensor:null});continue}let l=e[0],a=e[0];for(let r=1;r<e.length;r++)e[r].value<l.value&&(l=e[r]),e[r].value>a.value&&(a=e[r]);t.push({time:u,min:l.value,max:a.value,delta:e.length<2?null:a.value-l.value,minSensor:l.name,maxSensor:a.name});}return t}function U(n){let i=[];for(let e of n){let l=0,a=0,r=1/0,o=-1/0;for(let m of e.data)m.value!==null&&(l+=m.value,a++,m.value<r&&(r=m.value),m.value>o&&(o=m.value));a!==0&&i.push({name:e.name,mean:l/a,min:r,max:o,count:a});}if(i.length===0)return {sensors:[],hottest:null,coldest:null,meanDelta:null};let t=i[0],u=i[0];for(let e=1;e<i.length;e++)i[e].mean>t.mean&&(t=i[e]),i[e].mean<u.mean&&(u=i[e]);return {sensors:i,hottest:t,coldest:u,meanDelta:t.mean-u.mean}}function N(n){let i=0,t=0,u=0,e=0,l=0;for(let m of n)m.value!==null&&(i++,t+=m.time,u+=m.value,e+=m.time*m.value,l+=m.time*m.time);if(i<2)return null;let a=i*l-t*t;if(a===0)return null;let r=(i*e-t*u)/a,o=(u-r*t)/i;return {slope:r,intercept:o,n:i}}function A(n,i){if(n.length===0)return null;let t=n[n.length-1].time,u=t-i,e=n.filter(l=>l.time>=u&&l.time<=t);return N(e)}function V(n,i){let t=[],u=0;for(let e=0;e<n.length;e++){let l=n[e].time,a=l-i;for(;u<n.length&&n[u].time<a;)u++;let r=N(n.slice(u,e+1));t.push({time:l,fit:r});}return t}function W(n,i,t={}){let u=t.lookbackMs??9e5,e=t.side??"either";if(n.length===0)return null;let l=n[n.length-1];if(l.value===null)return null;let a=A(n,u);if(!a||a.slope===0)return null;let r=a.slope>0&&l.value<i,o=a.slope<0&&l.value>i;if(e==="above"&&!r||e==="below"&&!o||e==="either"&&!r&&!o)return null;let m=(i-a.intercept)/a.slope,s=m-l.time;return s<=0?null:{msUntil:s,eta:m,fit:a,currentValue:l.value}}function Y(n,i={}){let t=i.refTempCelsius??121.11,u=i.zValueKelvin??10,e=0;for(let l=0;l<n.length-1;l++){let a=n[l],r=n[l+1];if(a.value===null||r.value===null)continue;let o=(r.time-a.time)/6e4;if(o<=0)continue;let m=Math.pow(10,(a.value-t)/u),s=Math.pow(10,(r.value-t)/u);e+=(m+s)/2*o;}return e}/*!
2
2
  * MLTimeGraph — Copyright (c) 2026 Michael Lechner
3
3
  * MIT with Attribution: free use incl. commercial requires visible credit to
4
4
  * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
@@ -12,4 +12,4 @@ var H=class{static interpolateDataPoint(n,i,t){return {time:n.time+t*(i.time-n.t
12
12
  * MLTimeGraph — Copyright (c) 2026 Michael Lechner
13
13
  * MIT with Attribution: free use incl. commercial requires visible credit to
14
14
  * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
15
- */export{R as DEFAULT_ACTIVATION_ENERGY,L as PRODUCT_PROFILES,H as SeriesProcessor,P as StatsAggregator,D as aggregateBySlot,C as aggregateWithStats,I as analyzeLongTermTrends,G as computeLimitExcursions,A as createAggr,E as currentTrend,B as detectGaps,z as downsample,Y as f0Sterilization,U as hotColdSpots,y as linearFit,F as mkt,W as predictTimeToThreshold,q as rollingMkt,K as rollingStdDev,V as rollingTrend,S as sampleStdDev,O as spatialDelta,k as stdDev,_ as varianceCoefficient};
15
+ */export{q as DEFAULT_ACTIVATION_ENERGY,S as PRODUCT_PROFILES,H as SeriesProcessor,P as StatsAggregator,D as aggregateBySlot,C as aggregateWithStats,z as analyzeLongTermTrends,K as computeLimitExcursions,E as createAggr,A as currentTrend,B as detectGaps,I as downsample,Y as f0Sterilization,U as hotColdSpots,N as linearFit,k as mkt,W as predictTimeToThreshold,R as rollingMkt,G as rollingStdDev,V as rollingTrend,L as sampleStdDev,O as spatialDelta,F as stdDev,_ as varianceCoefficient};
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { e as LegendOrientation, f as Renderer, M as Marker, d as LegendItem, b as LayoutResult, R as RenderOutput, t as theme } from './layout-DDdMtPrB.js';
2
- import { U as LineVariant, g as AnySeries, aa as Threshold, P as Highlight, I as Gap, N as GapsConfig, d as Annotation, e as AnnotationBandConfig, o as DrawCommand, ad as TimeScale, V as LinearScale, O as HatchVariant, f as AnnotationBandItem, _ as PointStyleType, ac as ThresholdLabelPosition, a5 as SeriesStyle, G as FillSpec, af as TimeSeries, A as AggregatedPoint, a as AggregatedSeries, k as DataPoint } from './scale-DcFBNLdU.js';
3
- export { b as AggregationConfig, c as AggregationMode, l as DataPointRef, E as EnumMap, F as FillBound, x as FillDirectionType, y as FillRegion, z as FillSide, H as FillStyle, J as GapConfig, K as GapLabel, L as GapRegion, M as GapStyle, R as LineDashStyle, T as LineStyle, X as MarkerStyle, a2 as SensorType, a3 as SeriesOverlay, a4 as SeriesOverlayKind, a6 as SeriesType, a7 as ShadowStyle, ab as ThresholdLabelConfig } from './scale-DcFBNLdU.js';
1
+ import { e as LegendOrientation, f as Renderer, M as Marker, d as LegendItem, b as LayoutResult, R as RenderOutput, t as theme } from './layout-C_t250fG.js';
2
+ import { U as LineVariant, g as AnySeries, aa as Threshold, P as Highlight, I as Gap, N as GapsConfig, d as Annotation, e as AnnotationBandConfig, o as DrawCommand, ad as TimeScale, V as LinearScale, a5 as SeriesStyle, G as FillSpec, af as TimeSeries, O as HatchVariant, ac as ThresholdLabelPosition, _ as PointStyleType, f as AnnotationBandItem, A as AggregatedPoint, a as AggregatedSeries, k as DataPoint } from './scale-DFyECKZq.js';
3
+ export { b as AggregationConfig, c as AggregationMode, l as DataPointRef, E as EnumMap, F as FillBound, x as FillDirectionType, y as FillRegion, z as FillSide, H as FillStyle, J as GapConfig, K as GapLabel, L as GapRegion, M as GapStyle, R as LineDashStyle, T as LineStyle, X as MarkerStyle, a2 as SensorType, a3 as SeriesOverlay, a4 as SeriesOverlayKind, a6 as SeriesType, a7 as ShadowStyle, ab as ThresholdLabelConfig } from './scale-DFyECKZq.js';
4
4
 
5
5
  /*!
6
6
  * MLTimeGraph — Copyright (c) 2026 Michael Lechner
@@ -47,6 +47,8 @@ interface AxisLabelsStyle {
47
47
  interface YAxisConfig {
48
48
  /** Position is 'left' or 'right' to determine placement side. Defaults to 'left'. */
49
49
  position?: "left" | "right";
50
+ /** Optional explicit horizontal pixel offset from chart edge (defaults to index * 50px). */
51
+ offset?: number;
50
52
  /** Rotated label drawn next to the axis. */
51
53
  label?: string;
52
54
  /** Override the value domain. `'auto'` lets the chart compute it from data. */
@@ -82,6 +84,7 @@ interface XAxisConfig {
82
84
  /**
83
85
  * Top-level axes container on {@link MLTimeGraphOptions}.
84
86
  * `left` and `right` correspond to `yAxisIndex: 0` and `yAxisIndex: 1` series.
87
+ * Additional axes can be defined in the `y` array, with indices mapping to series via `yAxisIndex`.
85
88
  */
86
89
  interface AxesConfig {
87
90
  x?: XAxisConfig;
@@ -105,7 +108,7 @@ interface Margin {
105
108
  left: number;
106
109
  }
107
110
  /** Legend placement position — inside or outside the chart area. */
108
- type LegendPosition = 'inside-right' | 'inside-left' | 'outside-right' | 'outside-left' | 'separate';
111
+ type LegendPosition = "inside-right" | "inside-left" | "outside-right" | "outside-left" | "separate";
109
112
  interface LegendOptions {
110
113
  /** Show the legend (default false). */
111
114
  show?: boolean;
@@ -294,7 +297,8 @@ interface TooltipOptions {
294
297
  * Returns a cleanup function that removes the listeners and the tooltip
295
298
  * element. Idempotent — calling cleanup twice is a no-op.
296
299
  *
297
- * Style the tooltip via CSS:
300
+ * Style the tooltip via CSS. Do NOT set `transform` — placement (incl. the
301
+ * flip that keeps the box inside the chart) is computed in JS via `transform`:
298
302
  *
299
303
  * ```css
300
304
  * .mlc-tooltip {
@@ -306,7 +310,6 @@ interface TooltipOptions {
306
310
  * border: 1px solid rgba(217, 119, 6, 0.35);
307
311
  * border-radius: 4px;
308
312
  * font-size: 12px;
309
- * transform: translate(8px, 8px);
310
313
  * box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
311
314
  * }
312
315
  * .mlc-tooltip__time { opacity: 0.7; margin-bottom: 4px; font-weight: 600; }
@@ -465,6 +468,13 @@ declare class GraphBuilder {
465
468
  */
466
469
  mount(target: HTMLElement | string): MLTimeGraph;
467
470
  }
471
+
472
+ /*!
473
+ * MLTimeGraph — Copyright (c) 2026 Michael Lechner
474
+ * MIT with Attribution: free use incl. commercial requires visible credit to
475
+ * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
476
+ */
477
+
468
478
  /**
469
479
  * TimeSeriesBuilder simplifies constructing raw TimeSeries datasets.
470
480
  */
@@ -473,6 +483,11 @@ declare class TimeSeriesBuilder {
473
483
  constructor(name: string);
474
484
  /** Appends a value-point to the series. */
475
485
  addPoint(time: number, value: number | null): this;
486
+ /** Sets the data points array for the series. */
487
+ setData(points: Array<{
488
+ time: number;
489
+ value: number | null;
490
+ }>): this;
476
491
  /** Appends an array of data points directly to the series. */
477
492
  addPoints(points: Array<{
478
493
  time: number;
@@ -482,6 +497,10 @@ declare class TimeSeriesBuilder {
482
497
  addFloats(times: number[], values: Array<number | null>): this;
483
498
  /** Appends a null point (signaling gaps/offline state). */
484
499
  addNullPoint(time: number): this;
500
+ /** Assigns the target Y-axis index (0=left, 1=right, 2+=multi-axis). */
501
+ setYAxisIndex(index: number): this;
502
+ /** Shortcut to apply a primary stroke color to the series line. */
503
+ setColor(color: string): this;
485
504
  /** Assigns the visual styles configuration. */
486
505
  setStyle(style: SeriesStyle): this;
487
506
  /** Shortcut to apply custom stroke styling (color, width, dash style). */
@@ -497,6 +516,13 @@ declare class TimeSeriesBuilder {
497
516
  /** Compiles and returns the TimeSeries object. */
498
517
  build(): TimeSeries;
499
518
  }
519
+
520
+ /*!
521
+ * MLTimeGraph — Copyright (c) 2026 Michael Lechner
522
+ * MIT with Attribution: free use incl. commercial requires visible credit to
523
+ * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
524
+ */
525
+
500
526
  /**
501
527
  * ThresholdBuilder simplifies constructing value boundaries.
502
528
  */
@@ -518,6 +544,13 @@ declare class ThresholdBuilder {
518
544
  /** Compiles and returns the Threshold object. */
519
545
  build(): Threshold;
520
546
  }
547
+
548
+ /*!
549
+ * MLTimeGraph — Copyright (c) 2026 Michael Lechner
550
+ * MIT with Attribution: free use incl. commercial requires visible credit to
551
+ * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
552
+ */
553
+
521
554
  /**
522
555
  * MarkerBuilder simplifies constructing vertical pins/markers.
523
556
  */
@@ -539,6 +572,13 @@ declare class MarkerBuilder {
539
572
  /** Compiles and returns the Marker object. */
540
573
  build(): Marker;
541
574
  }
575
+
576
+ /*!
577
+ * MLTimeGraph — Copyright (c) 2026 Michael Lechner
578
+ * MIT with Attribution: free use incl. commercial requires visible credit to
579
+ * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
580
+ */
581
+
542
582
  /**
543
583
  * HighlightBuilder simplifies constructing highlighted time bands.
544
584
  */
@@ -558,6 +598,13 @@ declare class HighlightBuilder {
558
598
  /** Compiles and returns the Highlight object. */
559
599
  build(): Highlight;
560
600
  }
601
+
602
+ /*!
603
+ * MLTimeGraph — Copyright (c) 2026 Michael Lechner
604
+ * MIT with Attribution: free use incl. commercial requires visible credit to
605
+ * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
606
+ */
607
+
561
608
  /**
562
609
  * AnnotationBuilder simplifies constructing free-form annotations.
563
610
  */
@@ -607,6 +654,13 @@ declare class AnnotationBuilder {
607
654
  /** Compiles and returns the Annotation object. */
608
655
  build(): Annotation;
609
656
  }
657
+
658
+ /*!
659
+ * MLTimeGraph — Copyright (c) 2026 Michael Lechner
660
+ * MIT with Attribution: free use incl. commercial requires visible credit to
661
+ * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
662
+ */
663
+
610
664
  /**
611
665
  * AnnotationBandBuilder simplifies constructing annotation bands below the chart.
612
666
  */
@@ -645,7 +699,7 @@ declare class AnnotationBandItemBuilder {
645
699
  /** Sets the border stroke width. */
646
700
  setStrokeWidth(w: number): this;
647
701
  /** Sets the text label and optional label formatting fields. */
648
- setLabel(lbl: string, fontSize?: number, fill?: string, baseline?: 'top' | 'middle' | 'bottom'): this;
702
+ setLabel(lbl: string, fontSize?: number, fill?: string, baseline?: "top" | "middle" | "bottom"): this;
649
703
  /** Compiles and returns the AnnotationBandItem object. */
650
704
  build(): AnnotationBandItem;
651
705
  }
@@ -661,7 +715,11 @@ declare class SVGRenderer extends Renderer {
661
715
  constructor(options?: {
662
716
  width?: number | string;
663
717
  height?: number | string;
718
+ /** Optional solid background colour, drawn as a full-size rect (e.g. "white"). */
719
+ background?: string;
664
720
  });
721
+ /** Set a solid background colour, drawn as a full-size rect behind the chart. */
722
+ setBackgroundColor(color: string): void;
665
723
  render(commands: DrawCommand[]): RenderOutput;
666
724
  _toSVG(cmd: DrawCommand): string;
667
725
  /** Register or retrieve a hatch pattern by variant. Returns the pattern id for use as fill="url(#id)". */