@toclocoinc/lattice-grid 1.67.0 → 1.68.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.
Files changed (163) hide show
  1. package/README.md +1 -1
  2. package/angular/package.json +1 -1
  3. package/docs/API.html +21 -16
  4. package/docs/api-detail.html +40 -5
  5. package/lattice-grid.d.ts +12 -2
  6. package/lattice-grid.esm.min.js +27 -7
  7. package/lattice-grid.min.cjs +27 -7
  8. package/lattice-grid.min.js +27 -7
  9. package/modules/ai.d.ts +1 -1
  10. package/modules/ai.esm.min.js +3 -3
  11. package/modules/ai.min.cjs +3 -3
  12. package/modules/ai.min.js +3 -3
  13. package/modules/angular.d.ts +1 -1
  14. package/modules/angular.esm.min.js +3 -3
  15. package/modules/angular.min.cjs +3 -3
  16. package/modules/angular.min.js +3 -3
  17. package/modules/chart-alluvial.d.ts +1 -1
  18. package/modules/chart-alluvial.esm.min.js +1 -1
  19. package/modules/chart-alluvial.min.cjs +1 -1
  20. package/modules/chart-alluvial.min.js +1 -1
  21. package/modules/chart-arc.d.ts +1 -1
  22. package/modules/chart-arc.esm.min.js +1 -1
  23. package/modules/chart-arc.min.cjs +1 -1
  24. package/modules/chart-arc.min.js +1 -1
  25. package/modules/chart-bubblemap.d.ts +1 -1
  26. package/modules/chart-bubblemap.esm.min.js +1 -1
  27. package/modules/chart-bubblemap.min.cjs +1 -1
  28. package/modules/chart-bubblemap.min.js +1 -1
  29. package/modules/chart-bump.d.ts +1 -1
  30. package/modules/chart-bump.esm.min.js +1 -1
  31. package/modules/chart-bump.min.cjs +1 -1
  32. package/modules/chart-bump.min.js +1 -1
  33. package/modules/chart-calendar.d.ts +1 -1
  34. package/modules/chart-calendar.esm.min.js +1 -1
  35. package/modules/chart-calendar.min.cjs +1 -1
  36. package/modules/chart-calendar.min.js +1 -1
  37. package/modules/chart-decomposition.d.ts +1 -1
  38. package/modules/chart-decomposition.esm.min.js +1 -1
  39. package/modules/chart-decomposition.min.cjs +1 -1
  40. package/modules/chart-decomposition.min.js +1 -1
  41. package/modules/chart-diverging.d.ts +1 -1
  42. package/modules/chart-diverging.esm.min.js +1 -1
  43. package/modules/chart-diverging.min.cjs +1 -1
  44. package/modules/chart-diverging.min.js +1 -1
  45. package/modules/chart-dumbbell.d.ts +1 -1
  46. package/modules/chart-dumbbell.esm.min.js +1 -1
  47. package/modules/chart-dumbbell.min.cjs +1 -1
  48. package/modules/chart-dumbbell.min.js +1 -1
  49. package/modules/chart-fan.d.ts +1 -1
  50. package/modules/chart-fan.esm.min.js +1 -1
  51. package/modules/chart-fan.min.cjs +1 -1
  52. package/modules/chart-fan.min.js +1 -1
  53. package/modules/chart-hexbin.d.ts +1 -1
  54. package/modules/chart-hexbin.esm.min.js +1 -1
  55. package/modules/chart-hexbin.min.cjs +1 -1
  56. package/modules/chart-hexbin.min.js +1 -1
  57. package/modules/chart-hexmap.d.ts +1 -1
  58. package/modules/chart-hexmap.esm.min.js +1 -1
  59. package/modules/chart-hexmap.min.cjs +1 -1
  60. package/modules/chart-hexmap.min.js +1 -1
  61. package/modules/chart-icicle.d.ts +1 -1
  62. package/modules/chart-icicle.esm.min.js +1 -1
  63. package/modules/chart-icicle.min.cjs +1 -1
  64. package/modules/chart-icicle.min.js +1 -1
  65. package/modules/chart-markermap.d.ts +1 -1
  66. package/modules/chart-markermap.esm.min.js +1 -1
  67. package/modules/chart-markermap.min.cjs +1 -1
  68. package/modules/chart-markermap.min.js +1 -1
  69. package/modules/chart-parallel.d.ts +1 -1
  70. package/modules/chart-parallel.esm.min.js +1 -1
  71. package/modules/chart-parallel.min.cjs +1 -1
  72. package/modules/chart-parallel.min.js +1 -1
  73. package/modules/chart-ridgeline.d.ts +1 -1
  74. package/modules/chart-ridgeline.esm.min.js +1 -1
  75. package/modules/chart-ridgeline.min.cjs +1 -1
  76. package/modules/chart-ridgeline.min.js +1 -1
  77. package/modules/chart-roc.d.ts +1 -1
  78. package/modules/chart-roc.esm.min.js +1 -1
  79. package/modules/chart-roc.min.cjs +1 -1
  80. package/modules/chart-roc.min.js +1 -1
  81. package/modules/chart-slope.d.ts +1 -1
  82. package/modules/chart-slope.esm.min.js +1 -1
  83. package/modules/chart-slope.min.cjs +1 -1
  84. package/modules/chart-slope.min.js +1 -1
  85. package/modules/chart-splom.d.ts +1 -1
  86. package/modules/chart-splom.esm.min.js +1 -1
  87. package/modules/chart-splom.min.cjs +1 -1
  88. package/modules/chart-splom.min.js +1 -1
  89. package/modules/chart-waffle.d.ts +1 -1
  90. package/modules/chart-waffle.esm.min.js +1 -1
  91. package/modules/chart-waffle.min.cjs +1 -1
  92. package/modules/chart-waffle.min.js +1 -1
  93. package/modules/charts.d.ts +1 -1
  94. package/modules/charts.esm.min.js +1376 -1249
  95. package/modules/charts.min.cjs +1376 -1249
  96. package/modules/charts.min.js +1376 -1249
  97. package/modules/data-router.d.ts +1 -1
  98. package/modules/data-router.esm.min.js +204 -8
  99. package/modules/data-router.min.cjs +204 -8
  100. package/modules/data-router.min.js +204 -8
  101. package/modules/devtools.d.ts +1 -1
  102. package/modules/devtools.esm.min.js +1 -1
  103. package/modules/devtools.min.cjs +1 -1
  104. package/modules/devtools.min.js +1 -1
  105. package/modules/dhtmlx-compat.d.ts +1 -1
  106. package/modules/dhtmlx-compat.esm.min.js +3 -3
  107. package/modules/dhtmlx-compat.min.cjs +3 -3
  108. package/modules/dhtmlx-compat.min.js +3 -3
  109. package/modules/gantt.d.ts +1 -1
  110. package/modules/gantt.esm.min.js +32 -7
  111. package/modules/gantt.min.cjs +32 -7
  112. package/modules/gantt.min.js +32 -7
  113. package/modules/geo-europe-nuts.d.ts +1 -1
  114. package/modules/geo-europe-nuts.esm.min.js +1 -1
  115. package/modules/geo-uk.d.ts +1 -1
  116. package/modules/geo-uk.esm.min.js +1 -1
  117. package/modules/geo-us-states.d.ts +1 -1
  118. package/modules/geo-us-states.esm.min.js +1 -1
  119. package/modules/geo-world-110m.d.ts +1 -1
  120. package/modules/geo-world-110m.esm.min.js +1 -1
  121. package/modules/geo-world-50m.d.ts +1 -1
  122. package/modules/geo-world-50m.esm.min.js +1 -1
  123. package/modules/htmx.d.ts +1 -1
  124. package/modules/htmx.esm.min.js +27 -7
  125. package/modules/htmx.min.cjs +27 -7
  126. package/modules/htmx.min.js +27 -7
  127. package/modules/kanban.d.ts +1 -1
  128. package/modules/kanban.esm.min.js +3 -3
  129. package/modules/kanban.min.cjs +3 -3
  130. package/modules/kanban.min.js +3 -3
  131. package/modules/kpi.d.ts +26 -8
  132. package/modules/kpi.esm.min.js +31 -6
  133. package/modules/kpi.min.cjs +31 -6
  134. package/modules/kpi.min.js +31 -6
  135. package/modules/layout.d.ts +1 -1
  136. package/modules/layout.esm.min.js +3 -3
  137. package/modules/layout.min.cjs +3 -3
  138. package/modules/layout.min.js +3 -3
  139. package/modules/mock-socket.d.ts +1 -1
  140. package/modules/mock-socket.esm.min.js +1 -1
  141. package/modules/mock-socket.min.cjs +1 -1
  142. package/modules/mock-socket.min.js +1 -1
  143. package/modules/react.d.ts +1 -1
  144. package/modules/react.esm.min.js +3 -3
  145. package/modules/react.min.cjs +3 -3
  146. package/modules/react.min.js +3 -3
  147. package/modules/svelte.d.ts +1 -1
  148. package/modules/svelte.esm.min.js +3 -3
  149. package/modules/svelte.min.cjs +3 -3
  150. package/modules/svelte.min.js +3 -3
  151. package/modules/tabs.d.ts +1 -1
  152. package/modules/tabs.esm.min.js +13 -14
  153. package/modules/tabs.min.cjs +13 -14
  154. package/modules/tabs.min.js +13 -14
  155. package/modules/vue.d.ts +1 -1
  156. package/modules/vue.esm.min.js +3 -3
  157. package/modules/vue.min.cjs +3 -3
  158. package/modules/vue.min.js +3 -3
  159. package/modules/webcomponent.d.ts +1 -1
  160. package/modules/webcomponent.esm.min.js +27 -7
  161. package/modules/webcomponent.min.cjs +27 -7
  162. package/modules/webcomponent.min.js +27 -7
  163. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.67.0, charts module
2
+ * Lattice Grid 1.68.0, charts module
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -996,6 +996,8 @@ const STEPS=[1,2,2.5,5,10];
996
996
  const MINUTE=60000;
997
997
  const HOUR=3600000;
998
998
  const DAY=86400000;
999
+ const YEAR=365.25*DAY;
1000
+ const YEAR_COUNTS=[1,2,5,10,20,25,50,100];
999
1001
  const TIME_STEPS=[
1000
1002
  1,5,10,25,50,100,250,500,
1001
1003
  1000,5000,15000,30000,
@@ -1003,7 +1005,7 @@ MINUTE,5*MINUTE,15*MINUTE,30*MINUTE,
1003
1005
  HOUR,3*HOUR,6*HOUR,12*HOUR,
1004
1006
  DAY,2*DAY,7*DAY,14*DAY,
1005
1007
  30*DAY,90*DAY,180*DAY,
1006
- 365*DAY,2*365*DAY,5*365*DAY,10*365*DAY,100*365*DAY,
1008
+ ...YEAR_COUNTS.map((years)=>years*YEAR),
1007
1009
  ];
1008
1010
  function isNumber(v){
1009
1011
  return typeof v==='number'&&Number.isFinite(v);
@@ -1089,12 +1091,35 @@ out.push(Math.abs(v)<step/1e6?0:Number(v.toPrecision(12)));
1089
1091
  return out;
1090
1092
  }
1091
1093
  function timeStepFor(span,count=5){
1092
- const target=span/Math.max(1,count);
1093
- for(const candidate of TIME_STEPS)if(candidate>=target)return candidate;
1094
- return TIME_STEPS[TIME_STEPS.length-1];
1094
+ const wanted=Math.max(1,count);
1095
+ const target=span/wanted;
1096
+ let picked=TIME_STEPS[TIME_STEPS.length-1];
1097
+ for(const candidate of TIME_STEPS){
1098
+ if(candidate>=target){picked=candidate;break;}
1099
+ }
1100
+ if(picked<YEAR)return picked;
1101
+ let best=picked;
1102
+ let bestDiff=Math.abs(span/picked-wanted);
1103
+ for(const candidate of TIME_STEPS){
1104
+ if(candidate<YEAR)continue;
1105
+ const diff=Math.abs(span/candidate-wanted);
1106
+ if(diff<bestDiff){bestDiff=diff;best=candidate;}
1107
+ }
1108
+ return best;
1109
+ }
1110
+ function calendarYearTicks(domain,years){
1111
+ const minYear=new Date(domain.min).getUTCFullYear();
1112
+ let year=Math.ceil(minYear/years)*years;
1113
+ if(Date.UTC(year,0,1)<domain.min)year+=years;
1114
+ const out=[];
1115
+ for(;Date.UTC(year,0,1)<=domain.max;year+=years){
1116
+ out.push(Date.UTC(year,0,1));
1117
+ }
1118
+ return out;
1095
1119
  }
1096
1120
  function timeTicks(domain,count=5){
1097
1121
  const step=timeStepFor(domain.max-domain.min,count);
1122
+ if(step>=YEAR)return calendarYearTicks(domain,Math.round(step/YEAR));
1098
1123
  const out=[];
1099
1124
  const first=Math.ceil(domain.min/step)*step;
1100
1125
  for(let t=first;t<=domain.max;t+=step)out.push(t);
@@ -1545,1438 +1570,1540 @@ canvas.style.height=`${opts.height}px`;
1545
1570
  return typeof canvas.getContext==='function'?canvas.getContext('2d'):null;
1546
1571
  }
1547
1572
  });
1548
- __def("packages/modules/charts/annotations.js",function(__exports,__req){
1573
+ __def("packages/core/src/internal/util.js",function(__exports,__req){
1549
1574
  'use strict';
1550
- Object.defineProperty(__exports,"overlayLayer",{enumerable:true,get:function(){return overlayLayer;}});
1551
- Object.defineProperty(__exports,"annotationValue",{enumerable:true,get:function(){return annotationValue;}});
1552
- Object.defineProperty(__exports,"annotationAxis",{enumerable:true,get:function(){return annotationAxis;}});
1553
- Object.defineProperty(__exports,"annotationText",{enumerable:true,get:function(){return annotationText;}});
1554
- Object.defineProperty(__exports,"drawAnnotations",{enumerable:true,get:function(){return drawAnnotations;}});
1555
- const __m0=__req("packages/modules/charts/frame.js");
1556
- const Pool=__m0["Pool"];
1557
- const __m1=__req("packages/modules/charts/scale.js");
1558
- const isNumber=__m1["isNumber"];
1559
- const toNumber=__m1["toNumber"];
1560
- const __m2=__req("packages/modules/charts/svg.js");
1561
- const attrs=__m2["attrs"];
1562
- const path=__m2["path"];
1563
- const round=__m2["round"];
1564
- const setText=__m2["setText"];
1565
- const SVG_NS=__m2["SVG_NS"];
1566
- const __m3=__req("packages/modules/charts/styles.js");
1567
- const NS=__m3["NS"];
1568
- function overlayLayer(group,className){
1569
- for(const child of group.childNodes||[]){
1570
- if(child&&String(child.getAttribute&&child.getAttribute('class'))===className){
1571
- return(child);
1575
+ Object.defineProperty(__exports,"VERSION",{enumerable:true,get:function(){return VERSION;}});
1576
+ Object.defineProperty(__exports,"reportedWarnings",{enumerable:true,get:function(){return reportedWarnings;}});
1577
+ Object.defineProperty(__exports,"warnOnce",{enumerable:true,get:function(){return warnOnce;}});
1578
+ Object.defineProperty(__exports,"infoOnce",{enumerable:true,get:function(){return infoOnce;}});
1579
+ Object.defineProperty(__exports,"resetWarnings",{enumerable:true,get:function(){return resetWarnings;}});
1580
+ Object.defineProperty(__exports,"fail",{enumerable:true,get:function(){return fail;}});
1581
+ Object.defineProperty(__exports,"invariant",{enumerable:true,get:function(){return invariant;}});
1582
+ Object.defineProperty(__exports,"DEV",{enumerable:true,get:function(){return DEV;}});
1583
+ Object.defineProperty(__exports,"isObject",{enumerable:true,get:function(){return isObject;}});
1584
+ Object.defineProperty(__exports,"isFunction",{enumerable:true,get:function(){return isFunction;}});
1585
+ Object.defineProperty(__exports,"isNil",{enumerable:true,get:function(){return isNil;}});
1586
+ Object.defineProperty(__exports,"isBlank",{enumerable:true,get:function(){return isBlank;}});
1587
+ Object.defineProperty(__exports,"isCtor",{enumerable:true,get:function(){return isCtor;}});
1588
+ Object.defineProperty(__exports,"pathGetter",{enumerable:true,get:function(){return pathGetter;}});
1589
+ Object.defineProperty(__exports,"pathSetter",{enumerable:true,get:function(){return pathSetter;}});
1590
+ Object.defineProperty(__exports,"getPath",{enumerable:true,get:function(){return getPath;}});
1591
+ Object.defineProperty(__exports,"setPath",{enumerable:true,get:function(){return setPath;}});
1592
+ Object.defineProperty(__exports,"humanise",{enumerable:true,get:function(){return humanise;}});
1593
+ Object.defineProperty(__exports,"escapeHtml",{enumerable:true,get:function(){return escapeHtml;}});
1594
+ Object.defineProperty(__exports,"titleCase",{enumerable:true,get:function(){return titleCase;}});
1595
+ Object.defineProperty(__exports,"expand",{enumerable:true,get:function(){return expand;}});
1596
+ Object.defineProperty(__exports,"toArray",{enumerable:true,get:function(){return toArray;}});
1597
+ Object.defineProperty(__exports,"merge",{enumerable:true,get:function(){return merge;}});
1598
+ Object.defineProperty(__exports,"mergeRow",{enumerable:true,get:function(){return mergeRow;}});
1599
+ Object.defineProperty(__exports,"Lru",{enumerable:true,get:function(){return Lru;}});
1600
+ Object.defineProperty(__exports,"collator",{enumerable:true,get:function(){return collator;}});
1601
+ Object.defineProperty(__exports,"defaultCompare",{enumerable:true,get:function(){return defaultCompare;}});
1602
+ Object.defineProperty(__exports,"now",{enumerable:true,get:function(){return now;}});
1603
+ Object.defineProperty(__exports,"nextFrame",{enumerable:true,get:function(){return nextFrame;}});
1604
+ Object.defineProperty(__exports,"cancelFrame",{enumerable:true,get:function(){return cancelFrame;}});
1605
+ Object.defineProperty(__exports,"frameBatched",{enumerable:true,get:function(){return frameBatched;}});
1606
+ Object.defineProperty(__exports,"settleDebounce",{enumerable:true,get:function(){return settleDebounce;}});
1607
+ Object.defineProperty(__exports,"whenIdle",{enumerable:true,get:function(){return whenIdle;}});
1608
+ Object.defineProperty(__exports,"uid",{enumerable:true,get:function(){return uid;}});
1609
+ const STAMPED_VERSION="1.68.0";
1610
+ async function resolveVersion(){
1611
+ if(STAMPED_VERSION!=='0.0.0-source')return STAMPED_VERSION;
1612
+ return STAMPED_VERSION;
1572
1613
  }
1614
+ const VERSION="1.68.0";
1615
+ const warned=new Set();
1616
+ const WARNED_LIMIT=2000;
1617
+ function rememberWarned(key){
1618
+ warned.add(key);
1619
+ if(warned.size>WARNED_LIMIT){
1620
+ const oldest=warned.values().next().value;
1621
+ if(oldest!==undefined)warned.delete(oldest);
1573
1622
  }
1574
- const host=group.ownerDocument.createElementNS(SVG_NS,'g');
1575
- host.setAttribute('class',className);
1576
- group.appendChild(host);
1577
- return host;
1578
1623
  }
1579
- const KINDS=new Set(['line','target','band','callout','event']);
1580
- function xPixel(xScale,raw,edge){
1581
- if(xScale.kind==='band'){
1582
- const left=xScale.of(raw);
1583
- if(!Number.isFinite(left))return NaN;
1584
- const width=Number(xScale.bandwidth)||0;
1585
- return edge==='right'?left+width:left;
1624
+ const reported=[];
1625
+ const REPORT_LIMIT=500;
1626
+ function record(key,level,message){
1627
+ reported.push({
1628
+ key,
1629
+ level,
1630
+ message:message.map((m)=>(typeof m==='string'?m:safeString(m))).join(' '),
1631
+ at:Date.now(),
1632
+ });
1633
+ if(reported.length>REPORT_LIMIT)reported.shift();
1586
1634
  }
1587
- return xScale.of(toNumber(raw));
1635
+ function safeString(value){
1636
+ if(value instanceof Error)return value.message;
1637
+ try{return JSON.stringify(value);}catch{return String(value);}
1588
1638
  }
1589
- function xText(raw,fmt){
1590
- if(raw instanceof Date){
1591
- const t=raw.getTime();
1592
- return Number.isFinite(t)?raw.toISOString().slice(0,10):String(raw);
1639
+ function reportedWarnings(){return reported.map((r)=>({...r}));}
1640
+ function warnOnce(key,...message){
1641
+ if(warned.has(key))return;
1642
+ rememberWarned(key);
1643
+ record(key,'warn',message);
1644
+ console.warn('[lattice]',...message);
1593
1645
  }
1594
- if(typeof raw==='number')return Number.isFinite(raw)?fmt(raw):String(raw);
1595
- const n=toNumber(raw);
1596
- return n===null||typeof raw==='string'?String(raw):fmt(n);
1646
+ function infoOnce(key,...message){
1647
+ if(warned.has(key))return;
1648
+ rememberWarned(key);
1649
+ record(key,'info',message);
1650
+ console.info('[lattice]',...message);
1597
1651
  }
1598
- function poolFor(series,anno,axis){
1599
- const out=[];
1600
- for(const s of series){
1601
- const side=s.axis==='right'?'right':'left';
1602
- if(side!==axis)continue;
1603
- if(anno.series!==undefined&&s.key!==anno.series)continue;
1604
- for(const p of s.points)if(isNumber(p.y))out.push(Number(p.y));
1652
+ function resetWarnings(){
1653
+ warned.clear();
1654
+ reported.length=0;
1605
1655
  }
1606
- return out;
1656
+ function fail(message,extra){
1657
+ const err=new Error(`[lattice] ${message}`);
1658
+ if(extra!==undefined)err.cause=extra;
1659
+ throw err;
1607
1660
  }
1608
- function percentile(values,p){
1609
- if(!values.length)return null;
1610
- const sorted=[...values].sort((a,b)=>a-b);
1611
- if(sorted.length===1)return sorted[0];
1612
- const rank=(Math.min(100,Math.max(0,p))/100)*(sorted.length-1);
1613
- const low=Math.floor(rank);
1614
- const high=Math.ceil(rank);
1615
- if(low===high)return sorted[low];
1616
- return sorted[low]+(sorted[high]-sorted[low])*(rank-low);
1661
+ function invariant(condition,message){
1662
+ if(!condition)fail(message);
1617
1663
  }
1618
- function annotationValue(anno,series,axis){
1619
- if(!anno.compute){
1620
- return toNumber(anno.value);
1664
+ const DEV=(()=>{
1665
+ try{
1666
+ return!(typeof process!=='undefined'&&process.env
1667
+ &&process.env.NODE_ENV==='production');
1668
+ }catch{
1669
+ return true;
1621
1670
  }
1622
- const values=poolFor(series,anno,axis);
1623
- if(!values.length)return null;
1624
- const compute=String(anno.compute).toLowerCase();
1625
- if(compute==='mean'||compute==='avg'){
1626
- return values.reduce((t,v)=>t+v,0)/values.length;
1671
+ })();
1672
+ function isObject(v){
1673
+ return v!==null&&typeof v==='object'&&!Array.isArray(v);
1627
1674
  }
1628
- if(compute==='median'||compute==='p50')return percentile(values,50);
1629
- if(compute==='min')return Math.min(...values);
1630
- if(compute==='max')return Math.max(...values);
1631
- const match=/^p(\d+(?:\.\d+)?)$/.exec(compute);
1632
- if(match)return percentile(values,Number(match[1]));
1633
- return null;
1675
+ function isFunction(v){
1676
+ return typeof v==='function';
1634
1677
  }
1635
- function annotationAxis(anno){
1636
- return anno.axis==='right'||anno.axis==='y2'?'right':'left';
1678
+ function isNil(v){
1679
+ return v===null||v===undefined;
1637
1680
  }
1638
- function annotationText(anno,resolved,fmt,dual){
1639
- const label=anno.label?`${anno.label}: `:'';
1640
- const axis=dual?` (${annotationAxis(anno)==='right'?'right axis':'left axis'})`:'';
1641
- const kind=anno.kind||'line';
1642
- if(kind==='band'){
1643
- if(!Array.isArray(resolved)||resolved[0]===null||resolved[1]===null)return'';
1644
- const[lo,hi]=resolved;
1645
- return`${label||'Band: '}${fmt(Math.min(lo,hi))} to ${fmt(Math.max(lo,hi))}${axis}`;
1681
+ function isBlank(v){
1682
+ return v===null||v===undefined||v==='';
1646
1683
  }
1647
- if(resolved===null||resolved===undefined)return'';
1648
- const noun=kind==='target'?'Target':(kind==='callout'?'Callout':'Reference');
1649
- return`${label||`${noun}: `}${fmt((resolved))}${axis}`;
1684
+ function isCtor(v){
1685
+ if(typeof v!=='function')return false;
1686
+ if(/^class[\s{]/.test(Function.prototype.toString.call(v)))return true;
1687
+ return!!(v.prototype&&Object.getOwnPropertyNames(v.prototype).length>1);
1650
1688
  }
1651
- function bandFirst(x){
1652
- return(x.kind||'line')==='band'?0:1;
1689
+ const pathCache=new Map();
1690
+ function pathGetter(path){
1691
+ let fn=pathCache.get(path);
1692
+ if(fn)return fn;
1693
+ if(!path.includes('.')){
1694
+ fn=(o)=>(o==null?undefined:o[path]);
1695
+ }else{
1696
+ const parts=path.split('.');
1697
+ const n=parts.length;
1698
+ fn=(o)=>{
1699
+ let cur=o;
1700
+ for(let i=0;i<n;i++){
1701
+ if(cur==null)return undefined;
1702
+ cur=cur[parts[i]];
1653
1703
  }
1654
- function drawAnnotations(opts){
1655
- const{
1656
- group,plot,xScale,yScale,rightScale,series,format,
1657
- }=opts;
1658
- const pool=new Pool(overlayLayer(group,`${NS}__annotations`));
1659
- const described=[];
1660
- const list=Array.isArray(opts.annotations)?opts.annotations:[];
1661
- const ordered=[...list].sort((a,b)=>bandFirst(a)-bandFirst(b));
1662
- for(const anno of ordered){
1663
- const kind=anno.kind||'line';
1664
- if(!KINDS.has(kind))continue;
1665
- const axis=annotationAxis(anno);
1666
- const scale=axis==='right'&&rightScale?rightScale:yScale;
1667
- if(kind==='band'){
1668
- if(anno.orient==='vertical'){
1669
- const x1=xPixel(xScale,anno.from,'left');
1670
- const x2=xPixel(xScale,anno.to,'right');
1671
- if(Number.isNaN(x1)||Number.isNaN(x2))continue;
1672
- const left=round(Math.min(x1,x2));
1673
- const right=round(Math.max(x1,x2));
1674
- attrs(pool.next('rect',`${NS}__annotation-band ${anno.className||''}`.trim()),{
1675
- x:left,
1676
- y:round(plot.top),
1677
- width:Math.max(0,right-left),
1678
- height:round(Math.max(0,plot.bottom-plot.top)),
1679
- fill:anno.colour||'currentColor',
1680
- 'fill-opacity':anno.opacity===undefined?0.12:anno.opacity,
1681
- });
1682
- if(anno.label){
1683
- const text=pool.next('text',`${NS}__annotation-label`);
1684
- attrs(text,{x:left+4,y:round(plot.top+12),'text-anchor':'start'});
1685
- setText(text,anno.label);
1704
+ return cur;
1705
+ };
1686
1706
  }
1687
- const noun=anno.label?`${anno.label}: `:'Event window: ';
1688
- described.push({text:`${noun}${xText(anno.from,format)} to ${xText(anno.to,format)}`});
1689
- continue;
1707
+ pathCache.set(path,fn);
1708
+ return fn;
1690
1709
  }
1691
- const from=annotationValue({...anno,value:anno.from,compute:anno.fromCompute},series,axis);
1692
- const to=annotationValue({...anno,value:anno.to,compute:anno.toCompute},series,axis);
1693
- if(from===null||to===null)continue;
1694
- const y1=round(scale.of(from));
1695
- const y2=round(scale.of(to));
1696
- attrs(pool.next('rect',`${NS}__annotation-band ${anno.className||''}`.trim()),{
1697
- x:round(plot.left),
1698
- y:Math.min(y1,y2),
1699
- width:round(Math.max(0,plot.right-plot.left)),
1700
- height:Math.max(0,Math.abs(y2-y1)),
1701
- fill:anno.colour||'currentColor',
1702
- 'fill-opacity':anno.opacity===undefined?0.12:anno.opacity,
1703
- });
1704
- if(anno.label){
1705
- const text=pool.next('text',`${NS}__annotation-label`);
1706
- attrs(text,{x:round(plot.left+4),y:Math.min(y1,y2)+12,'text-anchor':'start'});
1707
- setText(text,anno.label);
1710
+ const setterCache=new Map();
1711
+ function pathSetter(path){
1712
+ let fn=setterCache.get(path);
1713
+ if(fn)return fn;
1714
+ if(!path.includes('.')){
1715
+ fn=(o,v)=>{if(o!=null)o[path]=v;};
1716
+ }else{
1717
+ const parts=path.split('.');
1718
+ const last=parts.length-1;
1719
+ fn=(o,v)=>{
1720
+ let cur=o;
1721
+ for(let i=0;i<last;i++){
1722
+ if(cur==null)return;
1723
+ const k=parts[i];
1724
+ if(cur[k]==null)cur[k]={};
1725
+ cur=cur[k];
1708
1726
  }
1709
- described.push({text:annotationText(anno,[from,to],format,opts.dual)});
1710
- continue;
1727
+ if(cur!=null)cur[parts[last]]=v;
1728
+ };
1711
1729
  }
1712
- if(kind==='event'){
1713
- const raw=anno.x!==undefined?anno.x:anno.at;
1714
- const at=round(xPixel(xScale,raw,'left')+(xScale.kind==='band'?(Number(xScale.bandwidth)||0)/2:0));
1715
- if(Number.isNaN(at))continue;
1716
- attrs(pool.next('path',`${NS}__annotation-event`),{
1717
- d:path(['M',at,round(plot.top),'L',at,round(plot.bottom)]),
1718
- stroke:anno.colour||'currentColor',
1719
- fill:'none',
1720
- });
1721
- attrs(pool.next('circle',`${NS}__annotation-event-flag`),{
1722
- cx:at,cy:round(plot.top),r:3,fill:anno.colour||'currentColor',
1723
- });
1724
- if(anno.label){
1725
- const text=pool.next('text',`${NS}__annotation-label`);
1726
- attrs(text,{x:at+5,y:round(plot.top+10),'text-anchor':'start'});
1727
- setText(text,anno.label);
1730
+ setterCache.set(path,fn);
1731
+ return fn;
1728
1732
  }
1729
- const noun=anno.label?`${anno.label} — `:'Event — ';
1730
- described.push({text:`${noun}at ${xText(raw,format)}`});
1731
- continue;
1733
+ function getPath(obj,path){
1734
+ return pathGetter(path)(obj);
1732
1735
  }
1733
- if(kind==='callout'){
1734
- const value=annotationValue(anno,series,axis);
1735
- if(value===null)continue;
1736
- const cx=anno.at!==undefined
1737
- ?round(xScale.kind==='band'?xScale.centre(anno.at):xScale.of(toNumber(anno.at)))
1738
- :round(plot.right-4);
1739
- const cy=round(scale.of(value));
1740
- if(Number.isNaN(cx))continue;
1741
- attrs(pool.next('circle',`${NS}__annotation-point`),{
1742
- cx,cy,r:3.5,fill:anno.colour||'currentColor',
1743
- });
1744
- if(anno.label){
1745
- const text=pool.next('text',`${NS}__annotation-label`);
1746
- attrs(text,{x:cx+6,y:cy-6,'text-anchor':'start'});
1747
- setText(text,anno.label);
1736
+ function setPath(obj,path,value){
1737
+ pathSetter(path)(obj,value);
1748
1738
  }
1749
- described.push({text:annotationText(anno,value,format,opts.dual)});
1750
- continue;
1739
+ function humanise(field){
1740
+ if(!field)return'';
1741
+ const leaf=field.includes('.')?field.slice(field.lastIndexOf('.')+1):field;
1742
+ return leaf
1743
+ .replace(/[_-]+/g,' ')
1744
+ .replace(/([a-z0-9])([A-Z])/g,'$1 $2')
1745
+ .replace(/([A-Z]+)([A-Z][a-z])/g,'$1 $2')
1746
+ .replace(/\s+/g,' ')
1747
+ .trim()
1748
+ .replace(/^./,(c)=>c.toUpperCase());
1751
1749
  }
1752
- if(anno.orient==='vertical'||anno.x!==undefined){
1753
- const raw=anno.x!==undefined?anno.x:anno.value;
1754
- const at=xScale.kind==='band'
1755
- ?round(xScale.centre(raw))
1756
- :round(xScale.of(toNumber(raw)));
1757
- if(Number.isNaN(at))continue;
1758
- const cls=kind==='target'?`${NS}__annotation-target`:`${NS}__annotation-line`;
1759
- attrs(pool.next('path',cls),{
1760
- d:path(['M',at,round(plot.top),'L',at,round(plot.bottom)]),
1761
- stroke:anno.colour||'currentColor',
1762
- fill:'none',
1763
- });
1764
- if(anno.label){
1765
- const text=pool.next('text',`${NS}__annotation-label`);
1766
- attrs(text,{
1767
- x:at+4,y:round(plot.top+10),'text-anchor':'start',
1768
- });
1769
- setText(text,anno.label);
1750
+ const ESCAPES={'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'};
1751
+ function escapeHtml(s){
1752
+ const str=s==null?'':String(s);
1753
+ return/[&<>"']/.test(str)?str.replace(/[&<>"']/g,(c)=>ESCAPES[c]):str;
1770
1754
  }
1771
- described.push({text:anno.label?`${anno.label} — at ${xText(raw,format)}`:''});
1772
- continue;
1755
+ function titleCase(s){
1756
+ return String(s).replace(/\w\S*/g,(t)=>t[0].toUpperCase()+t.slice(1).toLowerCase());
1773
1757
  }
1774
- const value=annotationValue(anno,series,axis);
1775
- if(value===null)continue;
1776
- const at=round(scale.of(value));
1777
- const cls=kind==='target'?`${NS}__annotation-target`:`${NS}__annotation-line`;
1778
- attrs(pool.next('path',cls),{
1779
- d:path(['M',round(plot.left),at,'L',round(plot.right),at]),
1780
- stroke:anno.colour||'currentColor',
1781
- fill:'none',
1782
- });
1783
- if(anno.label){
1784
- const text=pool.next('text',`${NS}__annotation-label`);
1785
- attrs(text,{x:round(plot.right-4),y:at-4,'text-anchor':'end'});
1786
- setText(text,anno.label);
1758
+ function expand(value,key,whenTrue){
1759
+ if(value===undefined)return undefined;
1760
+ if(value===true)return{enabled:true,...whenTrue};
1761
+ if(value===false)return{enabled:false};
1762
+ if(isObject(value))return value;
1763
+ return{[key]:value,enabled:true};
1787
1764
  }
1788
- described.push({text:annotationText(anno,value,format,opts.dual)});
1765
+ function toArray(v){
1766
+ if(v===undefined||v===null)return[];
1767
+ return Array.isArray(v)?v:[v];
1789
1768
  }
1790
- pool.finish();
1791
- return described.filter((d)=>d.text);
1769
+ const MERGE_FORBIDDEN_KEYS=Object.freeze(new Set(['__proto__','constructor','prototype']));
1770
+ function merge(a,b){
1771
+ if(!isObject(a))return isObject(b)?{...b}:b;
1772
+ if(!isObject(b))return b===undefined?a:b;
1773
+ const out={...a};
1774
+ for(const k of Object.keys(b)){
1775
+ if(MERGE_FORBIDDEN_KEYS.has(k))continue;
1776
+ const bv=b[k];
1777
+ if(bv===undefined)continue;
1778
+ out[k]=isObject(bv)&&isObject(out[k])?merge(out[k],bv):bv;
1792
1779
  }
1793
- });
1794
- __def("packages/modules/charts/combo.js",function(__exports,__req){
1795
- 'use strict';
1796
- Object.defineProperty(__exports,"toPareto",{enumerable:true,get:function(){return toPareto;}});
1797
- Object.defineProperty(__exports,"drawCombo",{enumerable:true,get:function(){return drawCombo;}});
1798
- Object.defineProperty(__exports,"drawRightAxis",{enumerable:true,get:function(){return drawRightAxis;}});
1799
- const __m0=__req("packages/modules/charts/axis.js");
1800
- const drawAxisLines=__m0["drawAxisLines"];
1801
- const drawCategoryAxis=__m0["drawCategoryAxis"];
1802
- const drawMeasureAxis=__m0["drawMeasureAxis"];
1803
- const overhangFor=__m0["overhangFor"];
1804
- const setPlotClip=__m0["setPlotClip"];
1805
- const __m1=__req("packages/modules/charts/frame.js");
1806
- const Pool=__m1["Pool"];
1807
- const shouldRotate=__m1["shouldRotate"];
1808
- const __m2=__req("packages/modules/charts/format.js");
1809
- const formatters=__m2["formatters"];
1810
- const __m3=__req("packages/modules/charts/scale.js");
1811
- const bandScale=__m3["bandScale"];
1812
- const isNumber=__m3["isNumber"];
1813
- const linearScale=__m3["linearScale"];
1814
- const measureDomain=__m3["measureDomain"];
1815
- const __m4=__req("packages/modules/charts/svg.js");
1816
- const attrs=__m4["attrs"];
1817
- const path=__m4["path"];
1818
- const round=__m4["round"];
1819
- const __m5=__req("packages/modules/charts/styles.js");
1820
- const NS=__m5["NS"];
1821
- const __m6=__req("packages/modules/charts/scheme.js");
1822
- const resolveScheme=__m6["resolveScheme"];
1823
- const __m7=__req("packages/modules/charts/labels.js");
1824
- const drawDataLabels=__m7["drawDataLabels"];
1825
- const __m8=__req("packages/modules/charts/annotations.js");
1826
- const drawAnnotations=__m8["drawAnnotations"];
1827
- function byAxis(series){
1828
- return{
1829
- left:series.filter((s)=>s.axis!=='right'),
1830
- right:series.filter((s)=>s.axis==='right'),
1831
- };
1780
+ return out;
1832
1781
  }
1833
- function domainFor(series,zero){
1834
- const values=[];
1835
- for(const s of series)for(const p of s.points)values.push(p.y);
1836
- return measureDomain(values,{zero});
1782
+ function mergeRow(previous,patch){
1783
+ if(!isObject(previous)||!isObject(patch)||previous===patch)return patch;
1784
+ const out=Object.create(Object.getPrototypeOf(previous));
1785
+ Object.assign(out,previous,patch);
1786
+ return out;
1837
1787
  }
1838
- function toPareto(bound){
1839
- const source=bound.series[0];
1840
- if(!source)return bound;
1841
- const ranked=source.points
1842
- .map((point,i)=>({point,label:bound.labels[i],category:bound.categories[i]}))
1843
- .filter((entry)=>isNumber(entry.point.y))
1844
- .sort((a,b)=>(b.point.y)-(a.point.y));
1845
- const total=ranked.reduce((t,entry)=>t+(entry.point.y),0);
1846
- let running=0;
1847
- const cumulative=ranked.map((entry)=>{
1848
- running+=(entry.point.y);
1849
- return total>0?(running/total)*100:0;
1850
- });
1851
- return{
1852
- ...bound,
1853
- categories:ranked.map((entry)=>entry.category),
1854
- labels:ranked.map((entry)=>entry.label),
1855
- series:[
1856
- {
1857
- ...source,
1858
- index:0,
1859
- mark:'bar',
1860
- axis:'left',
1861
- points:ranked.map((entry)=>entry.point),
1862
- },
1863
- {
1864
- key:'cumulative',
1865
- label:bound.cumulativeLabel||'%',
1866
- index:1,
1867
- mark:'line',
1868
- axis:'right',
1869
- points:ranked.map((entry,i)=>({
1870
- x:entry.category,
1871
- xKey:entry.point.xKey,
1872
- label:entry.label,
1873
- y:cumulative[i],
1874
- rows:entry.point.rows,
1875
- rowKey:entry.point.rowKey,
1876
- })),
1877
- },
1878
- ],
1879
- };
1788
+ class Lru{
1789
+ #max;
1790
+ #map=new Map();
1791
+ #onEvict;
1792
+ constructor(max=256,onEvict=null){
1793
+ this.#max=max;
1794
+ this.#onEvict=onEvict;
1880
1795
  }
1881
- function drawCombo(ctx){
1882
- const scheme=ctx.scheme||resolveScheme();
1883
- const{plot}=ctx;
1884
- const bound=ctx.pareto?toPareto(ctx.bound):ctx.bound;
1885
- const visible=bound.series.filter((s)=>!ctx.hidden.has(s.key));
1886
- const sides=byAxis(visible);
1887
- const hasBars=sides.left.some((s)=>(s.mark||'bar')==='bar');
1888
- const leftDomain=domainFor(sides.left,hasBars);
1889
- const yScale=linearScale(leftDomain,[plot.bottom,plot.top]);
1890
- const rightDomain=sides.right.length
1891
- ?(ctx.pareto?{min:0,max:100}:domainFor(sides.right,false))
1892
- :null;
1893
- const rightScale=rightDomain?linearScale(rightDomain,[plot.bottom,plot.top]):null;
1894
- const xScale=bandScale(bound.categories,[plot.left,plot.right],0.2);
1895
- drawMeasureAxis({
1896
- gridGroup:ctx.groups.grid,
1897
- axisGroup:ctx.groups.axis,
1898
- plot,
1899
- scale:yScale,
1900
- format:formatters(ctx.grid,{
1901
- kind:'linear',step:(leftDomain.max-leftDomain.min)/5,
1902
- }),
1903
- });
1904
- drawCategoryAxis({
1905
- group:ctx.groups.overlay,
1906
- plot,
1907
- scale:xScale,
1908
- labels:bound.labels,
1909
- fontSize:ctx.fontSize,
1910
- rotated:shouldRotate(bound.labels,xScale.step,ctx.fontSize),
1911
- });
1912
- drawAxisLines({group:ctx.groups.rules,plot});
1913
- if(rightScale){
1914
- drawRightAxis({
1915
- group:ctx.groups.axis,
1916
- plot,
1917
- scale:rightScale,
1918
- colour:scheme.series(sides.right[0].index),
1919
- format:formatters(ctx.grid,{
1920
- kind:'linear',step:(rightDomain.max-rightDomain.min)/5,
1921
- }),
1922
- suffix:ctx.pareto?'%':'',
1923
- });
1796
+ get size(){
1797
+ return this.#map.size;
1924
1798
  }
1925
- setPlotClip(ctx.groups,plot,overhangFor(ctx.type));
1926
- const pool=new Pool(ctx.groups.marks);
1927
- const bars=visible.filter((s)=>(s.mark||'bar')==='bar');
1928
- const slot=bars.length?xScale.bandwidth/bars.length:xScale.bandwidth;
1929
- let barIndex=0;
1930
- for(const series of visible){
1931
- const mark=series.mark||'bar';
1932
- const scale=series.axis==='right'&&rightScale?rightScale:yScale;
1933
- const colour=scheme.series(series.index);
1934
- if(mark==='bar'){
1935
- const offset=barIndex*slot;
1936
- barIndex++;
1937
- const base=scale.of(Math.max(scale.domain.min,0));
1938
- for(let i=0;i<series.points.length;i++){
1939
- const point=series.points[i];
1940
- if(!isNumber(point.y))continue;
1941
- const start=xScale.of(point.x);
1942
- if(Number.isNaN(start))continue;
1943
- const top=scale.of((point.y));
1944
- attrs(pool.next('rect',`${NS}__bar ${NS}__mark`),{
1945
- x:round(start+offset),
1946
- y:round(Math.min(top,base)),
1947
- width:round(Math.max(0,slot)),
1948
- height:round(Math.max(1,Math.abs(base-top))),
1949
- fill:colour,
1950
- 'data-series':series.index,
1951
- 'data-point':i,
1952
- });
1953
- point.px=round(start+offset+slot/2);
1954
- point.py=round(Math.min(top,base));
1799
+ get max(){
1800
+ return this.#max;
1955
1801
  }
1956
- continue;
1802
+ set max(v){
1803
+ this.#max=v;
1804
+ this.#trim();
1957
1805
  }
1958
- const commands=[];
1959
- let open=false;
1960
- for(const point of series.points){
1961
- if(!isNumber(point.y)){open=false;continue;}
1962
- const x=round(xScale.centre(point.x));
1963
- if(Number.isNaN(x)){open=false;continue;}
1964
- const y=round(scale.of((point.y)));
1965
- commands.push(open?'L':'M',x,y);
1966
- open=true;
1967
- point.px=x;
1968
- point.py=y;
1806
+ has(k){
1807
+ return this.#map.has(k);
1969
1808
  }
1970
- attrs(pool.next('path',`${NS}__line ${NS}__mark`),{
1971
- d:commands.length?path(commands):'',
1972
- stroke:colour,
1973
- 'data-series':series.index,
1974
- });
1975
- for(const point of series.points){
1976
- if(point.px===undefined)continue;
1977
- attrs(pool.next('circle',`${NS}__point ${NS}__mark`),{
1978
- cx:point.px,cy:point.py,r:3,fill:colour,
1979
- });
1809
+ get(k){
1810
+ const m=this.#map;
1811
+ if(!m.has(k))return undefined;
1812
+ const v=m.get(k);
1813
+ m.delete(k);
1814
+ m.set(k,v);
1815
+ return v;
1980
1816
  }
1817
+ peek(k){
1818
+ return this.#map.get(k);
1981
1819
  }
1982
- pool.finish();
1983
- if(ctx.labels&&ctx.labels.show){
1984
- drawDataLabels({
1985
- group:ctx.groups.labels,
1986
- series:visible,
1987
- plot,
1988
- options:ctx.labels,
1989
- grid:ctx.grid,
1990
- size:(ctx.typography&&ctx.typography.small)||ctx.fontSize,
1991
- });
1820
+ set(k,v){
1821
+ const m=this.#map;
1822
+ if(m.has(k))m.delete(k);
1823
+ m.set(k,v);
1824
+ this.#trim();
1825
+ return v;
1992
1826
  }
1993
- let annotations=null;
1994
- if(ctx.annotations&&ctx.annotations.length){
1995
- annotations=drawAnnotations({
1996
- group:ctx.groups.overlay,
1997
- plot,
1998
- xScale,
1999
- yScale,
2000
- rightScale,
2001
- series:visible,
2002
- annotations:ctx.annotations,
2003
- dual:!!rightScale,
2004
- format:formatters(ctx.grid,{
2005
- kind:'linear',step:(leftDomain.max-leftDomain.min)/5,
2006
- }).tick,
2007
- });
1827
+ delete(k){
1828
+ const v=this.#map.get(k);
1829
+ if(this.#map.delete(k)&&this.#onEvict)this.#onEvict(v,k);
1830
+ return v;
2008
1831
  }
2009
- return{
2010
- xScale,yScale,rightScale,dual:!!rightScale,series:visible,bound,annotations,
2011
- };
1832
+ clear(){
1833
+ if(this.#onEvict)for(const[k,v]of this.#map)this.#onEvict(v,k);
1834
+ this.#map.clear();
1835
+ }
1836
+ keys(){
1837
+ return this.#map.keys();
1838
+ }
1839
+ values(){
1840
+ return this.#map.values();
1841
+ }
1842
+ #trim(){
1843
+ const m=this.#map;
1844
+ while(m.size>this.#max){
1845
+ const oldest=m.keys().next().value;
1846
+ const v=m.get(oldest);
1847
+ m.delete(oldest);
1848
+ if(this.#onEvict)this.#onEvict(v,oldest);
2012
1849
  }
2013
- function drawRightAxis(opts){
2014
- const{group,plot,scale,format}=opts;
2015
- const pool=new Pool(group);
2016
- attrs(pool.next('line',`${NS}__axis-right`),{
2017
- x1:round(plot.right),x2:round(plot.right),
2018
- y1:round(plot.top),y2:round(plot.bottom),
2019
- stroke:opts.colour,
2020
- });
2021
- for(const value of scale.ticks(5)){
2022
- const at=round(scale.of(value));
2023
- const label=pool.next('text',`${NS}__tick`);
2024
- attrs(label,{
2025
- x:round(plot.right+6),y:at,'text-anchor':'start',
2026
- 'dominant-baseline':'middle',fill:opts.colour,
2027
- });
2028
- label.textContent=`${format.tick(value)}${opts.suffix||''}`;
2029
1850
  }
2030
- pool.finish();
2031
1851
  }
1852
+ const collators=new Map();
1853
+ function collator(locale,opts){
1854
+ const key=`${locale||''}|${opts?JSON.stringify(opts):''}`;
1855
+ let c=collators.get(key);
1856
+ if(!c){
1857
+ c=new Intl.Collator(locale||undefined,{
1858
+ numeric:true,sensitivity:'variant',...opts,
2032
1859
  });
2033
- __def("packages/modules/charts/quantile.js",function(__exports,__req){
2034
- 'use strict';
2035
- Object.defineProperty(__exports,"normalQuantile",{enumerable:true,get:function(){return normalQuantile;}});
2036
- Object.defineProperty(__exports,"studentTQuantile",{enumerable:true,get:function(){return studentTQuantile;}});
2037
- const LANCZOS=Object.freeze([
2038
- 676.5203681218851,-1259.1392167224028,771.32342877765313,
2039
- -176.61502916214059,12.507343278686905,-0.13857109526572012,
2040
- 9.9843695780195716e-6,1.5056327351493116e-7,
2041
- ]);
2042
- const EPS=3e-12;
2043
- const TINY=1e-300;
2044
- function logGamma(x){
2045
- if(x<0.5)return Math.log(Math.PI/Math.sin(Math.PI*x))-logGamma(1-x);
2046
- const z=x-1;
2047
- let a=0.99999999999980993;
2048
- const t=z+7.5;
2049
- for(let i=0;i<LANCZOS.length;i++)a+=LANCZOS[i]/(z+i+1);
2050
- return 0.5*Math.log(2*Math.PI)+(z+0.5)*Math.log(t)-t+Math.log(a);
1860
+ collators.set(key,c);
2051
1861
  }
2052
- function betaContinuedFraction(a,b,x){
2053
- const qab=a+b;
2054
- const qap=a+1;
2055
- const qam=a-1;
2056
- let c=1;
2057
- let d=1-(qab*x)/qap;
2058
- if(Math.abs(d)<TINY)d=TINY;
2059
- d=1/d;
2060
- let h=d;
2061
- for(let m=1;m<=300;m++){
2062
- const m2=2*m;
2063
- let aa=(m*(b-m)*x)/((qam+m2)*(a+m2));
2064
- d=1+aa*d;
2065
- if(Math.abs(d)<TINY)d=TINY;
2066
- c=1+aa/c;
2067
- if(Math.abs(c)<TINY)c=TINY;
2068
- d=1/d;
2069
- h*=d*c;
2070
- aa=(-(a+m)*(qab+m)*x)/((a+m2)*(qap+m2));
2071
- d=1+aa*d;
2072
- if(Math.abs(d)<TINY)d=TINY;
2073
- c=1+aa/c;
2074
- if(Math.abs(c)<TINY)c=TINY;
2075
- d=1/d;
2076
- const step=d*c;
2077
- h*=step;
2078
- if(Math.abs(step-1)<EPS)break;
1862
+ return c;
1863
+ }
1864
+ function defaultCompare(a,b){
1865
+ if(a===b)return 0;
1866
+ if(a===null||a===undefined)return 1;
1867
+ if(b===null||b===undefined)return-1;
1868
+ if(typeof a==='number'&&typeof b==='number'){
1869
+ if(Number.isNaN(a))return Number.isNaN(b)?0:1;
1870
+ if(Number.isNaN(b))return-1;
1871
+ return a<b?-1:a>b?1:0;
2079
1872
  }
2080
- return h;
1873
+ const sa=String(a);
1874
+ const sb=String(b);
1875
+ return sa<sb?-1:sa>sb?1:0;
2081
1876
  }
2082
- function incompleteBeta(a,b,x){
2083
- if(!(a>0)||!(b>0)||!Number.isFinite(x))return Number.NaN;
2084
- if(x<=0)return 0;
2085
- if(x>=1)return 1;
2086
- const front=Math.exp(
2087
- logGamma(a+b)-logGamma(a)-logGamma(b)+a*Math.log(x)+b*Math.log(1-x),
2088
- );
2089
- return x<(a+1)/(a+b+2)
2090
- ?(front*betaContinuedFraction(a,b,x))/a
2091
- :1-(front*betaContinuedFraction(b,a,1-x))/b;
1877
+ function now(){
1878
+ return typeof performance!=='undefined'&&performance.now
1879
+ ?performance.now()
1880
+ :Date.now();
2092
1881
  }
2093
- function erfc(x){
2094
- const z=Math.abs(x);
2095
- const t=2/(2+z);
2096
- const ty=4*t-2;
2097
- const cof=[-1.3026537197817094,6.4196979235649026e-1,1.9476473204185836e-2,
2098
- -9.561514786808631e-3,-9.46595344482036e-4,3.66839497852761e-4,
2099
- 4.2523324806907e-5,-2.0278578112534e-5,-1.624290004647e-6,
2100
- 1.303655835580e-6,1.5626441722e-8,-8.5238095915e-8,6.529054439e-9,
2101
- 5.059343495e-9,-9.91364156e-10,-2.27365122e-10,9.6467911e-11,
2102
- 2.394038e-12,-6.886027e-12,8.94487e-13,3.13092e-13,-1.12708e-13,
2103
- 3.81e-16,7.106e-15];
2104
- let dd=0;
2105
- let dv=0;
2106
- let tmp;
2107
- for(let j=cof.length-1;j>0;j--){
2108
- tmp=dv;
2109
- dv=ty*dv-dd+cof[j];
2110
- dd=tmp;
1882
+ const hasRaf=typeof requestAnimationFrame==='function';
1883
+ function nextFrame(fn){
1884
+ if(hasRaf)return requestAnimationFrame(fn);
1885
+ return setTimeout(()=>fn(now()),16);
2111
1886
  }
2112
- const ans=t*Math.exp(-z*z+0.5*(cof[0]+ty*dv)-dd);
2113
- return x>=0?ans:2-ans;
1887
+ function cancelFrame(handle){
1888
+ if(handle==null)return;
1889
+ if(hasRaf)cancelAnimationFrame(handle);
1890
+ else clearTimeout(handle);
2114
1891
  }
2115
- function normalQuantile(p){
2116
- if(!(p>0)||!(p<1))return p===0?-Infinity:(p===1?Infinity:Number.NaN);
2117
- const a=[-3.969683028665376e+1,2.209460984245205e+2,-2.759285104469687e+2,
2118
- 1.383577518672690e+2,-3.066479806614716e+1,2.506628277459239];
2119
- const b=[-5.447609879822406e+1,1.615858368580409e+2,-1.556989798598866e+2,
2120
- 6.680131188771972e+1,-1.328068155288572e+1];
2121
- const c=[-7.784894002430293e-3,-3.223964580411365e-1,-2.400758277161838,
2122
- -2.549732539343734,4.374664141464968,2.938163982698783];
2123
- const d=[7.784695709041462e-3,3.224671290700398e-1,2.445134137142996,
2124
- 3.754408661907416];
2125
- const low=0.02425;
2126
- let q;
2127
- let r;
2128
- let x;
2129
- if(p<low){
2130
- q=Math.sqrt(-2*Math.log(p));
2131
- x=(((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5])
2132
- / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
2133
- }else if(p<=1-low){
2134
- q=p-0.5;
2135
- r=q*q;
2136
- x=((((((a[0]*r+a[1])*r+a[2])*r+a[3])*r+a[4])*r+a[5])*q)
2137
- / (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1);
1892
+ function frameBatched(fn){
1893
+ let handle=null;
1894
+ let lastArgs=null;
1895
+ const run=()=>{
1896
+ handle=null;
1897
+ const a=lastArgs;
1898
+ lastArgs=null;
1899
+ fn(...(a||[]));
1900
+ };
1901
+ const wrapped=(...args)=>{
1902
+ lastArgs=args;
1903
+ if(handle===null)handle=nextFrame(run);
1904
+ };
1905
+ wrapped.cancel=()=>{
1906
+ cancelFrame(handle);
1907
+ handle=null;
1908
+ lastArgs=null;
1909
+ };
1910
+ wrapped.flush=()=>{
1911
+ if(handle!==null){
1912
+ cancelFrame(handle);
1913
+ run();
1914
+ }
1915
+ };
1916
+ return wrapped;
1917
+ }
1918
+ function settleDebounce(fn,waitMs){
1919
+ let timer=null;
1920
+ let held=null;
1921
+ const trailing=()=>{
1922
+ timer=null;
1923
+ if(held===null)return;
1924
+ const args=held;
1925
+ held=null;
1926
+ fn(...args);
1927
+ arm();
1928
+ };
1929
+ const arm=()=>{
1930
+ timer=setTimeout(trailing,waitMs);
1931
+ if(typeof timer?.unref==='function')timer.unref();
1932
+ };
1933
+ const wrapped=(...args)=>{
1934
+ if(timer===null){
1935
+ fn(...args);
1936
+ arm();
2138
1937
  }else{
2139
- q=Math.sqrt(-2*Math.log(1-p));
2140
- x=-(((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5])
2141
- / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
1938
+ held=args;
1939
+ clearTimeout(timer);
1940
+ arm();
2142
1941
  }
2143
- const e=0.5*erfc(-x/Math.SQRT2)-p;
2144
- const u=e*Math.sqrt(2*Math.PI)*Math.exp((x*x)/2);
2145
- return x-u/(1+(x*u)/2);
1942
+ };
1943
+ wrapped.flush=()=>{
1944
+ if(timer!==null)clearTimeout(timer);
1945
+ timer=null;
1946
+ if(held===null)return;
1947
+ const args=held;
1948
+ held=null;
1949
+ fn(...args);
1950
+ };
1951
+ wrapped.cancel=()=>{
1952
+ if(timer!==null)clearTimeout(timer);
1953
+ timer=null;
1954
+ held=null;
1955
+ };
1956
+ wrapped.pending=()=>timer!==null||held!==null;
1957
+ return wrapped;
2146
1958
  }
2147
- function studentT(t,df){
2148
- if(!(df>0)||!Number.isFinite(t))return Number.NaN;
2149
- const tail=0.5*incompleteBeta(df/2,0.5,df/(df+t*t));
2150
- return t>0?1-tail:tail;
1959
+ function whenIdle(fn,timeout=50){
1960
+ if(typeof requestIdleCallback==='function'){
1961
+ return requestIdleCallback(fn,{timeout});
2151
1962
  }
2152
- function studentTQuantile(p,df){
2153
- if(!(p>0)||!(p<1)||!(df>0))return Number.NaN;
2154
- if(df>1e7)return normalQuantile(p);
2155
- let lo=-1e4;
2156
- let hi=1e4;
2157
- let x=normalQuantile(p);
2158
- const logBeta=logGamma(df/2)+logGamma(0.5)-logGamma((df+1)/2);
2159
- for(let i=0;i<60;i++){
2160
- const cdf=studentT(x,df);
2161
- if(cdf<p)lo=x;else hi=x;
2162
- const pdf=Math.exp(-((df+1)/2)*Math.log(1+(x*x)/df)-logBeta)
2163
- / Math.sqrt(df);
2164
- const step=pdf>0?(cdf-p)/pdf:0;
2165
- if(Math.abs(step)<1e-12)break;
2166
- const next=x-step;
2167
- x=next>lo&&next<hi&&Number.isFinite(next)?next:(lo+hi)/2;
2168
- if(hi-lo<1e-12)break;
1963
+ return setTimeout(()=>fn({timeRemaining:()=>0,didTimeout:true}),1);
2169
1964
  }
2170
- return x;
1965
+ let idSeq=0;
1966
+ function uid(prefix='l'){
1967
+ return`${prefix}${(++idSeq).toString(36)}`;
2171
1968
  }
2172
1969
  });
2173
- __def("packages/modules/charts/trendline.js",function(__exports,__req){
1970
+ __def("packages/modules/charts/annotations.js",function(__exports,__req){
2174
1971
  'use strict';
2175
- Object.defineProperty(__exports,"normaliseTrends",{enumerable:true,get:function(){return normaliseTrends;}});
2176
- Object.defineProperty(__exports,"linearTrend",{enumerable:true,get:function(){return linearTrend;}});
2177
- Object.defineProperty(__exports,"movingAverageTrend",{enumerable:true,get:function(){return movingAverageTrend;}});
2178
- Object.defineProperty(__exports,"exponentialTrend",{enumerable:true,get:function(){return exponentialTrend;}});
2179
- Object.defineProperty(__exports,"computeTrend",{enumerable:true,get:function(){return computeTrend;}});
2180
- Object.defineProperty(__exports,"trendText",{enumerable:true,get:function(){return trendText;}});
2181
- Object.defineProperty(__exports,"drawTrendlines",{enumerable:true,get:function(){return drawTrendlines;}});
1972
+ Object.defineProperty(__exports,"overlayLayer",{enumerable:true,get:function(){return overlayLayer;}});
1973
+ Object.defineProperty(__exports,"annotationValue",{enumerable:true,get:function(){return annotationValue;}});
1974
+ Object.defineProperty(__exports,"annotationAxis",{enumerable:true,get:function(){return annotationAxis;}});
1975
+ Object.defineProperty(__exports,"annotationText",{enumerable:true,get:function(){return annotationText;}});
1976
+ Object.defineProperty(__exports,"drawAnnotations",{enumerable:true,get:function(){return drawAnnotations;}});
2182
1977
  const __m0=__req("packages/modules/charts/frame.js");
2183
1978
  const Pool=__m0["Pool"];
2184
- const __m1=__req("packages/modules/charts/svg.js");
2185
- const attrs=__m1["attrs"];
2186
- const path=__m1["path"];
2187
- const ribbonPath=__m1["ribbonPath"];
2188
- const round=__m1["round"];
2189
- const setText=__m1["setText"];
2190
- const SVG_NS=__m1["SVG_NS"];
2191
- const __m2=__req("packages/modules/charts/typography.js");
2192
- const applySize=__m2["applySize"];
1979
+ const __m1=__req("packages/modules/charts/scale.js");
1980
+ const isNumber=__m1["isNumber"];
1981
+ const toNumber=__m1["toNumber"];
1982
+ const toTime=__m1["toTime"];
1983
+ const __m2=__req("packages/modules/charts/svg.js");
1984
+ const attrs=__m2["attrs"];
1985
+ const path=__m2["path"];
1986
+ const round=__m2["round"];
1987
+ const setText=__m2["setText"];
1988
+ const SVG_NS=__m2["SVG_NS"];
2193
1989
  const __m3=__req("packages/modules/charts/styles.js");
2194
1990
  const NS=__m3["NS"];
2195
- const __m4=__req("packages/modules/charts/scale.js");
2196
- const toNumber=__m4["toNumber"];
2197
- const toTime=__m4["toTime"];
2198
- const __m5=__req("packages/modules/charts/quantile.js");
2199
- const studentTQuantile=__m5["studentTQuantile"];
2200
- const METHODS={
2201
- linear:new Set(['linear','lr','ols','regression','least-squares']),
2202
- movingAverage:new Set(['movingaverage','moving-average','ma','sma','rolling']),
2203
- exponential:new Set(['exponential','exp','ewma','ses','holt','smoothing']),
2204
- };
2205
- const SMOOTH_LO=1e-4;
2206
- const SMOOTH_HI=1-1e-4;
2207
- function methodOf(name){
2208
- const key=String(name||'linear').toLowerCase();
2209
- if(METHODS.linear.has(key))return'linear';
2210
- if(METHODS.movingAverage.has(key))return'movingAverage';
2211
- if(METHODS.exponential.has(key))return'exponential';
2212
- return null;
1991
+ const __m4=__req("packages/core/src/internal/util.js");
1992
+ const warnOnce=__m4["warnOnce"];
1993
+ function overlayLayer(group,className){
1994
+ for(const child of group.childNodes||[]){
1995
+ if(child&&String(child.getAttribute&&child.getAttribute('class'))===className){
1996
+ return(child);
2213
1997
  }
2214
- function normaliseTrends(trend){
2215
- if(!trend)return[];
2216
- const raw=Array.isArray(trend)?trend:[trend];
2217
- const out=[];
2218
- for(const entry of raw){
2219
- if(entry===true){out.push({method:'linear'});continue;}
2220
- if(typeof entry==='string'){
2221
- const method=methodOf(entry);
2222
- if(method)out.push({method});
2223
- continue;
2224
1998
  }
2225
- if(entry&&typeof entry==='object'){
2226
- const method=methodOf(entry.method);
2227
- if(method)out.push({...entry,method});
1999
+ const host=group.ownerDocument.createElementNS(SVG_NS,'g');
2000
+ host.setAttribute('class',className);
2001
+ group.appendChild(host);
2002
+ return host;
2003
+ }
2004
+ const KINDS=new Set(['line','target','band','callout','event']);
2005
+ function xNumber(scale,raw){
2006
+ return scale.kind==='time'?toTime(raw):toNumber(raw);
2007
+ }
2008
+ function xPixel(xScale,raw,edge){
2009
+ if(xScale.kind==='band'){
2010
+ const left=xScale.of(raw);
2011
+ if(!Number.isFinite(left))return NaN;
2012
+ const width=Number(xScale.bandwidth)||0;
2013
+ return edge==='right'?left+width:left;
2014
+ }
2015
+ const n=xNumber(xScale,raw);
2016
+ return n===null?NaN:xScale.of(n);
2017
+ }
2018
+ function warnDroppedAnnotation(anno,axis,reason){
2019
+ const kind=anno.kind||'line';
2020
+ const named=anno.label?` (${JSON.stringify(anno.label)})`:'';
2021
+ warnOnce(
2022
+ `chart:annotation:dropped:${kind}:${axis}:${reason}`,
2023
+ `chart annotation${named} of kind ${JSON.stringify(kind)}, reading the ${axis} axis, `
2024
+ +`was not drawn: ${reason}.`,
2025
+ );
2026
+ }
2027
+ function xText(raw,fmt){
2028
+ if(raw instanceof Date){
2029
+ const t=raw.getTime();
2030
+ return Number.isFinite(t)?raw.toISOString().slice(0,10):String(raw);
2228
2031
  }
2032
+ if(typeof raw==='number')return Number.isFinite(raw)?fmt(raw):String(raw);
2033
+ const n=toNumber(raw);
2034
+ return n===null||typeof raw==='string'?String(raw):fmt(n);
2229
2035
  }
2230
- return out;
2036
+ function poolFor(series,anno,axis){
2037
+ const out=[];
2038
+ for(const s of series){
2039
+ const side=s.axis==='right'?'right':'left';
2040
+ if(side!==axis)continue;
2041
+ if(anno.series!==undefined&&s.key!==anno.series)continue;
2042
+ for(const p of s.points)if(isNumber(p.y))out.push(Number(p.y));
2231
2043
  }
2232
- function placeX(scale,rawX){
2233
- if(scale.kind==='band')return scale.centre(rawX);
2234
- const n=scale.kind==='time'?toTime(rawX):toNumber(rawX);
2235
- return n===null?NaN:scale.of(n);
2044
+ return out;
2236
2045
  }
2237
- function sequenceOf(series){
2238
- const seq=[];
2239
- for(const point of series.points){
2240
- const y=Number(point.y);
2241
- seq.push({value:Number.isFinite(y)?y:null,rawX:point.x});
2046
+ function percentile(values,p){
2047
+ if(!values.length)return null;
2048
+ const sorted=[...values].sort((a,b)=>a-b);
2049
+ if(sorted.length===1)return sorted[0];
2050
+ const rank=(Math.min(100,Math.max(0,p))/100)*(sorted.length-1);
2051
+ const low=Math.floor(rank);
2052
+ const high=Math.ceil(rank);
2053
+ if(low===high)return sorted[low];
2054
+ return sorted[low]+(sorted[high]-sorted[low])*(rank-low);
2242
2055
  }
2243
- return seq;
2056
+ function annotationValue(anno,series,axis){
2057
+ if(!anno.compute){
2058
+ return toNumber(anno.value);
2244
2059
  }
2245
- function numericPairs(series,xScale){
2246
- const pairs=[];
2247
- for(const point of series.points){
2248
- const x=xScale.kind==='time'?toTime(point.x):toNumber(point.x);
2249
- const y=Number(point.y);
2250
- if(x===null||!Number.isFinite(x)||!Number.isFinite(y))continue;
2251
- pairs.push({x,y});
2060
+ const values=poolFor(series,anno,axis);
2061
+ if(!values.length)return null;
2062
+ const compute=String(anno.compute).toLowerCase();
2063
+ if(compute==='mean'||compute==='avg'){
2064
+ return values.reduce((t,v)=>t+v,0)/values.length;
2252
2065
  }
2253
- pairs.sort((a,b)=>a.x-b.x);
2254
- return pairs;
2066
+ if(compute==='median'||compute==='p50')return percentile(values,50);
2067
+ if(compute==='min')return Math.min(...values);
2068
+ if(compute==='max')return Math.max(...values);
2069
+ const match=/^p(\d+(?:\.\d+)?)$/.exec(compute);
2070
+ if(match)return percentile(values,Number(match[1]));
2071
+ return null;
2255
2072
  }
2256
- function linearTrend(pairs,forecast,opts={}){
2257
- const n=pairs.length;
2258
- if(n<2)return null;
2259
- let mx=0;
2260
- let my=0;
2261
- for(const p of pairs){mx+=p.x;my+=p.y;}
2262
- mx/=n;
2263
- my/=n;
2264
- let sxx=0;
2265
- let sxy=0;
2266
- let syy=0;
2267
- for(const p of pairs){
2268
- const dx=p.x-mx;
2269
- const dy=p.y-my;
2270
- sxx+=dx*dx;
2271
- sxy+=dx*dy;
2272
- syy+=dy*dy;
2073
+ function annotationAxis(anno){
2074
+ return anno.axis==='right'||anno.axis==='y2'?'right':'left';
2273
2075
  }
2274
- if(sxx===0)return null;
2275
- const slope=sxy/sxx;
2276
- const intercept=my-slope*mx;
2277
- const r2=syy===0?1:Math.max(0,Math.min(1,(sxy*sxy)/(sxx*syy)));
2278
- const lo=pairs[0].x;
2279
- const hi=pairs[n-1].x;
2280
- const at=(x)=>({x,y:intercept+slope*x});
2281
- const fit=[at(lo),at(hi)];
2282
- const projected=[];
2283
- let band=null;
2284
- const steps=Math.max(0,Math.floor(Number(forecast)||0));
2285
- if(steps>0&&hi>lo){
2286
- const step=(hi-lo)/(n-1);
2287
- if(step>0){
2288
- projected.push(at(hi),at(hi+steps*step));
2289
- if(n>2){
2290
- const conf=Number.isFinite(opts.confidence)
2291
- &&opts.confidence>0&&opts.confidence<1?Number(opts.confidence):0.95;
2292
- const tail=1-(1-conf)/2;
2293
- const sse=Math.max(0,syy-slope*sxy);
2294
- const s=Math.sqrt(sse/(n-2));
2295
- const t=studentTQuantile(tail,n-2);
2296
- const points=[];
2297
- for(let h=0;h<=steps;h++){
2298
- const x0=hi+h*step;
2299
- const mean=intercept+slope*x0;
2300
- const leverage=1/n+((x0-mx)**2)/sxx;
2301
- const sePred=s*Math.sqrt(1+leverage);
2302
- const seMean=s*Math.sqrt(leverage);
2303
- points.push({
2304
- x:x0,
2305
- mean,
2306
- lower:mean-t*sePred,
2307
- upper:mean+t*sePred,
2308
- lowerMean:mean-t*seMean,
2309
- upperMean:mean+t*seMean,
2310
- });
2076
+ function annotationText(anno,resolved,fmt,dual){
2077
+ const label=anno.label?`${anno.label}: `:'';
2078
+ const axis=dual?` (${annotationAxis(anno)==='right'?'right axis':'left axis'})`:'';
2079
+ const kind=anno.kind||'line';
2080
+ if(kind==='band'){
2081
+ if(!Array.isArray(resolved)||resolved[0]===null||resolved[1]===null)return'';
2082
+ const[lo,hi]=resolved;
2083
+ return`${label||'Band: '}${fmt(Math.min(lo,hi))} to ${fmt(Math.max(lo,hi))}${axis}`;
2311
2084
  }
2312
- band={confidence:conf,points};
2085
+ if(resolved===null||resolved===undefined)return'';
2086
+ const noun=kind==='target'?'Target':(kind==='callout'?'Callout':'Reference');
2087
+ return`${label||`${noun}: `}${fmt((resolved))}${axis}`;
2313
2088
  }
2089
+ function bandFirst(x){
2090
+ return(x.kind||'line')==='band'?0:1;
2314
2091
  }
2092
+ function drawAnnotations(opts){
2093
+ const{
2094
+ group,plot,xScale,yScale,rightScale,series,format,
2095
+ }=opts;
2096
+ const pool=new Pool(overlayLayer(group,`${NS}__annotations`));
2097
+ const described=[];
2098
+ const list=Array.isArray(opts.annotations)?opts.annotations:[];
2099
+ const ordered=[...list].sort((a,b)=>bandFirst(a)-bandFirst(b));
2100
+ for(const anno of ordered){
2101
+ const kind=anno.kind||'line';
2102
+ if(!KINDS.has(kind))continue;
2103
+ const axis=annotationAxis(anno);
2104
+ const scale=axis==='right'&&rightScale?rightScale:yScale;
2105
+ if(kind==='band'){
2106
+ if(anno.orient==='vertical'){
2107
+ const x1=xPixel(xScale,anno.from,'left');
2108
+ const x2=xPixel(xScale,anno.to,'right');
2109
+ if(Number.isNaN(x1)||Number.isNaN(x2)){
2110
+ const edge=Number.isNaN(x1)&&Number.isNaN(x2)?'from and to':(Number.isNaN(x1)?'from':'to');
2111
+ warnDroppedAnnotation(anno,axis,`its ${edge} value could not be placed on the x scale`);
2112
+ continue;
2315
2113
  }
2316
- return{
2317
- fit,
2318
- forecast:projected,
2319
- band,
2320
- stats:{
2321
- method:'linear',slope,intercept,r2,n,forecast:projected.length?steps:0,
2322
- confidence:band?band.confidence:null,
2323
- },
2324
- };
2114
+ const left=round(Math.min(x1,x2));
2115
+ const right=round(Math.max(x1,x2));
2116
+ attrs(pool.next('rect',`${NS}__annotation-band ${anno.className||''}`.trim()),{
2117
+ x:left,
2118
+ y:round(plot.top),
2119
+ width:Math.max(0,right-left),
2120
+ height:round(Math.max(0,plot.bottom-plot.top)),
2121
+ fill:anno.colour||'currentColor',
2122
+ 'fill-opacity':anno.opacity===undefined?0.12:anno.opacity,
2123
+ });
2124
+ if(anno.label){
2125
+ const text=pool.next('text',`${NS}__annotation-label`);
2126
+ attrs(text,{x:left+4,y:round(plot.top+12),'text-anchor':'start'});
2127
+ setText(text,anno.label);
2325
2128
  }
2326
- function movingAverageTrend(seq,window){
2327
- const span=Math.max(1,Math.floor(Number(window)||3));
2328
- const ring=[];
2329
- let sum=0;
2330
- const fit=[];
2331
- for(const row of seq){
2332
- if(Number.isFinite(row.value)){
2333
- ring.push(row.value);
2334
- sum+=row.value;
2335
- if(ring.length>span)sum-=ring.shift();
2129
+ const noun=anno.label?`${anno.label}: `:'Event window: ';
2130
+ described.push({text:`${noun}${xText(anno.from,format)} to ${xText(anno.to,format)}`});
2131
+ continue;
2336
2132
  }
2337
- if(!ring.length)continue;
2338
- fit.push({x:row.rawX,y:sum/ring.length});
2133
+ const from=annotationValue({...anno,value:anno.from,compute:anno.fromCompute},series,axis);
2134
+ const to=annotationValue({...anno,value:anno.to,compute:anno.toCompute},series,axis);
2135
+ if(from===null||to===null)continue;
2136
+ const y1=round(scale.of(from));
2137
+ const y2=round(scale.of(to));
2138
+ attrs(pool.next('rect',`${NS}__annotation-band ${anno.className||''}`.trim()),{
2139
+ x:round(plot.left),
2140
+ y:Math.min(y1,y2),
2141
+ width:round(Math.max(0,plot.right-plot.left)),
2142
+ height:Math.max(0,Math.abs(y2-y1)),
2143
+ fill:anno.colour||'currentColor',
2144
+ 'fill-opacity':anno.opacity===undefined?0.12:anno.opacity,
2145
+ });
2146
+ if(anno.label){
2147
+ const text=pool.next('text',`${NS}__annotation-label`);
2148
+ attrs(text,{x:round(plot.left+4),y:Math.min(y1,y2)+12,'text-anchor':'start'});
2149
+ setText(text,anno.label);
2339
2150
  }
2340
- if(fit.length<2)return null;
2341
- return{fit,stats:{method:'movingAverage',window:span}};
2151
+ described.push({text:annotationText(anno,[from,to],format,opts.dual)});
2152
+ continue;
2342
2153
  }
2343
- function runSes(y,alpha){
2344
- const n=y.length;
2345
- const smoothed=new Array(n).fill(null);
2346
- let level=null;
2347
- let sse=0;
2348
- for(let i=0;i<n;i++){
2349
- const v=y[i];
2350
- if(!Number.isFinite(v)){
2351
- if(level!==null)smoothed[i]=level;
2154
+ if(kind==='event'){
2155
+ const raw=anno.x!==undefined?anno.x:anno.at;
2156
+ const at=round(xPixel(xScale,raw,'left')+(xScale.kind==='band'?(Number(xScale.bandwidth)||0)/2:0));
2157
+ if(Number.isNaN(at)){
2158
+ warnDroppedAnnotation(anno,axis,'its x value could not be placed on the x scale');
2352
2159
  continue;
2353
2160
  }
2354
- if(level===null){level=v;smoothed[i]=v;continue;}
2355
- sse+=(v-level)**2;
2356
- level=alpha*v+(1-alpha)*level;
2357
- smoothed[i]=level;
2161
+ attrs(pool.next('path',`${NS}__annotation-event`),{
2162
+ d:path(['M',at,round(plot.top),'L',at,round(plot.bottom)]),
2163
+ stroke:anno.colour||'currentColor',
2164
+ fill:'none',
2165
+ });
2166
+ attrs(pool.next('circle',`${NS}__annotation-event-flag`),{
2167
+ cx:at,cy:round(plot.top),r:3,fill:anno.colour||'currentColor',
2168
+ });
2169
+ if(anno.label){
2170
+ const text=pool.next('text',`${NS}__annotation-label`);
2171
+ attrs(text,{x:at+5,y:round(plot.top+10),'text-anchor':'start'});
2172
+ setText(text,anno.label);
2358
2173
  }
2359
- return{smoothed,sse};
2174
+ const noun=anno.label?`${anno.label} — `:'Event — ';
2175
+ described.push({text:`${noun}at ${xText(raw,format)}`});
2176
+ continue;
2360
2177
  }
2361
- function runHolt(y,alpha,beta){
2362
- const n=y.length;
2363
- const smoothed=new Array(n).fill(null);
2364
- let level=null;
2365
- let trend=null;
2366
- let sse=0;
2367
- for(let i=0;i<n;i++){
2368
- const v=y[i];
2369
- if(!Number.isFinite(v)){
2370
- if(level!==null){level+=trend;smoothed[i]=level;}
2178
+ if(kind==='callout'){
2179
+ const value=annotationValue(anno,series,axis);
2180
+ if(value===null)continue;
2181
+ const cx=anno.at!==undefined
2182
+ ?round(xScale.kind==='band'?xScale.centre(anno.at):xScale.of(xNumber(xScale,anno.at)??NaN))
2183
+ :round(plot.right-4);
2184
+ const cy=round(scale.of(value));
2185
+ if(Number.isNaN(cx)){
2186
+ warnDroppedAnnotation(anno,axis,'its x value could not be placed on the x scale');
2371
2187
  continue;
2372
2188
  }
2373
- if(level===null){level=v;smoothed[i]=v;continue;}
2374
- if(trend===null){
2375
- trend=v-level;
2376
- const forecast=level+trend;
2377
- sse+=(v-forecast)**2;
2378
- const prev=level;
2379
- level=alpha*v+(1-alpha)*(prev+trend);
2380
- trend=beta*(level-prev)+(1-beta)*trend;
2381
- smoothed[i]=level;
2189
+ attrs(pool.next('circle',`${NS}__annotation-point`),{
2190
+ cx,cy,r:3.5,fill:anno.colour||'currentColor',
2191
+ });
2192
+ if(anno.label){
2193
+ const text=pool.next('text',`${NS}__annotation-label`);
2194
+ attrs(text,{x:cx+6,y:cy-6,'text-anchor':'start'});
2195
+ setText(text,anno.label);
2196
+ }
2197
+ described.push({text:annotationText(anno,value,format,opts.dual)});
2382
2198
  continue;
2383
2199
  }
2384
- const forecast=level+trend;
2385
- sse+=(v-forecast)**2;
2386
- const prev=level;
2387
- level=alpha*v+(1-alpha)*(prev+trend);
2388
- trend=beta*(level-prev)+(1-beta)*trend;
2389
- smoothed[i]=level;
2200
+ if(anno.orient==='vertical'||anno.x!==undefined){
2201
+ const raw=anno.x!==undefined?anno.x:anno.value;
2202
+ const at=xScale.kind==='band'
2203
+ ?round(xScale.centre(raw))
2204
+ :round(xScale.of(xNumber(xScale,raw)??NaN));
2205
+ if(Number.isNaN(at)){
2206
+ warnDroppedAnnotation(anno,axis,'its x value could not be placed on the x scale');
2207
+ continue;
2390
2208
  }
2391
- return{smoothed,sse};
2209
+ const cls=kind==='target'?`${NS}__annotation-target`:`${NS}__annotation-line`;
2210
+ attrs(pool.next('path',cls),{
2211
+ d:path(['M',at,round(plot.top),'L',at,round(plot.bottom)]),
2212
+ stroke:anno.colour||'currentColor',
2213
+ fill:'none',
2214
+ });
2215
+ if(anno.label){
2216
+ const text=pool.next('text',`${NS}__annotation-label`);
2217
+ attrs(text,{
2218
+ x:at+4,y:round(plot.top+10),'text-anchor':'start',
2219
+ });
2220
+ setText(text,anno.label);
2392
2221
  }
2393
- function goldenMin(f,lo,hi,iters=80){
2394
- const invphi=(Math.sqrt(5)-1)/2;
2395
- let a=lo;
2396
- let b=hi;
2397
- let c=b-invphi*(b-a);
2398
- let d=a+invphi*(b-a);
2399
- let fc=f(c);
2400
- let fd=f(d);
2401
- for(let i=0;i<iters;i++){
2402
- if(fc<fd){b=d;d=c;fd=fc;c=b-invphi*(b-a);fc=f(c);}
2403
- else{a=c;c=d;fc=fd;d=a+invphi*(b-a);fd=f(d);}
2222
+ described.push({text:anno.label?`${anno.label} — at ${xText(raw,format)}`:''});
2223
+ continue;
2404
2224
  }
2405
- return(a+b)/2;
2225
+ const value=annotationValue(anno,series,axis);
2226
+ if(value===null)continue;
2227
+ const at=round(scale.of(value));
2228
+ const cls=kind==='target'?`${NS}__annotation-target`:`${NS}__annotation-line`;
2229
+ attrs(pool.next('path',cls),{
2230
+ d:path(['M',round(plot.left),at,'L',round(plot.right),at]),
2231
+ stroke:anno.colour||'currentColor',
2232
+ fill:'none',
2233
+ });
2234
+ if(anno.label){
2235
+ const text=pool.next('text',`${NS}__annotation-label`);
2236
+ attrs(text,{x:round(plot.right-4),y:at-4,'text-anchor':'end'});
2237
+ setText(text,anno.label);
2406
2238
  }
2407
- function exponentialTrend(seq,spec){
2408
- const holt=String(spec.kind||spec.model||'').toLowerCase()==='holt';
2409
- const y=seq.map((row)=>(Number.isFinite(row.value)?row.value:null));
2410
- const finite=y.filter((v)=>v!==null).length;
2411
- if(finite<(holt?2:1))return null;
2412
- const clamp=(x)=>Math.min(SMOOTH_HI,Math.max(SMOOTH_LO,x));
2413
- let alpha=Number.isFinite(spec.alpha)?clamp(Number(spec.alpha)):null;
2414
- let beta=holt&&Number.isFinite(spec.beta)?clamp(Number(spec.beta)):null;
2415
- if(holt){
2416
- if(alpha===null||beta===null){
2417
- let a=alpha===null?0.5:alpha;
2418
- let b=beta===null?0.5:beta;
2419
- for(let r=0;r<6;r++){
2420
- if(alpha===null)a=goldenMin((x)=>runHolt(y,x,b).sse,SMOOTH_LO,SMOOTH_HI);
2421
- if(beta===null)b=goldenMin((x)=>runHolt(y,a,x).sse,SMOOTH_LO,SMOOTH_HI);
2239
+ described.push({text:annotationText(anno,value,format,opts.dual)});
2422
2240
  }
2423
- alpha=alpha===null?a:alpha;
2424
- beta=beta===null?b:beta;
2241
+ pool.finish();
2242
+ return described.filter((d)=>d.text);
2425
2243
  }
2426
- }else if(alpha===null){
2427
- alpha=goldenMin((x)=>runSes(y,x).sse,SMOOTH_LO,SMOOTH_HI);
2244
+ });
2245
+ __def("packages/modules/charts/combo.js",function(__exports,__req){
2246
+ 'use strict';
2247
+ Object.defineProperty(__exports,"toPareto",{enumerable:true,get:function(){return toPareto;}});
2248
+ Object.defineProperty(__exports,"drawCombo",{enumerable:true,get:function(){return drawCombo;}});
2249
+ Object.defineProperty(__exports,"drawRightAxis",{enumerable:true,get:function(){return drawRightAxis;}});
2250
+ const __m0=__req("packages/modules/charts/axis.js");
2251
+ const drawAxisLines=__m0["drawAxisLines"];
2252
+ const drawCategoryAxis=__m0["drawCategoryAxis"];
2253
+ const drawContinuousAxis=__m0["drawContinuousAxis"];
2254
+ const drawMeasureAxis=__m0["drawMeasureAxis"];
2255
+ const overhangFor=__m0["overhangFor"];
2256
+ const setPlotClip=__m0["setPlotClip"];
2257
+ const __m1=__req("packages/modules/charts/frame.js");
2258
+ const Pool=__m1["Pool"];
2259
+ const shouldRotate=__m1["shouldRotate"];
2260
+ const __m2=__req("packages/modules/charts/format.js");
2261
+ const formatters=__m2["formatters"];
2262
+ const __m3=__req("packages/modules/charts/scale.js");
2263
+ const bandScale=__m3["bandScale"];
2264
+ const extent=__m3["extent"];
2265
+ const isNumber=__m3["isNumber"];
2266
+ const linearScale=__m3["linearScale"];
2267
+ const measureDomain=__m3["measureDomain"];
2268
+ const timeScale=__m3["timeScale"];
2269
+ const toNumber=__m3["toNumber"];
2270
+ const toTime=__m3["toTime"];
2271
+ const __m4=__req("packages/modules/charts/svg.js");
2272
+ const attrs=__m4["attrs"];
2273
+ const path=__m4["path"];
2274
+ const round=__m4["round"];
2275
+ const __m5=__req("packages/modules/charts/styles.js");
2276
+ const NS=__m5["NS"];
2277
+ const __m6=__req("packages/modules/charts/scheme.js");
2278
+ const resolveScheme=__m6["resolveScheme"];
2279
+ const __m7=__req("packages/modules/charts/labels.js");
2280
+ const drawDataLabels=__m7["drawDataLabels"];
2281
+ const __m8=__req("packages/modules/charts/annotations.js");
2282
+ const drawAnnotations=__m8["drawAnnotations"];
2283
+ function byAxis(series){
2284
+ return{
2285
+ left:series.filter((s)=>s.axis!=='right'),
2286
+ right:series.filter((s)=>s.axis==='right'),
2287
+ };
2428
2288
  }
2429
- const run=holt?runHolt(y,alpha,beta):runSes(y,alpha);
2430
- const fit=[];
2431
- for(let i=0;i<seq.length;i++){
2432
- const level=run.smoothed[i];
2433
- if(level===null||!Number.isFinite(level))continue;
2434
- fit.push({x:seq[i].rawX,y:level});
2289
+ function domainFor(series,zero){
2290
+ const values=[];
2291
+ for(const s of series)for(const p of s.points)values.push(p.y);
2292
+ return measureDomain(values,{zero});
2435
2293
  }
2436
- if(fit.length<2)return null;
2294
+ function toPareto(bound){
2295
+ const source=bound.series[0];
2296
+ if(!source)return bound;
2297
+ const ranked=source.points
2298
+ .map((point,i)=>({point,label:bound.labels[i],category:bound.categories[i]}))
2299
+ .filter((entry)=>isNumber(entry.point.y))
2300
+ .sort((a,b)=>(b.point.y)-(a.point.y));
2301
+ const total=ranked.reduce((t,entry)=>t+(entry.point.y),0);
2302
+ let running=0;
2303
+ const cumulative=ranked.map((entry)=>{
2304
+ running+=(entry.point.y);
2305
+ return total>0?(running/total)*100:0;
2306
+ });
2437
2307
  return{
2438
- fit,stats:{method:'exponential',kind:holt?'holt':'ses',alpha,beta:holt?beta:null},
2308
+ ...bound,
2309
+ categories:ranked.map((entry)=>entry.category),
2310
+ labels:ranked.map((entry)=>entry.label),
2311
+ series:[
2312
+ {
2313
+ ...source,
2314
+ index:0,
2315
+ mark:'bar',
2316
+ axis:'left',
2317
+ points:ranked.map((entry)=>entry.point),
2318
+ },
2319
+ {
2320
+ key:'cumulative',
2321
+ label:bound.cumulativeLabel||'%',
2322
+ index:1,
2323
+ mark:'line',
2324
+ axis:'right',
2325
+ points:ranked.map((entry,i)=>({
2326
+ x:entry.category,
2327
+ xKey:entry.point.xKey,
2328
+ label:entry.label,
2329
+ y:cumulative[i],
2330
+ rows:entry.point.rows,
2331
+ rowKey:entry.point.rowKey,
2332
+ })),
2333
+ },
2334
+ ],
2439
2335
  };
2440
2336
  }
2441
- function computeTrend(series,spec,xScale){
2442
- if(spec.method==='linear'){
2443
- const pairs=numericPairs(series,xScale);
2444
- return linearTrend(pairs,spec.forecast,{confidence:spec.confidence});
2337
+ function comboXScale(bound,plot){
2338
+ const range=([plot.left,plot.right]);
2339
+ if(bound.kind!=='time'&&bound.kind!=='linear'){
2340
+ return bandScale(bound.categories,range,0.2);
2445
2341
  }
2446
- const seq=sequenceOf(series);
2447
- if(spec.method==='movingAverage'){
2448
- return movingAverageTrend(seq,spec.window||spec.period);
2342
+ const numbers=bound.kind==='time'
2343
+ ?bound.categories.map(toTime)
2344
+ :bound.categories.map(toNumber);
2345
+ if(bound.kind==='time'){
2346
+ const found=extent(numbers);
2347
+ if(found&&found.min===found.max){
2348
+ return timeScale({min:found.min-30000,max:found.max+30000},range);
2449
2349
  }
2450
- return exponentialTrend(seq,spec);
2451
2350
  }
2452
- function trendText(stats,label,fmt,forecast,band,bandKind){
2453
- const who=label?` for ${label}`:'';
2454
- if(stats.method==='linear'){
2455
- const r2=Number.isFinite(stats.r2)?`, R² ${stats.r2.toFixed(2)}`:'';
2456
- let text=`Trend (linear)${who}: slope ${fmt(stats.slope)}${r2}`;
2457
- if(stats.forecast>0&&forecast&&forecast.length){
2458
- const end=forecast[forecast.length-1];
2459
- text+=`; forecast ${stats.forecast} step${stats.forecast===1?'':'s'} ahead to ${fmt(end.y)}`;
2460
- if(band&&band.points.length){
2461
- const last=band.points[band.points.length-1];
2462
- const mean=bandKind==='confidence'||bandKind==='mean';
2463
- const lo=mean?last.lowerMean:last.lower;
2464
- const up=mean?last.upperMean:last.upper;
2465
- const kind=mean?'confidence':'prediction';
2466
- const pct=Math.round(band.confidence*100);
2467
- text+=`; ${pct}% ${kind} band ${fmt(lo)} to ${fmt(up)} at the horizon`;
2351
+ const domain=measureDomain(numbers,{zero:false,nice:bound.kind!=='time'});
2352
+ return bound.kind==='time'?timeScale(domain,range):linearScale(domain,range);
2468
2353
  }
2354
+ function continuousBarWidth(numbers,scale){
2355
+ const distinct=[...new Set(numbers.filter((n)=>Number.isFinite(n)))]
2356
+ .sort((a,b)=>(a)-(b));
2357
+ if(distinct.length<2){
2358
+ const[from,to]=scale.range;
2359
+ return Math.max(4,(to-from)/10);
2360
+ }
2361
+ const gaps=[];
2362
+ for(let i=1;i<distinct.length;i++){
2363
+ gaps.push((distinct[i])-(distinct[i-1]));
2364
+ }
2365
+ gaps.sort((a,b)=>a-b);
2366
+ const mid=Math.floor(gaps.length/2);
2367
+ const medianGap=gaps.length%2?gaps[mid]:(gaps[mid-1]+gaps[mid])/2;
2368
+ const pxGap=Math.abs(
2369
+ scale.of((distinct[0])+medianGap)-scale.of((distinct[0])),
2370
+ );
2371
+ return Math.max(1,pxGap*0.8);
2469
2372
  }
2470
- return text;
2373
+ function drawCombo(ctx){
2374
+ const scheme=ctx.scheme||resolveScheme();
2375
+ const{plot}=ctx;
2376
+ const bound=ctx.pareto?toPareto(ctx.bound):ctx.bound;
2377
+ const visible=bound.series.filter((s)=>!ctx.hidden.has(s.key));
2378
+ const sides=byAxis(visible);
2379
+ const hasBars=sides.left.some((s)=>(s.mark||'bar')==='bar');
2380
+ const leftDomain=domainFor(sides.left,hasBars);
2381
+ const yScale=linearScale(leftDomain,[plot.bottom,plot.top]);
2382
+ const rightDomain=sides.right.length
2383
+ ?(ctx.pareto?{min:0,max:100}:domainFor(sides.right,false))
2384
+ :null;
2385
+ const rightScale=rightDomain?linearScale(rightDomain,[plot.bottom,plot.top]):null;
2386
+ const xScale=comboXScale(bound,plot);
2387
+ const continuousX=xScale.kind!=='band';
2388
+ const xNumbers=continuousX
2389
+ ?bound.categories.map((v)=>(bound.kind==='time'?toTime(v):toNumber(v)))
2390
+ :null;
2391
+ drawMeasureAxis({
2392
+ gridGroup:ctx.groups.grid,
2393
+ axisGroup:ctx.groups.axis,
2394
+ plot,
2395
+ scale:yScale,
2396
+ format:formatters(ctx.grid,{
2397
+ kind:'linear',step:(leftDomain.max-leftDomain.min)/5,
2398
+ }),
2399
+ });
2400
+ if(continuousX){
2401
+ drawContinuousAxis({
2402
+ group:ctx.groups.overlay,
2403
+ plot,
2404
+ scale:xScale,
2405
+ format:formatters(ctx.grid,{
2406
+ kind:bound.kind,
2407
+ span:xScale.domain.max-xScale.domain.min,
2408
+ step:(xScale.domain.max-xScale.domain.min)/5,
2409
+ }),
2410
+ grid:ctx.grid,
2411
+ size:ctx.fontSize,
2412
+ });
2413
+ }else{
2414
+ drawCategoryAxis({
2415
+ group:ctx.groups.overlay,
2416
+ plot,
2417
+ scale:xScale,
2418
+ labels:bound.labels,
2419
+ fontSize:ctx.fontSize,
2420
+ rotated:shouldRotate(bound.labels,xScale.step,ctx.fontSize),
2421
+ });
2471
2422
  }
2472
- if(stats.method==='movingAverage'){
2473
- return`Trend (moving average, window ${stats.window})${who}`;
2423
+ drawAxisLines({group:ctx.groups.rules,plot});
2424
+ if(rightScale){
2425
+ drawRightAxis({
2426
+ group:ctx.groups.axis,
2427
+ plot,
2428
+ scale:rightScale,
2429
+ colour:scheme.series(sides.right[0].index),
2430
+ format:formatters(ctx.grid,{
2431
+ kind:'linear',step:(rightDomain.max-rightDomain.min)/5,
2432
+ }),
2433
+ suffix:ctx.pareto?'%':'',
2434
+ });
2474
2435
  }
2475
- const kind=stats.kind==='holt'?'Holt':'single';
2476
- const a=Number.isFinite(stats.alpha)?`, α ${stats.alpha.toFixed(2)}`:'';
2477
- const b=Number.isFinite(stats.beta)?`, β ${stats.beta.toFixed(2)}`:'';
2478
- return`Trend (exponential, ${kind})${who}${a}${b}`;
2436
+ setPlotClip(ctx.groups,plot,overhangFor(ctx.type));
2437
+ const pool=new Pool(ctx.groups.marks);
2438
+ const bars=visible.filter((s)=>(s.mark||'bar')==='bar');
2439
+ const barWidth=continuousX?continuousBarWidth(xNumbers,xScale):xScale.bandwidth;
2440
+ const slot=bars.length?barWidth/bars.length:barWidth;
2441
+ let barIndex=0;
2442
+ for(const series of visible){
2443
+ const mark=series.mark||'bar';
2444
+ const scale=series.axis==='right'&&rightScale?rightScale:yScale;
2445
+ const colour=scheme.series(series.index);
2446
+ if(mark==='bar'){
2447
+ const offset=barIndex*slot;
2448
+ barIndex++;
2449
+ const base=scale.of(Math.max(scale.domain.min,0));
2450
+ for(let i=0;i<series.points.length;i++){
2451
+ const point=series.points[i];
2452
+ if(!isNumber(point.y))continue;
2453
+ let start;
2454
+ if(continuousX){
2455
+ const n=bound.kind==='time'?toTime(point.x):toNumber(point.x);
2456
+ start=n===null?NaN:xScale.of(n)-barWidth/2;
2457
+ }else{
2458
+ start=xScale.of(point.x);
2479
2459
  }
2480
- function drawTrendlines(opts){
2481
- const specs=normaliseTrends(opts.trend);
2482
- if(!specs.length)return[];
2483
- const doc=opts.group.ownerDocument;
2484
- let host=null;
2485
- for(const child of opts.group.childNodes||[]){
2486
- if(child&&String(child.getAttribute&&child.getAttribute('class'))===`${NS}__trends`){
2487
- host=child;
2488
- break;
2460
+ if(Number.isNaN(start))continue;
2461
+ const top=scale.of((point.y));
2462
+ attrs(pool.next('rect',`${NS}__bar ${NS}__mark`),{
2463
+ x:round(start+offset),
2464
+ y:round(Math.min(top,base)),
2465
+ width:round(Math.max(0,slot)),
2466
+ height:round(Math.max(1,Math.abs(base-top))),
2467
+ fill:colour,
2468
+ 'data-series':series.index,
2469
+ 'data-point':i,
2470
+ });
2471
+ point.px=round(start+offset+slot/2);
2472
+ point.py=round(Math.min(top,base));
2489
2473
  }
2474
+ continue;
2490
2475
  }
2491
- if(!host){
2492
- host=doc.createElementNS(SVG_NS,'g');
2493
- host.setAttribute('class',`${NS}__trends`);
2494
- opts.group.appendChild(host);
2476
+ const commands=[];
2477
+ let open=false;
2478
+ for(const point of series.points){
2479
+ if(!isNumber(point.y)){open=false;continue;}
2480
+ let x;
2481
+ if(continuousX){
2482
+ const n=bound.kind==='time'?toTime(point.x):toNumber(point.x);
2483
+ x=n===null?NaN:round(xScale.of(n));
2484
+ }else{
2485
+ x=round(xScale.centre(point.x));
2495
2486
  }
2496
- if(opts.clip)host.setAttribute('clip-path',`url(#${opts.clip})`);
2497
- else host.removeAttribute('clip-path');
2498
- const pool=new Pool(host);
2499
- const described=[];
2500
- for(const spec of specs){
2501
- for(const series of opts.series){
2502
- const yScale=(series.axis==='right'&&opts.rightScale)?opts.rightScale:opts.yScale;
2503
- const overlay=computeTrend(series,spec,opts.xScale);
2504
- if(!overlay)continue;
2505
- const colour=opts.scheme.series(series.index);
2506
- const line=[];
2507
- for(const point of overlay.fit){
2508
- const px=placeX(opts.xScale,point.x);
2509
- const py=yScale.of(point.y);
2510
- if(!Number.isFinite(px)||!Number.isFinite(py))continue;
2511
- line.push(line.length?'L':'M',round(px),round(py));
2487
+ if(Number.isNaN(x)){open=false;continue;}
2488
+ const y=round(scale.of((point.y)));
2489
+ commands.push(open?'L':'M',x,y);
2490
+ open=true;
2491
+ point.px=x;
2492
+ point.py=y;
2512
2493
  }
2513
- if(line.length<4)continue;
2514
- attrs(pool.next('path',`${NS}__trend`),{
2515
- d:path(line),
2516
- fill:'none',
2494
+ attrs(pool.next('path',`${NS}__line ${NS}__mark`),{
2495
+ d:commands.length?path(commands):'',
2517
2496
  stroke:colour,
2518
2497
  'data-series':series.index,
2519
2498
  });
2520
- const wantsBand=spec.band!==false&&overlay.band&&overlay.band.points.length>=2;
2521
- if(wantsBand){
2522
- const mean=spec.band==='confidence'||spec.band==='mean';
2523
- const rows=[];
2524
- for(const point of overlay.band.points){
2525
- const px=placeX(opts.xScale,point.x);
2526
- const lo=mean?point.lowerMean:point.lower;
2527
- const up=mean?point.upperMean:point.upper;
2528
- const pl=yScale.of(lo);
2529
- const pu=yScale.of(up);
2530
- if(!Number.isFinite(px)||!Number.isFinite(pl)||!Number.isFinite(pu))continue;
2531
- rows.push({x:round(px),lower:round(pl),upper:round(pu)});
2532
- }
2533
- const d=ribbonPath(rows);
2534
- if(d){
2535
- attrs(pool.next('path',`${NS}__trend-band`),{
2536
- d,
2537
- fill:colour,
2538
- 'fill-opacity':0.15,
2539
- stroke:'none',
2540
- 'data-series':series.index,
2499
+ for(const point of series.points){
2500
+ if(point.px===undefined)continue;
2501
+ attrs(pool.next('circle',`${NS}__point ${NS}__mark`),{
2502
+ cx:point.px,cy:point.py,r:3,fill:colour,
2541
2503
  });
2542
2504
  }
2543
2505
  }
2544
- const forecast=overlay.forecast||[];
2545
- if(forecast.length>=2){
2546
- const seg=[];
2547
- for(const point of forecast){
2548
- const px=placeX(opts.xScale,point.x);
2549
- const py=yScale.of(point.y);
2550
- if(!Number.isFinite(px)||!Number.isFinite(py))continue;
2551
- seg.push(seg.length?'L':'M',round(px),round(py));
2506
+ pool.finish();
2507
+ if(ctx.labels&&ctx.labels.show){
2508
+ drawDataLabels({
2509
+ group:ctx.groups.labels,
2510
+ series:visible,
2511
+ plot,
2512
+ options:ctx.labels,
2513
+ grid:ctx.grid,
2514
+ size:(ctx.typography&&ctx.typography.small)||ctx.fontSize,
2515
+ });
2552
2516
  }
2553
- if(seg.length>=4){
2554
- attrs(pool.next('path',`${NS}__trend-forecast`),{
2555
- d:path(seg),
2556
- fill:'none',
2557
- stroke:colour,
2558
- 'data-series':series.index,
2517
+ let annotations=null;
2518
+ if(ctx.annotations&&ctx.annotations.length){
2519
+ annotations=drawAnnotations({
2520
+ group:ctx.groups.overlay,
2521
+ plot,
2522
+ xScale,
2523
+ yScale,
2524
+ rightScale,
2525
+ series:visible,
2526
+ annotations:ctx.annotations,
2527
+ dual:!!rightScale,
2528
+ format:formatters(ctx.grid,{
2529
+ kind:'linear',step:(leftDomain.max-leftDomain.min)/5,
2530
+ }).tick,
2559
2531
  });
2560
2532
  }
2533
+ return{
2534
+ xScale,yScale,rightScale,dual:!!rightScale,series:visible,bound,annotations,
2535
+ };
2561
2536
  }
2562
- if(spec.label!==false&&overlay.stats.method==='linear'
2563
- &&Number.isFinite(overlay.stats.r2)){
2564
- const last=overlay.fit[overlay.fit.length-1];
2565
- const text=pool.next('text',`${NS}__trend-label`);
2566
- applySize(text,opts.size);
2567
- attrs(text,{
2568
- x:round(placeX(opts.xScale,last.x)),
2569
- y:round(yScale.of(last.y))-4,
2570
- 'text-anchor':'end',
2571
- fill:colour,
2537
+ function drawRightAxis(opts){
2538
+ const{group,plot,scale,format}=opts;
2539
+ const pool=new Pool(group);
2540
+ attrs(pool.next('line',`${NS}__axis-right`),{
2541
+ x1:round(plot.right),x2:round(plot.right),
2542
+ y1:round(plot.top),y2:round(plot.bottom),
2543
+ stroke:opts.colour,
2572
2544
  });
2573
- setText(text,`R² ${overlay.stats.r2.toFixed(2)}`);
2545
+ for(const value of scale.ticks(5)){
2546
+ const at=round(scale.of(value));
2547
+ const label=pool.next('text',`${NS}__tick`);
2548
+ attrs(label,{
2549
+ x:round(plot.right+6),y:at,'text-anchor':'start',
2550
+ 'dominant-baseline':'middle',fill:opts.colour,
2551
+ });
2552
+ label.textContent=`${format.tick(value)}${opts.suffix||''}`;
2553
+ }
2554
+ pool.finish();
2574
2555
  }
2575
- described.push({
2576
- text:trendText(overlay.stats,series.label,opts.format,forecast,
2577
- spec.band===false?null:overlay.band,spec.band),
2578
2556
  });
2557
+ __def("packages/modules/charts/quantile.js",function(__exports,__req){
2558
+ 'use strict';
2559
+ Object.defineProperty(__exports,"normalQuantile",{enumerable:true,get:function(){return normalQuantile;}});
2560
+ Object.defineProperty(__exports,"studentTQuantile",{enumerable:true,get:function(){return studentTQuantile;}});
2561
+ const LANCZOS=Object.freeze([
2562
+ 676.5203681218851,-1259.1392167224028,771.32342877765313,
2563
+ -176.61502916214059,12.507343278686905,-0.13857109526572012,
2564
+ 9.9843695780195716e-6,1.5056327351493116e-7,
2565
+ ]);
2566
+ const EPS=3e-12;
2567
+ const TINY=1e-300;
2568
+ function logGamma(x){
2569
+ if(x<0.5)return Math.log(Math.PI/Math.sin(Math.PI*x))-logGamma(1-x);
2570
+ const z=x-1;
2571
+ let a=0.99999999999980993;
2572
+ const t=z+7.5;
2573
+ for(let i=0;i<LANCZOS.length;i++)a+=LANCZOS[i]/(z+i+1);
2574
+ return 0.5*Math.log(2*Math.PI)+(z+0.5)*Math.log(t)-t+Math.log(a);
2575
+ }
2576
+ function betaContinuedFraction(a,b,x){
2577
+ const qab=a+b;
2578
+ const qap=a+1;
2579
+ const qam=a-1;
2580
+ let c=1;
2581
+ let d=1-(qab*x)/qap;
2582
+ if(Math.abs(d)<TINY)d=TINY;
2583
+ d=1/d;
2584
+ let h=d;
2585
+ for(let m=1;m<=300;m++){
2586
+ const m2=2*m;
2587
+ let aa=(m*(b-m)*x)/((qam+m2)*(a+m2));
2588
+ d=1+aa*d;
2589
+ if(Math.abs(d)<TINY)d=TINY;
2590
+ c=1+aa/c;
2591
+ if(Math.abs(c)<TINY)c=TINY;
2592
+ d=1/d;
2593
+ h*=d*c;
2594
+ aa=(-(a+m)*(qab+m)*x)/((a+m2)*(qap+m2));
2595
+ d=1+aa*d;
2596
+ if(Math.abs(d)<TINY)d=TINY;
2597
+ c=1+aa/c;
2598
+ if(Math.abs(c)<TINY)c=TINY;
2599
+ d=1/d;
2600
+ const step=d*c;
2601
+ h*=step;
2602
+ if(Math.abs(step-1)<EPS)break;
2603
+ }
2604
+ return h;
2605
+ }
2606
+ function incompleteBeta(a,b,x){
2607
+ if(!(a>0)||!(b>0)||!Number.isFinite(x))return Number.NaN;
2608
+ if(x<=0)return 0;
2609
+ if(x>=1)return 1;
2610
+ const front=Math.exp(
2611
+ logGamma(a+b)-logGamma(a)-logGamma(b)+a*Math.log(x)+b*Math.log(1-x),
2612
+ );
2613
+ return x<(a+1)/(a+b+2)
2614
+ ?(front*betaContinuedFraction(a,b,x))/a
2615
+ :1-(front*betaContinuedFraction(b,a,1-x))/b;
2579
2616
  }
2617
+ function erfc(x){
2618
+ const z=Math.abs(x);
2619
+ const t=2/(2+z);
2620
+ const ty=4*t-2;
2621
+ const cof=[-1.3026537197817094,6.4196979235649026e-1,1.9476473204185836e-2,
2622
+ -9.561514786808631e-3,-9.46595344482036e-4,3.66839497852761e-4,
2623
+ 4.2523324806907e-5,-2.0278578112534e-5,-1.624290004647e-6,
2624
+ 1.303655835580e-6,1.5626441722e-8,-8.5238095915e-8,6.529054439e-9,
2625
+ 5.059343495e-9,-9.91364156e-10,-2.27365122e-10,9.6467911e-11,
2626
+ 2.394038e-12,-6.886027e-12,8.94487e-13,3.13092e-13,-1.12708e-13,
2627
+ 3.81e-16,7.106e-15];
2628
+ let dd=0;
2629
+ let dv=0;
2630
+ let tmp;
2631
+ for(let j=cof.length-1;j>0;j--){
2632
+ tmp=dv;
2633
+ dv=ty*dv-dd+cof[j];
2634
+ dd=tmp;
2580
2635
  }
2581
- pool.finish();
2582
- return described.filter((d)=>d.text);
2636
+ const ans=t*Math.exp(-z*z+0.5*(cof[0]+ty*dv)-dd);
2637
+ return x>=0?ans:2-ans;
2583
2638
  }
2584
- });
2585
- __def("packages/core/src/internal/util.js",function(__exports,__req){
2586
- 'use strict';
2587
- Object.defineProperty(__exports,"VERSION",{enumerable:true,get:function(){return VERSION;}});
2588
- Object.defineProperty(__exports,"reportedWarnings",{enumerable:true,get:function(){return reportedWarnings;}});
2589
- Object.defineProperty(__exports,"warnOnce",{enumerable:true,get:function(){return warnOnce;}});
2590
- Object.defineProperty(__exports,"infoOnce",{enumerable:true,get:function(){return infoOnce;}});
2591
- Object.defineProperty(__exports,"resetWarnings",{enumerable:true,get:function(){return resetWarnings;}});
2592
- Object.defineProperty(__exports,"fail",{enumerable:true,get:function(){return fail;}});
2593
- Object.defineProperty(__exports,"invariant",{enumerable:true,get:function(){return invariant;}});
2594
- Object.defineProperty(__exports,"DEV",{enumerable:true,get:function(){return DEV;}});
2595
- Object.defineProperty(__exports,"isObject",{enumerable:true,get:function(){return isObject;}});
2596
- Object.defineProperty(__exports,"isFunction",{enumerable:true,get:function(){return isFunction;}});
2597
- Object.defineProperty(__exports,"isNil",{enumerable:true,get:function(){return isNil;}});
2598
- Object.defineProperty(__exports,"isBlank",{enumerable:true,get:function(){return isBlank;}});
2599
- Object.defineProperty(__exports,"isCtor",{enumerable:true,get:function(){return isCtor;}});
2600
- Object.defineProperty(__exports,"pathGetter",{enumerable:true,get:function(){return pathGetter;}});
2601
- Object.defineProperty(__exports,"pathSetter",{enumerable:true,get:function(){return pathSetter;}});
2602
- Object.defineProperty(__exports,"getPath",{enumerable:true,get:function(){return getPath;}});
2603
- Object.defineProperty(__exports,"setPath",{enumerable:true,get:function(){return setPath;}});
2604
- Object.defineProperty(__exports,"humanise",{enumerable:true,get:function(){return humanise;}});
2605
- Object.defineProperty(__exports,"escapeHtml",{enumerable:true,get:function(){return escapeHtml;}});
2606
- Object.defineProperty(__exports,"titleCase",{enumerable:true,get:function(){return titleCase;}});
2607
- Object.defineProperty(__exports,"expand",{enumerable:true,get:function(){return expand;}});
2608
- Object.defineProperty(__exports,"toArray",{enumerable:true,get:function(){return toArray;}});
2609
- Object.defineProperty(__exports,"merge",{enumerable:true,get:function(){return merge;}});
2610
- Object.defineProperty(__exports,"mergeRow",{enumerable:true,get:function(){return mergeRow;}});
2611
- Object.defineProperty(__exports,"Lru",{enumerable:true,get:function(){return Lru;}});
2612
- Object.defineProperty(__exports,"collator",{enumerable:true,get:function(){return collator;}});
2613
- Object.defineProperty(__exports,"defaultCompare",{enumerable:true,get:function(){return defaultCompare;}});
2614
- Object.defineProperty(__exports,"now",{enumerable:true,get:function(){return now;}});
2615
- Object.defineProperty(__exports,"nextFrame",{enumerable:true,get:function(){return nextFrame;}});
2616
- Object.defineProperty(__exports,"cancelFrame",{enumerable:true,get:function(){return cancelFrame;}});
2617
- Object.defineProperty(__exports,"frameBatched",{enumerable:true,get:function(){return frameBatched;}});
2618
- Object.defineProperty(__exports,"settleDebounce",{enumerable:true,get:function(){return settleDebounce;}});
2619
- Object.defineProperty(__exports,"whenIdle",{enumerable:true,get:function(){return whenIdle;}});
2620
- Object.defineProperty(__exports,"uid",{enumerable:true,get:function(){return uid;}});
2621
- const STAMPED_VERSION="1.67.0";
2622
- async function resolveVersion(){
2623
- if(STAMPED_VERSION!=='0.0.0-source')return STAMPED_VERSION;
2624
- return STAMPED_VERSION;
2639
+ function normalQuantile(p){
2640
+ if(!(p>0)||!(p<1))return p===0?-Infinity:(p===1?Infinity:Number.NaN);
2641
+ const a=[-3.969683028665376e+1,2.209460984245205e+2,-2.759285104469687e+2,
2642
+ 1.383577518672690e+2,-3.066479806614716e+1,2.506628277459239];
2643
+ const b=[-5.447609879822406e+1,1.615858368580409e+2,-1.556989798598866e+2,
2644
+ 6.680131188771972e+1,-1.328068155288572e+1];
2645
+ const c=[-7.784894002430293e-3,-3.223964580411365e-1,-2.400758277161838,
2646
+ -2.549732539343734,4.374664141464968,2.938163982698783];
2647
+ const d=[7.784695709041462e-3,3.224671290700398e-1,2.445134137142996,
2648
+ 3.754408661907416];
2649
+ const low=0.02425;
2650
+ let q;
2651
+ let r;
2652
+ let x;
2653
+ if(p<low){
2654
+ q=Math.sqrt(-2*Math.log(p));
2655
+ x=(((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5])
2656
+ / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
2657
+ }else if(p<=1-low){
2658
+ q=p-0.5;
2659
+ r=q*q;
2660
+ x=((((((a[0]*r+a[1])*r+a[2])*r+a[3])*r+a[4])*r+a[5])*q)
2661
+ / (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1);
2662
+ }else{
2663
+ q=Math.sqrt(-2*Math.log(1-p));
2664
+ x=-(((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5])
2665
+ / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
2625
2666
  }
2626
- const VERSION="1.67.0";
2627
- const warned=new Set();
2628
- const WARNED_LIMIT=2000;
2629
- function rememberWarned(key){
2630
- warned.add(key);
2631
- if(warned.size>WARNED_LIMIT){
2632
- const oldest=warned.values().next().value;
2633
- if(oldest!==undefined)warned.delete(oldest);
2667
+ const e=0.5*erfc(-x/Math.SQRT2)-p;
2668
+ const u=e*Math.sqrt(2*Math.PI)*Math.exp((x*x)/2);
2669
+ return x-u/(1+(x*u)/2);
2634
2670
  }
2671
+ function studentT(t,df){
2672
+ if(!(df>0)||!Number.isFinite(t))return Number.NaN;
2673
+ const tail=0.5*incompleteBeta(df/2,0.5,df/(df+t*t));
2674
+ return t>0?1-tail:tail;
2635
2675
  }
2636
- const reported=[];
2637
- const REPORT_LIMIT=500;
2638
- function record(key,level,message){
2639
- reported.push({
2640
- key,
2641
- level,
2642
- message:message.map((m)=>(typeof m==='string'?m:safeString(m))).join(' '),
2643
- at:Date.now(),
2644
- });
2645
- if(reported.length>REPORT_LIMIT)reported.shift();
2676
+ function studentTQuantile(p,df){
2677
+ if(!(p>0)||!(p<1)||!(df>0))return Number.NaN;
2678
+ if(df>1e7)return normalQuantile(p);
2679
+ let lo=-1e4;
2680
+ let hi=1e4;
2681
+ let x=normalQuantile(p);
2682
+ const logBeta=logGamma(df/2)+logGamma(0.5)-logGamma((df+1)/2);
2683
+ for(let i=0;i<60;i++){
2684
+ const cdf=studentT(x,df);
2685
+ if(cdf<p)lo=x;else hi=x;
2686
+ const pdf=Math.exp(-((df+1)/2)*Math.log(1+(x*x)/df)-logBeta)
2687
+ / Math.sqrt(df);
2688
+ const step=pdf>0?(cdf-p)/pdf:0;
2689
+ if(Math.abs(step)<1e-12)break;
2690
+ const next=x-step;
2691
+ x=next>lo&&next<hi&&Number.isFinite(next)?next:(lo+hi)/2;
2692
+ if(hi-lo<1e-12)break;
2646
2693
  }
2647
- function safeString(value){
2648
- if(value instanceof Error)return value.message;
2649
- try{return JSON.stringify(value);}catch{return String(value);}
2694
+ return x;
2650
2695
  }
2651
- function reportedWarnings(){return reported.map((r)=>({...r}));}
2652
- function warnOnce(key,...message){
2653
- if(warned.has(key))return;
2654
- rememberWarned(key);
2655
- record(key,'warn',message);
2656
- console.warn('[lattice]',...message);
2696
+ });
2697
+ __def("packages/modules/charts/trendline.js",function(__exports,__req){
2698
+ 'use strict';
2699
+ Object.defineProperty(__exports,"normaliseTrends",{enumerable:true,get:function(){return normaliseTrends;}});
2700
+ Object.defineProperty(__exports,"linearTrend",{enumerable:true,get:function(){return linearTrend;}});
2701
+ Object.defineProperty(__exports,"movingAverageTrend",{enumerable:true,get:function(){return movingAverageTrend;}});
2702
+ Object.defineProperty(__exports,"exponentialTrend",{enumerable:true,get:function(){return exponentialTrend;}});
2703
+ Object.defineProperty(__exports,"computeTrend",{enumerable:true,get:function(){return computeTrend;}});
2704
+ Object.defineProperty(__exports,"trendText",{enumerable:true,get:function(){return trendText;}});
2705
+ Object.defineProperty(__exports,"drawTrendlines",{enumerable:true,get:function(){return drawTrendlines;}});
2706
+ const __m0=__req("packages/modules/charts/frame.js");
2707
+ const Pool=__m0["Pool"];
2708
+ const __m1=__req("packages/modules/charts/svg.js");
2709
+ const attrs=__m1["attrs"];
2710
+ const path=__m1["path"];
2711
+ const ribbonPath=__m1["ribbonPath"];
2712
+ const round=__m1["round"];
2713
+ const setText=__m1["setText"];
2714
+ const SVG_NS=__m1["SVG_NS"];
2715
+ const __m2=__req("packages/modules/charts/typography.js");
2716
+ const applySize=__m2["applySize"];
2717
+ const __m3=__req("packages/modules/charts/styles.js");
2718
+ const NS=__m3["NS"];
2719
+ const __m4=__req("packages/modules/charts/scale.js");
2720
+ const toNumber=__m4["toNumber"];
2721
+ const toTime=__m4["toTime"];
2722
+ const __m5=__req("packages/modules/charts/quantile.js");
2723
+ const studentTQuantile=__m5["studentTQuantile"];
2724
+ const METHODS={
2725
+ linear:new Set(['linear','lr','ols','regression','least-squares']),
2726
+ movingAverage:new Set(['movingaverage','moving-average','ma','sma','rolling']),
2727
+ exponential:new Set(['exponential','exp','ewma','ses','holt','smoothing']),
2728
+ };
2729
+ const SMOOTH_LO=1e-4;
2730
+ const SMOOTH_HI=1-1e-4;
2731
+ function methodOf(name){
2732
+ const key=String(name||'linear').toLowerCase();
2733
+ if(METHODS.linear.has(key))return'linear';
2734
+ if(METHODS.movingAverage.has(key))return'movingAverage';
2735
+ if(METHODS.exponential.has(key))return'exponential';
2736
+ return null;
2657
2737
  }
2658
- function infoOnce(key,...message){
2659
- if(warned.has(key))return;
2660
- rememberWarned(key);
2661
- record(key,'info',message);
2662
- console.info('[lattice]',...message);
2738
+ function normaliseTrends(trend){
2739
+ if(!trend)return[];
2740
+ const raw=Array.isArray(trend)?trend:[trend];
2741
+ const out=[];
2742
+ for(const entry of raw){
2743
+ if(entry===true){out.push({method:'linear'});continue;}
2744
+ if(typeof entry==='string'){
2745
+ const method=methodOf(entry);
2746
+ if(method)out.push({method});
2747
+ continue;
2663
2748
  }
2664
- function resetWarnings(){
2665
- warned.clear();
2666
- reported.length=0;
2749
+ if(entry&&typeof entry==='object'){
2750
+ const method=methodOf(entry.method);
2751
+ if(method)out.push({...entry,method});
2667
2752
  }
2668
- function fail(message,extra){
2669
- const err=new Error(`[lattice] ${message}`);
2670
- if(extra!==undefined)err.cause=extra;
2671
- throw err;
2672
2753
  }
2673
- function invariant(condition,message){
2674
- if(!condition)fail(message);
2754
+ return out;
2675
2755
  }
2676
- const DEV=(()=>{
2677
- try{
2678
- return!(typeof process!=='undefined'&&process.env
2679
- &&process.env.NODE_ENV==='production');
2680
- }catch{
2681
- return true;
2756
+ function placeX(scale,rawX){
2757
+ if(scale.kind==='band')return scale.centre(rawX);
2758
+ const n=scale.kind==='time'?toTime(rawX):toNumber(rawX);
2759
+ return n===null?NaN:scale.of(n);
2682
2760
  }
2683
- })();
2684
- function isObject(v){
2685
- return v!==null&&typeof v==='object'&&!Array.isArray(v);
2761
+ function sequenceOf(series){
2762
+ const seq=[];
2763
+ for(const point of series.points){
2764
+ const y=Number(point.y);
2765
+ seq.push({value:Number.isFinite(y)?y:null,rawX:point.x});
2686
2766
  }
2687
- function isFunction(v){
2688
- return typeof v==='function';
2767
+ return seq;
2689
2768
  }
2690
- function isNil(v){
2691
- return v===null||v===undefined;
2769
+ function numericPairs(series,xScale){
2770
+ const pairs=[];
2771
+ for(const point of series.points){
2772
+ const x=xScale.kind==='time'?toTime(point.x):toNumber(point.x);
2773
+ const y=Number(point.y);
2774
+ if(x===null||!Number.isFinite(x)||!Number.isFinite(y))continue;
2775
+ pairs.push({x,y});
2692
2776
  }
2693
- function isBlank(v){
2694
- return v===null||v===undefined||v==='';
2777
+ pairs.sort((a,b)=>a.x-b.x);
2778
+ return pairs;
2695
2779
  }
2696
- function isCtor(v){
2697
- if(typeof v!=='function')return false;
2698
- if(/^class[\s{]/.test(Function.prototype.toString.call(v)))return true;
2699
- return!!(v.prototype&&Object.getOwnPropertyNames(v.prototype).length>1);
2780
+ function linearTrend(pairs,forecast,opts={}){
2781
+ const n=pairs.length;
2782
+ if(n<2)return null;
2783
+ let mx=0;
2784
+ let my=0;
2785
+ for(const p of pairs){mx+=p.x;my+=p.y;}
2786
+ mx/=n;
2787
+ my/=n;
2788
+ let sxx=0;
2789
+ let sxy=0;
2790
+ let syy=0;
2791
+ for(const p of pairs){
2792
+ const dx=p.x-mx;
2793
+ const dy=p.y-my;
2794
+ sxx+=dx*dx;
2795
+ sxy+=dx*dy;
2796
+ syy+=dy*dy;
2700
2797
  }
2701
- const pathCache=new Map();
2702
- function pathGetter(path){
2703
- let fn=pathCache.get(path);
2704
- if(fn)return fn;
2705
- if(!path.includes('.')){
2706
- fn=(o)=>(o==null?undefined:o[path]);
2707
- }else{
2708
- const parts=path.split('.');
2709
- const n=parts.length;
2710
- fn=(o)=>{
2711
- let cur=o;
2712
- for(let i=0;i<n;i++){
2713
- if(cur==null)return undefined;
2714
- cur=cur[parts[i]];
2798
+ if(sxx===0)return null;
2799
+ const slope=sxy/sxx;
2800
+ const intercept=my-slope*mx;
2801
+ const r2=syy===0?1:Math.max(0,Math.min(1,(sxy*sxy)/(sxx*syy)));
2802
+ const lo=pairs[0].x;
2803
+ const hi=pairs[n-1].x;
2804
+ const at=(x)=>({x,y:intercept+slope*x});
2805
+ const fit=[at(lo),at(hi)];
2806
+ const projected=[];
2807
+ let band=null;
2808
+ const steps=Math.max(0,Math.floor(Number(forecast)||0));
2809
+ if(steps>0&&hi>lo){
2810
+ const step=(hi-lo)/(n-1);
2811
+ if(step>0){
2812
+ projected.push(at(hi),at(hi+steps*step));
2813
+ if(n>2){
2814
+ const conf=Number.isFinite(opts.confidence)
2815
+ &&opts.confidence>0&&opts.confidence<1?Number(opts.confidence):0.95;
2816
+ const tail=1-(1-conf)/2;
2817
+ const sse=Math.max(0,syy-slope*sxy);
2818
+ const s=Math.sqrt(sse/(n-2));
2819
+ const t=studentTQuantile(tail,n-2);
2820
+ const points=[];
2821
+ for(let h=0;h<=steps;h++){
2822
+ const x0=hi+h*step;
2823
+ const mean=intercept+slope*x0;
2824
+ const leverage=1/n+((x0-mx)**2)/sxx;
2825
+ const sePred=s*Math.sqrt(1+leverage);
2826
+ const seMean=s*Math.sqrt(leverage);
2827
+ points.push({
2828
+ x:x0,
2829
+ mean,
2830
+ lower:mean-t*sePred,
2831
+ upper:mean+t*sePred,
2832
+ lowerMean:mean-t*seMean,
2833
+ upperMean:mean+t*seMean,
2834
+ });
2715
2835
  }
2716
- return cur;
2717
- };
2836
+ band={confidence:conf,points};
2718
2837
  }
2719
- pathCache.set(path,fn);
2720
- return fn;
2721
2838
  }
2722
- const setterCache=new Map();
2723
- function pathSetter(path){
2724
- let fn=setterCache.get(path);
2725
- if(fn)return fn;
2726
- if(!path.includes('.')){
2727
- fn=(o,v)=>{if(o!=null)o[path]=v;};
2728
- }else{
2729
- const parts=path.split('.');
2730
- const last=parts.length-1;
2731
- fn=(o,v)=>{
2732
- let cur=o;
2733
- for(let i=0;i<last;i++){
2734
- if(cur==null)return;
2735
- const k=parts[i];
2736
- if(cur[k]==null)cur[k]={};
2737
- cur=cur[k];
2738
2839
  }
2739
- if(cur!=null)cur[parts[last]]=v;
2840
+ return{
2841
+ fit,
2842
+ forecast:projected,
2843
+ band,
2844
+ stats:{
2845
+ method:'linear',slope,intercept,r2,n,forecast:projected.length?steps:0,
2846
+ confidence:band?band.confidence:null,
2847
+ },
2740
2848
  };
2741
2849
  }
2742
- setterCache.set(path,fn);
2743
- return fn;
2744
- }
2745
- function getPath(obj,path){
2746
- return pathGetter(path)(obj);
2850
+ function movingAverageTrend(seq,window){
2851
+ const span=Math.max(1,Math.floor(Number(window)||3));
2852
+ const ring=[];
2853
+ let sum=0;
2854
+ const fit=[];
2855
+ for(const row of seq){
2856
+ if(Number.isFinite(row.value)){
2857
+ ring.push(row.value);
2858
+ sum+=row.value;
2859
+ if(ring.length>span)sum-=ring.shift();
2747
2860
  }
2748
- function setPath(obj,path,value){
2749
- pathSetter(path)(obj,value);
2861
+ if(!ring.length)continue;
2862
+ fit.push({x:row.rawX,y:sum/ring.length});
2750
2863
  }
2751
- function humanise(field){
2752
- if(!field)return'';
2753
- const leaf=field.includes('.')?field.slice(field.lastIndexOf('.')+1):field;
2754
- return leaf
2755
- .replace(/[_-]+/g,' ')
2756
- .replace(/([a-z0-9])([A-Z])/g,'$1 $2')
2757
- .replace(/([A-Z]+)([A-Z][a-z])/g,'$1 $2')
2758
- .replace(/\s+/g,' ')
2759
- .trim()
2760
- .replace(/^./,(c)=>c.toUpperCase());
2864
+ if(fit.length<2)return null;
2865
+ return{fit,stats:{method:'movingAverage',window:span}};
2761
2866
  }
2762
- const ESCAPES={'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'};
2763
- function escapeHtml(s){
2764
- const str=s==null?'':String(s);
2765
- return/[&<>"']/.test(str)?str.replace(/[&<>"']/g,(c)=>ESCAPES[c]):str;
2867
+ function runSes(y,alpha){
2868
+ const n=y.length;
2869
+ const smoothed=new Array(n).fill(null);
2870
+ let level=null;
2871
+ let sse=0;
2872
+ for(let i=0;i<n;i++){
2873
+ const v=y[i];
2874
+ if(!Number.isFinite(v)){
2875
+ if(level!==null)smoothed[i]=level;
2876
+ continue;
2766
2877
  }
2767
- function titleCase(s){
2768
- return String(s).replace(/\w\S*/g,(t)=>t[0].toUpperCase()+t.slice(1).toLowerCase());
2878
+ if(level===null){level=v;smoothed[i]=v;continue;}
2879
+ sse+=(v-level)**2;
2880
+ level=alpha*v+(1-alpha)*level;
2881
+ smoothed[i]=level;
2769
2882
  }
2770
- function expand(value,key,whenTrue){
2771
- if(value===undefined)return undefined;
2772
- if(value===true)return{enabled:true,...whenTrue};
2773
- if(value===false)return{enabled:false};
2774
- if(isObject(value))return value;
2775
- return{[key]:value,enabled:true};
2883
+ return{smoothed,sse};
2776
2884
  }
2777
- function toArray(v){
2778
- if(v===undefined||v===null)return[];
2779
- return Array.isArray(v)?v:[v];
2885
+ function runHolt(y,alpha,beta){
2886
+ const n=y.length;
2887
+ const smoothed=new Array(n).fill(null);
2888
+ let level=null;
2889
+ let trend=null;
2890
+ let sse=0;
2891
+ for(let i=0;i<n;i++){
2892
+ const v=y[i];
2893
+ if(!Number.isFinite(v)){
2894
+ if(level!==null){level+=trend;smoothed[i]=level;}
2895
+ continue;
2780
2896
  }
2781
- const MERGE_FORBIDDEN_KEYS=Object.freeze(new Set(['__proto__','constructor','prototype']));
2782
- function merge(a,b){
2783
- if(!isObject(a))return isObject(b)?{...b}:b;
2784
- if(!isObject(b))return b===undefined?a:b;
2785
- const out={...a};
2786
- for(const k of Object.keys(b)){
2787
- if(MERGE_FORBIDDEN_KEYS.has(k))continue;
2788
- const bv=b[k];
2789
- if(bv===undefined)continue;
2790
- out[k]=isObject(bv)&&isObject(out[k])?merge(out[k],bv):bv;
2897
+ if(level===null){level=v;smoothed[i]=v;continue;}
2898
+ if(trend===null){
2899
+ trend=v-level;
2900
+ const forecast=level+trend;
2901
+ sse+=(v-forecast)**2;
2902
+ const prev=level;
2903
+ level=alpha*v+(1-alpha)*(prev+trend);
2904
+ trend=beta*(level-prev)+(1-beta)*trend;
2905
+ smoothed[i]=level;
2906
+ continue;
2791
2907
  }
2792
- return out;
2908
+ const forecast=level+trend;
2909
+ sse+=(v-forecast)**2;
2910
+ const prev=level;
2911
+ level=alpha*v+(1-alpha)*(prev+trend);
2912
+ trend=beta*(level-prev)+(1-beta)*trend;
2913
+ smoothed[i]=level;
2793
2914
  }
2794
- function mergeRow(previous,patch){
2795
- if(!isObject(previous)||!isObject(patch)||previous===patch)return patch;
2796
- const out=Object.create(Object.getPrototypeOf(previous));
2797
- Object.assign(out,previous,patch);
2798
- return out;
2915
+ return{smoothed,sse};
2799
2916
  }
2800
- class Lru{
2801
- #max;
2802
- #map=new Map();
2803
- #onEvict;
2804
- constructor(max=256,onEvict=null){
2805
- this.#max=max;
2806
- this.#onEvict=onEvict;
2917
+ function goldenMin(f,lo,hi,iters=80){
2918
+ const invphi=(Math.sqrt(5)-1)/2;
2919
+ let a=lo;
2920
+ let b=hi;
2921
+ let c=b-invphi*(b-a);
2922
+ let d=a+invphi*(b-a);
2923
+ let fc=f(c);
2924
+ let fd=f(d);
2925
+ for(let i=0;i<iters;i++){
2926
+ if(fc<fd){b=d;d=c;fd=fc;c=b-invphi*(b-a);fc=f(c);}
2927
+ else{a=c;c=d;fc=fd;d=a+invphi*(b-a);fd=f(d);}
2807
2928
  }
2808
- get size(){
2809
- return this.#map.size;
2929
+ return(a+b)/2;
2810
2930
  }
2811
- get max(){
2812
- return this.#max;
2931
+ function exponentialTrend(seq,spec){
2932
+ const holt=String(spec.kind||spec.model||'').toLowerCase()==='holt';
2933
+ const y=seq.map((row)=>(Number.isFinite(row.value)?row.value:null));
2934
+ const finite=y.filter((v)=>v!==null).length;
2935
+ if(finite<(holt?2:1))return null;
2936
+ const clamp=(x)=>Math.min(SMOOTH_HI,Math.max(SMOOTH_LO,x));
2937
+ let alpha=Number.isFinite(spec.alpha)?clamp(Number(spec.alpha)):null;
2938
+ let beta=holt&&Number.isFinite(spec.beta)?clamp(Number(spec.beta)):null;
2939
+ if(holt){
2940
+ if(alpha===null||beta===null){
2941
+ let a=alpha===null?0.5:alpha;
2942
+ let b=beta===null?0.5:beta;
2943
+ for(let r=0;r<6;r++){
2944
+ if(alpha===null)a=goldenMin((x)=>runHolt(y,x,b).sse,SMOOTH_LO,SMOOTH_HI);
2945
+ if(beta===null)b=goldenMin((x)=>runHolt(y,a,x).sse,SMOOTH_LO,SMOOTH_HI);
2813
2946
  }
2814
- set max(v){
2815
- this.#max=v;
2816
- this.#trim();
2947
+ alpha=alpha===null?a:alpha;
2948
+ beta=beta===null?b:beta;
2817
2949
  }
2818
- has(k){
2819
- return this.#map.has(k);
2950
+ }else if(alpha===null){
2951
+ alpha=goldenMin((x)=>runSes(y,x).sse,SMOOTH_LO,SMOOTH_HI);
2820
2952
  }
2821
- get(k){
2822
- const m=this.#map;
2823
- if(!m.has(k))return undefined;
2824
- const v=m.get(k);
2825
- m.delete(k);
2826
- m.set(k,v);
2827
- return v;
2953
+ const run=holt?runHolt(y,alpha,beta):runSes(y,alpha);
2954
+ const fit=[];
2955
+ for(let i=0;i<seq.length;i++){
2956
+ const level=run.smoothed[i];
2957
+ if(level===null||!Number.isFinite(level))continue;
2958
+ fit.push({x:seq[i].rawX,y:level});
2828
2959
  }
2829
- peek(k){
2830
- return this.#map.get(k);
2960
+ if(fit.length<2)return null;
2961
+ return{
2962
+ fit,stats:{method:'exponential',kind:holt?'holt':'ses',alpha,beta:holt?beta:null},
2963
+ };
2831
2964
  }
2832
- set(k,v){
2833
- const m=this.#map;
2834
- if(m.has(k))m.delete(k);
2835
- m.set(k,v);
2836
- this.#trim();
2837
- return v;
2965
+ function computeTrend(series,spec,xScale){
2966
+ if(spec.method==='linear'){
2967
+ const pairs=numericPairs(series,xScale);
2968
+ return linearTrend(pairs,spec.forecast,{confidence:spec.confidence});
2838
2969
  }
2839
- delete(k){
2840
- const v=this.#map.get(k);
2841
- if(this.#map.delete(k)&&this.#onEvict)this.#onEvict(v,k);
2842
- return v;
2970
+ const seq=sequenceOf(series);
2971
+ if(spec.method==='movingAverage'){
2972
+ return movingAverageTrend(seq,spec.window||spec.period);
2843
2973
  }
2844
- clear(){
2845
- if(this.#onEvict)for(const[k,v]of this.#map)this.#onEvict(v,k);
2846
- this.#map.clear();
2974
+ return exponentialTrend(seq,spec);
2847
2975
  }
2848
- keys(){
2849
- return this.#map.keys();
2976
+ function trendText(stats,label,fmt,forecast,band,bandKind){
2977
+ const who=label?` for ${label}`:'';
2978
+ if(stats.method==='linear'){
2979
+ const r2=Number.isFinite(stats.r2)?`, R² ${stats.r2.toFixed(2)}`:'';
2980
+ let text=`Trend (linear)${who}: slope ${fmt(stats.slope)}${r2}`;
2981
+ if(stats.forecast>0&&forecast&&forecast.length){
2982
+ const end=forecast[forecast.length-1];
2983
+ text+=`; forecast ${stats.forecast} step${stats.forecast===1?'':'s'} ahead to ${fmt(end.y)}`;
2984
+ if(band&&band.points.length){
2985
+ const last=band.points[band.points.length-1];
2986
+ const mean=bandKind==='confidence'||bandKind==='mean';
2987
+ const lo=mean?last.lowerMean:last.lower;
2988
+ const up=mean?last.upperMean:last.upper;
2989
+ const kind=mean?'confidence':'prediction';
2990
+ const pct=Math.round(band.confidence*100);
2991
+ text+=`; ${pct}% ${kind} band ${fmt(lo)} to ${fmt(up)} at the horizon`;
2850
2992
  }
2851
- values(){
2852
- return this.#map.values();
2853
2993
  }
2854
- #trim(){
2855
- const m=this.#map;
2856
- while(m.size>this.#max){
2857
- const oldest=m.keys().next().value;
2858
- const v=m.get(oldest);
2859
- m.delete(oldest);
2860
- if(this.#onEvict)this.#onEvict(v,oldest);
2994
+ return text;
2861
2995
  }
2996
+ if(stats.method==='movingAverage'){
2997
+ return`Trend (moving average, window ${stats.window})${who}`;
2862
2998
  }
2999
+ const kind=stats.kind==='holt'?'Holt':'single';
3000
+ const a=Number.isFinite(stats.alpha)?`, α ${stats.alpha.toFixed(2)}`:'';
3001
+ const b=Number.isFinite(stats.beta)?`, β ${stats.beta.toFixed(2)}`:'';
3002
+ return`Trend (exponential, ${kind})${who}${a}${b}`;
2863
3003
  }
2864
- const collators=new Map();
2865
- function collator(locale,opts){
2866
- const key=`${locale||''}|${opts?JSON.stringify(opts):''}`;
2867
- let c=collators.get(key);
2868
- if(!c){
2869
- c=new Intl.Collator(locale||undefined,{
2870
- numeric:true,sensitivity:'variant',...opts,
2871
- });
2872
- collators.set(key,c);
3004
+ function drawTrendlines(opts){
3005
+ const specs=normaliseTrends(opts.trend);
3006
+ if(!specs.length)return[];
3007
+ const doc=opts.group.ownerDocument;
3008
+ let host=null;
3009
+ for(const child of opts.group.childNodes||[]){
3010
+ if(child&&String(child.getAttribute&&child.getAttribute('class'))===`${NS}__trends`){
3011
+ host=child;
3012
+ break;
2873
3013
  }
2874
- return c;
2875
3014
  }
2876
- function defaultCompare(a,b){
2877
- if(a===b)return 0;
2878
- if(a===null||a===undefined)return 1;
2879
- if(b===null||b===undefined)return-1;
2880
- if(typeof a==='number'&&typeof b==='number'){
2881
- if(Number.isNaN(a))return Number.isNaN(b)?0:1;
2882
- if(Number.isNaN(b))return-1;
2883
- return a<b?-1:a>b?1:0;
3015
+ if(!host){
3016
+ host=doc.createElementNS(SVG_NS,'g');
3017
+ host.setAttribute('class',`${NS}__trends`);
3018
+ opts.group.appendChild(host);
2884
3019
  }
2885
- const sa=String(a);
2886
- const sb=String(b);
2887
- return sa<sb?-1:sa>sb?1:0;
3020
+ if(opts.clip)host.setAttribute('clip-path',`url(#${opts.clip})`);
3021
+ else host.removeAttribute('clip-path');
3022
+ const pool=new Pool(host);
3023
+ const described=[];
3024
+ for(const spec of specs){
3025
+ for(const series of opts.series){
3026
+ const yScale=(series.axis==='right'&&opts.rightScale)?opts.rightScale:opts.yScale;
3027
+ const overlay=computeTrend(series,spec,opts.xScale);
3028
+ if(!overlay)continue;
3029
+ const colour=opts.scheme.series(series.index);
3030
+ const line=[];
3031
+ for(const point of overlay.fit){
3032
+ const px=placeX(opts.xScale,point.x);
3033
+ const py=yScale.of(point.y);
3034
+ if(!Number.isFinite(px)||!Number.isFinite(py))continue;
3035
+ line.push(line.length?'L':'M',round(px),round(py));
2888
3036
  }
2889
- function now(){
2890
- return typeof performance!=='undefined'&&performance.now
2891
- ?performance.now()
2892
- :Date.now();
3037
+ if(line.length<4)continue;
3038
+ attrs(pool.next('path',`${NS}__trend`),{
3039
+ d:path(line),
3040
+ fill:'none',
3041
+ stroke:colour,
3042
+ 'data-series':series.index,
3043
+ });
3044
+ const wantsBand=spec.band!==false&&overlay.band&&overlay.band.points.length>=2;
3045
+ if(wantsBand){
3046
+ const mean=spec.band==='confidence'||spec.band==='mean';
3047
+ const rows=[];
3048
+ for(const point of overlay.band.points){
3049
+ const px=placeX(opts.xScale,point.x);
3050
+ const lo=mean?point.lowerMean:point.lower;
3051
+ const up=mean?point.upperMean:point.upper;
3052
+ const pl=yScale.of(lo);
3053
+ const pu=yScale.of(up);
3054
+ if(!Number.isFinite(px)||!Number.isFinite(pl)||!Number.isFinite(pu))continue;
3055
+ rows.push({x:round(px),lower:round(pl),upper:round(pu)});
2893
3056
  }
2894
- const hasRaf=typeof requestAnimationFrame==='function';
2895
- function nextFrame(fn){
2896
- if(hasRaf)return requestAnimationFrame(fn);
2897
- return setTimeout(()=>fn(now()),16);
3057
+ const d=ribbonPath(rows);
3058
+ if(d){
3059
+ attrs(pool.next('path',`${NS}__trend-band`),{
3060
+ d,
3061
+ fill:colour,
3062
+ 'fill-opacity':0.15,
3063
+ stroke:'none',
3064
+ 'data-series':series.index,
3065
+ });
2898
3066
  }
2899
- function cancelFrame(handle){
2900
- if(handle==null)return;
2901
- if(hasRaf)cancelAnimationFrame(handle);
2902
- else clearTimeout(handle);
2903
3067
  }
2904
- function frameBatched(fn){
2905
- let handle=null;
2906
- let lastArgs=null;
2907
- const run=()=>{
2908
- handle=null;
2909
- const a=lastArgs;
2910
- lastArgs=null;
2911
- fn(...(a||[]));
2912
- };
2913
- const wrapped=(...args)=>{
2914
- lastArgs=args;
2915
- if(handle===null)handle=nextFrame(run);
2916
- };
2917
- wrapped.cancel=()=>{
2918
- cancelFrame(handle);
2919
- handle=null;
2920
- lastArgs=null;
2921
- };
2922
- wrapped.flush=()=>{
2923
- if(handle!==null){
2924
- cancelFrame(handle);
2925
- run();
3068
+ const forecast=overlay.forecast||[];
3069
+ if(forecast.length>=2){
3070
+ const seg=[];
3071
+ for(const point of forecast){
3072
+ const px=placeX(opts.xScale,point.x);
3073
+ const py=yScale.of(point.y);
3074
+ if(!Number.isFinite(px)||!Number.isFinite(py))continue;
3075
+ seg.push(seg.length?'L':'M',round(px),round(py));
2926
3076
  }
2927
- };
2928
- return wrapped;
3077
+ if(seg.length>=4){
3078
+ attrs(pool.next('path',`${NS}__trend-forecast`),{
3079
+ d:path(seg),
3080
+ fill:'none',
3081
+ stroke:colour,
3082
+ 'data-series':series.index,
3083
+ });
2929
3084
  }
2930
- function settleDebounce(fn,waitMs){
2931
- let timer=null;
2932
- let held=null;
2933
- const trailing=()=>{
2934
- timer=null;
2935
- if(held===null)return;
2936
- const args=held;
2937
- held=null;
2938
- fn(...args);
2939
- arm();
2940
- };
2941
- const arm=()=>{
2942
- timer=setTimeout(trailing,waitMs);
2943
- if(typeof timer?.unref==='function')timer.unref();
2944
- };
2945
- const wrapped=(...args)=>{
2946
- if(timer===null){
2947
- fn(...args);
2948
- arm();
2949
- }else{
2950
- held=args;
2951
- clearTimeout(timer);
2952
- arm();
2953
3085
  }
2954
- };
2955
- wrapped.flush=()=>{
2956
- if(timer!==null)clearTimeout(timer);
2957
- timer=null;
2958
- if(held===null)return;
2959
- const args=held;
2960
- held=null;
2961
- fn(...args);
2962
- };
2963
- wrapped.cancel=()=>{
2964
- if(timer!==null)clearTimeout(timer);
2965
- timer=null;
2966
- held=null;
2967
- };
2968
- wrapped.pending=()=>timer!==null||held!==null;
2969
- return wrapped;
3086
+ if(spec.label!==false&&overlay.stats.method==='linear'
3087
+ &&Number.isFinite(overlay.stats.r2)){
3088
+ const last=overlay.fit[overlay.fit.length-1];
3089
+ const text=pool.next('text',`${NS}__trend-label`);
3090
+ applySize(text,opts.size);
3091
+ attrs(text,{
3092
+ x:round(placeX(opts.xScale,last.x)),
3093
+ y:round(yScale.of(last.y))-4,
3094
+ 'text-anchor':'end',
3095
+ fill:colour,
3096
+ });
3097
+ setText(text,`R² ${overlay.stats.r2.toFixed(2)}`);
2970
3098
  }
2971
- function whenIdle(fn,timeout=50){
2972
- if(typeof requestIdleCallback==='function'){
2973
- return requestIdleCallback(fn,{timeout});
3099
+ described.push({
3100
+ text:trendText(overlay.stats,series.label,opts.format,forecast,
3101
+ spec.band===false?null:overlay.band,spec.band),
3102
+ });
2974
3103
  }
2975
- return setTimeout(()=>fn({timeRemaining:()=>0,didTimeout:true}),1);
2976
3104
  }
2977
- let idSeq=0;
2978
- function uid(prefix='l'){
2979
- return`${prefix}${(++idSeq).toString(36)}`;
3105
+ pool.finish();
3106
+ return described.filter((d)=>d.text);
2980
3107
  }
2981
3108
  });
2982
3109
  __def("packages/modules/charts/cartesian.js",function(__exports,__req){
@@ -4476,7 +4603,7 @@ const measure=typeof entry==='string'?{col:entry}:entry;
4476
4603
  return{
4477
4604
  spec:measure,
4478
4605
  result:bindSeries(grid,{
4479
- x:spec.x,from:spec.from,limit:spec.limit,
4606
+ x:spec.x,from:spec.from,limit:spec.limit,axis:spec.axis,
4480
4607
  y:{col:measure.col,fn:measure.fn},
4481
4608
  }),
4482
4609
  };
@@ -4495,7 +4622,7 @@ if(at!==undefined)aligned[at]=point;
4495
4622
  }
4496
4623
  return{
4497
4624
  key:measure.col,
4498
- label:measure.label||(column&&column.title)||measure.col,
4625
+ label:measure.title||measure.label||(column&&column.title)||measure.col,
4499
4626
  index:i,
4500
4627
  mark:measure.type||null,
4501
4628
  axis:measure.axis==='right'?'right':'left',