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