minerva-design 1.13.2 → 1.14.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 (202) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/custom-elements.json +194 -12
  3. package/dist/angular/fesm2022/minerva-angular.mjs +20 -0
  4. package/dist/angular/fesm2022/minerva-angular.mjs.map +1 -1
  5. package/dist/angular/types/minerva-angular.d.ts +10 -1
  6. package/dist/charts/canvas.d.cts +1 -0
  7. package/dist/charts/canvas.d.ts +1 -0
  8. package/dist/charts/engine/canvas.cjs +1 -1
  9. package/dist/charts/engine/canvas.cjs.map +1 -1
  10. package/dist/charts/engine/canvas.js +152 -128
  11. package/dist/charts/engine/canvas.js.map +1 -1
  12. package/dist/charts/engine/chart.cjs +1 -1
  13. package/dist/charts/engine/chart.cjs.map +1 -1
  14. package/dist/charts/engine/chart.js +475 -405
  15. package/dist/charts/engine/chart.js.map +1 -1
  16. package/dist/charts/engine/controller.cjs +2 -2
  17. package/dist/charts/engine/controller.cjs.map +1 -1
  18. package/dist/charts/engine/controller.js +263 -248
  19. package/dist/charts/engine/controller.js.map +1 -1
  20. package/dist/charts/engine/dom.cjs +1 -1
  21. package/dist/charts/engine/dom.cjs.map +1 -1
  22. package/dist/charts/engine/dom.d.cts +34 -0
  23. package/dist/charts/engine/dom.d.ts +34 -0
  24. package/dist/charts/engine/dom.js +62 -51
  25. package/dist/charts/engine/dom.js.map +1 -1
  26. package/dist/charts/engine/graph/chart.cjs +1 -1
  27. package/dist/charts/engine/graph/chart.cjs.map +1 -1
  28. package/dist/charts/engine/graph/chart.js +835 -821
  29. package/dist/charts/engine/graph/chart.js.map +1 -1
  30. package/dist/charts/engine/graph/dom.cjs +1 -1
  31. package/dist/charts/engine/graph/dom.cjs.map +1 -1
  32. package/dist/charts/engine/graph/dom.js +44 -51
  33. package/dist/charts/engine/graph/dom.js.map +1 -1
  34. package/dist/charts/engine/hover-rect.cjs +2 -0
  35. package/dist/charts/engine/hover-rect.cjs.map +1 -0
  36. package/dist/charts/engine/hover-rect.d.cts +10 -0
  37. package/dist/charts/engine/hover-rect.d.ts +10 -0
  38. package/dist/charts/engine/hover-rect.js +47 -0
  39. package/dist/charts/engine/hover-rect.js.map +1 -0
  40. package/dist/charts/engine/items.cjs +1 -1
  41. package/dist/charts/engine/items.cjs.map +1 -1
  42. package/dist/charts/engine/items.js +8 -8
  43. package/dist/charts/engine/model.cjs +1 -1
  44. package/dist/charts/engine/model.cjs.map +1 -1
  45. package/dist/charts/engine/model.js +191 -191
  46. package/dist/charts/engine/model.js.map +1 -1
  47. package/dist/charts/engine/orderbook/depth.cjs +1 -1
  48. package/dist/charts/engine/orderbook/depth.cjs.map +1 -1
  49. package/dist/charts/engine/orderbook/depth.js +63 -63
  50. package/dist/charts/engine/orderbook/heatmap.cjs +1 -1
  51. package/dist/charts/engine/orderbook/heatmap.cjs.map +1 -1
  52. package/dist/charts/engine/orderbook/heatmap.js +155 -155
  53. package/dist/charts/engine/orderbook/host.cjs +1 -1
  54. package/dist/charts/engine/orderbook/host.cjs.map +1 -1
  55. package/dist/charts/engine/orderbook/host.js +87 -75
  56. package/dist/charts/engine/orderbook/host.js.map +1 -1
  57. package/dist/charts/engine/orderbook/ladder.cjs +1 -1
  58. package/dist/charts/engine/orderbook/ladder.cjs.map +1 -1
  59. package/dist/charts/engine/orderbook/ladder.js +41 -41
  60. package/dist/charts/engine/orderbook/tape.cjs +1 -1
  61. package/dist/charts/engine/orderbook/tape.cjs.map +1 -1
  62. package/dist/charts/engine/orderbook/tape.js +31 -31
  63. package/dist/charts/engine/presets.cjs +2 -2
  64. package/dist/charts/engine/presets.cjs.map +1 -1
  65. package/dist/charts/engine/presets.js +15 -15
  66. package/dist/charts/engine/renderers/vector-gpu.cjs +24 -0
  67. package/dist/charts/engine/renderers/vector-gpu.cjs.map +1 -0
  68. package/dist/charts/engine/renderers/vector-gpu.d.cts +22 -0
  69. package/dist/charts/engine/renderers/vector-gpu.d.ts +22 -0
  70. package/dist/charts/engine/renderers/vector-gpu.js +301 -0
  71. package/dist/charts/engine/renderers/vector-gpu.js.map +1 -0
  72. package/dist/charts/engine/renderers/vector.cjs +2 -0
  73. package/dist/charts/engine/renderers/vector.cjs.map +1 -0
  74. package/dist/charts/engine/renderers/vector.d.cts +140 -0
  75. package/dist/charts/engine/renderers/vector.d.ts +140 -0
  76. package/dist/charts/engine/renderers/vector.js +568 -0
  77. package/dist/charts/engine/renderers/vector.js.map +1 -0
  78. package/dist/charts/engine/scene.cjs +1 -1
  79. package/dist/charts/engine/scene.cjs.map +1 -1
  80. package/dist/charts/engine/scene.d.cts +43 -3
  81. package/dist/charts/engine/scene.d.ts +43 -3
  82. package/dist/charts/engine/scene.js +411 -338
  83. package/dist/charts/engine/scene.js.map +1 -1
  84. package/dist/charts/engine/shapes/bullet.cjs +1 -1
  85. package/dist/charts/engine/shapes/bullet.cjs.map +1 -1
  86. package/dist/charts/engine/shapes/bullet.js +36 -36
  87. package/dist/charts/engine/shapes/calendar.cjs +1 -1
  88. package/dist/charts/engine/shapes/calendar.cjs.map +1 -1
  89. package/dist/charts/engine/shapes/calendar.js +53 -53
  90. package/dist/charts/engine/shapes/chart.cjs +1 -1
  91. package/dist/charts/engine/shapes/chart.cjs.map +1 -1
  92. package/dist/charts/engine/shapes/chart.d.cts +3 -2
  93. package/dist/charts/engine/shapes/chart.d.ts +3 -2
  94. package/dist/charts/engine/shapes/chart.js +192 -162
  95. package/dist/charts/engine/shapes/chart.js.map +1 -1
  96. package/dist/charts/engine/shapes/draw.cjs +1 -1
  97. package/dist/charts/engine/shapes/draw.cjs.map +1 -1
  98. package/dist/charts/engine/shapes/draw.js +87 -86
  99. package/dist/charts/engine/shapes/draw.js.map +1 -1
  100. package/dist/charts/engine/shapes/layout.cjs +2 -2
  101. package/dist/charts/engine/shapes/layout.cjs.map +1 -1
  102. package/dist/charts/engine/shapes/layout.d.cts +8 -7
  103. package/dist/charts/engine/shapes/layout.d.ts +8 -7
  104. package/dist/charts/engine/shapes/layout.js +262 -166
  105. package/dist/charts/engine/shapes/layout.js.map +1 -1
  106. package/dist/charts/engine/shapes/types.d.cts +13 -1
  107. package/dist/charts/engine/shapes/types.d.ts +13 -1
  108. package/dist/charts/engine/three-d/chart.cjs +1 -1
  109. package/dist/charts/engine/three-d/chart.cjs.map +1 -1
  110. package/dist/charts/engine/three-d/chart.js +266 -257
  111. package/dist/charts/engine/three-d/chart.js.map +1 -1
  112. package/dist/charts/engine/three-d/dom.cjs +1 -1
  113. package/dist/charts/engine/three-d/dom.cjs.map +1 -1
  114. package/dist/charts/engine/three-d/dom.js +28 -35
  115. package/dist/charts/engine/three-d/dom.js.map +1 -1
  116. package/dist/charts/engine/three-d/geometry.cjs +1 -1
  117. package/dist/charts/engine/three-d/geometry.cjs.map +1 -1
  118. package/dist/charts/engine/three-d/geometry.js +70 -70
  119. package/dist/charts/engine/three-d/overlay.cjs +1 -1
  120. package/dist/charts/engine/three-d/overlay.cjs.map +1 -1
  121. package/dist/charts/engine/three-d/overlay.js +15 -15
  122. package/dist/charts/engine/tooltip.cjs +2 -0
  123. package/dist/charts/engine/tooltip.cjs.map +1 -0
  124. package/dist/charts/engine/tooltip.d.cts +94 -0
  125. package/dist/charts/engine/tooltip.d.ts +94 -0
  126. package/dist/charts/engine/tooltip.js +242 -0
  127. package/dist/charts/engine/tooltip.js.map +1 -0
  128. package/dist/charts/engine/trading/chart.cjs +1 -1
  129. package/dist/charts/engine/trading/chart.cjs.map +1 -1
  130. package/dist/charts/engine/trading/chart.js +135 -135
  131. package/dist/charts/react/shapes.cjs +1 -1
  132. package/dist/charts/react/shapes.cjs.map +1 -1
  133. package/dist/charts/react/shapes.d.cts +10 -1
  134. package/dist/charts/react/shapes.d.ts +10 -1
  135. package/dist/charts/react/shapes.js +24 -19
  136. package/dist/charts/react/shapes.js.map +1 -1
  137. package/dist/core/contracts.cjs +1 -1
  138. package/dist/core/contracts.cjs.map +1 -1
  139. package/dist/core/contracts.js +1 -1
  140. package/dist/core/contracts.js.map +1 -1
  141. package/dist/native/charts/src/canvas.cjs +1 -1
  142. package/dist/native/charts/src/canvas.js +8 -7
  143. package/dist/native/charts/src/engine/canvas.cjs +1 -1
  144. package/dist/native/charts/src/engine/canvas.js +2 -0
  145. package/dist/native/charts/src/engine/controller.cjs +2 -2
  146. package/dist/native/charts/src/engine/controller.cjs.map +1 -1
  147. package/dist/native/charts/src/engine/controller.js +102 -87
  148. package/dist/native/charts/src/engine/controller.js.map +1 -1
  149. package/dist/native/charts/src/engine/hover-rect.cjs +2 -0
  150. package/dist/native/charts/src/engine/hover-rect.cjs.map +1 -0
  151. package/dist/native/charts/src/engine/hover-rect.js +47 -0
  152. package/dist/native/charts/src/engine/hover-rect.js.map +1 -0
  153. package/dist/native/charts/src/engine/scene.cjs +1 -1
  154. package/dist/native/charts/src/engine/scene.cjs.map +1 -1
  155. package/dist/native/charts/src/engine/scene.js +318 -245
  156. package/dist/native/charts/src/engine/scene.js.map +1 -1
  157. package/dist/native/charts/src/engine/shapes/draw.cjs +1 -1
  158. package/dist/native/charts/src/engine/shapes/draw.cjs.map +1 -1
  159. package/dist/native/charts/src/engine/shapes/draw.js +81 -80
  160. package/dist/native/charts/src/engine/shapes/draw.js.map +1 -1
  161. package/dist/native/charts/src/engine/shapes/layout.cjs +2 -2
  162. package/dist/native/charts/src/engine/shapes/layout.cjs.map +1 -1
  163. package/dist/native/charts/src/engine/shapes/layout.js +239 -143
  164. package/dist/native/charts/src/engine/shapes/layout.js.map +1 -1
  165. package/dist/native/charts/src/engine/tooltip.cjs +2 -0
  166. package/dist/native/charts/src/engine/tooltip.cjs.map +1 -0
  167. package/dist/native/charts/src/engine/tooltip.js +164 -0
  168. package/dist/native/charts/src/engine/tooltip.js.map +1 -0
  169. package/dist/native/components/Chart/scene.cjs +1 -1
  170. package/dist/native/components/Chart/scene.cjs.map +1 -1
  171. package/dist/native/components/Chart/scene.js +210 -187
  172. package/dist/native/components/Chart/scene.js.map +1 -1
  173. package/dist/native/source/components/Chart/scene.tsx +55 -2
  174. package/dist/taro/index.js +2026 -1626
  175. package/dist/uni/charts-canvas.js +1 -0
  176. package/dist/vue/components/Chart/BulletChart.vue.d.ts +2 -0
  177. package/dist/vue/components/Chart/CalendarHeatmap.vue.d.ts +2 -0
  178. package/dist/vue/components/Chart/ChordChart.vue.d.ts +2 -0
  179. package/dist/vue/components/Chart/DonutChart.vue.d.ts +2 -0
  180. package/dist/vue/components/Chart/FunnelChart.vue.d.ts +2 -0
  181. package/dist/vue/components/Chart/GaugeChart.vue.d.ts +2 -0
  182. package/dist/vue/components/Chart/PieChart.vue.d.ts +2 -0
  183. package/dist/vue/components/Chart/RadarChart.vue.d.ts +2 -0
  184. package/dist/vue/components/Chart/RadialBarChart.vue.d.ts +2 -0
  185. package/dist/vue/components/Chart/SankeyChart.vue.d.ts +2 -0
  186. package/dist/vue/components/Chart/SunburstChart.vue.d.ts +2 -0
  187. package/dist/vue/components/Chart/TreemapChart.vue.d.ts +2 -0
  188. package/dist/vue/components/Chart/props.d.ts +2 -0
  189. package/dist/vue/components/Chart/props.js +1 -0
  190. package/dist/vue/components/Chart/props.js.map +1 -1
  191. package/dist/vue/components/Chart/shape.d.ts +2 -1
  192. package/dist/vue/components/Chart/shape.js +2 -0
  193. package/dist/vue/components/Chart/shape.js.map +1 -1
  194. package/dist/vue/components/Chart/types.d.ts +2 -0
  195. package/dist/weapp/charts/canvas.js +2576 -1969
  196. package/dist/weapp/charts/core.js +1 -0
  197. package/dist/web-components/components/chart/shape-chart.d.ts +9 -7
  198. package/dist/web-components/components/chart/shape-chart.js +3 -0
  199. package/dist/web-components/components/chart/shape-chart.js.map +1 -1
  200. package/dist/web-components/components/chart/shapes-extended.d.ts +6 -6
  201. package/dist/web-components/components/chart/shapes-extended.js.map +1 -1
  202. package/package.json +1 -1
@@ -708,117 +708,6 @@ var decimateMinMax = (points, start, end, column, gap = Infinity) => {
708
708
  };
709
709
  };
710
710
  //#endregion
711
- //#region ../charts/src/engine/layout.ts
712
- var createScales = (plot, view, reverseX, reverseY, horizontal = false) => {
713
- const [x0, x1] = view.x;
714
- const [y0, y1] = view.y;
715
- const left = plot.x;
716
- const right = plot.x + plot.width;
717
- const top = plot.y;
718
- const bottom = plot.y + plot.height;
719
- if (horizontal) {
720
- const xs = plot.height / (x1 - x0 || 1);
721
- const ys = plot.width / (y1 - y0 || 1);
722
- return {
723
- plot,
724
- view,
725
- horizontal,
726
- xToPx: reverseX ? (v) => bottom - (v - x0) * xs : (v) => top + (v - x0) * xs,
727
- yToPx: reverseY ? (v) => right - (v - y0) * ys : (v) => left + (v - y0) * ys,
728
- pxToX: reverseX ? (p) => x0 + (bottom - p) / xs : (p) => x0 + (p - top) / xs,
729
- pxToY: reverseY ? (p) => y0 + (right - p) / ys : (p) => y0 + (p - left) / ys,
730
- xPerPx: 1 / xs,
731
- yPerPx: 1 / ys
732
- };
733
- }
734
- const xs = plot.width / (x1 - x0 || 1);
735
- const ys = plot.height / (y1 - y0 || 1);
736
- return {
737
- plot,
738
- view,
739
- horizontal,
740
- xToPx: reverseX ? (v) => right - (v - x0) * xs : (v) => left + (v - x0) * xs,
741
- yToPx: reverseY ? (v) => top + (v - y0) * ys : (v) => bottom - (v - y0) * ys,
742
- pxToX: reverseX ? (p) => x0 + (right - p) / xs : (p) => x0 + (p - left) / xs,
743
- pxToY: reverseY ? (p) => y0 + (p - top) / ys : (p) => y0 + (bottom - p) / ys,
744
- xPerPx: 1 / xs,
745
- yPerPx: 1 / ys
746
- };
747
- };
748
- /** Screen pixel of a data point */
749
- var toScreen = (s, x, y) => s.horizontal ? {
750
- x: s.yToPx(y),
751
- y: s.xToPx(x)
752
- } : {
753
- x: s.xToPx(x),
754
- y: s.yToPx(y)
755
- };
756
- /** Data point under a screen pixel */
757
- var toData = (s, px, py) => s.horizontal ? {
758
- x: s.pxToX(py),
759
- y: s.pxToY(px)
760
- } : {
761
- x: s.pxToX(px),
762
- y: s.pxToY(py)
763
- };
764
- /** Coordinate of a screen pixel along the x axis (`xToPx` space) */
765
- var alongX = (s, px, py) => s.horizontal ? py : px;
766
- /** Coordinate of a screen pixel along the value axis (`yToPx` space) */
767
- var alongY = (s, px, py) => s.horizontal ? px : py;
768
- /**
769
- * Screen rectangle between two pixels along x (`xa`, `xb`) and two along
770
- * y (`ya`, `yb`)
771
- */
772
- var screenRect = (s, xa, xb, ya, yb) => {
773
- const a = Math.min(xa, xb);
774
- const b = Math.max(xa, xb);
775
- const c = Math.min(ya, yb);
776
- const d = Math.max(ya, yb);
777
- return s.horizontal ? {
778
- x: c,
779
- y: a,
780
- width: d - c,
781
- height: b - a
782
- } : {
783
- x: a,
784
- y: c,
785
- width: b - a,
786
- height: d - c
787
- };
788
- };
789
- /**
790
- * Inset (per side) and corner radius of heatmap cells of `width` ×
791
- * `height` CSS pixels: a hairline apart (1px, 2px on large cells), corners
792
- * of `radius` at most 20% of the cell; tiny cells touch
793
- */
794
- var heatCellStyle = (width, height, radius) => {
795
- const size = Math.min(Math.abs(width), Math.abs(height));
796
- if (!(size >= 4)) return {
797
- inset: 0,
798
- radius: 0
799
- };
800
- return {
801
- inset: Math.round(Math.min(1, .5 + size / 96) * 4) / 4,
802
- radius: Math.round(Math.max(0, Math.min(radius, size * .2)) * 4) / 4
803
- };
804
- };
805
- /** Room for the axes, then the plot area */
806
- var computePlot = (input) => {
807
- const { width, height, padding, fontSize } = input;
808
- const line = Math.ceil(fontSize * 1.4);
809
- const left = padding.left + (input.yLabelWidth > 0 ? input.yLabelWidth + 8 : 0) + (input.yTitle ? line + 6 : 0);
810
- const bottom = padding.bottom + (input.xAxisVisible ? line + 8 : 0) + (input.xTitle ? line + 4 : 0);
811
- const top = padding.top;
812
- const y2Width = input.y2LabelWidth ?? 0;
813
- const right = y2Width > 0 || input.y2Title ? Math.max(4, padding.left) + (y2Width > 0 ? y2Width + 8 : 0) + (input.y2Title ? line + 6 : 0) : padding.right;
814
- return {
815
- x: Math.round(left),
816
- y: Math.round(top),
817
- width: Math.max(10, Math.round(width - left - right)),
818
- height: Math.max(10, Math.round(height - top - bottom))
819
- };
820
- };
821
- //#endregion
822
711
  //#region ../charts/src/engine/locale.ts
823
712
  /**
824
713
  * Locale of a chart: the `locale` option, else the `lang` of the document
@@ -1087,1920 +976,2031 @@ var formatPercent = (value, locale, fixed = false) => locale ? numberFormat(loca
1087
976
  maximumFractionDigits: 1
1088
977
  }).format(value / 100) : `${fixed ? value.toFixed(1) : +value.toFixed(1)}%`;
1089
978
  //#endregion
1090
- //#region ../charts/src/engine/labels.ts
1091
- var CHART_LABELS = {
1092
- en: {
1093
- open: "Open",
1094
- high: "High",
1095
- low: "Low",
1096
- close: "Close",
1097
- max: "Max",
1098
- q3: "Q3",
1099
- median: "Median",
1100
- q1: "Q1",
1101
- min: "Min",
1102
- outliers: "Outliers",
1103
- value: "Value",
1104
- x: "x",
1105
- y: "y",
1106
- series: "Series",
1107
- chart: "Chart",
1108
- chartOf: "Chart: {series}",
1109
- start: "Start",
1110
- end: "End",
1111
- duration: "Duration",
1112
- progress: "Progress",
1113
- dependencies: "Depends on",
1114
- outlier: "Outlier"
979
+ //#region ../charts/src/engine/time.ts
980
+ var SECOND = 1e3;
981
+ var MINUTE = 60 * SECOND;
982
+ var HOUR = 60 * MINUTE;
983
+ var DAY = 24 * HOUR;
984
+ var MONTH = 30 * DAY;
985
+ var YEAR = 365 * DAY;
986
+ var INTERVALS = [
987
+ {
988
+ unit: "second",
989
+ step: 1,
990
+ ms: SECOND
1115
991
  },
1116
- zh: {
1117
- open: "开盘",
1118
- high: "最高",
1119
- low: "最低",
1120
- close: "收盘",
1121
- max: "最大值",
1122
- q3: "上四分位",
1123
- median: "中位数",
1124
- q1: "下四分位",
1125
- min: "最小值",
1126
- outliers: "异常值",
1127
- value: "数值",
1128
- series: "系列",
1129
- chart: "图表",
1130
- chartOf: "图表:{series}",
1131
- start: "开始",
1132
- end: "结束",
1133
- duration: "时长",
1134
- progress: "进度",
1135
- dependencies: "前置任务",
1136
- outlier: "异常值"
992
+ {
993
+ unit: "second",
994
+ step: 5,
995
+ ms: 5 * SECOND
1137
996
  },
1138
- ja: {
1139
- open: "始値",
1140
- high: "高値",
1141
- low: "安値",
1142
- close: "終値",
1143
- max: "最大値",
1144
- q3: "第3四分位",
1145
- median: "中央値",
1146
- q1: "第1四分位",
1147
- min: "最小値",
1148
- outliers: "外れ値",
1149
- value: "値",
1150
- series: "系列",
1151
- chart: "グラフ",
1152
- chartOf: "グラフ:{series}",
1153
- start: "開始",
1154
- end: "終了",
1155
- duration: "期間",
1156
- progress: "進捗",
1157
- dependencies: "先行タスク",
1158
- outlier: "外れ値"
997
+ {
998
+ unit: "second",
999
+ step: 15,
1000
+ ms: 15 * SECOND
1159
1001
  },
1160
- fr: {
1161
- open: "Ouverture",
1162
- high: "Plus haut",
1163
- low: "Plus bas",
1164
- close: "Clôture",
1165
- max: "Max",
1166
- q3: "Q3",
1167
- median: "Médiane",
1168
- q1: "Q1",
1169
- min: "Min",
1170
- outliers: "Valeurs aberrantes",
1171
- value: "Valeur",
1172
- series: "Série",
1173
- chart: "Graphique",
1174
- chartOf: "Graphique : {series}",
1175
- start: "Début",
1176
- end: "Fin",
1177
- duration: "Durée",
1178
- progress: "Avancement",
1179
- dependencies: "Dépend de",
1180
- outlier: "Valeur aberrante"
1181
- }
1182
- };
1183
- /** Texts of a locale with the overrides */
1184
- var chartLabels = (locale, overrides) => localeLabels(CHART_LABELS, locale, overrides);
1185
- /** Separator of the list of series names in accessible names */
1186
- var listSeparator = (locale) => /^(zh|ja)\b/i.test(locale) ? "、" : ", ";
1187
- /** Default accessible name of a chart model: "Chart: Revenue, Costs" (unnamed series left out) */
1188
- var chartAriaLabel = (model) => {
1189
- const named = model.series.filter((s) => s.name);
1190
- return named.length ? model.labels.chartOf.replace("{series}", named.map((s) => s.name).join(listSeparator(model.locale))) : model.labels.chart;
1191
- };
1192
- /**
1193
- * Localized accessible description of a chart, for platforms that build their
1194
- * own accessible name ("Chart: Revenue, Costs", "营收:营收、成本").
1195
- * With a `title`, the title replaces the generic "Chart" word.
1196
- */
1197
- function describeChart$1(input) {
1198
- const locale = input.locale ?? "en";
1199
- const labels = chartLabels(locale, input.labels);
1200
- const series = input.series ?? [];
1201
- const title = input.title?.trim();
1202
- const generic = chartAriaLabel({
1203
- labels,
1204
- locale,
1205
- series
1206
- });
1207
- if (!title) return generic;
1208
- if (!series.length) return title;
1209
- const index = labels.chartOf.indexOf(labels.chart);
1210
- if (index < 0 || !labels.chart) return `${title}. ${generic}`;
1211
- const names = series.map((s) => s.name).join(listSeparator(locale));
1212
- const fill = (part) => part.split("{series}").join(names);
1213
- return fill(labels.chartOf.slice(0, index)) + title + fill(labels.chartOf.slice(index + labels.chart.length));
1214
- }
1215
- //#endregion
1216
- //#region ../charts/src/engine/spatial.ts
1217
- var GridIndex = class {
1218
- constructor(x, y, count, pointsPerCell = 4) {
1219
- let xMin = Infinity;
1220
- let xMax = -Infinity;
1221
- let yMin = Infinity;
1222
- let yMax = -Infinity;
1223
- let valid = 0;
1224
- for (let i = 0; i < count; i++) {
1225
- const xi = x[i];
1226
- const yi = y[i];
1227
- if (!Number.isFinite(xi) || !Number.isFinite(yi)) continue;
1228
- valid++;
1229
- if (xi < xMin) xMin = xi;
1230
- if (xi > xMax) xMax = xi;
1231
- if (yi < yMin) yMin = yi;
1232
- if (yi > yMax) yMax = yi;
1233
- }
1234
- const side = Math.min(1024, Math.max(1, Math.ceil(Math.sqrt(valid / pointsPerCell))));
1235
- this.cols = side;
1236
- this.rows = side;
1237
- this.x0 = valid ? xMin : 0;
1238
- this.y0 = valid ? yMin : 0;
1239
- this.cellW = valid && xMax > xMin ? (xMax - xMin) / side : 1;
1240
- this.cellH = valid && yMax > yMin ? (yMax - yMin) / side : 1;
1241
- const cells = new Int32Array(count);
1242
- const counts = new Uint32Array(side * side + 1);
1243
- for (let i = 0; i < count; i++) {
1244
- const xi = x[i];
1245
- const yi = y[i];
1246
- if (!Number.isFinite(xi) || !Number.isFinite(yi)) {
1247
- cells[i] = -1;
1248
- continue;
1249
- }
1250
- const cell = this.cellOf(xi, yi);
1251
- cells[i] = cell;
1252
- counts[cell + 1]++;
1253
- }
1254
- for (let c = 1; c < counts.length; c++) counts[c] += counts[c - 1];
1255
- this.starts = counts.slice();
1256
- const cursor = counts;
1257
- this.order = new Uint32Array(valid);
1258
- for (let i = 0; i < count; i++) {
1259
- const cell = cells[i];
1260
- if (cell >= 0) this.order[cursor[cell]++] = i;
1261
- }
1262
- }
1263
- column(xi) {
1264
- const c = Math.floor((xi - this.x0) / this.cellW);
1265
- return c < 0 ? 0 : c >= this.cols ? this.cols - 1 : c;
1266
- }
1267
- row(yi) {
1268
- const r = Math.floor((yi - this.y0) / this.cellH);
1269
- return r < 0 ? 0 : r >= this.rows ? this.rows - 1 : r;
1270
- }
1271
- cellOf(xi, yi) {
1272
- return this.row(yi) * this.cols + this.column(xi);
1002
+ {
1003
+ unit: "second",
1004
+ step: 30,
1005
+ ms: 30 * SECOND
1006
+ },
1007
+ {
1008
+ unit: "minute",
1009
+ step: 1,
1010
+ ms: MINUTE
1011
+ },
1012
+ {
1013
+ unit: "minute",
1014
+ step: 5,
1015
+ ms: 5 * MINUTE
1016
+ },
1017
+ {
1018
+ unit: "minute",
1019
+ step: 15,
1020
+ ms: 15 * MINUTE
1021
+ },
1022
+ {
1023
+ unit: "minute",
1024
+ step: 30,
1025
+ ms: 30 * MINUTE
1026
+ },
1027
+ {
1028
+ unit: "hour",
1029
+ step: 1,
1030
+ ms: HOUR
1031
+ },
1032
+ {
1033
+ unit: "hour",
1034
+ step: 2,
1035
+ ms: 2 * HOUR
1036
+ },
1037
+ {
1038
+ unit: "hour",
1039
+ step: 3,
1040
+ ms: 3 * HOUR
1041
+ },
1042
+ {
1043
+ unit: "hour",
1044
+ step: 6,
1045
+ ms: 6 * HOUR
1046
+ },
1047
+ {
1048
+ unit: "hour",
1049
+ step: 12,
1050
+ ms: 12 * HOUR
1051
+ },
1052
+ {
1053
+ unit: "day",
1054
+ step: 1,
1055
+ ms: DAY
1056
+ },
1057
+ {
1058
+ unit: "day",
1059
+ step: 2,
1060
+ ms: 2 * DAY
1061
+ },
1062
+ {
1063
+ unit: "day",
1064
+ step: 7,
1065
+ ms: 7 * DAY
1066
+ },
1067
+ {
1068
+ unit: "month",
1069
+ step: 1,
1070
+ ms: MONTH
1071
+ },
1072
+ {
1073
+ unit: "month",
1074
+ step: 3,
1075
+ ms: 3 * MONTH
1076
+ },
1077
+ {
1078
+ unit: "month",
1079
+ step: 6,
1080
+ ms: 6 * MONTH
1273
1081
  }
1274
- /** Calls `visit` with every point that may lie inside the box */
1275
- query(xMin, xMax, yMin, yMax, visit) {
1276
- if (this.order.length === 0) return;
1277
- const c0 = this.column(Math.min(xMin, xMax));
1278
- const c1 = this.column(Math.max(xMin, xMax));
1279
- const r0 = this.row(Math.min(yMin, yMax));
1280
- const r1 = this.row(Math.max(yMin, yMax));
1281
- for (let r = r0; r <= r1; r++) for (let c = c0; c <= c1; c++) {
1282
- const cell = r * this.cols + c;
1283
- for (let k = this.starts[cell]; k < this.starts[cell + 1]; k++) visit(this.order[k]);
1082
+ ];
1083
+ var pad = (n, width = 2) => String(n).padStart(width, "0");
1084
+ var partsOf = (t, utc) => {
1085
+ const d = new Date(t);
1086
+ return utc ? {
1087
+ year: d.getUTCFullYear(),
1088
+ month: d.getUTCMonth(),
1089
+ date: d.getUTCDate(),
1090
+ day: d.getUTCDay(),
1091
+ hours: d.getUTCHours(),
1092
+ minutes: d.getUTCMinutes(),
1093
+ seconds: d.getUTCSeconds(),
1094
+ ms: d.getUTCMilliseconds()
1095
+ } : {
1096
+ year: d.getFullYear(),
1097
+ month: d.getMonth(),
1098
+ date: d.getDate(),
1099
+ day: d.getDay(),
1100
+ hours: d.getHours(),
1101
+ minutes: d.getMinutes(),
1102
+ seconds: d.getSeconds(),
1103
+ ms: d.getMilliseconds()
1104
+ };
1105
+ };
1106
+ /** Timestamp of calendar fields (out of range fields carry over) */
1107
+ var timeOf = (utc, year, month, date = 1, hours = 0, minutes = 0, seconds = 0) => {
1108
+ if (utc) {
1109
+ const t = Date.UTC(year, month, date, hours, minutes, seconds);
1110
+ if (year >= 0 && year < 100) {
1111
+ const d = new Date(t);
1112
+ d.setUTCFullYear(d.getUTCFullYear() - 1900);
1113
+ return d.getTime();
1284
1114
  }
1115
+ return t;
1285
1116
  }
1117
+ const d = new Date(year, month, date, hours, minutes, seconds);
1118
+ if (year >= 0 && year < 100) d.setFullYear(d.getFullYear() - 1900);
1119
+ return d.getTime();
1286
1120
  };
1287
- //#endregion
1288
- //#region ../charts/src/engine/model.ts
1289
- /** Plotted units of a log axis: the base-10 logarithm of the values */
1290
- var LOG_MAP = {
1291
- scale: 1,
1292
- offset: 0,
1293
- log: true
1121
+ var chooseInterval = (span, count) => {
1122
+ const target = span / Math.max(1, count);
1123
+ if (target < SECOND * .75) return {
1124
+ unit: "millisecond",
1125
+ step: tickStep(0, span, count),
1126
+ ms: 1
1127
+ };
1128
+ for (const interval of INTERVALS) if (interval.ms >= target * .75) return interval;
1129
+ const years = Math.max(1, tickStep(0, span / YEAR, count));
1130
+ return {
1131
+ unit: "year",
1132
+ step: Math.round(years),
1133
+ ms: Math.round(years) * YEAR
1134
+ };
1294
1135
  };
1295
- var firstX = (data) => {
1296
- if (typeof data === "object" && data !== null && !("length" in data)) {
1297
- const xs = data.x;
1298
- return xs && xs.length ? xs[0] : void 0;
1299
- }
1300
- const list = data;
1301
- for (let i = 0; i < Math.min(list.length, 8); i++) {
1302
- const item = list[i];
1303
- if (Array.isArray(item)) return item[0];
1304
- if (item && typeof item === "object") return item.x;
1136
+ /** Start of the tick sequence: `t` floored to the unit, aligned on the step */
1137
+ var floorTo = (t, { unit, step }, utc) => {
1138
+ const p = partsOf(t, utc);
1139
+ switch (unit) {
1140
+ case "second": return timeOf(utc, p.year, p.month, p.date, p.hours, p.minutes, p.seconds - p.seconds % step);
1141
+ case "minute": return timeOf(utc, p.year, p.month, p.date, p.hours, p.minutes - p.minutes % step);
1142
+ case "hour": return timeOf(utc, p.year, p.month, p.date, p.hours - p.hours % step);
1143
+ case "day": return timeOf(utc, p.year, p.month, p.date - (step === 7 ? (p.day + 6) % 7 : 0));
1144
+ case "month": return timeOf(utc, p.year, p.month - p.month % step);
1145
+ case "year": return timeOf(utc, p.year - p.year % step, 0);
1146
+ default: return t;
1305
1147
  }
1306
1148
  };
1307
- /** Axis type when not given: dates -> time, strings -> category */
1308
- var detectXType = (datas) => {
1309
- for (const data of datas) {
1310
- const x = firstX(data);
1311
- if (x instanceof Date) return "time";
1312
- if (typeof x === "string") return "category";
1149
+ var advance = (t, { unit, step }, utc) => {
1150
+ const { year, month, date, hours, minutes, seconds } = partsOf(t, utc);
1151
+ switch (unit) {
1152
+ case "second": return timeOf(utc, year, month, date, hours, minutes, seconds + step);
1153
+ case "minute": return timeOf(utc, year, month, date, hours, minutes + step, seconds);
1154
+ case "hour": return timeOf(utc, year, month, date, hours + step, minutes, seconds);
1155
+ case "day": return timeOf(utc, year, month, date + step, hours, minutes, seconds);
1156
+ case "month": return timeOf(utc, year, month + step, date, hours, minutes, seconds);
1157
+ case "year": return timeOf(utc, year + step, month, date, hours, minutes, seconds);
1158
+ default: return t + step;
1313
1159
  }
1314
- return "linear";
1315
1160
  };
1316
- var axisModel = (options, type, categories, gridDefault, locale) => ({
1317
- type,
1318
- reverse: !!options?.reverse,
1319
- categories,
1320
- title: options?.title,
1321
- format: options?.format,
1322
- grid: options?.grid ?? gridDefault,
1323
- visible: options?.visible ?? true,
1324
- tickCount: options?.tickCount,
1325
- locale,
1326
- timeZone: options?.timeZone ?? "local"
1327
- });
1328
- /** Defined entries only (`{ open: undefined }` keeps the built-in text) */
1329
- var definedEntries = (value) => {
1330
- const out = {};
1331
- if (!value) return out;
1332
- for (const key of Object.keys(value)) if (value[key] !== void 0) out[key] = value[key];
1333
- return out;
1334
- };
1335
- var extent = (values) => {
1336
- let min = Infinity;
1337
- let max = -Infinity;
1338
- for (let i = 0; i < values.length; i++) {
1339
- const v = values[i];
1340
- if (!Number.isFinite(v)) continue;
1341
- if (v < min) min = v;
1342
- if (v > max) max = v;
1161
+ /**
1162
+ * Calendar-aligned ticks between two timestamps
1163
+ * @param locale labels in this locale (ISO-like labels without it)
1164
+ * @param timeZone calendar of the steps and labels
1165
+ */
1166
+ var timeTicks = (min, max, count, locale, timeZone = "local") => {
1167
+ if (min > max) [min, max] = [max, min];
1168
+ const interval = chooseInterval(max - min, count);
1169
+ if (interval.unit === "millisecond") return {
1170
+ values: linearTicks(min, max, count),
1171
+ unit: "millisecond",
1172
+ format: (v) => formatTime(v, "millisecond", locale, timeZone)
1173
+ };
1174
+ const utc = timeZone === "utc";
1175
+ const values = [];
1176
+ let t = floorTo(min, interval, utc);
1177
+ while (t < min) t = advance(t, interval, utc);
1178
+ while (t <= max && values.length < 500) {
1179
+ values.push(t);
1180
+ t = advance(t, interval, utc);
1343
1181
  }
1344
- return [min, max];
1182
+ const { unit } = interval;
1183
+ return {
1184
+ values,
1185
+ unit,
1186
+ format: (v) => formatTick(v, unit, locale, timeZone)
1187
+ };
1345
1188
  };
1346
- /** Smallest positive gap between consecutive values (bar width on numeric axes) */
1347
- var smallestStep = (columns, sorted) => {
1348
- let step = Infinity;
1349
- columns.forEach((values, k) => {
1350
- const xs = sorted[k] ? values : Float64Array.from(values).sort();
1351
- for (let i = 1; i < xs.length; i++) {
1352
- const d = xs[i] - xs[i - 1];
1353
- if (d > 0 && d < step) step = d;
1354
- }
1355
- });
1356
- return step;
1189
+ var ymd$1 = (p) => `${p.year}-${pad(p.month + 1)}-${pad(p.date)}`;
1190
+ var hm = (p) => `${pad(p.hours)}:${pad(p.minutes)}`;
1191
+ var HM = {
1192
+ hour: "2-digit",
1193
+ minute: "2-digit",
1194
+ hourCycle: "h23"
1357
1195
  };
1358
- var bandOf = (list, axis, category) => {
1359
- if (category) return 1;
1360
- const step = smallestStep(list.map((s) => axis === "x" ? s.cols.x : s.cols.y), list.map((s) => axis === "x" && s.cols.sorted));
1361
- return Number.isFinite(step) ? step : 1;
1196
+ var HMS = {
1197
+ ...HM,
1198
+ second: "2-digit"
1362
1199
  };
1363
- /**
1364
- * Stacks the y values of series sharing a stack id (positive and negative
1365
- * values separately); `percent` scales every stack to 100 at each x
1366
- */
1367
- var stackValues = (list, percent) => {
1368
- const totals = /* @__PURE__ */ new Map();
1369
- if (percent) for (const { s, stack } of list) {
1370
- let byX = totals.get(stack);
1371
- if (!byX) totals.set(stack, byX = /* @__PURE__ */ new Map());
1372
- const { x, y, count } = s.cols;
1373
- for (let i = 0; i < count; i++) {
1374
- const v = y[i];
1375
- if (!Number.isFinite(v)) continue;
1376
- const t = byX.get(x[i]) ?? [0, 0];
1377
- if (v >= 0) t[0] += v;
1378
- else t[1] -= v;
1379
- byX.set(x[i], t);
1200
+ var MONTH_DAY = {
1201
+ month: "short",
1202
+ day: "numeric"
1203
+ };
1204
+ var DATE = {
1205
+ year: "numeric",
1206
+ month: "short",
1207
+ day: "numeric"
1208
+ };
1209
+ /** Cached formatter of a locale and time zone */
1210
+ var intl = (locale, timeZone, options) => dateFormat(locale, timeZone === "utc" ? {
1211
+ ...options,
1212
+ timeZone: "UTC"
1213
+ } : options);
1214
+ /** Short tick label; day boundaries show the date, year boundaries the year */
1215
+ var formatTick = (value, unit, locale, timeZone) => {
1216
+ const p = partsOf(value, timeZone === "utc");
1217
+ const midnight = p.hours === 0 && p.minutes === 0 && p.seconds === 0;
1218
+ const newYear = p.month === 0 && p.date === 1;
1219
+ if (locale) {
1220
+ const f = (options) => intl(locale, timeZone, options).format(value);
1221
+ switch (unit) {
1222
+ case "second": return f(HMS);
1223
+ case "minute":
1224
+ case "hour": return f(midnight ? MONTH_DAY : HM);
1225
+ case "day": return f(newYear ? { year: "numeric" } : MONTH_DAY);
1226
+ case "month": return f(p.month === 0 ? { year: "numeric" } : { month: "short" });
1227
+ default: return f({ year: "numeric" });
1380
1228
  }
1381
1229
  }
1382
- const running = /* @__PURE__ */ new Map();
1383
- const members = /* @__PURE__ */ new Map();
1384
- for (const { stack } of list) members.set(stack, (members.get(stack) ?? 0) + 1);
1385
- return list.map(({ s, stack }) => {
1386
- const { x, y, count } = s.cols;
1387
- if (!percent && members.get(stack) === 1) {
1388
- const base = new Float64Array(count);
1389
- const top = new Float64Array(count);
1390
- for (let i = 0; i < count; i++) {
1391
- const v = y[i];
1392
- const valid = Number.isFinite(v);
1393
- base[i] = valid ? 0 : NaN;
1394
- top[i] = valid ? v : NaN;
1395
- }
1396
- return {
1397
- base,
1398
- top
1399
- };
1400
- }
1401
- let byX = running.get(stack);
1402
- if (!byX) running.set(stack, byX = /* @__PURE__ */ new Map());
1403
- const sums = totals.get(stack);
1404
- const base = new Float64Array(count);
1405
- const top = new Float64Array(count);
1406
- for (let i = 0; i < count; i++) {
1407
- let v = y[i];
1408
- if (!Number.isFinite(v)) {
1409
- base[i] = NaN;
1410
- top[i] = NaN;
1411
- continue;
1412
- }
1413
- if (sums) {
1414
- const t = sums.get(x[i]);
1415
- const total = t ? v >= 0 ? t[0] : t[1] : 0;
1416
- v = total > 0 ? v / total * 100 : 0;
1417
- }
1418
- const t = byX.get(x[i]) ?? [0, 0];
1419
- const b = v >= 0 ? t[0] : t[1];
1420
- base[i] = b;
1421
- top[i] = b + v;
1422
- if (v >= 0) t[0] += v;
1423
- else t[1] += v;
1424
- byX.set(x[i], t);
1425
- }
1426
- return {
1427
- base,
1428
- top
1429
- };
1430
- });
1230
+ switch (unit) {
1231
+ case "second": return `${hm(p)}:${pad(p.seconds)}`;
1232
+ case "minute":
1233
+ case "hour": return midnight ? `${pad(p.month + 1)}-${pad(p.date)}` : hm(p);
1234
+ case "day": return newYear ? String(p.year) : `${pad(p.month + 1)}-${pad(p.date)}`;
1235
+ case "month": return p.month === 0 ? String(p.year) : `${p.year}-${pad(p.month + 1)}`;
1236
+ default: return String(p.year);
1237
+ }
1431
1238
  };
1432
- var DEFAULT_UP = "var(--success-color, #16a34a)";
1433
- var DEFAULT_DOWN = "var(--danger-color, #dc2626)";
1434
- /** Dates in the y values (or starts) of objects: a time value axis */
1435
- var hasDateY = (datas) => {
1436
- for (const data of datas) {
1437
- if (typeof data !== "object" || data === null || !("length" in data)) continue;
1438
- const list = data;
1439
- for (let i = 0; i < Math.min(list.length, 8); i++) {
1440
- const item = list[i];
1441
- if (item && typeof item === "object" && !Array.isArray(item)) {
1442
- const d = item;
1443
- if (d.y instanceof Date || d.y0 instanceof Date) return true;
1444
- }
1239
+ /**
1240
+ * Full label (tooltips): precision follows the unit of the ticks; dates
1241
+ * show the weekday with a `locale`
1242
+ */
1243
+ var formatTime = (value, unit = "minute", locale, timeZone = "local") => {
1244
+ if (!Number.isFinite(value) || Number.isNaN(new Date(value).getTime())) return "–";
1245
+ const p = partsOf(value, timeZone === "utc");
1246
+ if (locale) {
1247
+ const f = (options) => intl(locale, timeZone, options).format(value);
1248
+ switch (unit) {
1249
+ case "millisecond": return `${f({
1250
+ ...DATE,
1251
+ ...HMS
1252
+ })}.${pad(p.ms, 3)}`;
1253
+ case "second": return f({
1254
+ ...DATE,
1255
+ ...HMS
1256
+ });
1257
+ case "minute":
1258
+ case "hour": return f({
1259
+ ...DATE,
1260
+ ...HM
1261
+ });
1262
+ default: return f({
1263
+ ...DATE,
1264
+ weekday: "short"
1265
+ });
1445
1266
  }
1446
1267
  }
1447
- return false;
1448
- };
1449
- var precise = (v) => Number(v.toPrecision(12));
1450
- /** Next step of the 1, 2, 5 × 10^k sequence */
1451
- var nextStep = (step) => {
1452
- const power = 10 ** Math.floor(Math.log10(step) + 1e-9);
1453
- const f = step / power;
1454
- return (f < 1.5 ? 2 : f < 3.5 ? 5 : 10) * power;
1268
+ switch (unit) {
1269
+ case "millisecond": return `${ymd$1(p)} ${hm(p)}:${pad(p.seconds)}.${pad(p.ms, 3)}`;
1270
+ case "second": return `${ymd$1(p)} ${hm(p)}:${pad(p.seconds)}`;
1271
+ case "minute":
1272
+ case "hour": return `${ymd$1(p)} ${hm(p)}`;
1273
+ default: return ymd$1(p);
1274
+ }
1455
1275
  };
1276
+ //#endregion
1277
+ //#region ../charts/src/engine/axis.ts
1456
1278
  /**
1457
- * Range of the right axis covering [lo, hi] with as many tick intervals as
1458
- * the left range has (its ticks then line up with the left grid)
1279
+ * @param count approximate number of ticks (numeric / time axes) or the
1280
+ * maximum number of labels (category axes)
1459
1281
  */
1460
- var alignedDomain = (lo, hi, left, count) => {
1461
- const [a0, a1] = left;
1462
- const stepL = tickStep(a0, a1, count);
1463
- const n = stepL > 0 ? Math.round((a1 - a0) / stepL) : 0;
1464
- [lo, hi] = ensureSpan(lo, hi);
1465
- if (n < 1 || Math.abs(n * stepL - (a1 - a0)) > stepL * 1e-6) return niceDomain(lo, hi, count);
1466
- let step = tickStep(lo, hi, n);
1467
- for (let k = 0; k < 40 && step > 0; k++) {
1468
- const start = Math.floor(lo / step + 1e-9) * step;
1469
- if (start + n * step >= hi - step * 1e-9) return [precise(start), precise(start + n * step)];
1470
- step = nextStep(step);
1282
+ var axisTicks = (axis, range, count) => {
1283
+ const [min, max] = range[0] <= range[1] ? range : [range[1], range[0]];
1284
+ if (axis.type === "category") {
1285
+ const n = axis.categories.length;
1286
+ const first = Math.max(0, Math.ceil(min - 1e-9));
1287
+ const last = Math.min(n - 1, Math.floor(max + 1e-9));
1288
+ const visible = Math.max(0, last - first + 1);
1289
+ const step = Math.max(1, Math.ceil(visible / Math.max(1, count)));
1290
+ const values = [];
1291
+ for (let i = first - first % step; i <= last; i += step) if (i >= first) values.push(i);
1292
+ const name = (v) => {
1293
+ const i = Math.round(v);
1294
+ return axis.format ? axis.format(i) : axis.categories[i] ?? String(i);
1295
+ };
1296
+ return {
1297
+ values,
1298
+ labels: values.map(name),
1299
+ formatValue: name
1300
+ };
1471
1301
  }
1472
- return niceDomain(lo, hi, count);
1473
- };
1474
- var mapped = /* @__PURE__ */ new WeakMap();
1475
- /** Columns of a right-axis series in left axis units (cached with the data) */
1476
- var mapColumns = (src, map) => {
1477
- const key = `${map.scale},${map.offset},${!!map.log}`;
1478
- const hit = mapped.get(src);
1479
- if (hit && hit.key === key) return hit.cols;
1480
- const f = mapper(map);
1481
- const column = (a) => a ? a.map(f) : null;
1482
- const raw = src;
1483
- const cols = {
1484
- ...raw,
1485
- y: raw.y.map(f),
1486
- open: column(raw.open),
1487
- high: column(raw.high),
1488
- low: column(raw.low),
1489
- y0: column(raw.y0),
1490
- q1: column(raw.q1),
1491
- q3: column(raw.q3),
1492
- outliers: raw.outliers ? raw.outliers.map((list) => list?.map(f)) : null
1493
- };
1494
- if (map.log) {
1495
- const range = [Infinity, -Infinity];
1496
- const add = (a) => {
1497
- if (!a) return;
1498
- for (let i = 0; i < a.length; i++) {
1499
- const v = a[i];
1500
- if (!Number.isFinite(v)) continue;
1501
- if (v < range[0]) range[0] = v;
1502
- if (v > range[1]) range[1] = v;
1503
- }
1302
+ if (axis.type === "time") {
1303
+ const { locale, timeZone = "local" } = axis;
1304
+ const ticks = timeTicks(min, max, count, locale, timeZone);
1305
+ const unit = ticks.unit === "day" || ticks.unit === "month" || ticks.unit === "year" ? "day" : ticks.unit;
1306
+ const full = axis.format ?? ((v) => formatTime(v, unit, locale, timeZone));
1307
+ return {
1308
+ values: ticks.values,
1309
+ labels: ticks.values.map(axis.format ?? ticks.format),
1310
+ formatValue: full
1504
1311
  };
1505
- for (const c of [
1506
- cols.y,
1507
- cols.y0,
1508
- cols.high,
1509
- cols.low,
1510
- cols.q1,
1511
- cols.q3
1512
- ]) add(c);
1513
- cols.outliers?.forEach((list) => add(list));
1514
- cols.yMin = range[0] <= range[1] ? range[0] : NaN;
1515
- cols.yMax = range[0] <= range[1] ? range[1] : NaN;
1516
- } else {
1517
- const lo = f(raw.yMin);
1518
- const hi = f(raw.yMax);
1519
- cols.yMin = Math.min(lo, hi);
1520
- cols.yMax = Math.max(lo, hi);
1521
1312
  }
1522
- mapped.set(src, {
1523
- key,
1524
- cols
1525
- });
1526
- return cols;
1527
- };
1528
- /** A log range widened to whole decades (`[0.3, 2.4]` -> `[0, 3]`) */
1529
- var decades = (range) => {
1530
- const lo = Math.floor(range[0] + 1e-9);
1531
- const hi = Math.ceil(range[1] - 1e-9);
1532
- return [lo, hi > lo ? hi : lo + 1];
1313
+ const values = linearTicks(min, max, count);
1314
+ const step = tickStep(min, max, count);
1315
+ if (axis.type === "log") return logTicks(axis, min, max, count);
1316
+ const tick = axis.format ?? tickFormatter(step, Math.max(Math.abs(min), Math.abs(max)), axis.locale);
1317
+ return {
1318
+ values,
1319
+ labels: values.map(tick),
1320
+ formatValue: axis.format ?? ((value) => formatValue(value, axis.locale))
1321
+ };
1533
1322
  };
1534
- /** Exponent of `m × 10^k`, exact for the values of the ticks */
1535
- var niceExponent = (m, k) => Math.log10(Number((m * 10 ** k).toPrecision(12)));
1536
1323
  /**
1537
- * A log range widened to the closest 1, 2 or 5 × 10^k bounds (the values
1538
- * 900 - 140k -> 500 - 200k), tighter than whole decades
1324
+ * Ticks of a log axis (`min` / `max` are exponents): every decade, with 2
1325
+ * and 5 between them on short ranges, every few decades on long ones;
1326
+ * labelled with the values (`1k`, `10k`)
1539
1327
  */
1540
- var niceLogRange = (range) => {
1541
- const below = (e) => {
1542
- const k = Math.floor(e + 1e-9);
1543
- let best = k;
1544
- for (const m of [2, 5]) {
1545
- const v = niceExponent(m, k);
1546
- if (v <= e + 1e-9) best = v;
1328
+ var logTicks = (axis, min, max, count) => {
1329
+ const span = max - min;
1330
+ const values = [];
1331
+ const raw = [];
1332
+ const push = (v) => {
1333
+ const e = Math.log10(v);
1334
+ if (e >= min - 1e-9 && e <= max + 1e-9) {
1335
+ values.push(e);
1336
+ raw.push(v);
1547
1337
  }
1548
- return best;
1549
1338
  };
1550
- const above = (e) => {
1551
- const k = Math.floor(e - 1e-9);
1552
- for (const m of [
1553
- 1,
1554
- 2,
1555
- 5
1556
- ]) {
1557
- const v = niceExponent(m, k);
1558
- if (v >= e - 1e-9) return v;
1339
+ if (span < 1) {
1340
+ for (const v of linearTicks(10 ** min, 10 ** max, count)) if (v > 0) push(v);
1341
+ } else {
1342
+ const every = Math.max(1, Math.ceil(span / Math.max(1, count)));
1343
+ const between = span * 3 <= count * 1.6 ? [2, 5] : [];
1344
+ for (let e = Math.floor(min); e <= Math.ceil(max); e++) {
1345
+ if (e % every !== 0) continue;
1346
+ const decade = Number((10 ** e).toPrecision(12));
1347
+ push(decade);
1348
+ for (const m of between) push(Number((m * decade).toPrecision(12)));
1559
1349
  }
1560
- return k + 1;
1350
+ }
1351
+ const label = axis.format ?? ((v) => formatCompact(v, 2, axis.locale));
1352
+ return {
1353
+ values,
1354
+ labels: raw.map(label),
1355
+ formatValue: axis.format ?? ((value) => formatValue(value, axis.locale))
1561
1356
  };
1562
- const lo = below(range[0]);
1563
- const hi = above(range[1]);
1564
- return [lo, hi > lo + 1e-9 ? hi : above(lo + 1e-6)];
1565
1357
  };
1566
- /**
1567
- * Plots a series through a map: its columns, bars, stacks and lower
1568
- * edges (bars / areas of the right axis start at `zero`)
1569
- */
1570
- var logColumns = (s, map, zero = -Infinity) => {
1571
- const f = mapper(map);
1572
- const column = (a) => a ? a.map(f) : null;
1573
- const rawBase = s.stackBase;
1574
- s.cols = mapColumns(s.cols, map);
1575
- s.barBase = s.barBase && !s.floating ? s.barBase.map((v) => v === 0 ? map.log ? -Infinity : f(0) : f(v)) : column(s.barBase);
1576
- s.barTop = column(s.barTop);
1577
- s.stackTop = column(s.stackTop);
1578
- s.stackBase = rawBase && rawBase === s.raw.y0 ? s.cols.y0 : rawBase ? column(rawBase) : s.type === "area" ? new Float64Array(s.cols.count).fill(zero) : null;
1579
- };
1580
- /** Value -> plotted units of a map (`-Infinity` for 0 on log axes) */
1581
- var mapper = (map) => (v) => (map.log ? Math.log10(v) : v) * map.scale + map.offset;
1582
- /** Value of the y axis -> plotted y (the logarithm on log axes) */
1583
- var toPlotY = (model, value) => model.y.type === "log" ? Math.log10(value) : value;
1584
- /** Plotted y -> value of the y axis */
1585
- var fromPlotY = (model, value) => model.y.type === "log" ? 10 ** value : value;
1586
- /** Value of the x axis -> plotted x (the logarithm on log axes) */
1587
- var toPlotX = (model, value) => model.x.type === "log" ? Math.log10(value) : value;
1588
- /** Plotted x -> value of the x axis */
1589
- var fromPlotX = (model, value) => model.x.type === "log" ? 10 ** value : value;
1590
- /** A view in plotted units -> the values of the axes (zoom events, `getView`) */
1591
- var viewToValues = (model, view) => ({
1592
- x: [fromPlotX(model, view.x[0]), fromPlotX(model, view.x[1])],
1593
- y: [fromPlotY(model, view.y[0]), fromPlotY(model, view.y[1])]
1594
- });
1595
- /** Values of the axes -> a view in plotted units (`setView`); missing axes keep `current` */
1596
- var viewFromValues = (model, next, current) => ({
1597
- x: next.x ? [toPlotX(model, next.x[0]), toPlotX(model, next.x[1])] : current.x,
1598
- y: next.y ? [toPlotY(model, next.y[0]), toPlotY(model, next.y[1])] : current.y
1599
- });
1600
- var logXCache = /* @__PURE__ */ new WeakMap();
1601
- /**
1602
- * Columns plotted on a log x axis: the logarithms of the x values; points
1603
- * without one (x <= 0) become gaps (cached with the data)
1604
- */
1605
- var logXColumns = (raw) => {
1606
- const hit = logXCache.get(raw);
1607
- if (hit) return hit;
1608
- const x = raw.x.map((v) => v > 0 ? Math.log10(v) : NaN);
1609
- let y = raw.y;
1610
- let gaps = false;
1611
- for (let i = 0; i < raw.count; i++) {
1612
- if (Number.isFinite(x[i])) continue;
1613
- if (!gaps) y = Float64Array.from(raw.y);
1614
- gaps = true;
1615
- y[i] = NaN;
1358
+ //#endregion
1359
+ //#region ../charts/src/engine/layout.ts
1360
+ var createScales = (plot, view, reverseX, reverseY, horizontal = false) => {
1361
+ const [x0, x1] = view.x;
1362
+ const [y0, y1] = view.y;
1363
+ const left = plot.x;
1364
+ const right = plot.x + plot.width;
1365
+ const top = plot.y;
1366
+ const bottom = plot.y + plot.height;
1367
+ if (horizontal) {
1368
+ const xs = plot.height / (x1 - x0 || 1);
1369
+ const ys = plot.width / (y1 - y0 || 1);
1370
+ return {
1371
+ plot,
1372
+ view,
1373
+ horizontal,
1374
+ xToPx: reverseX ? (v) => bottom - (v - x0) * xs : (v) => top + (v - x0) * xs,
1375
+ yToPx: reverseY ? (v) => right - (v - y0) * ys : (v) => left + (v - y0) * ys,
1376
+ pxToX: reverseX ? (p) => x0 + (bottom - p) / xs : (p) => x0 + (p - top) / xs,
1377
+ pxToY: reverseY ? (p) => y0 + (right - p) / ys : (p) => y0 + (p - left) / ys,
1378
+ xPerPx: 1 / xs,
1379
+ yPerPx: 1 / ys
1380
+ };
1616
1381
  }
1617
- const [xMin, xMax] = extent(x);
1618
- const cols = {
1619
- ...raw,
1620
- x,
1621
- y,
1622
- xMin,
1623
- xMax,
1624
- sorted: raw.sorted && !gaps
1382
+ const xs = plot.width / (x1 - x0 || 1);
1383
+ const ys = plot.height / (y1 - y0 || 1);
1384
+ return {
1385
+ plot,
1386
+ view,
1387
+ horizontal,
1388
+ xToPx: reverseX ? (v) => right - (v - x0) * xs : (v) => left + (v - x0) * xs,
1389
+ yToPx: reverseY ? (v) => top + (v - y0) * ys : (v) => bottom - (v - y0) * ys,
1390
+ pxToX: reverseX ? (p) => x0 + (right - p) / xs : (p) => x0 + (p - left) / xs,
1391
+ pxToY: reverseY ? (p) => y0 + (p - top) / ys : (p) => y0 + (bottom - p) / ys,
1392
+ xPerPx: 1 / xs,
1393
+ yPerPx: 1 / ys
1625
1394
  };
1626
- logXCache.set(raw, cols);
1627
- return cols;
1628
1395
  };
1629
- /** Replaces the bases of bars / areas on log axes (`-Infinity`) by the bottom of the axis */
1630
- var floorBases = (s, floor) => {
1631
- for (const a of [s.barBase, s.stackBase]) {
1632
- if (!a) continue;
1633
- for (let i = 0; i < a.length; i++) if (a[i] === -Infinity) a[i] = floor;
1634
- }
1396
+ /** Screen pixel of a data point */
1397
+ var toScreen = (s, x, y) => s.horizontal ? {
1398
+ x: s.yToPx(y),
1399
+ y: s.xToPx(x)
1400
+ } : {
1401
+ x: s.xToPx(x),
1402
+ y: s.yToPx(y)
1635
1403
  };
1636
- /** Position (0-1) of a heatmap value on its color ramp */
1637
- var heatPosition = (s, v) => {
1638
- const [lo, hi] = s.valueRange;
1639
- const mid = s.colorMidpoint;
1640
- let t;
1641
- if (mid !== null && mid > lo && mid < hi) t = v < mid ? (v - lo) / (mid - lo) * .5 : .5 + (v - mid) / (hi - mid) * .5;
1642
- else t = (v - lo) / (hi - lo || 1);
1643
- return t < 0 ? 0 : t > 1 ? 1 : t;
1404
+ /** Data point under a screen pixel */
1405
+ var toData = (s, px, py) => s.horizontal ? {
1406
+ x: s.pxToX(py),
1407
+ y: s.pxToY(px)
1408
+ } : {
1409
+ x: s.pxToX(px),
1410
+ y: s.pxToY(py)
1644
1411
  };
1412
+ /** Coordinate of a screen pixel along the x axis (`xToPx` space) */
1413
+ var alongX = (s, px, py) => s.horizontal ? py : px;
1414
+ /** Coordinate of a screen pixel along the value axis (`yToPx` space) */
1415
+ var alongY = (s, px, py) => s.horizontal ? px : py;
1645
1416
  /**
1646
- * Built-in ramps of heatmaps, perceptual (OKLab) and theme-aware: light
1647
- * themes go from a near-white tint to a deep hue, dark themes from a dark
1648
- * tint of the surface to a bright saturated hue; diverging ramps meet in a
1649
- * neutral middle
1417
+ * Screen rectangle between two pixels along x (`xa`, `xb`) and two along
1418
+ * y (`ya`, `yb`)
1650
1419
  */
1651
- var heatmapStops = (scheme, color, theme, negative) => {
1652
- const dark = luminance(theme.background) < .4;
1653
- if (scheme === "diverging") {
1654
- const neutral = mixOklab(theme.surface, theme.textMuted, dark ? .12 : .08);
1655
- const end = (c) => dark ? withLightness(c, .7, 1.25) : withLightness(c, .5, 1.05);
1656
- const side = (c) => mixOklab(neutral, end(c), .5);
1657
- return [
1658
- end(negative),
1659
- side(negative),
1660
- neutral,
1661
- side(color),
1662
- end(color)
1663
- ];
1664
- }
1665
- return dark ? [
1666
- mixOklab(theme.surface, color, .14),
1667
- withLightness(color, .46, .95),
1668
- withLightness(color, .64, 1.2),
1669
- withLightness(color, .8, 1.15)
1670
- ] : [
1671
- mixOklab(theme.surface, color, .06),
1672
- withLightness(color, .8, .6),
1673
- withLightness(color, .58, 1),
1674
- withLightness(color, .36, .9)
1675
- ];
1676
- };
1677
- var buildModel = (options, theme, hidden, cache, resolver = null) => {
1678
- const resolve = resolver ?? theme.resolve ?? ((value, fallback) => resolveStaticColor(value, {}, fallback));
1679
- const input = options.series;
1680
- const locale = resolveLocale(options.locale);
1681
- const tooltipOptions = typeof options.tooltip === "object" ? options.tooltip : void 0;
1682
- const labels = chartLabels(locale, {
1683
- ...definedEntries(tooltipOptions?.labels),
1684
- ...definedEntries(options.labels)
1685
- });
1686
- const datas = input.map((s) => s.data);
1687
- const xType = options.xAxis?.type ?? detectXType(datas);
1688
- const categories = xType === "category" ? [...options.xAxis?.categories ?? collectCategories(datas)] : [];
1689
- const categoryMap = new Map(categories.map((c, i) => [c, i]));
1690
- const resolveX = xResolver(xType, (name) => categoryMap.get(name) ?? NaN);
1691
- const yOpts = options.yAxis;
1692
- const heatmapData = input.filter((s) => s.type === "heatmap").map((s) => s.data);
1693
- const yCategories = yOpts?.type === "category" || !yOpts?.type && heatmapData.length ? [...yOpts?.categories ?? collectYCategories(heatmapData)] : [];
1694
- const yType = yOpts?.type === "category" || yCategories.length ? "category" : yOpts?.type === "time" || !yOpts?.type && hasDateY(datas) ? "time" : yOpts?.type === "log" ? "log" : "linear";
1695
- const yLog = yType === "log";
1696
- const yCategoryMap = new Map(yCategories.map((c, i) => [c, i]));
1697
- const resolveY = (v) => typeof v === "number" ? v : typeof v === "string" ? yType === "category" ? yCategoryMap.get(v) ?? NaN : yType === "time" ? Date.parse(v) : Number(v) : v instanceof Date ? v.getTime() : NaN;
1698
- const resolverKey = `${xType}|${categories.join("")}|${yType}|${yCategories.join("")}`;
1699
- const percent = options.stackMode === "percent";
1700
- const horizontal = options.orientation === "horizontal";
1701
- const usedKeys = /* @__PURE__ */ new Set();
1702
- const series = input.map((s, index) => {
1703
- let key = s.id ?? `#${index}`;
1704
- while (usedKeys.has(key)) key += "'";
1705
- usedKeys.add(key);
1706
- let entry = cache.get(key);
1707
- if (!entry || entry.data !== s.data || entry.resolverKey !== resolverKey) {
1708
- entry = {
1709
- data: s.data,
1710
- resolverKey,
1711
- cols: normalizeData(s.data, resolveX, resolveY)
1712
- };
1713
- cache.set(key, entry);
1714
- }
1715
- const type = s.type ?? "line";
1716
- const paletteColor = theme.palette[index % theme.palette.length];
1717
- const color = s.color ? resolve(s.color, paletteColor) : paletteColor;
1718
- const cols = entry.cols;
1719
- let pointColors = null;
1720
- if ((type === "scatter" || type === "bar" || type === "boxplot") && cols.colors) {
1721
- const resolved = /* @__PURE__ */ new Map();
1722
- pointColors = cols.colors.map((c) => {
1723
- if (c === void 0) return void 0;
1724
- let rgba = resolved.get(c);
1725
- if (!rgba) resolved.set(c, rgba = resolve(c, color));
1726
- return rgba;
1727
- });
1728
- }
1729
- let colorScale = [];
1730
- let valueRange = [0, 1];
1731
- let cellColors = null;
1732
- let colorMidpoint = null;
1733
- let colorLut = null;
1734
- if (type === "heatmap") {
1735
- const scale = s.colorScale;
1736
- const scheme = scale === "diverging" ? "diverging" : "sequential";
1737
- colorScale = typeof scale === "object" && scale.length ? scale.map((c) => resolve(c, color)) : heatmapStops(scheme, color, theme, resolve(DEFAULT_DOWN, [
1738
- .86,
1739
- .15,
1740
- .15,
1741
- 1
1742
- ]));
1743
- valueRange = s.valueRange ? [s.valueRange[0], s.valueRange[1]] : [cols.valueMin, cols.valueMax];
1744
- if (!Number.isFinite(valueRange[0])) valueRange = [0, 1];
1745
- const [lo, hi] = valueRange;
1746
- colorMidpoint = s.colorMidpoint ?? (scheme === "diverging" && typeof scale !== "object" ? lo < 0 && hi > 0 ? 0 : (lo + hi) / 2 : null);
1747
- const lut = oklabRamp(colorScale);
1748
- colorLut = lut;
1749
- const values = cols.value ?? cols.y;
1750
- const ramp = {
1751
- valueRange,
1752
- colorMidpoint
1753
- };
1754
- cellColors = new Uint32Array(cols.count);
1755
- for (let i = 0; i < cols.count; i++) {
1756
- const v = values[i];
1757
- if (!Number.isFinite(v) || !Number.isFinite(cols.y[i])) continue;
1758
- cellColors[i] = lut[Math.round(heatPosition(ramp, v) * 255)];
1759
- }
1760
- }
1761
- const connector = s.connector ? typeof s.connector === "object" ? s.connector : {} : null;
1762
- const id = s.id ?? String(index);
1763
- return {
1764
- index,
1765
- key,
1766
- id,
1767
- name: s.name ?? `${labels.series} ${index + 1}`,
1768
- type,
1769
- color,
1770
- colorCss: toCss(color),
1771
- lineWidth: s.lineWidth ?? (type === "candlestick" ? 1 : type === "boxplot" ? 1.5 : 2),
1772
- pointSize: s.pointSize ?? (type === "boxplot" ? 6 : 8),
1773
- opacity: s.opacity ?? (type === "area" ? .28 : type === "scatter" ? .78 : type === "boxplot" ? .22 : 1),
1774
- hidden: !!s.hidden || hidden.has(id),
1775
- showLabels: !!s.showLabels,
1776
- labelsInside: s.showLabels === "inside" && type === "bar",
1777
- curve: s.curve ?? "linear",
1778
- cols,
1779
- raw: cols,
1780
- yAxis: s.yAxis === "right" && !horizontal ? "right" : "left",
1781
- floating: type === "bar" && !!cols.y0,
1782
- barOffset: 0,
1783
- barHalfWidth: 0,
1784
- barBase: null,
1785
- barTop: null,
1786
- stackBase: null,
1787
- stackTop: null,
1788
- pointColors,
1789
- upColor: type === "candlestick" ? resolve(s.upColor ?? DEFAULT_UP, [
1790
- .09,
1791
- .64,
1792
- .29,
1793
- 1
1794
- ]) : color,
1795
- downColor: type === "candlestick" ? resolve(s.downColor ?? DEFAULT_DOWN, [
1796
- .86,
1797
- .15,
1798
- .15,
1799
- 1
1800
- ]) : color,
1801
- cellColors,
1802
- cellHalf: {
1803
- x: .5,
1804
- y: .5
1805
- },
1806
- colorScale,
1807
- valueRange,
1808
- colorMidpoint,
1809
- colorLut,
1810
- tooltip: s.tooltip !== false,
1811
- stroke: s.stroke !== void 0 && Number.isFinite(s.stroke) ? Math.max(0, s.stroke) : null,
1812
- outlierShape: s.outlierShape ?? "hollow",
1813
- outlierSize: s.outlierSize ?? s.pointSize ?? 7,
1814
- connector: connector && type === "bar" ? {
1815
- color: connector.color ? resolve(connector.color, theme.textMuted) : theme.textMuted,
1816
- dash: [...connector.dash ?? [3, 3]],
1817
- width: connector.width ?? 1
1818
- } : null,
1819
- radius: s.radius ?? (type === "heatmap" ? 2 : type === "boxplot" ? 0 : 3),
1820
- format: s.format,
1821
- valueName: s.valueName
1822
- };
1823
- });
1824
- const visible = series.filter((s) => !s.hidden);
1825
- const xLog = xType === "log";
1826
- if (xLog) for (const s of series) s.cols = logXColumns(s.raw);
1827
- const allScatter = series.length > 0 && series.every((s) => s.type === "scatter" || s.type === "heatmap");
1828
- const slotted = visible.filter((s) => s.type === "bar" || s.type === "boxplot");
1829
- const bars = visible.filter((s) => s.type === "bar");
1830
- const band = bandOf(slotted, "x", xType === "category");
1831
- if (slotted.length) {
1832
- const slots = /* @__PURE__ */ new Map();
1833
- const stackId = (s) => input[s.index].stack ?? `\u0000${s.key}`;
1834
- for (const s of slotted) if (!slots.has(stackId(s))) slots.set(stackId(s), slots.size);
1835
- let share = 0;
1836
- for (const s of slotted) {
1837
- const w = input[s.index].barWidth;
1838
- if (w !== void 0 && w > share) share = Math.min(1, w);
1839
- }
1840
- const group = band * (share || (slots.size > 1 ? .78 : .62));
1841
- const slotWidth = group / slots.size;
1842
- for (const s of slotted) {
1843
- const slot = slots.get(stackId(s)) ?? 0;
1844
- s.barOffset = -group / 2 + slotWidth * (slot + .5);
1845
- s.barHalfWidth = slotWidth / 2 * (slots.size > 1 ? .86 : 1);
1846
- }
1847
- const stackedBars = bars.filter((s) => !s.floating);
1848
- const stacked = stackValues(stackedBars.map((s) => ({
1849
- s,
1850
- stack: stackId(s)
1851
- })), false);
1852
- const percentStacked = percent ? stackValues(stackedBars.filter((s) => input[s.index].stack !== void 0).map((s) => ({
1853
- s,
1854
- stack: stackId(s)
1855
- })), true) : [];
1856
- let p = 0;
1857
- stackedBars.forEach((s, k) => {
1858
- const values = percent && input[s.index].stack !== void 0 ? percentStacked[p++] : stacked[k];
1859
- s.barBase = values.base;
1860
- s.barTop = values.top;
1861
- });
1862
- for (const s of bars) {
1863
- const { y, y0, count } = s.cols;
1864
- if (!s.floating || !y0) continue;
1865
- const base = new Float64Array(count);
1866
- const top = new Float64Array(count);
1867
- for (let i = 0; i < count; i++) {
1868
- const valid = Number.isFinite(y[i]);
1869
- base[i] = valid ? Number.isFinite(y0[i]) ? y0[i] : 0 : NaN;
1870
- top[i] = valid ? y[i] : NaN;
1871
- }
1872
- s.barBase = base;
1873
- s.barTop = top;
1874
- }
1875
- }
1876
- for (const s of visible) if (s.type === "area" && s.cols.y0 && input[s.index].stack === void 0) s.stackBase = s.cols.y0;
1877
- const stackedLines = visible.filter((s) => (s.type === "line" || s.type === "area") && input[s.index].stack !== void 0);
1878
- if (stackedLines.length) {
1879
- const values = stackValues(stackedLines.map((s) => ({
1880
- s,
1881
- stack: input[s.index].stack ?? ""
1882
- })), percent);
1883
- stackedLines.forEach((s, k) => {
1884
- s.stackBase = values[k].base;
1885
- s.stackTop = values[k].top;
1886
- });
1887
- }
1888
- const candles = visible.filter((s) => s.type === "candlestick");
1889
- const candleBand = bandOf(candles, "x", xType === "category");
1890
- for (const s of candles) s.barHalfWidth = candleBand * .3;
1891
- const heatmaps = visible.filter((s) => s.type === "heatmap");
1892
- if (heatmaps.length) {
1893
- const cx = bandOf(heatmaps, "x", xType === "category") / 2;
1894
- const cy = bandOf(heatmaps, "y", yType === "category") / 2;
1895
- for (const s of heatmaps) s.cellHalf = {
1896
- x: cx,
1897
- y: cy
1898
- };
1899
- }
1900
- const xOpts = options.xAxis;
1901
- let xRange;
1902
- if (xType === "category") xRange = [-.5, Math.max(1, categories.length) - .5];
1903
- else {
1904
- const values = [];
1905
- for (const s of visible) {
1906
- values.push(s.cols.xMin, s.cols.xMax);
1907
- const pad = s.type === "bar" || s.type === "boxplot" ? band / 2 : s.type === "candlestick" ? candleBand / 2 : s.type === "heatmap" ? s.cellHalf.x : 0;
1908
- if (pad) values.push(s.cols.xMin - pad, s.cols.xMax + pad);
1909
- }
1910
- xRange = ensureSpan(...extent(values), xType === "time" ? [Date.now() - 864e5, Date.now()] : [0, 1]);
1911
- const pointsOnly = series.every((s) => s.type === "scatter");
1912
- if (pointsOnly) {
1913
- const pad = (xRange[1] - xRange[0]) * .04;
1914
- xRange = [xRange[0] - pad, xRange[1] + pad];
1915
- }
1916
- if ((xOpts?.nice ?? (pointsOnly && xType === "linear")) && xType === "linear") xRange = niceDomain(xRange[0], xRange[1], xOpts?.tickCount ?? 6);
1917
- if (xLog && xOpts?.nice !== false) xRange = niceLogRange(xRange);
1918
- }
1919
- const xBound = (v) => xLog ? Math.log10(v) : v;
1920
- if (xOpts?.min !== void 0) xRange[0] = xBound(xOpts.min);
1921
- if (xOpts?.max !== void 0) xRange[1] = xBound(xOpts.max);
1922
- xRange = ensureSpan(xRange[0], xRange[1]);
1923
- if (yLog) for (const s of series) {
1924
- if (s.yAxis === "right" || s.type === "heatmap") continue;
1925
- logColumns(s, LOG_MAP);
1926
- }
1927
- const valuesOf = (list, zero, log = false) => {
1928
- const values = [];
1929
- for (const s of list) if (s.type === "bar" && s.barBase && s.barTop) values.push(...extent(s.barBase), ...extent(s.barTop));
1930
- else if (s.stackBase && s.stackTop) values.push(...extent(s.stackBase), ...extent(s.stackTop));
1931
- else if (s.type === "heatmap") values.push(s.cols.yMin - s.cellHalf.y, s.cols.yMax + s.cellHalf.y);
1932
- else values.push(s.cols.yMin, s.cols.yMax);
1933
- const listBars = list.filter((s) => s.type === "bar");
1934
- if (!log && (listBars.some((s) => !s.floating) || zero)) values.push(0);
1935
- if (listBars.some((s) => s.showLabels && !s.labelsInside)) {
1936
- const [lo, hi] = extent(values);
1937
- if (hi > 0) values.push(hi + (hi - Math.min(0, lo)) * .1);
1938
- if (lo < 0) values.push(lo - (Math.max(0, hi) - lo) * .1);
1939
- }
1940
- return values;
1941
- };
1942
- const leftVisible = visible.filter((s) => s.yAxis === "left");
1943
- const leftSeries = leftVisible.length ? leftVisible : visible;
1944
- const leftBars = leftSeries.filter((s) => s.type === "bar");
1945
- let yRange;
1946
- if (yType === "category") yRange = [-.5, Math.max(1, yCategories.length) - .5];
1947
- else {
1948
- const yValues = valuesOf(leftSeries, yOpts?.zero, yLog);
1949
- const filled = leftBars.length > 0 || leftSeries.some((s) => s.type === "area");
1950
- yRange = ensureSpan(...extent(yValues));
1951
- if (series.every((s) => s.type === "scatter")) {
1952
- const pad = (yRange[1] - yRange[0]) * .06;
1953
- yRange = [yRange[0] - pad, yRange[1] + pad];
1954
- } else if ((candles.length || leftSeries.some((s) => s.type === "boxplot")) && !leftBars.length) {
1955
- const pad = (yRange[1] - yRange[0]) * .04;
1956
- yRange = [yRange[0] - pad, yRange[1] + pad];
1957
- }
1958
- if ((yOpts?.nice ?? !heatmaps.length) && yType === "linear") yRange = niceDomain(yRange[0], yRange[1], yOpts?.tickCount ?? 5);
1959
- if (yLog && yOpts?.nice !== false) yRange = decades(yRange);
1960
- if (percent && (bars.length || stackedLines.length)) yRange = [extent(yValues)[0] < 0 ? -100 : 0, 100];
1961
- if (filled && !yOpts?.zero && !yLog) {
1962
- if (yRange[0] < 0 && extent(yValues)[0] >= 0) yRange[0] = 0;
1963
- }
1964
- }
1965
- const yBound = (v) => yLog ? Math.log10(v) : v;
1966
- if (yOpts?.min !== void 0) yRange[0] = yBound(yOpts.min);
1967
- if (yOpts?.max !== void 0) yRange[1] = yBound(yOpts.max);
1968
- yRange = ensureSpan(yRange[0], yRange[1]);
1969
- if (yLog) {
1970
- for (const s of series) if (s.yAxis === "left") floorBases(s, yRange[0]);
1971
- }
1972
- const y2Opts = options.yAxis2;
1973
- let y2 = null;
1974
- let y2Map = null;
1975
- const rightSeries = series.filter((s) => s.yAxis === "right");
1976
- if (rightSeries.length && yType !== "category") {
1977
- const y2Log = y2Opts?.type === "log";
1978
- const rightVisible = rightSeries.filter((s) => !s.hidden);
1979
- const [lo, hi] = ensureSpan(...extent(y2Log ? rightVisible.flatMap((s) => {
1980
- const c = mapColumns(s.raw, LOG_MAP);
1981
- return [c.yMin, c.yMax];
1982
- }) : valuesOf(rightVisible, y2Opts?.zero)));
1983
- let domain = y2Opts?.nice === false ? [lo, hi] : y2Log ? decades([lo, hi]) : alignedDomain(lo, hi, yRange, yOpts?.tickCount ?? 5);
1984
- const y2Bound = (v) => y2Log ? Math.log10(v) : v;
1985
- if (y2Opts?.min !== void 0) domain[0] = y2Bound(y2Opts.min);
1986
- if (y2Opts?.max !== void 0) domain[1] = y2Bound(y2Opts.max);
1987
- domain = ensureSpan(domain[0], domain[1]);
1988
- const scale = (yRange[1] - yRange[0]) / (domain[1] - domain[0]);
1989
- const map = {
1990
- scale,
1991
- offset: yRange[0] - domain[0] * scale,
1992
- log: y2Log
1993
- };
1994
- y2Map = map;
1995
- const zero = y2Log ? yRange[0] : mapper(map)(Math.min(Math.max(0, domain[0]), domain[1]));
1996
- for (const s of rightSeries) {
1997
- logColumns(s, map, zero);
1998
- floorBases(s, yRange[0]);
1999
- }
2000
- y2 = axisModel(y2Opts, y2Log ? "log" : "linear", [], false, locale);
2001
- }
2002
- const timeline = yType === "time" && series.some((s) => s.type === "bar") && series.every((s) => s.type === "bar" ? s.floating : s.type === "scatter");
2003
- const zoomOption = options.interaction === false ? false : options.interaction === true || options.interaction === void 0 ? true : options.interaction.zoom ?? true;
2004
- const zoomAxes = zoomOption === false ? {
2005
- x: false,
2006
- y: false
2007
- } : zoomOption === true ? {
2008
- x: true,
2009
- y: allScatter
1420
+ var screenRect = (s, xa, xb, ya, yb) => {
1421
+ const a = Math.min(xa, xb);
1422
+ const b = Math.max(xa, xb);
1423
+ const c = Math.min(ya, yb);
1424
+ const d = Math.max(ya, yb);
1425
+ return s.horizontal ? {
1426
+ x: c,
1427
+ y: a,
1428
+ width: d - c,
1429
+ height: b - a
2010
1430
  } : {
2011
- x: zoomOption.includes("x"),
2012
- y: zoomOption.includes("y")
2013
- };
2014
- const yFormat = yOpts?.format ?? (percent && (bars.length || stackedLines.length) ? (v) => formatPercent(v, locale) : void 0);
2015
- return {
2016
- series,
2017
- x: axisModel(xOpts, xType, categories, false, locale),
2018
- y: {
2019
- ...axisModel(yOpts, yType, yCategories, yType !== "category", locale),
2020
- format: yFormat
2021
- },
2022
- y2,
2023
- y2Map,
2024
- horizontal,
2025
- locale,
2026
- labels,
2027
- formatValue: valueFormatter(locale),
2028
- base: {
2029
- x: xRange,
2030
- y: yRange
2031
- },
2032
- allScatter,
2033
- zoom: zoomAxes,
2034
- tooltipMode: tooltipOptions?.mode ?? (allScatter ? "nearest" : timeline ? "item" : "axis"),
2035
- baseline: yLog ? yRange[0] : Math.min(Math.max(0, yRange[0]), yRange[1]),
2036
- minSpan: {
2037
- x: xType === "category" ? 1 : (xRange[1] - xRange[0]) * 1e-7,
2038
- y: yType === "category" ? 1 : (yRange[1] - yRange[0]) * 1e-7
2039
- },
2040
- percent,
2041
- stacks: input.map((s) => s.stack !== void 0 && (s.type === "bar" || s.type === "line" || s.type === "area" || !s.type) ? `${s.type === "bar" ? "bar" : "line"}:${s.stack}` : null)
1431
+ x: a,
1432
+ y: c,
1433
+ width: b - a,
1434
+ height: d - c
2042
1435
  };
2043
1436
  };
2044
- /** Y value drawn for a point: its stacked top for stacks, the close for candles */
2045
- var plottedY = (s, i) => s.barTop ? s.barTop[i] : s.stackTop ? s.stackTop[i] : s.cols.y[i];
2046
- var grids = /* @__PURE__ */ new WeakMap();
2047
- /** Fill opacity of bar `i`: its own `opacity`, else the one of the series */
2048
- var barOpacity = (s, i) => {
2049
- const own = s.cols.opacities?.[i];
2050
- return own !== void 0 && Number.isFinite(own) ? own : s.opacity;
2051
- };
2052
- /** Spatial index of a scatter series, built on first use and kept with its data */
2053
- var gridOf = (s) => {
2054
- let grid = grids.get(s.cols);
2055
- if (!grid) {
2056
- grid = new GridIndex(s.cols.x, s.cols.y, s.cols.count);
2057
- grids.set(s.cols, grid);
2058
- }
2059
- return grid;
2060
- };
2061
- //#endregion
2062
- //#region ../charts/src/engine/overlay.ts
2063
- /** Padding of the labels inside the bars, CSS pixels */
2064
- var INSIDE_PAD$1 = 6;
2065
1437
  /**
2066
- * Color of a text written on a bar of color `fill` (alpha included) over
2067
- * `background`: white or near-black, whichever contrasts more; `undefined`
2068
- * for translucent bars (the theme text color with a halo reads better)
1438
+ * Inset (per side) and corner radius of heatmap cells of `width` ×
1439
+ * `height` CSS pixels: a hairline apart (1px, 2px on large cells), corners
1440
+ * of `radius` at most 20% of the cell; tiny cells touch
2069
1441
  */
2070
- var insideLabelColor = (fill, background) => {
2071
- const alpha = fill[3];
2072
- if (alpha < .6) return void 0;
2073
- const l = relativeLuminance$1(mix(background, fill, alpha));
2074
- return 1.05 / (l + .05) >= (l + .05) / .062 ? "#ffffff" : "rgba(17, 24, 39, 0.92)";
2075
- };
2076
- var font = (theme, size = theme.fontSize, weight = 400) => `${weight} ${size}px ${theme.fontFamily}`;
2077
- var crisp = (v) => Math.round(v) + .5;
2078
- /** Path of a rounded box with half-height corners */
2079
- var pill = (ctx, x, y, w, h) => {
2080
- const r = Math.min(h / 2, w / 2);
2081
- ctx.beginPath();
2082
- ctx.moveTo(x + r, y);
2083
- ctx.lineTo(x + w - r, y);
2084
- ctx.arcTo(x + w, y, x + w, y + r, r);
2085
- ctx.lineTo(x + w, y + h - r);
2086
- ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
2087
- ctx.lineTo(x + r, y + h);
2088
- ctx.arcTo(x, y + h, x, y + h - r, r);
2089
- ctx.lineTo(x, y + r);
2090
- ctx.arcTo(x, y, x + r, y, r);
2091
- ctx.closePath();
2092
- };
2093
- /** Box of a text annotation, kept inside the plot */
2094
- var textBox = (a, s, width, height) => {
2095
- const { plot } = s;
2096
- const at = toScreen(s, a.x ?? 0, a.y ?? 0);
2097
- const free = s.horizontal ? {
2098
- x: a.y === void 0,
2099
- y: a.x === void 0
2100
- } : {
2101
- x: a.x === void 0,
2102
- y: a.y === void 0
1442
+ var heatCellStyle = (width, height, radius) => {
1443
+ const size = Math.min(Math.abs(width), Math.abs(height));
1444
+ if (!(size >= 4)) return {
1445
+ inset: 0,
1446
+ radius: 0
2103
1447
  };
2104
- const px = free.x ? plot.x + plot.width / 2 : at.x;
2105
- const py = free.y ? plot.y + plot.height / 2 : at.y;
2106
- const gap = 6;
2107
- let x = px - width / 2;
2108
- let y = py - height / 2;
2109
- if (a.anchor === "top") y = py + gap;
2110
- else if (a.anchor === "bottom") y = py - gap - height;
2111
- else if (a.anchor === "left") x = px + gap;
2112
- else if (a.anchor === "right") x = px - gap - width;
2113
- x = Math.max(plot.x + gap, Math.min(x, plot.x + plot.width - gap - width));
2114
- y = Math.max(plot.y + 2, Math.min(y, plot.y + plot.height - 2 - height));
2115
1448
  return {
2116
- x,
2117
- y,
2118
- width,
2119
- height
1449
+ inset: Math.round(Math.min(1, .5 + size / 96) * 4) / 4,
1450
+ radius: Math.round(Math.max(0, Math.min(radius, size * .2)) * 4) / 4
2120
1451
  };
2121
1452
  };
2122
- /** Screen box of an area annotation, clipped to the plot */
2123
- var rectOf = (a, s) => {
2124
- const { plot } = s;
2125
- const [xLo, xHi] = s.horizontal ? [plot.y, plot.y + plot.height] : [plot.x, plot.x + plot.width];
2126
- const [yLo, yHi] = s.horizontal ? [plot.x, plot.x + plot.width] : [plot.y, plot.y + plot.height];
2127
- const along = (px, lo, hi) => px < lo ? lo : px > hi ? hi : px;
2128
- return screenRect(s, along(s.xToPx(a.x0 ?? -Infinity), xLo, xHi), along(s.xToPx(a.x1 ?? Infinity), xLo, xHi), along(s.yToPx(a.y0 ?? -Infinity), yLo, yHi), along(s.yToPx(a.y1 ?? Infinity), yLo, yHi));
1453
+ /** Room for the axes, then the plot area */
1454
+ var computePlot = (input) => {
1455
+ const { width, height, padding, fontSize } = input;
1456
+ const line = Math.ceil(fontSize * 1.4);
1457
+ const left = padding.left + (input.yLabelWidth > 0 ? input.yLabelWidth + 8 : 0) + (input.yTitle ? line + 6 : 0);
1458
+ const bottom = padding.bottom + (input.xAxisVisible ? line + 8 : 0) + (input.xTitle ? line + 4 : 0);
1459
+ const top = padding.top;
1460
+ const y2Width = input.y2LabelWidth ?? 0;
1461
+ const right = y2Width > 0 || input.y2Title ? Math.max(4, padding.left) + (y2Width > 0 ? y2Width + 8 : 0) + (input.y2Title ? line + 6 : 0) : padding.right;
1462
+ return {
1463
+ x: Math.round(left),
1464
+ y: Math.round(top),
1465
+ width: Math.max(10, Math.round(width - left - right)),
1466
+ height: Math.max(10, Math.round(height - top - bottom))
1467
+ };
1468
+ };
1469
+ //#endregion
1470
+ //#region ../charts/src/engine/labels.ts
1471
+ var CHART_LABELS = {
1472
+ en: {
1473
+ open: "Open",
1474
+ high: "High",
1475
+ low: "Low",
1476
+ close: "Close",
1477
+ max: "Max",
1478
+ q3: "Q3",
1479
+ median: "Median",
1480
+ q1: "Q1",
1481
+ min: "Min",
1482
+ outliers: "Outliers",
1483
+ value: "Value",
1484
+ x: "x",
1485
+ y: "y",
1486
+ series: "Series",
1487
+ chart: "Chart",
1488
+ chartOf: "Chart: {series}",
1489
+ start: "Start",
1490
+ end: "End",
1491
+ duration: "Duration",
1492
+ progress: "Progress",
1493
+ dependencies: "Depends on",
1494
+ outlier: "Outlier"
1495
+ },
1496
+ zh: {
1497
+ open: "开盘",
1498
+ high: "最高",
1499
+ low: "最低",
1500
+ close: "收盘",
1501
+ max: "最大值",
1502
+ q3: "上四分位",
1503
+ median: "中位数",
1504
+ q1: "下四分位",
1505
+ min: "最小值",
1506
+ outliers: "异常值",
1507
+ value: "数值",
1508
+ series: "系列",
1509
+ chart: "图表",
1510
+ chartOf: "图表:{series}",
1511
+ start: "开始",
1512
+ end: "结束",
1513
+ duration: "时长",
1514
+ progress: "进度",
1515
+ dependencies: "前置任务",
1516
+ outlier: "异常值"
1517
+ },
1518
+ ja: {
1519
+ open: "始値",
1520
+ high: "高値",
1521
+ low: "安値",
1522
+ close: "終値",
1523
+ max: "最大値",
1524
+ q3: "第3四分位",
1525
+ median: "中央値",
1526
+ q1: "第1四分位",
1527
+ min: "最小値",
1528
+ outliers: "外れ値",
1529
+ value: "値",
1530
+ series: "系列",
1531
+ chart: "グラフ",
1532
+ chartOf: "グラフ:{series}",
1533
+ start: "開始",
1534
+ end: "終了",
1535
+ duration: "期間",
1536
+ progress: "進捗",
1537
+ dependencies: "先行タスク",
1538
+ outlier: "外れ値"
1539
+ },
1540
+ fr: {
1541
+ open: "Ouverture",
1542
+ high: "Plus haut",
1543
+ low: "Plus bas",
1544
+ close: "Clôture",
1545
+ max: "Max",
1546
+ q3: "Q3",
1547
+ median: "Médiane",
1548
+ q1: "Q1",
1549
+ min: "Min",
1550
+ outliers: "Valeurs aberrantes",
1551
+ value: "Valeur",
1552
+ series: "Série",
1553
+ chart: "Graphique",
1554
+ chartOf: "Graphique : {series}",
1555
+ start: "Début",
1556
+ end: "Fin",
1557
+ duration: "Durée",
1558
+ progress: "Avancement",
1559
+ dependencies: "Dépend de",
1560
+ outlier: "Valeur aberrante"
1561
+ }
1562
+ };
1563
+ /** Texts of a locale with the overrides */
1564
+ var chartLabels = (locale, overrides) => localeLabels(CHART_LABELS, locale, overrides);
1565
+ /** Separator of the list of series names in accessible names */
1566
+ var listSeparator = (locale) => /^(zh|ja)\b/i.test(locale) ? "、" : ", ";
1567
+ /** Default accessible name of a chart model: "Chart: Revenue, Costs" (unnamed series left out) */
1568
+ var chartAriaLabel = (model) => {
1569
+ const named = model.series.filter((s) => s.name);
1570
+ return named.length ? model.labels.chartOf.replace("{series}", named.map((s) => s.name).join(listSeparator(model.locale))) : model.labels.chart;
1571
+ };
1572
+ /**
1573
+ * Localized accessible description of a chart, for platforms that build their
1574
+ * own accessible name ("Chart: Revenue, Costs", "营收:营收、成本").
1575
+ * With a `title`, the title replaces the generic "Chart" word.
1576
+ */
1577
+ function describeChart$1(input) {
1578
+ const locale = input.locale ?? "en";
1579
+ const labels = chartLabels(locale, input.labels);
1580
+ const series = input.series ?? [];
1581
+ const title = input.title?.trim();
1582
+ const generic = chartAriaLabel({
1583
+ labels,
1584
+ locale,
1585
+ series
1586
+ });
1587
+ if (!title) return generic;
1588
+ if (!series.length) return title;
1589
+ const index = labels.chartOf.indexOf(labels.chart);
1590
+ if (index < 0 || !labels.chart) return `${title}. ${generic}`;
1591
+ const names = series.map((s) => s.name).join(listSeparator(locale));
1592
+ const fill = (part) => part.split("{series}").join(names);
1593
+ return fill(labels.chartOf.slice(0, index)) + title + fill(labels.chartOf.slice(index + labels.chart.length));
1594
+ }
1595
+ //#endregion
1596
+ //#region ../charts/src/engine/spatial.ts
1597
+ var GridIndex = class {
1598
+ constructor(x, y, count, pointsPerCell = 4) {
1599
+ let xMin = Infinity;
1600
+ let xMax = -Infinity;
1601
+ let yMin = Infinity;
1602
+ let yMax = -Infinity;
1603
+ let valid = 0;
1604
+ for (let i = 0; i < count; i++) {
1605
+ const xi = x[i];
1606
+ const yi = y[i];
1607
+ if (!Number.isFinite(xi) || !Number.isFinite(yi)) continue;
1608
+ valid++;
1609
+ if (xi < xMin) xMin = xi;
1610
+ if (xi > xMax) xMax = xi;
1611
+ if (yi < yMin) yMin = yi;
1612
+ if (yi > yMax) yMax = yi;
1613
+ }
1614
+ const side = Math.min(1024, Math.max(1, Math.ceil(Math.sqrt(valid / pointsPerCell))));
1615
+ this.cols = side;
1616
+ this.rows = side;
1617
+ this.x0 = valid ? xMin : 0;
1618
+ this.y0 = valid ? yMin : 0;
1619
+ this.cellW = valid && xMax > xMin ? (xMax - xMin) / side : 1;
1620
+ this.cellH = valid && yMax > yMin ? (yMax - yMin) / side : 1;
1621
+ const cells = new Int32Array(count);
1622
+ const counts = new Uint32Array(side * side + 1);
1623
+ for (let i = 0; i < count; i++) {
1624
+ const xi = x[i];
1625
+ const yi = y[i];
1626
+ if (!Number.isFinite(xi) || !Number.isFinite(yi)) {
1627
+ cells[i] = -1;
1628
+ continue;
1629
+ }
1630
+ const cell = this.cellOf(xi, yi);
1631
+ cells[i] = cell;
1632
+ counts[cell + 1]++;
1633
+ }
1634
+ for (let c = 1; c < counts.length; c++) counts[c] += counts[c - 1];
1635
+ this.starts = counts.slice();
1636
+ const cursor = counts;
1637
+ this.order = new Uint32Array(valid);
1638
+ for (let i = 0; i < count; i++) {
1639
+ const cell = cells[i];
1640
+ if (cell >= 0) this.order[cursor[cell]++] = i;
1641
+ }
1642
+ }
1643
+ column(xi) {
1644
+ const c = Math.floor((xi - this.x0) / this.cellW);
1645
+ return c < 0 ? 0 : c >= this.cols ? this.cols - 1 : c;
1646
+ }
1647
+ row(yi) {
1648
+ const r = Math.floor((yi - this.y0) / this.cellH);
1649
+ return r < 0 ? 0 : r >= this.rows ? this.rows - 1 : r;
1650
+ }
1651
+ cellOf(xi, yi) {
1652
+ return this.row(yi) * this.cols + this.column(xi);
1653
+ }
1654
+ /** Calls `visit` with every point that may lie inside the box */
1655
+ query(xMin, xMax, yMin, yMax, visit) {
1656
+ if (this.order.length === 0) return;
1657
+ const c0 = this.column(Math.min(xMin, xMax));
1658
+ const c1 = this.column(Math.max(xMin, xMax));
1659
+ const r0 = this.row(Math.min(yMin, yMax));
1660
+ const r1 = this.row(Math.max(yMin, yMax));
1661
+ for (let r = r0; r <= r1; r++) for (let c = c0; c <= c1; c++) {
1662
+ const cell = r * this.cols + c;
1663
+ for (let k = this.starts[cell]; k < this.starts[cell + 1]; k++) visit(this.order[k]);
1664
+ }
1665
+ }
1666
+ };
1667
+ //#endregion
1668
+ //#region ../charts/src/engine/model.ts
1669
+ /** Plotted units of a log axis: the base-10 logarithm of the values */
1670
+ var LOG_MAP = {
1671
+ scale: 1,
1672
+ offset: 0,
1673
+ log: true
2129
1674
  };
2130
- /** Whether an area annotation has a title block */
2131
- var hasAreaTitle = (a) => !!(a.label || a.badge || a.detail || a.description);
2132
- /** Text cut with an ellipsis to fit `max` pixels ("" when nothing fits) */
2133
- var fitText = (text, max, width) => {
2134
- if (width(text) <= max) return text;
2135
- const chars = Array.from(text);
2136
- let lo = 0;
2137
- let hi = chars.length;
2138
- while (lo < hi) {
2139
- const mid = Math.ceil((lo + hi) / 2);
2140
- if (width(`${chars.slice(0, mid).join("")}…`) <= max) lo = mid;
2141
- else hi = mid - 1;
1675
+ var firstX = (data) => {
1676
+ if (typeof data === "object" && data !== null && !("length" in data)) {
1677
+ const xs = data.x;
1678
+ return xs && xs.length ? xs[0] : void 0;
1679
+ }
1680
+ const list = data;
1681
+ for (let i = 0; i < Math.min(list.length, 8); i++) {
1682
+ const item = list[i];
1683
+ if (Array.isArray(item)) return item[0];
1684
+ if (item && typeof item === "object") return item.x;
2142
1685
  }
2143
- return lo > 0 ? `${chars.slice(0, lo).join("").trimEnd()}…` : "";
2144
1686
  };
2145
- /**
2146
- * Layout of the title block of an area: `[badge] label detail` on the first
2147
- * line, the description under it; the block sits in the `labelPosition`
2148
- * corner, 10px from the edges, and the texts are cut to the area width
2149
- */
2150
- var areaTitleLayout = (a, r, fontSize, measure) => {
2151
- if (!hasAreaTitle(a) || r.width <= 0 || r.height <= 0) return null;
2152
- const pad = 10;
2153
- const lineHeight = fontSize + 4;
2154
- const max = Math.max(0, r.width - 20);
2155
- const labelSize = fontSize + 1;
2156
- const badgeSize = Math.max(9, fontSize - 1);
2157
- const gap = 6;
2158
- const badge = a.badge ? fitText(a.badge, Math.max(0, max - 10), (t) => measure(t, badgeSize, 700)) : "";
2159
- const badgeWidth = badge ? measure(badge, badgeSize, 700) + 10 : 0;
2160
- let detail = a.detail ?? "";
2161
- let detailWidth = detail ? measure(detail, fontSize, 500) : 0;
2162
- const room = () => max - (badge ? badgeWidth + gap : 0) - (detail ? detailWidth + gap : 0);
2163
- if (detail && a.label && room() < 32) {
2164
- detail = "";
2165
- detailWidth = 0;
1687
+ /** Axis type when not given: dates -> time, strings -> category */
1688
+ var detectXType = (datas) => {
1689
+ for (const data of datas) {
1690
+ const x = firstX(data);
1691
+ if (x instanceof Date) return "time";
1692
+ if (typeof x === "string") return "category";
2166
1693
  }
2167
- if (detail && room() < 0) {
2168
- detail = fitText(detail, Math.max(0, max - (badge ? badgeWidth + gap : 0)), (t) => measure(t, fontSize, 500));
2169
- detailWidth = detail ? measure(detail, fontSize, 500) : 0;
1694
+ return "linear";
1695
+ };
1696
+ var axisModel = (options, type, categories, gridDefault, locale) => ({
1697
+ type,
1698
+ reverse: !!options?.reverse,
1699
+ categories,
1700
+ title: options?.title,
1701
+ format: options?.format,
1702
+ grid: options?.grid ?? gridDefault,
1703
+ visible: options?.visible ?? true,
1704
+ tickCount: options?.tickCount,
1705
+ locale,
1706
+ timeZone: options?.timeZone ?? "local"
1707
+ });
1708
+ /** Defined entries only (`{ open: undefined }` keeps the built-in text) */
1709
+ var definedEntries = (value) => {
1710
+ const out = {};
1711
+ if (!value) return out;
1712
+ for (const key of Object.keys(value)) if (value[key] !== void 0) out[key] = value[key];
1713
+ return out;
1714
+ };
1715
+ var extent = (values) => {
1716
+ let min = Infinity;
1717
+ let max = -Infinity;
1718
+ for (let i = 0; i < values.length; i++) {
1719
+ const v = values[i];
1720
+ if (!Number.isFinite(v)) continue;
1721
+ if (v < min) min = v;
1722
+ if (v > max) max = v;
2170
1723
  }
2171
- const label = a.label ? fitText(a.label, Math.max(0, room()), (t) => measure(t, labelSize, 600)) : "";
2172
- const line = [];
2173
- if (badge) line.push({
2174
- kind: "badge",
2175
- text: badge,
2176
- x: 0,
2177
- y: 0,
2178
- width: badgeWidth,
2179
- height: lineHeight,
2180
- size: badgeSize,
2181
- weight: 700
2182
- });
2183
- if (label) line.push({
2184
- kind: "label",
2185
- text: label,
2186
- x: 0,
2187
- y: 0,
2188
- width: measure(label, labelSize, 600),
2189
- height: lineHeight,
2190
- size: labelSize,
2191
- weight: 600
2192
- });
2193
- if (detail) line.push({
2194
- kind: "detail",
2195
- text: detail,
2196
- x: 0,
2197
- y: 0,
2198
- width: detailWidth,
2199
- height: lineHeight,
2200
- size: fontSize,
2201
- weight: 500
1724
+ return [min, max];
1725
+ };
1726
+ /** Smallest positive gap between consecutive values (bar width on numeric axes) */
1727
+ var smallestStep = (columns, sorted) => {
1728
+ let step = Infinity;
1729
+ columns.forEach((values, k) => {
1730
+ const xs = sorted[k] ? values : Float64Array.from(values).sort();
1731
+ for (let i = 1; i < xs.length; i++) {
1732
+ const d = xs[i] - xs[i - 1];
1733
+ if (d > 0 && d < step) step = d;
1734
+ }
2202
1735
  });
2203
- const description = a.description ? fitText(a.description, max, (t) => measure(t, fontSize, 400)) : "";
2204
- const lineWidth = line.reduce((sum, p) => sum + p.width, 0) + Math.max(0, line.length - 1) * gap;
2205
- const descriptionWidth = description ? measure(description, fontSize, 400) : 0;
2206
- if (!line.length && !description) return null;
2207
- const height = (line.length ? lineHeight : 0) + (description ? lineHeight + (line.length ? 2 : 0) : 0);
2208
- const pos = a.labelPosition;
2209
- const top = pos.startsWith("top") ? r.y + pad : pos.startsWith("bottom") ? r.y + r.height - pad - height : r.y + (r.height - height) / 2;
2210
- const startOf = (width) => pos.endsWith("left") ? r.x + pad : pos.endsWith("right") ? r.x + r.width - pad - width : r.x + (r.width - width) / 2;
2211
- const parts = [];
2212
- let x = startOf(lineWidth);
2213
- for (const p of line) {
2214
- parts.push({
2215
- ...p,
2216
- x,
2217
- y: top
2218
- });
2219
- x += p.width + gap;
1736
+ return step;
1737
+ };
1738
+ var bandOf = (list, axis, category) => {
1739
+ if (category) return 1;
1740
+ const step = smallestStep(list.map((s) => axis === "x" ? s.cols.x : s.cols.y), list.map((s) => axis === "x" && s.cols.sorted));
1741
+ return Number.isFinite(step) ? step : 1;
1742
+ };
1743
+ /**
1744
+ * Stacks the y values of series sharing a stack id (positive and negative
1745
+ * values separately); `percent` scales every stack to 100 at each x
1746
+ */
1747
+ var stackValues = (list, percent) => {
1748
+ const totals = /* @__PURE__ */ new Map();
1749
+ if (percent) for (const { s, stack } of list) {
1750
+ let byX = totals.get(stack);
1751
+ if (!byX) totals.set(stack, byX = /* @__PURE__ */ new Map());
1752
+ const { x, y, count } = s.cols;
1753
+ for (let i = 0; i < count; i++) {
1754
+ const v = y[i];
1755
+ if (!Number.isFinite(v)) continue;
1756
+ const t = byX.get(x[i]) ?? [0, 0];
1757
+ if (v >= 0) t[0] += v;
1758
+ else t[1] -= v;
1759
+ byX.set(x[i], t);
1760
+ }
2220
1761
  }
2221
- if (description) parts.push({
2222
- kind: "description",
2223
- text: description,
2224
- x: startOf(descriptionWidth),
2225
- y: top + (line.length ? lineHeight + 2 : 0),
2226
- width: descriptionWidth,
2227
- height: lineHeight,
2228
- size: fontSize,
2229
- weight: 400
1762
+ const running = /* @__PURE__ */ new Map();
1763
+ const members = /* @__PURE__ */ new Map();
1764
+ for (const { stack } of list) members.set(stack, (members.get(stack) ?? 0) + 1);
1765
+ return list.map(({ s, stack }) => {
1766
+ const { x, y, count } = s.cols;
1767
+ if (!percent && members.get(stack) === 1) {
1768
+ const base = new Float64Array(count);
1769
+ const top = new Float64Array(count);
1770
+ for (let i = 0; i < count; i++) {
1771
+ const v = y[i];
1772
+ const valid = Number.isFinite(v);
1773
+ base[i] = valid ? 0 : NaN;
1774
+ top[i] = valid ? v : NaN;
1775
+ }
1776
+ return {
1777
+ base,
1778
+ top
1779
+ };
1780
+ }
1781
+ let byX = running.get(stack);
1782
+ if (!byX) running.set(stack, byX = /* @__PURE__ */ new Map());
1783
+ const sums = totals.get(stack);
1784
+ const base = new Float64Array(count);
1785
+ const top = new Float64Array(count);
1786
+ for (let i = 0; i < count; i++) {
1787
+ let v = y[i];
1788
+ if (!Number.isFinite(v)) {
1789
+ base[i] = NaN;
1790
+ top[i] = NaN;
1791
+ continue;
1792
+ }
1793
+ if (sums) {
1794
+ const t = sums.get(x[i]);
1795
+ const total = t ? v >= 0 ? t[0] : t[1] : 0;
1796
+ v = total > 0 ? v / total * 100 : 0;
1797
+ }
1798
+ const t = byX.get(x[i]) ?? [0, 0];
1799
+ const b = v >= 0 ? t[0] : t[1];
1800
+ base[i] = b;
1801
+ top[i] = b + v;
1802
+ if (v >= 0) t[0] += v;
1803
+ else t[1] += v;
1804
+ byX.set(x[i], t);
1805
+ }
1806
+ return {
1807
+ base,
1808
+ top
1809
+ };
2230
1810
  });
2231
- const width = Math.max(lineWidth, descriptionWidth);
2232
- return {
2233
- parts,
2234
- box: {
2235
- x: startOf(width),
2236
- y: top,
2237
- width,
2238
- height
1811
+ };
1812
+ var DEFAULT_UP = "var(--success-color, #16a34a)";
1813
+ var DEFAULT_DOWN = "var(--danger-color, #dc2626)";
1814
+ /** Dates in the y values (or starts) of objects: a time value axis */
1815
+ var hasDateY = (datas) => {
1816
+ for (const data of datas) {
1817
+ if (typeof data !== "object" || data === null || !("length" in data)) continue;
1818
+ const list = data;
1819
+ for (let i = 0; i < Math.min(list.length, 8); i++) {
1820
+ const item = list[i];
1821
+ if (item && typeof item === "object" && !Array.isArray(item)) {
1822
+ const d = item;
1823
+ if (d.y instanceof Date || d.y0 instanceof Date) return true;
1824
+ }
2239
1825
  }
2240
- };
1826
+ }
1827
+ return false;
1828
+ };
1829
+ var precise = (v) => Number(v.toPrecision(12));
1830
+ /** Next step of the 1, 2, 5 × 10^k sequence */
1831
+ var nextStep = (step) => {
1832
+ const power = 10 ** Math.floor(Math.log10(step) + 1e-9);
1833
+ const f = step / power;
1834
+ return (f < 1.5 ? 2 : f < 3.5 ? 5 : 10) * power;
2241
1835
  };
2242
1836
  /**
2243
- * Where a reference line goes: `vertical` lines run at screen x `at`,
2244
- * horizontal ones at screen y `at`
1837
+ * Range of the right axis covering [lo, hi] with as many tick intervals as
1838
+ * the left range has (its ticks then line up with the left grid)
2245
1839
  */
2246
- var lineOf = (a, s) => {
2247
- return {
2248
- vertical: a.axis === "x" !== s.horizontal,
2249
- at: a.axis === "x" ? s.xToPx(a.value) : s.yToPx(a.value)
2250
- };
2251
- };
2252
- var drawBackground = (ctx, input) => {
2253
- const { scales, theme, xTicks, yTicks } = input;
2254
- const { plot } = scales;
2255
- const x = scales.horizontal ? input.y : input.x;
2256
- const y = scales.horizontal ? input.x : input.y;
2257
- const bottomTicks = scales.horizontal ? yTicks : xTicks;
2258
- const leftTicks = scales.horizontal ? xTicks : yTicks;
2259
- const bottomPx = scales.horizontal ? scales.yToPx : scales.xToPx;
2260
- const leftPx = scales.horizontal ? scales.xToPx : scales.yToPx;
2261
- ctx.clearRect(0, 0, input.width, input.height);
2262
- ctx.save();
2263
- ctx.beginPath();
2264
- ctx.rect(plot.x, plot.y, plot.width, plot.height);
2265
- ctx.clip();
2266
- for (const a of input.annotations) {
2267
- if (a.type !== "rect") continue;
2268
- const r = rectOf(a, scales);
2269
- if (r.width <= 0 || r.height <= 0) continue;
2270
- ctx.fillStyle = toCss(a.color, a.opacity);
2271
- ctx.fillRect(r.x, r.y, r.width, r.height);
2272
- }
2273
- ctx.restore();
2274
- ctx.lineWidth = 1;
2275
- ctx.strokeStyle = toCss(theme.grid);
2276
- ctx.beginPath();
2277
- if (y.grid) for (const v of leftTicks.values) {
2278
- const py = crisp(leftPx(v));
2279
- if (py < plot.y - 1 || py > plot.y + plot.height + 1) continue;
2280
- ctx.moveTo(plot.x, py);
2281
- ctx.lineTo(plot.x + plot.width, py);
2282
- }
2283
- if (x.grid) for (const v of bottomTicks.values) {
2284
- const px = crisp(bottomPx(v));
2285
- if (px < plot.x - 1 || px > plot.x + plot.width + 1) continue;
2286
- ctx.moveTo(px, plot.y);
2287
- ctx.lineTo(px, plot.y + plot.height);
2288
- }
2289
- ctx.stroke();
2290
- const line = Math.ceil(theme.fontSize * 1.4);
2291
- ctx.font = font(theme);
2292
- ctx.fillStyle = toCss(theme.textMuted);
2293
- if (x.visible) {
2294
- ctx.strokeStyle = toCss(theme.axis);
2295
- ctx.beginPath();
2296
- const base = crisp(plot.y + plot.height);
2297
- ctx.moveTo(plot.x, base);
2298
- ctx.lineTo(plot.x + plot.width, base);
2299
- ctx.stroke();
2300
- ctx.textBaseline = "top";
2301
- ctx.textAlign = "center";
2302
- let lastRight = -Infinity;
2303
- const order = bottomTicks.values.map((v, i) => ({
2304
- px: bottomPx(v),
2305
- text: bottomTicks.labels[i]
2306
- })).sort((a, b) => a.px - b.px);
2307
- for (const { px, text } of order) {
2308
- if (px < plot.x - .5 || px > plot.x + plot.width + .5) continue;
2309
- const w = ctx.measureText(text).width;
2310
- const left = Math.max(0, Math.min(input.width - w, px - w / 2));
2311
- if (left < lastRight + 6) continue;
2312
- lastRight = left + w;
2313
- ctx.fillText(text, left + w / 2, plot.y + plot.height + 7);
2314
- }
2315
- if (x.title) {
2316
- ctx.font = font(theme, theme.fontSize + 1, 500);
2317
- ctx.fillStyle = toCss(theme.text, .8);
2318
- ctx.fillText(x.title, plot.x + plot.width / 2, plot.y + plot.height + line + 10);
2319
- }
1840
+ var alignedDomain = (lo, hi, left, count) => {
1841
+ const [a0, a1] = left;
1842
+ const stepL = tickStep(a0, a1, count);
1843
+ const n = stepL > 0 ? Math.round((a1 - a0) / stepL) : 0;
1844
+ [lo, hi] = ensureSpan(lo, hi);
1845
+ if (n < 1 || Math.abs(n * stepL - (a1 - a0)) > stepL * 1e-6) return niceDomain(lo, hi, count);
1846
+ let step = tickStep(lo, hi, n);
1847
+ for (let k = 0; k < 40 && step > 0; k++) {
1848
+ const start = Math.floor(lo / step + 1e-9) * step;
1849
+ if (start + n * step >= hi - step * 1e-9) return [precise(start), precise(start + n * step)];
1850
+ step = nextStep(step);
2320
1851
  }
2321
- if (y.visible) {
2322
- ctx.font = font(theme);
2323
- ctx.fillStyle = toCss(theme.textMuted);
2324
- ctx.textAlign = "right";
2325
- ctx.textBaseline = "middle";
2326
- let lastBottom = -Infinity;
2327
- const rows = leftTicks.values.map((v, i) => ({
2328
- py: leftPx(v),
2329
- text: leftTicks.labels[i]
2330
- })).sort((a, b) => a.py - b.py);
2331
- const rowHeight = theme.fontSize + 2;
2332
- for (const { py, text } of rows) {
2333
- if (py < plot.y - .5 || py > plot.y + plot.height + .5) continue;
2334
- if (scales.horizontal) {
2335
- if (py - rowHeight / 2 < lastBottom) continue;
2336
- lastBottom = py + rowHeight / 2;
1852
+ return niceDomain(lo, hi, count);
1853
+ };
1854
+ var mapped = /* @__PURE__ */ new WeakMap();
1855
+ /** Columns of a right-axis series in left axis units (cached with the data) */
1856
+ var mapColumns = (src, map) => {
1857
+ const key = `${map.scale},${map.offset},${!!map.log}`;
1858
+ const hit = mapped.get(src);
1859
+ if (hit && hit.key === key) return hit.cols;
1860
+ const f = mapper(map);
1861
+ const column = (a) => a ? a.map(f) : null;
1862
+ const raw = src;
1863
+ const cols = {
1864
+ ...raw,
1865
+ y: raw.y.map(f),
1866
+ open: column(raw.open),
1867
+ high: column(raw.high),
1868
+ low: column(raw.low),
1869
+ y0: column(raw.y0),
1870
+ q1: column(raw.q1),
1871
+ q3: column(raw.q3),
1872
+ outliers: raw.outliers ? raw.outliers.map((list) => list?.map(f)) : null
1873
+ };
1874
+ if (map.log) {
1875
+ const range = [Infinity, -Infinity];
1876
+ const add = (a) => {
1877
+ if (!a) return;
1878
+ for (let i = 0; i < a.length; i++) {
1879
+ const v = a[i];
1880
+ if (!Number.isFinite(v)) continue;
1881
+ if (v < range[0]) range[0] = v;
1882
+ if (v > range[1]) range[1] = v;
2337
1883
  }
2338
- ctx.fillText(text, plot.x - 8, py);
2339
- }
2340
- if (y.title) {
2341
- ctx.save();
2342
- ctx.font = font(theme, theme.fontSize + 1, 500);
2343
- ctx.fillStyle = toCss(theme.text, .8);
2344
- ctx.translate(line / 2 + 4, plot.y + plot.height / 2);
2345
- ctx.rotate(-Math.PI / 2);
2346
- ctx.textAlign = "center";
2347
- ctx.fillText(y.title, 0, 0);
2348
- ctx.restore();
2349
- }
1884
+ };
1885
+ for (const c of [
1886
+ cols.y,
1887
+ cols.y0,
1888
+ cols.high,
1889
+ cols.low,
1890
+ cols.q1,
1891
+ cols.q3
1892
+ ]) add(c);
1893
+ cols.outliers?.forEach((list) => add(list));
1894
+ cols.yMin = range[0] <= range[1] ? range[0] : NaN;
1895
+ cols.yMax = range[0] <= range[1] ? range[1] : NaN;
1896
+ } else {
1897
+ const lo = f(raw.yMin);
1898
+ const hi = f(raw.yMax);
1899
+ cols.yMin = Math.min(lo, hi);
1900
+ cols.yMax = Math.max(lo, hi);
2350
1901
  }
2351
- const y2 = scales.horizontal ? null : input.y2;
2352
- const y2Ticks = input.y2Ticks;
2353
- if (y2 && y2Ticks && y2.visible) {
2354
- const right = plot.x + plot.width;
2355
- ctx.font = font(theme);
2356
- ctx.fillStyle = toCss(theme.textMuted);
2357
- ctx.textAlign = "left";
2358
- ctx.textBaseline = "middle";
2359
- y2Ticks.values.forEach((v, i) => {
2360
- const py = scales.yToPx(v);
2361
- if (py < plot.y - .5 || py > plot.y + plot.height + .5) return;
2362
- ctx.fillText(y2Ticks.labels[i], right + 8, py);
2363
- });
2364
- if (y2.title) {
2365
- ctx.save();
2366
- ctx.font = font(theme, theme.fontSize + 1, 500);
2367
- ctx.fillStyle = toCss(theme.text, .8);
2368
- ctx.translate(input.width - line / 2 - 4, plot.y + plot.height / 2);
2369
- ctx.rotate(Math.PI / 2);
2370
- ctx.textAlign = "center";
2371
- ctx.fillText(y2.title, 0, 0);
2372
- ctx.restore();
1902
+ mapped.set(src, {
1903
+ key,
1904
+ cols
1905
+ });
1906
+ return cols;
1907
+ };
1908
+ /** A log range widened to whole decades (`[0.3, 2.4]` -> `[0, 3]`) */
1909
+ var decades = (range) => {
1910
+ const lo = Math.floor(range[0] + 1e-9);
1911
+ const hi = Math.ceil(range[1] - 1e-9);
1912
+ return [lo, hi > lo ? hi : lo + 1];
1913
+ };
1914
+ /** Exponent of `m × 10^k`, exact for the values of the ticks */
1915
+ var niceExponent = (m, k) => Math.log10(Number((m * 10 ** k).toPrecision(12)));
1916
+ /**
1917
+ * A log range widened to the closest 1, 2 or 5 × 10^k bounds (the values
1918
+ * 900 - 140k -> 500 - 200k), tighter than whole decades
1919
+ */
1920
+ var niceLogRange = (range) => {
1921
+ const below = (e) => {
1922
+ const k = Math.floor(e + 1e-9);
1923
+ let best = k;
1924
+ for (const m of [2, 5]) {
1925
+ const v = niceExponent(m, k);
1926
+ if (v <= e + 1e-9) best = v;
1927
+ }
1928
+ return best;
1929
+ };
1930
+ const above = (e) => {
1931
+ const k = Math.floor(e - 1e-9);
1932
+ for (const m of [
1933
+ 1,
1934
+ 2,
1935
+ 5
1936
+ ]) {
1937
+ const v = niceExponent(m, k);
1938
+ if (v >= e - 1e-9) return v;
2373
1939
  }
1940
+ return k + 1;
1941
+ };
1942
+ const lo = below(range[0]);
1943
+ const hi = above(range[1]);
1944
+ return [lo, hi > lo + 1e-9 ? hi : above(lo + 1e-6)];
1945
+ };
1946
+ /**
1947
+ * Plots a series through a map: its columns, bars, stacks and lower
1948
+ * edges (bars / areas of the right axis start at `zero`)
1949
+ */
1950
+ var logColumns = (s, map, zero = -Infinity) => {
1951
+ const f = mapper(map);
1952
+ const column = (a) => a ? a.map(f) : null;
1953
+ const rawBase = s.stackBase;
1954
+ s.cols = mapColumns(s.cols, map);
1955
+ s.barBase = s.barBase && !s.floating ? s.barBase.map((v) => v === 0 ? map.log ? -Infinity : f(0) : f(v)) : column(s.barBase);
1956
+ s.barTop = column(s.barTop);
1957
+ s.stackTop = column(s.stackTop);
1958
+ s.stackBase = rawBase && rawBase === s.raw.y0 ? s.cols.y0 : rawBase ? column(rawBase) : s.type === "area" ? new Float64Array(s.cols.count).fill(zero) : null;
1959
+ };
1960
+ /** Value -> plotted units of a map (`-Infinity` for 0 on log axes) */
1961
+ var mapper = (map) => (v) => (map.log ? Math.log10(v) : v) * map.scale + map.offset;
1962
+ /** Value of the y axis -> plotted y (the logarithm on log axes) */
1963
+ var toPlotY = (model, value) => model.y.type === "log" ? Math.log10(value) : value;
1964
+ /** Plotted y -> value of the y axis */
1965
+ var fromPlotY = (model, value) => model.y.type === "log" ? 10 ** value : value;
1966
+ /** Value of the x axis -> plotted x (the logarithm on log axes) */
1967
+ var toPlotX = (model, value) => model.x.type === "log" ? Math.log10(value) : value;
1968
+ /** Plotted x -> value of the x axis */
1969
+ var fromPlotX = (model, value) => model.x.type === "log" ? 10 ** value : value;
1970
+ /** A view in plotted units -> the values of the axes (zoom events, `getView`) */
1971
+ var viewToValues = (model, view) => ({
1972
+ x: [fromPlotX(model, view.x[0]), fromPlotX(model, view.x[1])],
1973
+ y: [fromPlotY(model, view.y[0]), fromPlotY(model, view.y[1])]
1974
+ });
1975
+ /** Values of the axes -> a view in plotted units (`setView`); missing axes keep `current` */
1976
+ var viewFromValues = (model, next, current) => ({
1977
+ x: next.x ? [toPlotX(model, next.x[0]), toPlotX(model, next.x[1])] : current.x,
1978
+ y: next.y ? [toPlotY(model, next.y[0]), toPlotY(model, next.y[1])] : current.y
1979
+ });
1980
+ var logXCache = /* @__PURE__ */ new WeakMap();
1981
+ /**
1982
+ * Columns plotted on a log x axis: the logarithms of the x values; points
1983
+ * without one (x <= 0) become gaps (cached with the data)
1984
+ */
1985
+ var logXColumns = (raw) => {
1986
+ const hit = logXCache.get(raw);
1987
+ if (hit) return hit;
1988
+ const x = raw.x.map((v) => v > 0 ? Math.log10(v) : NaN);
1989
+ let y = raw.y;
1990
+ let gaps = false;
1991
+ for (let i = 0; i < raw.count; i++) {
1992
+ if (Number.isFinite(x[i])) continue;
1993
+ if (!gaps) y = Float64Array.from(raw.y);
1994
+ gaps = true;
1995
+ y[i] = NaN;
1996
+ }
1997
+ const [xMin, xMax] = extent(x);
1998
+ const cols = {
1999
+ ...raw,
2000
+ x,
2001
+ y,
2002
+ xMin,
2003
+ xMax,
2004
+ sorted: raw.sorted && !gaps
2005
+ };
2006
+ logXCache.set(raw, cols);
2007
+ return cols;
2008
+ };
2009
+ /** Replaces the bases of bars / areas on log axes (`-Infinity`) by the bottom of the axis */
2010
+ var floorBases = (s, floor) => {
2011
+ for (const a of [s.barBase, s.stackBase]) {
2012
+ if (!a) continue;
2013
+ for (let i = 0; i < a.length; i++) if (a[i] === -Infinity) a[i] = floor;
2374
2014
  }
2375
2015
  };
2376
- var overlaps = (a, b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
2377
- var inside$1 = (r, plot) => r.x >= plot.x && r.y >= plot.y && r.x + r.width <= plot.x + plot.width && r.y + r.height <= plot.y + plot.height;
2378
- /** Text with a halo in the background color (readable over data) */
2379
- var haloText = (ctx, text, x, y, color, halo) => {
2380
- ctx.lineWidth = 3;
2381
- ctx.lineJoin = "round";
2382
- ctx.strokeStyle = halo;
2383
- ctx.strokeText(text, x, y);
2384
- ctx.fillStyle = color;
2385
- ctx.fillText(text, x, y);
2016
+ /** Position (0-1) of a heatmap value on its color ramp */
2017
+ var heatPosition = (s, v) => {
2018
+ const [lo, hi] = s.valueRange;
2019
+ const mid = s.colorMidpoint;
2020
+ let t;
2021
+ if (mid !== null && mid > lo && mid < hi) t = v < mid ? (v - lo) / (mid - lo) * .5 : .5 + (v - mid) / (hi - mid) * .5;
2022
+ else t = (v - lo) / (hi - lo || 1);
2023
+ return t < 0 ? 0 : t > 1 ? 1 : t;
2386
2024
  };
2387
2025
  /**
2388
- * Box of a label inside its bar (the visible part of it): the text cut with
2389
- * an ellipsis to fit, at the start of horizontal bars and centered in
2390
- * vertical ones; `null` when the bar is too small
2026
+ * Built-in ramps of heatmaps, perceptual (OKLab) and theme-aware: light
2027
+ * themes go from a near-white tint to a deep hue, dark themes from a dark
2028
+ * tint of the surface to a bright saturated hue; diverging ramps meet in a
2029
+ * neutral middle
2391
2030
  */
2392
- var insideBox = (label, plot, measure, height) => {
2393
- const bar = label.bar;
2394
- if (!bar) return null;
2395
- const x0 = Math.max(bar.x, plot.x);
2396
- const x1 = Math.min(bar.x + bar.width, plot.x + plot.width);
2397
- const y0 = Math.max(bar.y, plot.y);
2398
- const y1 = Math.min(bar.y + bar.height, plot.y + plot.height);
2399
- if (y1 - y0 < height - 2) return null;
2400
- const text = fitText(label.text, x1 - x0 - 12, measure);
2401
- if (!text) return null;
2402
- const w = measure(text);
2403
- const x = label.align === "start" ? x0 + INSIDE_PAD$1 : (x0 + x1) / 2 - w / 2;
2404
- return {
2405
- label: text === label.text ? label : {
2406
- ...label,
2407
- text
2408
- },
2409
- box: {
2410
- x,
2411
- y: (y0 + y1) / 2 - height / 2,
2412
- width: w,
2413
- height
2031
+ var heatmapStops = (scheme, color, theme, negative) => {
2032
+ const dark = luminance(theme.background) < .4;
2033
+ if (scheme === "diverging") {
2034
+ const neutral = mixOklab(theme.surface, theme.textMuted, dark ? .12 : .08);
2035
+ const end = (c) => dark ? withLightness(c, .7, 1.25) : withLightness(c, .5, 1.05);
2036
+ const side = (c) => mixOklab(neutral, end(c), .5);
2037
+ return [
2038
+ end(negative),
2039
+ side(negative),
2040
+ neutral,
2041
+ side(color),
2042
+ end(color)
2043
+ ];
2044
+ }
2045
+ return dark ? [
2046
+ mixOklab(theme.surface, color, .14),
2047
+ withLightness(color, .46, .95),
2048
+ withLightness(color, .64, 1.2),
2049
+ withLightness(color, .8, 1.15)
2050
+ ] : [
2051
+ mixOklab(theme.surface, color, .06),
2052
+ withLightness(color, .8, .6),
2053
+ withLightness(color, .58, 1),
2054
+ withLightness(color, .36, .9)
2055
+ ];
2056
+ };
2057
+ var buildModel = (options, theme, hidden, cache, resolver = null) => {
2058
+ const resolve = resolver ?? theme.resolve ?? ((value, fallback) => resolveStaticColor(value, {}, fallback));
2059
+ const input = options.series;
2060
+ const locale = resolveLocale(options.locale);
2061
+ const tooltipOptions = typeof options.tooltip === "object" ? options.tooltip : void 0;
2062
+ const labels = chartLabels(locale, {
2063
+ ...definedEntries(tooltipOptions?.labels),
2064
+ ...definedEntries(options.labels)
2065
+ });
2066
+ const datas = input.map((s) => s.data);
2067
+ const xType = options.xAxis?.type ?? detectXType(datas);
2068
+ const categories = xType === "category" ? [...options.xAxis?.categories ?? collectCategories(datas)] : [];
2069
+ const categoryMap = new Map(categories.map((c, i) => [c, i]));
2070
+ const resolveX = xResolver(xType, (name) => categoryMap.get(name) ?? NaN);
2071
+ const yOpts = options.yAxis;
2072
+ const heatmapData = input.filter((s) => s.type === "heatmap").map((s) => s.data);
2073
+ const yCategories = yOpts?.type === "category" || !yOpts?.type && heatmapData.length ? [...yOpts?.categories ?? collectYCategories(heatmapData)] : [];
2074
+ const yType = yOpts?.type === "category" || yCategories.length ? "category" : yOpts?.type === "time" || !yOpts?.type && hasDateY(datas) ? "time" : yOpts?.type === "log" ? "log" : "linear";
2075
+ const yLog = yType === "log";
2076
+ const yCategoryMap = new Map(yCategories.map((c, i) => [c, i]));
2077
+ const resolveY = (v) => typeof v === "number" ? v : typeof v === "string" ? yType === "category" ? yCategoryMap.get(v) ?? NaN : yType === "time" ? Date.parse(v) : Number(v) : v instanceof Date ? v.getTime() : NaN;
2078
+ const resolverKey = `${xType}|${categories.join("")}|${yType}|${yCategories.join("")}`;
2079
+ const percent = options.stackMode === "percent";
2080
+ const horizontal = options.orientation === "horizontal";
2081
+ const usedKeys = /* @__PURE__ */ new Set();
2082
+ const series = input.map((s, index) => {
2083
+ let key = s.id ?? `#${index}`;
2084
+ while (usedKeys.has(key)) key += "'";
2085
+ usedKeys.add(key);
2086
+ let entry = cache.get(key);
2087
+ if (!entry || entry.data !== s.data || entry.resolverKey !== resolverKey) {
2088
+ entry = {
2089
+ data: s.data,
2090
+ resolverKey,
2091
+ cols: normalizeData(s.data, resolveX, resolveY)
2092
+ };
2093
+ cache.set(key, entry);
2094
+ }
2095
+ const type = s.type ?? "line";
2096
+ const paletteColor = theme.palette[index % theme.palette.length];
2097
+ const color = s.color ? resolve(s.color, paletteColor) : paletteColor;
2098
+ const cols = entry.cols;
2099
+ let pointColors = null;
2100
+ if ((type === "scatter" || type === "bar" || type === "boxplot") && cols.colors) {
2101
+ const resolved = /* @__PURE__ */ new Map();
2102
+ pointColors = cols.colors.map((c) => {
2103
+ if (c === void 0) return void 0;
2104
+ let rgba = resolved.get(c);
2105
+ if (!rgba) resolved.set(c, rgba = resolve(c, color));
2106
+ return rgba;
2107
+ });
2108
+ }
2109
+ let colorScale = [];
2110
+ let valueRange = [0, 1];
2111
+ let cellColors = null;
2112
+ let colorMidpoint = null;
2113
+ let colorLut = null;
2114
+ if (type === "heatmap") {
2115
+ const scale = s.colorScale;
2116
+ const scheme = scale === "diverging" ? "diverging" : "sequential";
2117
+ colorScale = typeof scale === "object" && scale.length ? scale.map((c) => resolve(c, color)) : heatmapStops(scheme, color, theme, resolve(DEFAULT_DOWN, [
2118
+ .86,
2119
+ .15,
2120
+ .15,
2121
+ 1
2122
+ ]));
2123
+ valueRange = s.valueRange ? [s.valueRange[0], s.valueRange[1]] : [cols.valueMin, cols.valueMax];
2124
+ if (!Number.isFinite(valueRange[0])) valueRange = [0, 1];
2125
+ const [lo, hi] = valueRange;
2126
+ colorMidpoint = s.colorMidpoint ?? (scheme === "diverging" && typeof scale !== "object" ? lo < 0 && hi > 0 ? 0 : (lo + hi) / 2 : null);
2127
+ const lut = oklabRamp(colorScale);
2128
+ colorLut = lut;
2129
+ const values = cols.value ?? cols.y;
2130
+ const ramp = {
2131
+ valueRange,
2132
+ colorMidpoint
2133
+ };
2134
+ cellColors = new Uint32Array(cols.count);
2135
+ for (let i = 0; i < cols.count; i++) {
2136
+ const v = values[i];
2137
+ if (!Number.isFinite(v) || !Number.isFinite(cols.y[i])) continue;
2138
+ cellColors[i] = lut[Math.round(heatPosition(ramp, v) * 255)];
2139
+ }
2140
+ }
2141
+ const connector = s.connector ? typeof s.connector === "object" ? s.connector : {} : null;
2142
+ const id = s.id ?? String(index);
2143
+ return {
2144
+ index,
2145
+ key,
2146
+ id,
2147
+ name: s.name ?? `${labels.series} ${index + 1}`,
2148
+ type,
2149
+ color,
2150
+ colorCss: toCss(color),
2151
+ lineWidth: s.lineWidth ?? (type === "candlestick" ? 1 : type === "boxplot" ? 1.5 : 2),
2152
+ pointSize: s.pointSize ?? (type === "boxplot" ? 6 : 8),
2153
+ opacity: s.opacity ?? (type === "area" ? .28 : type === "scatter" ? .78 : type === "boxplot" ? .22 : 1),
2154
+ hidden: !!s.hidden || hidden.has(id),
2155
+ showLabels: !!s.showLabels,
2156
+ labelsInside: s.showLabels === "inside" && type === "bar",
2157
+ curve: s.curve ?? "linear",
2158
+ cols,
2159
+ raw: cols,
2160
+ yAxis: s.yAxis === "right" && !horizontal ? "right" : "left",
2161
+ floating: type === "bar" && !!cols.y0,
2162
+ barOffset: 0,
2163
+ barHalfWidth: 0,
2164
+ barBase: null,
2165
+ barTop: null,
2166
+ stackBase: null,
2167
+ stackTop: null,
2168
+ pointColors,
2169
+ upColor: type === "candlestick" ? resolve(s.upColor ?? DEFAULT_UP, [
2170
+ .09,
2171
+ .64,
2172
+ .29,
2173
+ 1
2174
+ ]) : color,
2175
+ downColor: type === "candlestick" ? resolve(s.downColor ?? DEFAULT_DOWN, [
2176
+ .86,
2177
+ .15,
2178
+ .15,
2179
+ 1
2180
+ ]) : color,
2181
+ cellColors,
2182
+ cellHalf: {
2183
+ x: .5,
2184
+ y: .5
2185
+ },
2186
+ colorScale,
2187
+ valueRange,
2188
+ colorMidpoint,
2189
+ colorLut,
2190
+ tooltip: s.tooltip !== false,
2191
+ stroke: s.stroke !== void 0 && Number.isFinite(s.stroke) ? Math.max(0, s.stroke) : null,
2192
+ outlierShape: s.outlierShape ?? "hollow",
2193
+ outlierSize: s.outlierSize ?? s.pointSize ?? 7,
2194
+ connector: connector && type === "bar" ? {
2195
+ color: connector.color ? resolve(connector.color, theme.textMuted) : theme.textMuted,
2196
+ dash: [...connector.dash ?? [3, 3]],
2197
+ width: connector.width ?? 1
2198
+ } : null,
2199
+ radius: s.radius ?? (type === "heatmap" ? 2 : type === "boxplot" ? 0 : 3),
2200
+ format: s.format,
2201
+ valueName: s.valueName
2202
+ };
2203
+ });
2204
+ const visible = series.filter((s) => !s.hidden);
2205
+ const xLog = xType === "log";
2206
+ if (xLog) for (const s of series) s.cols = logXColumns(s.raw);
2207
+ const allScatter = series.length > 0 && series.every((s) => s.type === "scatter" || s.type === "heatmap");
2208
+ const slotted = visible.filter((s) => s.type === "bar" || s.type === "boxplot");
2209
+ const bars = visible.filter((s) => s.type === "bar");
2210
+ const band = bandOf(slotted, "x", xType === "category");
2211
+ if (slotted.length) {
2212
+ const slots = /* @__PURE__ */ new Map();
2213
+ const stackId = (s) => input[s.index].stack ?? `\u0000${s.key}`;
2214
+ for (const s of slotted) if (!slots.has(stackId(s))) slots.set(stackId(s), slots.size);
2215
+ let share = 0;
2216
+ for (const s of slotted) {
2217
+ const w = input[s.index].barWidth;
2218
+ if (w !== void 0 && w > share) share = Math.min(1, w);
2414
2219
  }
2415
- };
2416
- };
2417
- /** Greedy placement: right, left, above, below the dot; skipped when no slot is free */
2418
- var placeLabels = (labels, plot, measure, height, occupied = []) => {
2419
- const placed = [];
2420
- for (const label of labels) {
2421
- if (label.placement === "inside") {
2422
- const fit = insideBox(label, plot, measure, height);
2423
- if (fit) placed.push(fit);
2424
- continue;
2220
+ const group = band * (share || (slots.size > 1 ? .78 : .62));
2221
+ const slotWidth = group / slots.size;
2222
+ for (const s of slotted) {
2223
+ const slot = slots.get(stackId(s)) ?? 0;
2224
+ s.barOffset = -group / 2 + slotWidth * (slot + .5);
2225
+ s.barHalfWidth = slotWidth / 2 * (slots.size > 1 ? .86 : 1);
2425
2226
  }
2426
- const w = measure(label.text);
2427
- const gap = label.r + 4;
2428
- const above = {
2429
- x: label.x - w / 2,
2430
- y: label.y - gap - height,
2431
- width: w,
2432
- height
2433
- };
2434
- const below = {
2435
- x: label.x - w / 2,
2436
- y: label.y + gap,
2437
- width: w,
2438
- height
2439
- };
2440
- const right = {
2441
- x: label.x + gap,
2442
- y: label.y - height / 2,
2443
- width: w,
2444
- height
2445
- };
2446
- const left = {
2447
- x: label.x - gap - w,
2448
- y: label.y - height / 2,
2449
- width: w,
2450
- height
2451
- };
2452
- const box = (label.placement === "above" ? [above] : label.placement === "below" ? [below] : label.placement === "right" ? [right, left] : label.placement === "left" ? [left, right] : [
2453
- {
2454
- x: label.x + gap,
2455
- y: label.y - height / 2,
2456
- width: w,
2457
- height
2458
- },
2459
- {
2460
- x: label.x - gap - w,
2461
- y: label.y - height / 2,
2462
- width: w,
2463
- height
2464
- },
2465
- above,
2466
- below
2467
- ]).find((c) => inside$1(c, plot) && !occupied.some((o) => overlaps(o, c)));
2468
- if (!box) continue;
2469
- occupied.push(box);
2470
- placed.push({
2471
- label,
2472
- box
2227
+ const stackedBars = bars.filter((s) => !s.floating);
2228
+ const stacked = stackValues(stackedBars.map((s) => ({
2229
+ s,
2230
+ stack: stackId(s)
2231
+ })), false);
2232
+ const percentStacked = percent ? stackValues(stackedBars.filter((s) => input[s.index].stack !== void 0).map((s) => ({
2233
+ s,
2234
+ stack: stackId(s)
2235
+ })), true) : [];
2236
+ let p = 0;
2237
+ stackedBars.forEach((s, k) => {
2238
+ const values = percent && input[s.index].stack !== void 0 ? percentStacked[p++] : stacked[k];
2239
+ s.barBase = values.base;
2240
+ s.barTop = values.top;
2473
2241
  });
2474
- }
2475
- return placed;
2476
- };
2477
- /** Elbow arrows: polylines with rounded corners and filled heads */
2478
- var drawArrows = (ctx, arrows) => {
2479
- for (const a of arrows) {
2480
- const pts = a.points;
2481
- const first = pts[0];
2482
- if (!first) continue;
2483
- ctx.strokeStyle = a.color;
2484
- ctx.fillStyle = a.color;
2485
- ctx.lineWidth = a.width;
2486
- ctx.lineJoin = "round";
2487
- ctx.setLineDash([]);
2488
- ctx.beginPath();
2489
- ctx.moveTo(first.x, first.y);
2490
- for (let i = 1; i < pts.length; i++) {
2491
- const p = pts[i];
2492
- const next = pts[i + 1];
2493
- const prev = pts[i - 1];
2494
- if (next && typeof ctx.arcTo === "function") {
2495
- const r = Math.min(4, Math.hypot(p.x - prev.x, p.y - prev.y) / 2, Math.hypot(next.x - p.x, next.y - p.y) / 2);
2496
- ctx.arcTo(p.x, p.y, next.x, next.y, r);
2497
- } else ctx.lineTo(p.x, p.y);
2498
- }
2499
- ctx.stroke();
2500
- const [tip, b0, b1] = a.head;
2501
- if (tip && b0 && b1) {
2502
- ctx.beginPath();
2503
- ctx.moveTo(tip.x, tip.y);
2504
- ctx.lineTo(b0.x, b0.y);
2505
- ctx.lineTo(b1.x, b1.y);
2506
- ctx.closePath();
2507
- ctx.fill();
2242
+ for (const s of bars) {
2243
+ const { y, y0, count } = s.cols;
2244
+ if (!s.floating || !y0) continue;
2245
+ const base = new Float64Array(count);
2246
+ const top = new Float64Array(count);
2247
+ for (let i = 0; i < count; i++) {
2248
+ const valid = Number.isFinite(y[i]);
2249
+ base[i] = valid ? Number.isFinite(y0[i]) ? y0[i] : 0 : NaN;
2250
+ top[i] = valid ? y[i] : NaN;
2251
+ }
2252
+ s.barBase = base;
2253
+ s.barTop = top;
2508
2254
  }
2509
2255
  }
2510
- };
2511
- var drawForeground = (ctx, input) => {
2512
- const { scales, theme } = input;
2513
- const { plot } = scales;
2514
- if (input.clear !== false) ctx.clearRect(0, 0, input.width, input.height);
2515
- ctx.save();
2516
- ctx.beginPath();
2517
- ctx.rect(plot.x, plot.y, plot.width, plot.height);
2518
- ctx.clip();
2519
- for (const c of input.connectors ?? []) {
2520
- ctx.strokeStyle = c.color;
2521
- ctx.lineWidth = c.width;
2522
- ctx.setLineDash([...c.dash]);
2523
- ctx.beginPath();
2524
- const along = c.y0 === c.y1;
2525
- const x0 = along ? c.x0 : crisp(c.x0);
2526
- const y0 = along ? crisp(c.y0) : c.y0;
2527
- ctx.moveTo(x0, y0);
2528
- ctx.lineTo(along ? c.x1 : x0, along ? y0 : c.y1);
2529
- ctx.stroke();
2256
+ for (const s of visible) if (s.type === "area" && s.cols.y0 && input[s.index].stack === void 0) s.stackBase = s.cols.y0;
2257
+ const stackedLines = visible.filter((s) => (s.type === "line" || s.type === "area") && input[s.index].stack !== void 0);
2258
+ if (stackedLines.length) {
2259
+ const values = stackValues(stackedLines.map((s) => ({
2260
+ s,
2261
+ stack: input[s.index].stack ?? ""
2262
+ })), percent);
2263
+ stackedLines.forEach((s, k) => {
2264
+ s.stackBase = values[k].base;
2265
+ s.stackTop = values[k].top;
2266
+ });
2530
2267
  }
2531
- ctx.setLineDash([]);
2532
- if (input.arrows?.length) drawArrows(ctx, input.arrows);
2533
- const halo = toCss(theme.background, .9);
2534
- const occupied = [];
2535
- const labelHeight = theme.fontSize + 4;
2536
- for (const a of input.annotations) {
2537
- if (a.type !== "line") continue;
2538
- const color = toCss(a.color);
2539
- ctx.strokeStyle = color;
2540
- ctx.lineWidth = a.width;
2541
- ctx.setLineDash(a.dash);
2542
- ctx.beginPath();
2543
- const where = lineOf(a, scales);
2544
- if (!where.vertical) {
2545
- const py = crisp(where.at);
2546
- ctx.moveTo(plot.x, py);
2547
- ctx.lineTo(plot.x + plot.width, py);
2548
- ctx.stroke();
2549
- if (a.label) {
2550
- ctx.setLineDash([]);
2551
- ctx.font = font(theme, theme.fontSize, 500);
2552
- ctx.textAlign = "right";
2553
- ctx.textBaseline = "bottom";
2554
- haloText(ctx, a.label, plot.x + plot.width - 6, py - 3, color, halo);
2555
- }
2556
- } else {
2557
- const px = crisp(where.at);
2558
- ctx.moveTo(px, plot.y);
2559
- ctx.lineTo(px, plot.y + plot.height);
2560
- ctx.stroke();
2561
- if (a.label) {
2562
- ctx.setLineDash([]);
2563
- ctx.font = font(theme, theme.fontSize, 500);
2564
- ctx.textAlign = "left";
2565
- ctx.textBaseline = "top";
2566
- haloText(ctx, a.label, px + 5, plot.y + 5, color, halo);
2567
- }
2568
- }
2268
+ const candles = visible.filter((s) => s.type === "candlestick");
2269
+ const candleBand = bandOf(candles, "x", xType === "category");
2270
+ for (const s of candles) s.barHalfWidth = candleBand * .3;
2271
+ const heatmaps = visible.filter((s) => s.type === "heatmap");
2272
+ if (heatmaps.length) {
2273
+ const cx = bandOf(heatmaps, "x", xType === "category") / 2;
2274
+ const cy = bandOf(heatmaps, "y", yType === "category") / 2;
2275
+ for (const s of heatmaps) s.cellHalf = {
2276
+ x: cx,
2277
+ y: cy
2278
+ };
2569
2279
  }
2570
- ctx.setLineDash([]);
2571
- for (const a of input.annotations) {
2572
- if (a.type !== "rect" || !hasAreaTitle(a)) continue;
2573
- const title = areaTitleLayout(a, rectOf(a, scales), theme.fontSize, (text, size, weight) => {
2574
- ctx.font = font(theme, size, weight);
2575
- return ctx.measureText(text).width;
2576
- });
2577
- if (!title) continue;
2578
- const color = toCss(a.labelColor);
2579
- const muted = toCss(theme.textMuted);
2580
- ctx.textAlign = "left";
2581
- ctx.textBaseline = "middle";
2582
- for (const p of title.parts) {
2583
- const cy = p.y + p.height / 2;
2584
- if (p.kind === "badge") {
2585
- const h = p.height - 2;
2586
- pill(ctx, p.x, cy - h / 2, p.width, h);
2587
- ctx.fillStyle = toCss(a.labelColor, .16);
2588
- ctx.fill();
2589
- ctx.font = font(theme, p.size, p.weight);
2590
- ctx.fillStyle = color;
2591
- ctx.fillText(p.text, p.x + 5, cy);
2592
- continue;
2593
- }
2594
- ctx.font = font(theme, p.size, p.weight);
2595
- ctx.fillStyle = p.kind === "label" ? color : muted;
2596
- ctx.fillText(p.text, p.x, cy);
2280
+ const xOpts = options.xAxis;
2281
+ let xRange;
2282
+ if (xType === "category") xRange = [-.5, Math.max(1, categories.length) - .5];
2283
+ else {
2284
+ const values = [];
2285
+ for (const s of visible) {
2286
+ values.push(s.cols.xMin, s.cols.xMax);
2287
+ const pad = s.type === "bar" || s.type === "boxplot" ? band / 2 : s.type === "candlestick" ? candleBand / 2 : s.type === "heatmap" ? s.cellHalf.x : 0;
2288
+ if (pad) values.push(s.cols.xMin - pad, s.cols.xMax + pad);
2597
2289
  }
2598
- occupied.push(title.box);
2290
+ xRange = ensureSpan(...extent(values), xType === "time" ? [Date.now() - 864e5, Date.now()] : [0, 1]);
2291
+ const pointsOnly = series.every((s) => s.type === "scatter");
2292
+ if (pointsOnly) {
2293
+ const pad = (xRange[1] - xRange[0]) * .04;
2294
+ xRange = [xRange[0] - pad, xRange[1] + pad];
2295
+ }
2296
+ if ((xOpts?.nice ?? (pointsOnly && xType === "linear")) && xType === "linear") xRange = niceDomain(xRange[0], xRange[1], xOpts?.tickCount ?? 6);
2297
+ if (xLog && xOpts?.nice !== false) xRange = niceLogRange(xRange);
2599
2298
  }
2600
- for (const a of input.annotations) {
2601
- if (a.type !== "text" || !a.text) continue;
2602
- ctx.font = font(theme, theme.fontSize, a.weight);
2603
- const box = textBox(a, scales, ctx.measureText(a.text).width, labelHeight);
2604
- ctx.textAlign = "left";
2605
- ctx.textBaseline = "middle";
2606
- haloText(ctx, a.text, box.x, box.y + box.height / 2, toCss(a.color), halo);
2607
- occupied.push(box);
2299
+ const xBound = (v) => xLog ? Math.log10(v) : v;
2300
+ if (xOpts?.min !== void 0) xRange[0] = xBound(xOpts.min);
2301
+ if (xOpts?.max !== void 0) xRange[1] = xBound(xOpts.max);
2302
+ xRange = ensureSpan(xRange[0], xRange[1]);
2303
+ if (yLog) for (const s of series) {
2304
+ if (s.yAxis === "right" || s.type === "heatmap") continue;
2305
+ logColumns(s, LOG_MAP);
2608
2306
  }
2609
- if (input.pointLabels.length) {
2610
- ctx.font = font(theme);
2611
- ctx.textAlign = "left";
2612
- ctx.textBaseline = "middle";
2613
- const textColor = toCss(theme.text, .85);
2614
- const placed = placeLabels(input.pointLabels, plot, (t) => ctx.measureText(t).width, labelHeight - 2, occupied);
2615
- for (const { label, box } of placed) {
2616
- const y = box.y + box.height / 2;
2617
- if (label.color) {
2618
- ctx.fillStyle = label.color;
2619
- ctx.fillText(label.text, box.x, y);
2620
- } else haloText(ctx, label.text, box.x, y, textColor, halo);
2307
+ const valuesOf = (list, zero, log = false) => {
2308
+ const values = [];
2309
+ for (const s of list) if (s.type === "bar" && s.barBase && s.barTop) values.push(...extent(s.barBase), ...extent(s.barTop));
2310
+ else if (s.stackBase && s.stackTop) values.push(...extent(s.stackBase), ...extent(s.stackTop));
2311
+ else if (s.type === "heatmap") values.push(s.cols.yMin - s.cellHalf.y, s.cols.yMax + s.cellHalf.y);
2312
+ else values.push(s.cols.yMin, s.cols.yMax);
2313
+ const listBars = list.filter((s) => s.type === "bar");
2314
+ if (!log && (listBars.some((s) => !s.floating) || zero)) values.push(0);
2315
+ if (listBars.some((s) => s.showLabels && !s.labelsInside)) {
2316
+ const [lo, hi] = extent(values);
2317
+ if (hi > 0) values.push(hi + (hi - Math.min(0, lo)) * .1);
2318
+ if (lo < 0) values.push(lo - (Math.max(0, hi) - lo) * .1);
2319
+ }
2320
+ return values;
2321
+ };
2322
+ const leftVisible = visible.filter((s) => s.yAxis === "left");
2323
+ const leftSeries = leftVisible.length ? leftVisible : visible;
2324
+ const leftBars = leftSeries.filter((s) => s.type === "bar");
2325
+ let yRange;
2326
+ if (yType === "category") yRange = [-.5, Math.max(1, yCategories.length) - .5];
2327
+ else {
2328
+ const yValues = valuesOf(leftSeries, yOpts?.zero, yLog);
2329
+ const filled = leftBars.length > 0 || leftSeries.some((s) => s.type === "area");
2330
+ yRange = ensureSpan(...extent(yValues));
2331
+ if (series.every((s) => s.type === "scatter")) {
2332
+ const pad = (yRange[1] - yRange[0]) * .06;
2333
+ yRange = [yRange[0] - pad, yRange[1] + pad];
2334
+ } else if ((candles.length || leftSeries.some((s) => s.type === "boxplot")) && !leftBars.length) {
2335
+ const pad = (yRange[1] - yRange[0]) * .04;
2336
+ yRange = [yRange[0] - pad, yRange[1] + pad];
2337
+ }
2338
+ if ((yOpts?.nice ?? !heatmaps.length) && yType === "linear") yRange = niceDomain(yRange[0], yRange[1], yOpts?.tickCount ?? 5);
2339
+ if (yLog && yOpts?.nice !== false) yRange = decades(yRange);
2340
+ if (percent && (bars.length || stackedLines.length)) yRange = [extent(yValues)[0] < 0 ? -100 : 0, 100];
2341
+ if (filled && !yOpts?.zero && !yLog) {
2342
+ if (yRange[0] < 0 && extent(yValues)[0] >= 0) yRange[0] = 0;
2621
2343
  }
2622
2344
  }
2623
- ctx.restore();
2624
- };
2625
- //#endregion
2626
- //#region ../charts/src/engine/time.ts
2627
- var SECOND = 1e3;
2628
- var MINUTE = 60 * SECOND;
2629
- var HOUR = 60 * MINUTE;
2630
- var DAY = 24 * HOUR;
2631
- var MONTH = 30 * DAY;
2632
- var YEAR = 365 * DAY;
2633
- var INTERVALS = [
2634
- {
2635
- unit: "second",
2636
- step: 1,
2637
- ms: SECOND
2638
- },
2639
- {
2640
- unit: "second",
2641
- step: 5,
2642
- ms: 5 * SECOND
2643
- },
2644
- {
2645
- unit: "second",
2646
- step: 15,
2647
- ms: 15 * SECOND
2648
- },
2649
- {
2650
- unit: "second",
2651
- step: 30,
2652
- ms: 30 * SECOND
2653
- },
2654
- {
2655
- unit: "minute",
2656
- step: 1,
2657
- ms: MINUTE
2658
- },
2659
- {
2660
- unit: "minute",
2661
- step: 5,
2662
- ms: 5 * MINUTE
2663
- },
2664
- {
2665
- unit: "minute",
2666
- step: 15,
2667
- ms: 15 * MINUTE
2668
- },
2669
- {
2670
- unit: "minute",
2671
- step: 30,
2672
- ms: 30 * MINUTE
2673
- },
2674
- {
2675
- unit: "hour",
2676
- step: 1,
2677
- ms: HOUR
2678
- },
2679
- {
2680
- unit: "hour",
2681
- step: 2,
2682
- ms: 2 * HOUR
2683
- },
2684
- {
2685
- unit: "hour",
2686
- step: 3,
2687
- ms: 3 * HOUR
2688
- },
2689
- {
2690
- unit: "hour",
2691
- step: 6,
2692
- ms: 6 * HOUR
2693
- },
2694
- {
2695
- unit: "hour",
2696
- step: 12,
2697
- ms: 12 * HOUR
2698
- },
2699
- {
2700
- unit: "day",
2701
- step: 1,
2702
- ms: DAY
2703
- },
2704
- {
2705
- unit: "day",
2706
- step: 2,
2707
- ms: 2 * DAY
2708
- },
2709
- {
2710
- unit: "day",
2711
- step: 7,
2712
- ms: 7 * DAY
2713
- },
2714
- {
2715
- unit: "month",
2716
- step: 1,
2717
- ms: MONTH
2718
- },
2719
- {
2720
- unit: "month",
2721
- step: 3,
2722
- ms: 3 * MONTH
2723
- },
2724
- {
2725
- unit: "month",
2726
- step: 6,
2727
- ms: 6 * MONTH
2345
+ const yBound = (v) => yLog ? Math.log10(v) : v;
2346
+ if (yOpts?.min !== void 0) yRange[0] = yBound(yOpts.min);
2347
+ if (yOpts?.max !== void 0) yRange[1] = yBound(yOpts.max);
2348
+ yRange = ensureSpan(yRange[0], yRange[1]);
2349
+ if (yLog) {
2350
+ for (const s of series) if (s.yAxis === "left") floorBases(s, yRange[0]);
2728
2351
  }
2729
- ];
2730
- var pad = (n, width = 2) => String(n).padStart(width, "0");
2731
- var partsOf = (t, utc) => {
2732
- const d = new Date(t);
2733
- return utc ? {
2734
- year: d.getUTCFullYear(),
2735
- month: d.getUTCMonth(),
2736
- date: d.getUTCDate(),
2737
- day: d.getUTCDay(),
2738
- hours: d.getUTCHours(),
2739
- minutes: d.getUTCMinutes(),
2740
- seconds: d.getUTCSeconds(),
2741
- ms: d.getUTCMilliseconds()
2352
+ const y2Opts = options.yAxis2;
2353
+ let y2 = null;
2354
+ let y2Map = null;
2355
+ const rightSeries = series.filter((s) => s.yAxis === "right");
2356
+ if (rightSeries.length && yType !== "category") {
2357
+ const y2Log = y2Opts?.type === "log";
2358
+ const rightVisible = rightSeries.filter((s) => !s.hidden);
2359
+ const [lo, hi] = ensureSpan(...extent(y2Log ? rightVisible.flatMap((s) => {
2360
+ const c = mapColumns(s.raw, LOG_MAP);
2361
+ return [c.yMin, c.yMax];
2362
+ }) : valuesOf(rightVisible, y2Opts?.zero)));
2363
+ let domain = y2Opts?.nice === false ? [lo, hi] : y2Log ? decades([lo, hi]) : alignedDomain(lo, hi, yRange, yOpts?.tickCount ?? 5);
2364
+ const y2Bound = (v) => y2Log ? Math.log10(v) : v;
2365
+ if (y2Opts?.min !== void 0) domain[0] = y2Bound(y2Opts.min);
2366
+ if (y2Opts?.max !== void 0) domain[1] = y2Bound(y2Opts.max);
2367
+ domain = ensureSpan(domain[0], domain[1]);
2368
+ const scale = (yRange[1] - yRange[0]) / (domain[1] - domain[0]);
2369
+ const map = {
2370
+ scale,
2371
+ offset: yRange[0] - domain[0] * scale,
2372
+ log: y2Log
2373
+ };
2374
+ y2Map = map;
2375
+ const zero = y2Log ? yRange[0] : mapper(map)(Math.min(Math.max(0, domain[0]), domain[1]));
2376
+ for (const s of rightSeries) {
2377
+ logColumns(s, map, zero);
2378
+ floorBases(s, yRange[0]);
2379
+ }
2380
+ y2 = axisModel(y2Opts, y2Log ? "log" : "linear", [], false, locale);
2381
+ }
2382
+ const timeline = yType === "time" && series.some((s) => s.type === "bar") && series.every((s) => s.type === "bar" ? s.floating : s.type === "scatter");
2383
+ const zoomOption = options.interaction === false ? false : options.interaction === true || options.interaction === void 0 ? true : options.interaction.zoom ?? true;
2384
+ const zoomAxes = zoomOption === false ? {
2385
+ x: false,
2386
+ y: false
2387
+ } : zoomOption === true ? {
2388
+ x: true,
2389
+ y: allScatter
2742
2390
  } : {
2743
- year: d.getFullYear(),
2744
- month: d.getMonth(),
2745
- date: d.getDate(),
2746
- day: d.getDay(),
2747
- hours: d.getHours(),
2748
- minutes: d.getMinutes(),
2749
- seconds: d.getSeconds(),
2750
- ms: d.getMilliseconds()
2391
+ x: zoomOption.includes("x"),
2392
+ y: zoomOption.includes("y")
2393
+ };
2394
+ const yFormat = yOpts?.format ?? (percent && (bars.length || stackedLines.length) ? (v) => formatPercent(v, locale) : void 0);
2395
+ return {
2396
+ series,
2397
+ x: axisModel(xOpts, xType, categories, false, locale),
2398
+ y: {
2399
+ ...axisModel(yOpts, yType, yCategories, yType !== "category", locale),
2400
+ format: yFormat
2401
+ },
2402
+ y2,
2403
+ y2Map,
2404
+ horizontal,
2405
+ locale,
2406
+ labels,
2407
+ formatValue: valueFormatter(locale),
2408
+ base: {
2409
+ x: xRange,
2410
+ y: yRange
2411
+ },
2412
+ allScatter,
2413
+ zoom: zoomAxes,
2414
+ tooltipMode: tooltipOptions?.mode ?? (allScatter ? "nearest" : timeline ? "item" : "axis"),
2415
+ baseline: yLog ? yRange[0] : Math.min(Math.max(0, yRange[0]), yRange[1]),
2416
+ minSpan: {
2417
+ x: xType === "category" ? 1 : (xRange[1] - xRange[0]) * 1e-7,
2418
+ y: yType === "category" ? 1 : (yRange[1] - yRange[0]) * 1e-7
2419
+ },
2420
+ percent,
2421
+ stacks: input.map((s) => s.stack !== void 0 && (s.type === "bar" || s.type === "line" || s.type === "area" || !s.type) ? `${s.type === "bar" ? "bar" : "line"}:${s.stack}` : null)
2751
2422
  };
2752
2423
  };
2753
- /** Timestamp of calendar fields (out of range fields carry over) */
2754
- var timeOf = (utc, year, month, date = 1, hours = 0, minutes = 0, seconds = 0) => {
2755
- if (utc) {
2756
- const t = Date.UTC(year, month, date, hours, minutes, seconds);
2757
- if (year >= 0 && year < 100) {
2758
- const d = new Date(t);
2759
- d.setUTCFullYear(d.getUTCFullYear() - 1900);
2760
- return d.getTime();
2761
- }
2762
- return t;
2424
+ /** Y value drawn for a point: its stacked top for stacks, the close for candles */
2425
+ var plottedY = (s, i) => s.barTop ? s.barTop[i] : s.stackTop ? s.stackTop[i] : s.cols.y[i];
2426
+ var grids = /* @__PURE__ */ new WeakMap();
2427
+ /** Fill opacity of bar `i`: its own `opacity`, else the one of the series */
2428
+ var barOpacity = (s, i) => {
2429
+ const own = s.cols.opacities?.[i];
2430
+ return own !== void 0 && Number.isFinite(own) ? own : s.opacity;
2431
+ };
2432
+ /** Spatial index of a scatter series, built on first use and kept with its data */
2433
+ var gridOf = (s) => {
2434
+ let grid = grids.get(s.cols);
2435
+ if (!grid) {
2436
+ grid = new GridIndex(s.cols.x, s.cols.y, s.cols.count);
2437
+ grids.set(s.cols, grid);
2763
2438
  }
2764
- const d = new Date(year, month, date, hours, minutes, seconds);
2765
- if (year >= 0 && year < 100) d.setFullYear(d.getFullYear() - 1900);
2766
- return d.getTime();
2439
+ return grid;
2767
2440
  };
2768
- var chooseInterval = (span, count) => {
2769
- const target = span / Math.max(1, count);
2770
- if (target < SECOND * .75) return {
2771
- unit: "millisecond",
2772
- step: tickStep(0, span, count),
2773
- ms: 1
2441
+ //#endregion
2442
+ //#region ../charts/src/engine/overlay.ts
2443
+ /** Padding of the labels inside the bars, CSS pixels */
2444
+ var INSIDE_PAD$1 = 6;
2445
+ /**
2446
+ * Color of a text written on a bar of color `fill` (alpha included) over
2447
+ * `background`: white or near-black, whichever contrasts more; `undefined`
2448
+ * for translucent bars (the theme text color with a halo reads better)
2449
+ */
2450
+ var insideLabelColor = (fill, background) => {
2451
+ const alpha = fill[3];
2452
+ if (alpha < .6) return void 0;
2453
+ const l = relativeLuminance$1(mix(background, fill, alpha));
2454
+ return 1.05 / (l + .05) >= (l + .05) / .062 ? "#ffffff" : "rgba(17, 24, 39, 0.92)";
2455
+ };
2456
+ var font = (theme, size = theme.fontSize, weight = 400) => `${weight} ${size}px ${theme.fontFamily}`;
2457
+ var crisp = (v) => Math.round(v) + .5;
2458
+ /** Path of a rounded box with half-height corners */
2459
+ var pill = (ctx, x, y, w, h) => {
2460
+ const r = Math.min(h / 2, w / 2);
2461
+ ctx.beginPath();
2462
+ ctx.moveTo(x + r, y);
2463
+ ctx.lineTo(x + w - r, y);
2464
+ ctx.arcTo(x + w, y, x + w, y + r, r);
2465
+ ctx.lineTo(x + w, y + h - r);
2466
+ ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
2467
+ ctx.lineTo(x + r, y + h);
2468
+ ctx.arcTo(x, y + h, x, y + h - r, r);
2469
+ ctx.lineTo(x, y + r);
2470
+ ctx.arcTo(x, y, x + r, y, r);
2471
+ ctx.closePath();
2472
+ };
2473
+ /** Box of a text annotation, kept inside the plot */
2474
+ var textBox$1 = (a, s, width, height) => {
2475
+ const { plot } = s;
2476
+ const at = toScreen(s, a.x ?? 0, a.y ?? 0);
2477
+ const free = s.horizontal ? {
2478
+ x: a.y === void 0,
2479
+ y: a.x === void 0
2480
+ } : {
2481
+ x: a.x === void 0,
2482
+ y: a.y === void 0
2774
2483
  };
2775
- for (const interval of INTERVALS) if (interval.ms >= target * .75) return interval;
2776
- const years = Math.max(1, tickStep(0, span / YEAR, count));
2484
+ const px = free.x ? plot.x + plot.width / 2 : at.x;
2485
+ const py = free.y ? plot.y + plot.height / 2 : at.y;
2486
+ const gap = 6;
2487
+ let x = px - width / 2;
2488
+ let y = py - height / 2;
2489
+ if (a.anchor === "top") y = py + gap;
2490
+ else if (a.anchor === "bottom") y = py - gap - height;
2491
+ else if (a.anchor === "left") x = px + gap;
2492
+ else if (a.anchor === "right") x = px - gap - width;
2493
+ x = Math.max(plot.x + gap, Math.min(x, plot.x + plot.width - gap - width));
2494
+ y = Math.max(plot.y + 2, Math.min(y, plot.y + plot.height - 2 - height));
2777
2495
  return {
2778
- unit: "year",
2779
- step: Math.round(years),
2780
- ms: Math.round(years) * YEAR
2496
+ x,
2497
+ y,
2498
+ width,
2499
+ height
2781
2500
  };
2782
2501
  };
2783
- /** Start of the tick sequence: `t` floored to the unit, aligned on the step */
2784
- var floorTo = (t, { unit, step }, utc) => {
2785
- const p = partsOf(t, utc);
2786
- switch (unit) {
2787
- case "second": return timeOf(utc, p.year, p.month, p.date, p.hours, p.minutes, p.seconds - p.seconds % step);
2788
- case "minute": return timeOf(utc, p.year, p.month, p.date, p.hours, p.minutes - p.minutes % step);
2789
- case "hour": return timeOf(utc, p.year, p.month, p.date, p.hours - p.hours % step);
2790
- case "day": return timeOf(utc, p.year, p.month, p.date - (step === 7 ? (p.day + 6) % 7 : 0));
2791
- case "month": return timeOf(utc, p.year, p.month - p.month % step);
2792
- case "year": return timeOf(utc, p.year - p.year % step, 0);
2793
- default: return t;
2502
+ /** Screen box of an area annotation, clipped to the plot */
2503
+ var rectOf = (a, s) => {
2504
+ const { plot } = s;
2505
+ const [xLo, xHi] = s.horizontal ? [plot.y, plot.y + plot.height] : [plot.x, plot.x + plot.width];
2506
+ const [yLo, yHi] = s.horizontal ? [plot.x, plot.x + plot.width] : [plot.y, plot.y + plot.height];
2507
+ const along = (px, lo, hi) => px < lo ? lo : px > hi ? hi : px;
2508
+ return screenRect(s, along(s.xToPx(a.x0 ?? -Infinity), xLo, xHi), along(s.xToPx(a.x1 ?? Infinity), xLo, xHi), along(s.yToPx(a.y0 ?? -Infinity), yLo, yHi), along(s.yToPx(a.y1 ?? Infinity), yLo, yHi));
2509
+ };
2510
+ /** Whether an area annotation has a title block */
2511
+ var hasAreaTitle = (a) => !!(a.label || a.badge || a.detail || a.description);
2512
+ /** Text cut with an ellipsis to fit `max` pixels ("" when nothing fits) */
2513
+ var fitText = (text, max, width) => {
2514
+ if (width(text) <= max) return text;
2515
+ const chars = Array.from(text);
2516
+ let lo = 0;
2517
+ let hi = chars.length;
2518
+ while (lo < hi) {
2519
+ const mid = Math.ceil((lo + hi) / 2);
2520
+ if (width(`${chars.slice(0, mid).join("")}…`) <= max) lo = mid;
2521
+ else hi = mid - 1;
2794
2522
  }
2523
+ return lo > 0 ? `${chars.slice(0, lo).join("").trimEnd()}…` : "";
2795
2524
  };
2796
- var advance = (t, { unit, step }, utc) => {
2797
- const { year, month, date, hours, minutes, seconds } = partsOf(t, utc);
2798
- switch (unit) {
2799
- case "second": return timeOf(utc, year, month, date, hours, minutes, seconds + step);
2800
- case "minute": return timeOf(utc, year, month, date, hours, minutes + step, seconds);
2801
- case "hour": return timeOf(utc, year, month, date, hours + step, minutes, seconds);
2802
- case "day": return timeOf(utc, year, month, date + step, hours, minutes, seconds);
2803
- case "month": return timeOf(utc, year, month + step, date, hours, minutes, seconds);
2804
- case "year": return timeOf(utc, year + step, month, date, hours, minutes, seconds);
2805
- default: return t + step;
2525
+ /**
2526
+ * Layout of the title block of an area: `[badge] label detail` on the first
2527
+ * line, the description under it; the block sits in the `labelPosition`
2528
+ * corner, 10px from the edges, and the texts are cut to the area width
2529
+ */
2530
+ var areaTitleLayout = (a, r, fontSize, measure) => {
2531
+ if (!hasAreaTitle(a) || r.width <= 0 || r.height <= 0) return null;
2532
+ const pad = 10;
2533
+ const lineHeight = fontSize + 4;
2534
+ const max = Math.max(0, r.width - 20);
2535
+ const labelSize = fontSize + 1;
2536
+ const badgeSize = Math.max(9, fontSize - 1);
2537
+ const gap = 6;
2538
+ const badge = a.badge ? fitText(a.badge, Math.max(0, max - 10), (t) => measure(t, badgeSize, 700)) : "";
2539
+ const badgeWidth = badge ? measure(badge, badgeSize, 700) + 10 : 0;
2540
+ let detail = a.detail ?? "";
2541
+ let detailWidth = detail ? measure(detail, fontSize, 500) : 0;
2542
+ const room = () => max - (badge ? badgeWidth + gap : 0) - (detail ? detailWidth + gap : 0);
2543
+ if (detail && a.label && room() < 32) {
2544
+ detail = "";
2545
+ detailWidth = 0;
2546
+ }
2547
+ if (detail && room() < 0) {
2548
+ detail = fitText(detail, Math.max(0, max - (badge ? badgeWidth + gap : 0)), (t) => measure(t, fontSize, 500));
2549
+ detailWidth = detail ? measure(detail, fontSize, 500) : 0;
2550
+ }
2551
+ const label = a.label ? fitText(a.label, Math.max(0, room()), (t) => measure(t, labelSize, 600)) : "";
2552
+ const line = [];
2553
+ if (badge) line.push({
2554
+ kind: "badge",
2555
+ text: badge,
2556
+ x: 0,
2557
+ y: 0,
2558
+ width: badgeWidth,
2559
+ height: lineHeight,
2560
+ size: badgeSize,
2561
+ weight: 700
2562
+ });
2563
+ if (label) line.push({
2564
+ kind: "label",
2565
+ text: label,
2566
+ x: 0,
2567
+ y: 0,
2568
+ width: measure(label, labelSize, 600),
2569
+ height: lineHeight,
2570
+ size: labelSize,
2571
+ weight: 600
2572
+ });
2573
+ if (detail) line.push({
2574
+ kind: "detail",
2575
+ text: detail,
2576
+ x: 0,
2577
+ y: 0,
2578
+ width: detailWidth,
2579
+ height: lineHeight,
2580
+ size: fontSize,
2581
+ weight: 500
2582
+ });
2583
+ const description = a.description ? fitText(a.description, max, (t) => measure(t, fontSize, 400)) : "";
2584
+ const lineWidth = line.reduce((sum, p) => sum + p.width, 0) + Math.max(0, line.length - 1) * gap;
2585
+ const descriptionWidth = description ? measure(description, fontSize, 400) : 0;
2586
+ if (!line.length && !description) return null;
2587
+ const height = (line.length ? lineHeight : 0) + (description ? lineHeight + (line.length ? 2 : 0) : 0);
2588
+ const pos = a.labelPosition;
2589
+ const top = pos.startsWith("top") ? r.y + pad : pos.startsWith("bottom") ? r.y + r.height - pad - height : r.y + (r.height - height) / 2;
2590
+ const startOf = (width) => pos.endsWith("left") ? r.x + pad : pos.endsWith("right") ? r.x + r.width - pad - width : r.x + (r.width - width) / 2;
2591
+ const parts = [];
2592
+ let x = startOf(lineWidth);
2593
+ for (const p of line) {
2594
+ parts.push({
2595
+ ...p,
2596
+ x,
2597
+ y: top
2598
+ });
2599
+ x += p.width + gap;
2806
2600
  }
2601
+ if (description) parts.push({
2602
+ kind: "description",
2603
+ text: description,
2604
+ x: startOf(descriptionWidth),
2605
+ y: top + (line.length ? lineHeight + 2 : 0),
2606
+ width: descriptionWidth,
2607
+ height: lineHeight,
2608
+ size: fontSize,
2609
+ weight: 400
2610
+ });
2611
+ const width = Math.max(lineWidth, descriptionWidth);
2612
+ return {
2613
+ parts,
2614
+ box: {
2615
+ x: startOf(width),
2616
+ y: top,
2617
+ width,
2618
+ height
2619
+ }
2620
+ };
2807
2621
  };
2808
2622
  /**
2809
- * Calendar-aligned ticks between two timestamps
2810
- * @param locale labels in this locale (ISO-like labels without it)
2811
- * @param timeZone calendar of the steps and labels
2623
+ * Where a reference line goes: `vertical` lines run at screen x `at`,
2624
+ * horizontal ones at screen y `at`
2812
2625
  */
2813
- var timeTicks = (min, max, count, locale, timeZone = "local") => {
2814
- if (min > max) [min, max] = [max, min];
2815
- const interval = chooseInterval(max - min, count);
2816
- if (interval.unit === "millisecond") return {
2817
- values: linearTicks(min, max, count),
2818
- unit: "millisecond",
2819
- format: (v) => formatTime(v, "millisecond", locale, timeZone)
2820
- };
2821
- const utc = timeZone === "utc";
2822
- const values = [];
2823
- let t = floorTo(min, interval, utc);
2824
- while (t < min) t = advance(t, interval, utc);
2825
- while (t <= max && values.length < 500) {
2826
- values.push(t);
2827
- t = advance(t, interval, utc);
2828
- }
2829
- const { unit } = interval;
2626
+ var lineOf = (a, s) => {
2830
2627
  return {
2831
- values,
2832
- unit,
2833
- format: (v) => formatTick(v, unit, locale, timeZone)
2628
+ vertical: a.axis === "x" !== s.horizontal,
2629
+ at: a.axis === "x" ? s.xToPx(a.value) : s.yToPx(a.value)
2834
2630
  };
2835
2631
  };
2836
- var ymd$1 = (p) => `${p.year}-${pad(p.month + 1)}-${pad(p.date)}`;
2837
- var hm = (p) => `${pad(p.hours)}:${pad(p.minutes)}`;
2838
- var HM = {
2839
- hour: "2-digit",
2840
- minute: "2-digit",
2841
- hourCycle: "h23"
2842
- };
2843
- var HMS = {
2844
- ...HM,
2845
- second: "2-digit"
2846
- };
2847
- var MONTH_DAY = {
2848
- month: "short",
2849
- day: "numeric"
2850
- };
2851
- var DATE = {
2852
- year: "numeric",
2853
- month: "short",
2854
- day: "numeric"
2855
- };
2856
- /** Cached formatter of a locale and time zone */
2857
- var intl = (locale, timeZone, options) => dateFormat(locale, timeZone === "utc" ? {
2858
- ...options,
2859
- timeZone: "UTC"
2860
- } : options);
2861
- /** Short tick label; day boundaries show the date, year boundaries the year */
2862
- var formatTick = (value, unit, locale, timeZone) => {
2863
- const p = partsOf(value, timeZone === "utc");
2864
- const midnight = p.hours === 0 && p.minutes === 0 && p.seconds === 0;
2865
- const newYear = p.month === 0 && p.date === 1;
2866
- if (locale) {
2867
- const f = (options) => intl(locale, timeZone, options).format(value);
2868
- switch (unit) {
2869
- case "second": return f(HMS);
2870
- case "minute":
2871
- case "hour": return f(midnight ? MONTH_DAY : HM);
2872
- case "day": return f(newYear ? { year: "numeric" } : MONTH_DAY);
2873
- case "month": return f(p.month === 0 ? { year: "numeric" } : { month: "short" });
2874
- default: return f({ year: "numeric" });
2875
- }
2632
+ var drawBackground = (ctx, input) => {
2633
+ const { scales, theme, xTicks, yTicks } = input;
2634
+ const { plot } = scales;
2635
+ const x = scales.horizontal ? input.y : input.x;
2636
+ const y = scales.horizontal ? input.x : input.y;
2637
+ const bottomTicks = scales.horizontal ? yTicks : xTicks;
2638
+ const leftTicks = scales.horizontal ? xTicks : yTicks;
2639
+ const bottomPx = scales.horizontal ? scales.yToPx : scales.xToPx;
2640
+ const leftPx = scales.horizontal ? scales.xToPx : scales.yToPx;
2641
+ ctx.clearRect(0, 0, input.width, input.height);
2642
+ ctx.save();
2643
+ ctx.beginPath();
2644
+ ctx.rect(plot.x, plot.y, plot.width, plot.height);
2645
+ ctx.clip();
2646
+ for (const a of input.annotations) {
2647
+ if (a.type !== "rect") continue;
2648
+ const r = rectOf(a, scales);
2649
+ if (r.width <= 0 || r.height <= 0) continue;
2650
+ ctx.fillStyle = toCss(a.color, a.opacity);
2651
+ ctx.fillRect(r.x, r.y, r.width, r.height);
2876
2652
  }
2877
- switch (unit) {
2878
- case "second": return `${hm(p)}:${pad(p.seconds)}`;
2879
- case "minute":
2880
- case "hour": return midnight ? `${pad(p.month + 1)}-${pad(p.date)}` : hm(p);
2881
- case "day": return newYear ? String(p.year) : `${pad(p.month + 1)}-${pad(p.date)}`;
2882
- case "month": return p.month === 0 ? String(p.year) : `${p.year}-${pad(p.month + 1)}`;
2883
- default: return String(p.year);
2653
+ ctx.restore();
2654
+ ctx.lineWidth = 1;
2655
+ ctx.strokeStyle = toCss(theme.grid);
2656
+ ctx.beginPath();
2657
+ if (y.grid) for (const v of leftTicks.values) {
2658
+ const py = crisp(leftPx(v));
2659
+ if (py < plot.y - 1 || py > plot.y + plot.height + 1) continue;
2660
+ ctx.moveTo(plot.x, py);
2661
+ ctx.lineTo(plot.x + plot.width, py);
2884
2662
  }
2885
- };
2886
- /**
2887
- * Full label (tooltips): precision follows the unit of the ticks; dates
2888
- * show the weekday with a `locale`
2889
- */
2890
- var formatTime = (value, unit = "minute", locale, timeZone = "local") => {
2891
- if (!Number.isFinite(value) || Number.isNaN(new Date(value).getTime())) return "–";
2892
- const p = partsOf(value, timeZone === "utc");
2893
- if (locale) {
2894
- const f = (options) => intl(locale, timeZone, options).format(value);
2895
- switch (unit) {
2896
- case "millisecond": return `${f({
2897
- ...DATE,
2898
- ...HMS
2899
- })}.${pad(p.ms, 3)}`;
2900
- case "second": return f({
2901
- ...DATE,
2902
- ...HMS
2903
- });
2904
- case "minute":
2905
- case "hour": return f({
2906
- ...DATE,
2907
- ...HM
2908
- });
2909
- default: return f({
2910
- ...DATE,
2911
- weekday: "short"
2912
- });
2663
+ if (x.grid) for (const v of bottomTicks.values) {
2664
+ const px = crisp(bottomPx(v));
2665
+ if (px < plot.x - 1 || px > plot.x + plot.width + 1) continue;
2666
+ ctx.moveTo(px, plot.y);
2667
+ ctx.lineTo(px, plot.y + plot.height);
2668
+ }
2669
+ ctx.stroke();
2670
+ const line = Math.ceil(theme.fontSize * 1.4);
2671
+ ctx.font = font(theme);
2672
+ ctx.fillStyle = toCss(theme.textMuted);
2673
+ if (x.visible) {
2674
+ ctx.strokeStyle = toCss(theme.axis);
2675
+ ctx.beginPath();
2676
+ const base = crisp(plot.y + plot.height);
2677
+ ctx.moveTo(plot.x, base);
2678
+ ctx.lineTo(plot.x + plot.width, base);
2679
+ ctx.stroke();
2680
+ ctx.textBaseline = "top";
2681
+ ctx.textAlign = "center";
2682
+ let lastRight = -Infinity;
2683
+ const order = bottomTicks.values.map((v, i) => ({
2684
+ px: bottomPx(v),
2685
+ text: bottomTicks.labels[i]
2686
+ })).sort((a, b) => a.px - b.px);
2687
+ for (const { px, text } of order) {
2688
+ if (px < plot.x - .5 || px > plot.x + plot.width + .5) continue;
2689
+ const w = ctx.measureText(text).width;
2690
+ const left = Math.max(0, Math.min(input.width - w, px - w / 2));
2691
+ if (left < lastRight + 6) continue;
2692
+ lastRight = left + w;
2693
+ ctx.fillText(text, left + w / 2, plot.y + plot.height + 7);
2694
+ }
2695
+ if (x.title) {
2696
+ ctx.font = font(theme, theme.fontSize + 1, 500);
2697
+ ctx.fillStyle = toCss(theme.text, .8);
2698
+ ctx.fillText(x.title, plot.x + plot.width / 2, plot.y + plot.height + line + 10);
2913
2699
  }
2914
2700
  }
2915
- switch (unit) {
2916
- case "millisecond": return `${ymd$1(p)} ${hm(p)}:${pad(p.seconds)}.${pad(p.ms, 3)}`;
2917
- case "second": return `${ymd$1(p)} ${hm(p)}:${pad(p.seconds)}`;
2918
- case "minute":
2919
- case "hour": return `${ymd$1(p)} ${hm(p)}`;
2920
- default: return ymd$1(p);
2701
+ if (y.visible) {
2702
+ ctx.font = font(theme);
2703
+ ctx.fillStyle = toCss(theme.textMuted);
2704
+ ctx.textAlign = "right";
2705
+ ctx.textBaseline = "middle";
2706
+ let lastBottom = -Infinity;
2707
+ const rows = leftTicks.values.map((v, i) => ({
2708
+ py: leftPx(v),
2709
+ text: leftTicks.labels[i]
2710
+ })).sort((a, b) => a.py - b.py);
2711
+ const rowHeight = theme.fontSize + 2;
2712
+ for (const { py, text } of rows) {
2713
+ if (py < plot.y - .5 || py > plot.y + plot.height + .5) continue;
2714
+ if (scales.horizontal) {
2715
+ if (py - rowHeight / 2 < lastBottom) continue;
2716
+ lastBottom = py + rowHeight / 2;
2717
+ }
2718
+ ctx.fillText(text, plot.x - 8, py);
2719
+ }
2720
+ if (y.title) {
2721
+ ctx.save();
2722
+ ctx.font = font(theme, theme.fontSize + 1, 500);
2723
+ ctx.fillStyle = toCss(theme.text, .8);
2724
+ ctx.translate(line / 2 + 4, plot.y + plot.height / 2);
2725
+ ctx.rotate(-Math.PI / 2);
2726
+ ctx.textAlign = "center";
2727
+ ctx.fillText(y.title, 0, 0);
2728
+ ctx.restore();
2729
+ }
2730
+ }
2731
+ const y2 = scales.horizontal ? null : input.y2;
2732
+ const y2Ticks = input.y2Ticks;
2733
+ if (y2 && y2Ticks && y2.visible) {
2734
+ const right = plot.x + plot.width;
2735
+ ctx.font = font(theme);
2736
+ ctx.fillStyle = toCss(theme.textMuted);
2737
+ ctx.textAlign = "left";
2738
+ ctx.textBaseline = "middle";
2739
+ y2Ticks.values.forEach((v, i) => {
2740
+ const py = scales.yToPx(v);
2741
+ if (py < plot.y - .5 || py > plot.y + plot.height + .5) return;
2742
+ ctx.fillText(y2Ticks.labels[i], right + 8, py);
2743
+ });
2744
+ if (y2.title) {
2745
+ ctx.save();
2746
+ ctx.font = font(theme, theme.fontSize + 1, 500);
2747
+ ctx.fillStyle = toCss(theme.text, .8);
2748
+ ctx.translate(input.width - line / 2 - 4, plot.y + plot.height / 2);
2749
+ ctx.rotate(Math.PI / 2);
2750
+ ctx.textAlign = "center";
2751
+ ctx.fillText(y2.title, 0, 0);
2752
+ ctx.restore();
2753
+ }
2921
2754
  }
2922
2755
  };
2923
- //#endregion
2924
- //#region ../charts/src/engine/axis.ts
2756
+ var overlaps = (a, b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
2757
+ var inside$2 = (r, plot) => r.x >= plot.x && r.y >= plot.y && r.x + r.width <= plot.x + plot.width && r.y + r.height <= plot.y + plot.height;
2758
+ /** Text with a halo in the background color (readable over data) */
2759
+ var haloText = (ctx, text, x, y, color, halo) => {
2760
+ ctx.lineWidth = 3;
2761
+ ctx.lineJoin = "round";
2762
+ ctx.strokeStyle = halo;
2763
+ ctx.strokeText(text, x, y);
2764
+ ctx.fillStyle = color;
2765
+ ctx.fillText(text, x, y);
2766
+ };
2925
2767
  /**
2926
- * @param count approximate number of ticks (numeric / time axes) or the
2927
- * maximum number of labels (category axes)
2768
+ * Box of a label inside its bar (the visible part of it): the text cut with
2769
+ * an ellipsis to fit, at the start of horizontal bars and centered in
2770
+ * vertical ones; `null` when the bar is too small
2928
2771
  */
2929
- var axisTicks = (axis, range, count) => {
2930
- const [min, max] = range[0] <= range[1] ? range : [range[1], range[0]];
2931
- if (axis.type === "category") {
2932
- const n = axis.categories.length;
2933
- const first = Math.max(0, Math.ceil(min - 1e-9));
2934
- const last = Math.min(n - 1, Math.floor(max + 1e-9));
2935
- const visible = Math.max(0, last - first + 1);
2936
- const step = Math.max(1, Math.ceil(visible / Math.max(1, count)));
2937
- const values = [];
2938
- for (let i = first - first % step; i <= last; i += step) if (i >= first) values.push(i);
2939
- const name = (v) => {
2940
- const i = Math.round(v);
2941
- return axis.format ? axis.format(i) : axis.categories[i] ?? String(i);
2772
+ var insideBox = (label, plot, measure, height) => {
2773
+ const bar = label.bar;
2774
+ if (!bar) return null;
2775
+ const x0 = Math.max(bar.x, plot.x);
2776
+ const x1 = Math.min(bar.x + bar.width, plot.x + plot.width);
2777
+ const y0 = Math.max(bar.y, plot.y);
2778
+ const y1 = Math.min(bar.y + bar.height, plot.y + plot.height);
2779
+ if (y1 - y0 < height - 2) return null;
2780
+ const text = fitText(label.text, x1 - x0 - 12, measure);
2781
+ if (!text) return null;
2782
+ const w = measure(text);
2783
+ const x = label.align === "start" ? x0 + INSIDE_PAD$1 : (x0 + x1) / 2 - w / 2;
2784
+ return {
2785
+ label: text === label.text ? label : {
2786
+ ...label,
2787
+ text
2788
+ },
2789
+ box: {
2790
+ x,
2791
+ y: (y0 + y1) / 2 - height / 2,
2792
+ width: w,
2793
+ height
2794
+ }
2795
+ };
2796
+ };
2797
+ /** Greedy placement: right, left, above, below the dot; skipped when no slot is free */
2798
+ var placeLabels = (labels, plot, measure, height, occupied = []) => {
2799
+ const placed = [];
2800
+ for (const label of labels) {
2801
+ if (label.placement === "inside") {
2802
+ const fit = insideBox(label, plot, measure, height);
2803
+ if (fit) placed.push(fit);
2804
+ continue;
2805
+ }
2806
+ const w = measure(label.text);
2807
+ const gap = label.r + 4;
2808
+ const above = {
2809
+ x: label.x - w / 2,
2810
+ y: label.y - gap - height,
2811
+ width: w,
2812
+ height
2942
2813
  };
2943
- return {
2944
- values,
2945
- labels: values.map(name),
2946
- formatValue: name
2814
+ const below = {
2815
+ x: label.x - w / 2,
2816
+ y: label.y + gap,
2817
+ width: w,
2818
+ height
2947
2819
  };
2948
- }
2949
- if (axis.type === "time") {
2950
- const { locale, timeZone = "local" } = axis;
2951
- const ticks = timeTicks(min, max, count, locale, timeZone);
2952
- const unit = ticks.unit === "day" || ticks.unit === "month" || ticks.unit === "year" ? "day" : ticks.unit;
2953
- const full = axis.format ?? ((v) => formatTime(v, unit, locale, timeZone));
2954
- return {
2955
- values: ticks.values,
2956
- labels: ticks.values.map(axis.format ?? ticks.format),
2957
- formatValue: full
2820
+ const right = {
2821
+ x: label.x + gap,
2822
+ y: label.y - height / 2,
2823
+ width: w,
2824
+ height
2825
+ };
2826
+ const left = {
2827
+ x: label.x - gap - w,
2828
+ y: label.y - height / 2,
2829
+ width: w,
2830
+ height
2958
2831
  };
2832
+ const box = (label.placement === "above" ? [above] : label.placement === "below" ? [below] : label.placement === "right" ? [right, left] : label.placement === "left" ? [left, right] : [
2833
+ {
2834
+ x: label.x + gap,
2835
+ y: label.y - height / 2,
2836
+ width: w,
2837
+ height
2838
+ },
2839
+ {
2840
+ x: label.x - gap - w,
2841
+ y: label.y - height / 2,
2842
+ width: w,
2843
+ height
2844
+ },
2845
+ above,
2846
+ below
2847
+ ]).find((c) => inside$2(c, plot) && !occupied.some((o) => overlaps(o, c)));
2848
+ if (!box) continue;
2849
+ occupied.push(box);
2850
+ placed.push({
2851
+ label,
2852
+ box
2853
+ });
2959
2854
  }
2960
- const values = linearTicks(min, max, count);
2961
- const step = tickStep(min, max, count);
2962
- if (axis.type === "log") return logTicks(axis, min, max, count);
2963
- const tick = axis.format ?? tickFormatter(step, Math.max(Math.abs(min), Math.abs(max)), axis.locale);
2964
- return {
2965
- values,
2966
- labels: values.map(tick),
2967
- formatValue: axis.format ?? ((value) => formatValue(value, axis.locale))
2968
- };
2855
+ return placed;
2969
2856
  };
2970
- /**
2971
- * Ticks of a log axis (`min` / `max` are exponents): every decade, with 2
2972
- * and 5 between them on short ranges, every few decades on long ones;
2973
- * labelled with the values (`1k`, `10k`)
2974
- */
2975
- var logTicks = (axis, min, max, count) => {
2976
- const span = max - min;
2977
- const values = [];
2978
- const raw = [];
2979
- const push = (v) => {
2980
- const e = Math.log10(v);
2981
- if (e >= min - 1e-9 && e <= max + 1e-9) {
2982
- values.push(e);
2983
- raw.push(v);
2857
+ /** Elbow arrows: polylines with rounded corners and filled heads */
2858
+ var drawArrows = (ctx, arrows) => {
2859
+ for (const a of arrows) {
2860
+ const pts = a.points;
2861
+ const first = pts[0];
2862
+ if (!first) continue;
2863
+ ctx.strokeStyle = a.color;
2864
+ ctx.fillStyle = a.color;
2865
+ ctx.lineWidth = a.width;
2866
+ ctx.lineJoin = "round";
2867
+ ctx.setLineDash([]);
2868
+ ctx.beginPath();
2869
+ ctx.moveTo(first.x, first.y);
2870
+ for (let i = 1; i < pts.length; i++) {
2871
+ const p = pts[i];
2872
+ const next = pts[i + 1];
2873
+ const prev = pts[i - 1];
2874
+ if (next && typeof ctx.arcTo === "function") {
2875
+ const r = Math.min(4, Math.hypot(p.x - prev.x, p.y - prev.y) / 2, Math.hypot(next.x - p.x, next.y - p.y) / 2);
2876
+ ctx.arcTo(p.x, p.y, next.x, next.y, r);
2877
+ } else ctx.lineTo(p.x, p.y);
2878
+ }
2879
+ ctx.stroke();
2880
+ const [tip, b0, b1] = a.head;
2881
+ if (tip && b0 && b1) {
2882
+ ctx.beginPath();
2883
+ ctx.moveTo(tip.x, tip.y);
2884
+ ctx.lineTo(b0.x, b0.y);
2885
+ ctx.lineTo(b1.x, b1.y);
2886
+ ctx.closePath();
2887
+ ctx.fill();
2888
+ }
2889
+ }
2890
+ };
2891
+ var drawForeground = (ctx, input) => {
2892
+ const { scales, theme } = input;
2893
+ const { plot } = scales;
2894
+ if (input.clear !== false) ctx.clearRect(0, 0, input.width, input.height);
2895
+ ctx.save();
2896
+ ctx.beginPath();
2897
+ ctx.rect(plot.x, plot.y, plot.width, plot.height);
2898
+ ctx.clip();
2899
+ for (const c of input.connectors ?? []) {
2900
+ ctx.strokeStyle = c.color;
2901
+ ctx.lineWidth = c.width;
2902
+ ctx.setLineDash([...c.dash]);
2903
+ ctx.beginPath();
2904
+ const along = c.y0 === c.y1;
2905
+ const x0 = along ? c.x0 : crisp(c.x0);
2906
+ const y0 = along ? crisp(c.y0) : c.y0;
2907
+ ctx.moveTo(x0, y0);
2908
+ ctx.lineTo(along ? c.x1 : x0, along ? y0 : c.y1);
2909
+ ctx.stroke();
2910
+ }
2911
+ ctx.setLineDash([]);
2912
+ if (input.arrows?.length) drawArrows(ctx, input.arrows);
2913
+ const halo = toCss(theme.background, .9);
2914
+ const occupied = [];
2915
+ const labelHeight = theme.fontSize + 4;
2916
+ for (const a of input.annotations) {
2917
+ if (a.type !== "line") continue;
2918
+ const color = toCss(a.color);
2919
+ ctx.strokeStyle = color;
2920
+ ctx.lineWidth = a.width;
2921
+ ctx.setLineDash(a.dash);
2922
+ ctx.beginPath();
2923
+ const where = lineOf(a, scales);
2924
+ if (!where.vertical) {
2925
+ const py = crisp(where.at);
2926
+ ctx.moveTo(plot.x, py);
2927
+ ctx.lineTo(plot.x + plot.width, py);
2928
+ ctx.stroke();
2929
+ if (a.label) {
2930
+ ctx.setLineDash([]);
2931
+ ctx.font = font(theme, theme.fontSize, 500);
2932
+ ctx.textAlign = "right";
2933
+ ctx.textBaseline = "bottom";
2934
+ haloText(ctx, a.label, plot.x + plot.width - 6, py - 3, color, halo);
2935
+ }
2936
+ } else {
2937
+ const px = crisp(where.at);
2938
+ ctx.moveTo(px, plot.y);
2939
+ ctx.lineTo(px, plot.y + plot.height);
2940
+ ctx.stroke();
2941
+ if (a.label) {
2942
+ ctx.setLineDash([]);
2943
+ ctx.font = font(theme, theme.fontSize, 500);
2944
+ ctx.textAlign = "left";
2945
+ ctx.textBaseline = "top";
2946
+ haloText(ctx, a.label, px + 5, plot.y + 5, color, halo);
2947
+ }
2948
+ }
2949
+ }
2950
+ ctx.setLineDash([]);
2951
+ for (const a of input.annotations) {
2952
+ if (a.type !== "rect" || !hasAreaTitle(a)) continue;
2953
+ const title = areaTitleLayout(a, rectOf(a, scales), theme.fontSize, (text, size, weight) => {
2954
+ ctx.font = font(theme, size, weight);
2955
+ return ctx.measureText(text).width;
2956
+ });
2957
+ if (!title) continue;
2958
+ const color = toCss(a.labelColor);
2959
+ const muted = toCss(theme.textMuted);
2960
+ ctx.textAlign = "left";
2961
+ ctx.textBaseline = "middle";
2962
+ for (const p of title.parts) {
2963
+ const cy = p.y + p.height / 2;
2964
+ if (p.kind === "badge") {
2965
+ const h = p.height - 2;
2966
+ pill(ctx, p.x, cy - h / 2, p.width, h);
2967
+ ctx.fillStyle = toCss(a.labelColor, .16);
2968
+ ctx.fill();
2969
+ ctx.font = font(theme, p.size, p.weight);
2970
+ ctx.fillStyle = color;
2971
+ ctx.fillText(p.text, p.x + 5, cy);
2972
+ continue;
2973
+ }
2974
+ ctx.font = font(theme, p.size, p.weight);
2975
+ ctx.fillStyle = p.kind === "label" ? color : muted;
2976
+ ctx.fillText(p.text, p.x, cy);
2984
2977
  }
2985
- };
2986
- if (span < 1) {
2987
- for (const v of linearTicks(10 ** min, 10 ** max, count)) if (v > 0) push(v);
2988
- } else {
2989
- const every = Math.max(1, Math.ceil(span / Math.max(1, count)));
2990
- const between = span * 3 <= count * 1.6 ? [2, 5] : [];
2991
- for (let e = Math.floor(min); e <= Math.ceil(max); e++) {
2992
- if (e % every !== 0) continue;
2993
- const decade = Number((10 ** e).toPrecision(12));
2994
- push(decade);
2995
- for (const m of between) push(Number((m * decade).toPrecision(12)));
2978
+ occupied.push(title.box);
2979
+ }
2980
+ for (const a of input.annotations) {
2981
+ if (a.type !== "text" || !a.text) continue;
2982
+ ctx.font = font(theme, theme.fontSize, a.weight);
2983
+ const box = textBox$1(a, scales, ctx.measureText(a.text).width, labelHeight);
2984
+ ctx.textAlign = "left";
2985
+ ctx.textBaseline = "middle";
2986
+ haloText(ctx, a.text, box.x, box.y + box.height / 2, toCss(a.color), halo);
2987
+ occupied.push(box);
2988
+ }
2989
+ if (input.pointLabels.length) {
2990
+ ctx.font = font(theme);
2991
+ ctx.textAlign = "left";
2992
+ ctx.textBaseline = "middle";
2993
+ const textColor = toCss(theme.text, .85);
2994
+ const placed = placeLabels(input.pointLabels, plot, (t) => ctx.measureText(t).width, labelHeight - 2, occupied);
2995
+ for (const { label, box } of placed) {
2996
+ const y = box.y + box.height / 2;
2997
+ if (label.color) {
2998
+ ctx.fillStyle = label.color;
2999
+ ctx.fillText(label.text, box.x, y);
3000
+ } else haloText(ctx, label.text, box.x, y, textColor, halo);
2996
3001
  }
2997
3002
  }
2998
- const label = axis.format ?? ((v) => formatCompact(v, 2, axis.locale));
2999
- return {
3000
- values,
3001
- labels: raw.map(label),
3002
- formatValue: axis.format ?? ((value) => formatValue(value, axis.locale))
3003
- };
3003
+ ctx.restore();
3004
3004
  };
3005
3005
  var DEFAULT_PADDING = {
3006
3006
  top: 12,
@@ -3718,6 +3718,7 @@ var hoverMarks = (model, sc, event) => {
3718
3718
  const half = Math.max(s.barHalfWidth / Math.abs(sc.xPerPx), 1);
3719
3719
  bandLo = Math.min(bandLo, c - half);
3720
3720
  bandHi = Math.max(bandHi, c + half);
3721
+ if (s.type === "bar") marks.push(...liftedBars(model, sc, s, it.index));
3721
3722
  continue;
3722
3723
  }
3723
3724
  if (s.type === "heatmap") {
@@ -3753,6 +3754,8 @@ var hoverMarks = (model, sc, event) => {
3753
3754
  y: cell.y + style.inset,
3754
3755
  width: Math.max(1, cell.width - style.inset * 2),
3755
3756
  height: Math.max(1, cell.height - style.inset * 2),
3757
+ seriesIndex: s.index,
3758
+ index: it.index,
3756
3759
  color: it.color,
3757
3760
  ring: contrastRing(fill),
3758
3761
  radius: style.radius
@@ -3797,6 +3800,127 @@ var hoverMarks = (model, sc, event) => {
3797
3800
  };
3798
3801
  /** Ring of a hovered cell: light on dark fills, dark on light ones */
3799
3802
  var contrastRing = (fill) => luminance(fill) > .45 ? "rgba(15, 23, 42, 0.78)" : "rgba(255, 255, 255, 0.9)";
3803
+ /** Bars below this opacity are translucent: ringed in their solid color */
3804
+ var TRANSLUCENT = .6;
3805
+ /**
3806
+ * Corner radius of the bars of a series, like the data layer: stacked
3807
+ * segments stay square, floating bars (ranges, Gantt) keep their corners
3808
+ */
3809
+ var barRadius = (model, s) => !s.floating && model.series.some((o) => o !== s && !o.hidden && o.type === "bar" && !o.floating && o.barOffset === s.barOffset) ? 0 : s.radius;
3810
+ /**
3811
+ * Room (data units) between a bar and the edge of its slot when it shares
3812
+ * its category with other slots (grouped bars); `null` when it is alone
3813
+ */
3814
+ var slotRoom = (model, s) => {
3815
+ let slot = Infinity;
3816
+ for (const o of model.series) {
3817
+ if (o.hidden || o.type !== "bar" && o.type !== "boxplot") continue;
3818
+ const d = Math.abs(o.barOffset - s.barOffset);
3819
+ if (d > 0 && d < slot) slot = d;
3820
+ }
3821
+ return Number.isFinite(slot) ? Math.max(0, slot / 2 - s.barHalfWidth) : null;
3822
+ };
3823
+ /** Pixels the thickness of a hovered bar grows by on each side */
3824
+ var barGrow = (model, sc, s, thickness) => {
3825
+ let grow = Math.min(5, Math.max(1.5, thickness * .08));
3826
+ const room = slotRoom(model, s);
3827
+ if (room !== null) grow = Math.min(grow, room / Math.abs(sc.xPerPx) * .5);
3828
+ return grow >= .75 ? Math.round(grow * 4) / 4 : 0;
3829
+ };
3830
+ /** Bar `i` of a series in pixels of the stage, `null` if not drawn */
3831
+ var barGeometry = (model, sc, s, i) => {
3832
+ const base = s.barBase?.[i];
3833
+ const top = s.barTop?.[i];
3834
+ const x = s.cols.x[i];
3835
+ if (base === void 0 || top === void 0) return null;
3836
+ if (Number.isNaN(base) || Number.isNaN(top) || !Number.isFinite(x)) return null;
3837
+ const { plot } = sc;
3838
+ const c = sc.xToPx(x + s.barOffset);
3839
+ const half = Math.max(s.barHalfWidth / Math.abs(sc.xPerPx), .5);
3840
+ const lo = sc.horizontal ? plot.x : plot.y;
3841
+ const hi = lo + (sc.horizontal ? plot.width : plot.height);
3842
+ const clamp = (v) => Math.min(hi + 16, Math.max(lo - 16, Number.isNaN(v) ? lo : v));
3843
+ const a = clamp(sc.yToPx(base));
3844
+ const b = clamp(sc.yToPx(top));
3845
+ if (Math.abs(a - b) < .5) return null;
3846
+ const rect = screenRect(sc, c - half, c + half, a, b);
3847
+ if (rect.x > plot.x + plot.width || rect.x + rect.width < plot.x || rect.y > plot.y + plot.height || rect.y + rect.height < plot.y) return null;
3848
+ return {
3849
+ ...rect,
3850
+ radius: Math.min(barRadius(model, s), rect.width / 2, rect.height / 2),
3851
+ base: sc.horizontal ? a < b ? "left" : "right" : a > b ? "bottom" : "top"
3852
+ };
3853
+ };
3854
+ /**
3855
+ * Lifted marks of hovered bar `i`, then the bars drawn over it by series
3856
+ * without a tooltip in its slot (the progress of a Gantt task), which the
3857
+ * lifted bar would hide otherwise
3858
+ */
3859
+ var liftedBars = (model, sc, s, i) => {
3860
+ const shape = barGeometry(model, sc, s, i);
3861
+ if (!shape) return [];
3862
+ const grow = barGrow(model, sc, s, shape.base === "top" || shape.base === "bottom" ? shape.width : shape.height);
3863
+ const fill = s.pointColors?.[i] ?? s.color;
3864
+ const alpha = fill[3] * barOpacity(s, i);
3865
+ const translucent = alpha < TRANSLUCENT;
3866
+ const marks = [{
3867
+ kind: "bar",
3868
+ ...shape,
3869
+ seriesIndex: s.index,
3870
+ index: i,
3871
+ color: toCss(fill, barOpacity(s, i)),
3872
+ backdrop: alpha < .98,
3873
+ ring: translucent ? toCss([
3874
+ fill[0],
3875
+ fill[1],
3876
+ fill[2],
3877
+ 1
3878
+ ]) : contrastRing(fill),
3879
+ grow
3880
+ }];
3881
+ const lo = Math.min(s.barBase?.[i] ?? NaN, s.barTop?.[i] ?? NaN);
3882
+ const hi = Math.max(s.barBase?.[i] ?? NaN, s.barTop?.[i] ?? NaN);
3883
+ if (!s.floating || !(hi >= lo)) return marks;
3884
+ const eps = (hi - lo) * 1e-9;
3885
+ for (const o of model.series) {
3886
+ if (o === s || o.hidden || o.tooltip || o.type !== "bar" || !o.floating || o.barOffset !== s.barOffset || !o.barBase || !o.barTop) continue;
3887
+ for (let j = 0; j < o.cols.count; j++) {
3888
+ if (o.cols.x[j] !== s.cols.x[i]) continue;
3889
+ const a = Math.min(o.barBase[j], o.barTop[j]);
3890
+ const b = Math.max(o.barBase[j], o.barTop[j]);
3891
+ if (!(a >= lo - eps && b <= hi + eps)) continue;
3892
+ const part = barGeometry(model, sc, o, j);
3893
+ if (!part) continue;
3894
+ const color = o.pointColors?.[j] ?? o.color;
3895
+ marks.push({
3896
+ kind: "bar",
3897
+ ...part,
3898
+ seriesIndex: o.index,
3899
+ index: j,
3900
+ color: toCss(color, barOpacity(o, j)),
3901
+ backdrop: false,
3902
+ ring: null,
3903
+ grow
3904
+ });
3905
+ }
3906
+ }
3907
+ return marks;
3908
+ };
3909
+ /**
3910
+ * Rectangle of a lifted bar: thicker by `grow` on both sides across its
3911
+ * thickness, the same length
3912
+ */
3913
+ var liftedBarRect = (mark) => mark.base === "top" || mark.base === "bottom" ? {
3914
+ x: mark.x - mark.grow,
3915
+ y: mark.y,
3916
+ width: mark.width + mark.grow * 2,
3917
+ height: mark.height
3918
+ } : {
3919
+ x: mark.x,
3920
+ y: mark.y - mark.grow,
3921
+ width: mark.width,
3922
+ height: mark.height + mark.grow * 2
3923
+ };
3800
3924
  /** Plotted value of the hovered outlier of a box */
3801
3925
  var plottedOutlier = (s, it) => {
3802
3926
  const raw = s.raw.outliers?.[it.index];
@@ -3912,7 +4036,7 @@ var blockedLanes = (boxes) => {
3912
4036
  }
3913
4037
  return out;
3914
4038
  };
3915
- var inside = (lanes, v) => lanes.some(([lo, hi]) => v > lo + 1e-6 && v < hi - 1e-6);
4039
+ var inside$1 = (lanes, v) => lanes.some(([lo, hi]) => v > lo + 1e-6 && v < hi - 1e-6);
3916
4040
  /** First free lane from `from` towards `to` (direction `dir`), or null */
3917
4041
  var freeLane = (lanes, from, to, dir) => {
3918
4042
  const candidates = [from];
@@ -3920,7 +4044,7 @@ var freeLane = (lanes, from, to, dir) => {
3920
4044
  let best = null;
3921
4045
  for (const v of candidates) {
3922
4046
  const along = dir * (v - from);
3923
- if (along < 0 || dir * (to - v) < 0 || inside(lanes, v)) continue;
4047
+ if (along < 0 || dir * (to - v) < 0 || inside$1(lanes, v)) continue;
3924
4048
  if (best === null || along < dir * (best - from)) best = v;
3925
4049
  }
3926
4050
  return best;
@@ -4126,6 +4250,277 @@ var crosshairAt = (model, sc, event, crosshair) => {
4126
4250
  return c >= lo && c <= hi ? c : null;
4127
4251
  };
4128
4252
  //#endregion
4253
+ //#region ../charts/src/engine/tooltip.ts
4254
+ var ORDER$1 = {
4255
+ side: [
4256
+ "right",
4257
+ "left",
4258
+ "bottom",
4259
+ "top"
4260
+ ],
4261
+ vertical: [
4262
+ "top",
4263
+ "bottom",
4264
+ "right",
4265
+ "left"
4266
+ ],
4267
+ right: [
4268
+ "right",
4269
+ "left",
4270
+ "bottom",
4271
+ "top"
4272
+ ],
4273
+ left: [
4274
+ "left",
4275
+ "right",
4276
+ "bottom",
4277
+ "top"
4278
+ ],
4279
+ top: [
4280
+ "top",
4281
+ "bottom",
4282
+ "right",
4283
+ "left"
4284
+ ],
4285
+ bottom: [
4286
+ "bottom",
4287
+ "top",
4288
+ "right",
4289
+ "left"
4290
+ ]
4291
+ };
4292
+ var inset$1 = (r, m) => ({
4293
+ x: r.x + m,
4294
+ y: r.y + m,
4295
+ width: Math.max(0, r.width - m * 2),
4296
+ height: Math.max(0, r.height - m * 2)
4297
+ });
4298
+ var intersection = (a, b) => {
4299
+ const x = Math.max(a.x, b.x);
4300
+ const y = Math.max(a.y, b.y);
4301
+ return {
4302
+ x,
4303
+ y,
4304
+ width: Math.max(0, Math.min(a.x + a.width, b.x + b.width) - x),
4305
+ height: Math.max(0, Math.min(a.y + a.height, b.y + b.height) - y)
4306
+ };
4307
+ };
4308
+ /** Smallest rectangle holding both */
4309
+ var unionRect = (a, b) => {
4310
+ if (!a) return {
4311
+ x: b.x,
4312
+ y: b.y,
4313
+ width: b.width,
4314
+ height: b.height
4315
+ };
4316
+ const x = Math.min(a.x, b.x);
4317
+ const y = Math.min(a.y, b.y);
4318
+ return {
4319
+ x,
4320
+ y,
4321
+ width: Math.max(a.x + a.width, b.x + b.width) - x,
4322
+ height: Math.max(a.y + a.height, b.y + b.height) - y
4323
+ };
4324
+ };
4325
+ /** Area covered by both rectangles */
4326
+ var overlapArea$1 = (a, b) => {
4327
+ const r = intersection(a, b);
4328
+ return r.width * r.height;
4329
+ };
4330
+ /** Start of a `len` long span at `v`, moved into [lo, hi] (at `lo` when longer) */
4331
+ var clampSpan = (v, len, lo, hi) => Math.max(lo, Math.min(v, hi - len));
4332
+ var inside = (area, x, y, w, h) => x >= area.x - .5 && y >= area.y - .5 && x + w <= area.x + area.width + .5 && y + h <= area.y + area.height + .5;
4333
+ /**
4334
+ * Top-left corner of a tooltip that covers neither the hovered marks
4335
+ * (`avoid`) nor the pointer. Candidates sit beside the avoided box, in the
4336
+ * order of `prefer`, centered on the pointer across that side and slid
4337
+ * into the area. The first one inside the stage wins; else, with `bounds`,
4338
+ * the candidate inside the bounds that covers the least of the stage (a
4339
+ * tooltip above a tiny sparkline rather than over it); else the clamped
4340
+ * spot covering the marks the least.
4341
+ */
4342
+ var placeTooltip = (input) => {
4343
+ const { anchor, prefer = "side", gap = 12, margin = 4 } = input;
4344
+ const { width: w, height: h } = input.size;
4345
+ const pointer = {
4346
+ x: anchor.x,
4347
+ y: anchor.y,
4348
+ width: 0,
4349
+ height: 0
4350
+ };
4351
+ const avoid = unionRect(input.avoid ?? null, pointer);
4352
+ const stageRect = {
4353
+ x: 0,
4354
+ y: 0,
4355
+ width: input.stage.width,
4356
+ height: input.stage.height
4357
+ };
4358
+ const bounds = input.bounds && input.bounds.width > 0 && input.bounds.height > 0 ? inset$1(input.bounds, margin) : null;
4359
+ const stageArea = inset$1(stageRect, margin);
4360
+ const visible = bounds ? intersection(stageArea, bounds) : stageArea;
4361
+ const inner = visible.width > 0 && visible.height > 0 ? visible : stageArea;
4362
+ const order = ORDER$1[prefer];
4363
+ const beside = (side, areas) => {
4364
+ let x;
4365
+ let y;
4366
+ if (side === "right" || side === "left") {
4367
+ x = side === "right" ? avoid.x + avoid.width + gap : avoid.x - gap - w;
4368
+ y = anchor.y - h / 2;
4369
+ for (const r of areas) y = clampSpan(y, h, r.y, r.y + r.height);
4370
+ } else {
4371
+ y = side === "bottom" ? avoid.y + avoid.height + gap : avoid.y - gap - h;
4372
+ x = anchor.x - w / 2;
4373
+ for (const r of areas) x = clampSpan(x, w, r.x, r.x + r.width);
4374
+ }
4375
+ return {
4376
+ x,
4377
+ y
4378
+ };
4379
+ };
4380
+ for (const side of order) {
4381
+ const p = beside(side, [inner]);
4382
+ if (inside(inner, p.x, p.y, w, h)) return {
4383
+ ...p,
4384
+ fit: "stage"
4385
+ };
4386
+ }
4387
+ if (bounds) {
4388
+ let best = null;
4389
+ let bestCover = Infinity;
4390
+ for (const side of order) {
4391
+ const p = beside(side, [inner, bounds]);
4392
+ if (!inside(bounds, p.x, p.y, w, h)) continue;
4393
+ const cover = overlapArea$1({
4394
+ x: p.x,
4395
+ y: p.y,
4396
+ width: w,
4397
+ height: h
4398
+ }, stageRect);
4399
+ if (cover < bestCover - .5) {
4400
+ best = p;
4401
+ bestCover = cover;
4402
+ }
4403
+ }
4404
+ if (best) return {
4405
+ ...best,
4406
+ fit: "bounds"
4407
+ };
4408
+ }
4409
+ let best = {
4410
+ x: inner.x,
4411
+ y: inner.y
4412
+ };
4413
+ let bestCover = Infinity;
4414
+ for (const area of bounds ? [inner, bounds] : [inner]) {
4415
+ const spots = [
4416
+ ...order.map((side) => beside(side, [area])),
4417
+ {
4418
+ x: area.x,
4419
+ y: area.y
4420
+ },
4421
+ {
4422
+ x: area.x + area.width - w,
4423
+ y: area.y
4424
+ },
4425
+ {
4426
+ x: area.x,
4427
+ y: area.y + area.height - h
4428
+ },
4429
+ {
4430
+ x: area.x + area.width - w,
4431
+ y: area.y + area.height - h
4432
+ }
4433
+ ];
4434
+ for (const s of spots) {
4435
+ const x = clampSpan(s.x, w, area.x, area.x + area.width);
4436
+ const y = clampSpan(s.y, h, area.y, area.y + area.height);
4437
+ const cover = overlapArea$1({
4438
+ x,
4439
+ y,
4440
+ width: w,
4441
+ height: h
4442
+ }, avoid);
4443
+ if (cover < bestCover - .5) {
4444
+ best = {
4445
+ x,
4446
+ y
4447
+ };
4448
+ bestCover = cover;
4449
+ }
4450
+ }
4451
+ }
4452
+ return {
4453
+ ...best,
4454
+ fit: "none"
4455
+ };
4456
+ };
4457
+ /** Opacity of the fill of canvas-drawn tooltips (a canvas cannot blur what is below) */
4458
+ var CANVAS_TOOLTIP_ALPHA = .86;
4459
+ //#endregion
4460
+ //#region ../charts/src/engine/hover-rect.ts
4461
+ /** Room around the marks (grown / lifted marks, halos) */
4462
+ var PAD = 3;
4463
+ /**
4464
+ * Box (stage pixels) of the hovered marks: bars from their base to their
4465
+ * end, candles and boxes from high to low, dots with their halo, the
4466
+ * hovered cell; clipped to the plot. `null` when nothing is drawn.
4467
+ */
4468
+ var hoverRect = (model, sc, event, marks = hoverMarks(model, sc, event)) => {
4469
+ let box = null;
4470
+ for (const mark of marks) {
4471
+ if (mark.kind === "band" || mark.kind === "guide") continue;
4472
+ if (mark.kind === "dot") {
4473
+ const r = mark.size / 2 + 4;
4474
+ box = unionRect(box, {
4475
+ x: mark.x - r,
4476
+ y: mark.y - r,
4477
+ width: r * 2,
4478
+ height: r * 2
4479
+ });
4480
+ } else if ("width" in mark && "height" in mark) box = unionRect(box, mark);
4481
+ }
4482
+ for (const it of event.items) {
4483
+ const s = model.series[it.seriesIndex];
4484
+ if (!s || it.outlier !== void 0) continue;
4485
+ if (s.type !== "bar" && s.type !== "candlestick" && s.type !== "boxplot") continue;
4486
+ const i = it.index;
4487
+ const x = (s.cols.x[i] ?? it.x) + (s.type === "candlestick" ? 0 : s.barOffset);
4488
+ const c = sc.xToPx(x);
4489
+ const half = Math.max(s.barHalfWidth / Math.abs(sc.xPerPx), s.type === "bar" ? 1 : 3);
4490
+ const { high, low, y } = s.cols;
4491
+ const a = s.type === "bar" ? s.barBase?.[i] ?? model.baseline : high ? high[i] : y[i];
4492
+ const b = s.type === "bar" ? s.barTop?.[i] ?? y[i] : low ? low[i] : y[i];
4493
+ const pa = sc.yToPx(a);
4494
+ const pb = sc.yToPx(b);
4495
+ if (!Number.isFinite(c) || !Number.isFinite(pa) || !Number.isFinite(pb)) continue;
4496
+ const lo = Math.min(pa, pb);
4497
+ const len = Math.abs(pb - pa);
4498
+ box = unionRect(box, sc.horizontal ? {
4499
+ x: lo,
4500
+ y: c - half,
4501
+ width: len,
4502
+ height: half * 2
4503
+ } : {
4504
+ x: c - half,
4505
+ y: lo,
4506
+ width: half * 2,
4507
+ height: len
4508
+ });
4509
+ }
4510
+ if (!box) return null;
4511
+ const { plot } = sc;
4512
+ const x = Math.max(plot.x - PAD, box.x - PAD);
4513
+ const y = Math.max(plot.y - PAD, box.y - PAD);
4514
+ const right = Math.min(plot.x + plot.width + PAD, box.x + box.width + PAD);
4515
+ const bottom = Math.min(plot.y + plot.height + PAD, box.y + box.height + PAD);
4516
+ return right > x && bottom > y ? {
4517
+ x,
4518
+ y,
4519
+ width: right - x,
4520
+ height: bottom - y
4521
+ } : null;
4522
+ };
4523
+ //#endregion
4129
4524
  //#region ../charts/src/engine/theme.ts
4130
4525
  /** Used when no CSS variable is defined (e.g. without the Minerva stylesheet) */
4131
4526
  var DEFAULT_PALETTE = [
@@ -4376,7 +4771,7 @@ var createChartController = (config) => {
4376
4771
  height: legendHeight
4377
4772
  };
4378
4773
  };
4379
- const tooltipLayout = (event, anchorX, anchorY) => {
4774
+ const tooltipLayout = (event, anchorX, anchorY, sc) => {
4380
4775
  const tooltip = options.tooltip;
4381
4776
  if (tooltip === false) return null;
4382
4777
  const custom = typeof tooltip === "object" ? tooltip.render : void 0;
@@ -4409,10 +4804,22 @@ var createChartController = (config) => {
4409
4804
  }
4410
4805
  const boxW = Math.ceil(Math.max(w, 96) + 16 + 4);
4411
4806
  const boxH = lines * lineHeight + 16;
4412
- let left = anchorX + 14;
4413
- if (left + boxW > width - 4) left = anchorX - 14 - boxW;
4414
- left = Math.max(4, Math.min(left, width - boxW - 4));
4415
- const top = Math.max(4, Math.min(anchorY - boxH / 2, height - boxH - 4));
4807
+ const { x: left, y: top } = placeTooltip({
4808
+ anchor: {
4809
+ x: anchorX,
4810
+ y: anchorY
4811
+ },
4812
+ avoid: hoverRect(model, sc, event),
4813
+ size: {
4814
+ width: boxW,
4815
+ height: boxH
4816
+ },
4817
+ stage: {
4818
+ width,
4819
+ height
4820
+ },
4821
+ prefer: sc.horizontal ? "vertical" : "side"
4822
+ });
4416
4823
  return {
4417
4824
  content,
4418
4825
  text,
@@ -4446,7 +4853,7 @@ var createChartController = (config) => {
4446
4853
  const labelHeight = theme.fontSize + 4;
4447
4854
  for (const a of annotations) {
4448
4855
  if (a.type === "text" && a.text) {
4449
- occupied.push(textBox(a, layout.scales, measure(a.text, theme.fontSize, a.weight), labelHeight));
4856
+ occupied.push(textBox$1(a, layout.scales, measure(a.text, theme.fontSize, a.weight), labelHeight));
4450
4857
  continue;
4451
4858
  }
4452
4859
  if (a.type !== "rect") continue;
@@ -4478,7 +4885,7 @@ var createChartController = (config) => {
4478
4885
  marks: hoverMarks(model, layout.scales, hover.event),
4479
4886
  crosshairX: horizontal ? null : guide,
4480
4887
  crosshairY: horizontal ? guide : null,
4481
- tooltip: gesture?.kind === "pan" || gesture?.kind === "point" ? null : tooltipLayout(hover.event, hover.x, hover.y)
4888
+ tooltip: gesture?.kind === "pan" || gesture?.kind === "point" ? null : tooltipLayout(hover.event, hover.x, hover.y, layout.scales)
4482
4889
  };
4483
4890
  const colors = legend?.colors;
4484
4891
  const it = hover.event.items[0];
@@ -6212,6 +6619,80 @@ var drawColorLegend = (ctx, ramp, scene) => {
6212
6619
  ctx.stroke();
6213
6620
  }
6214
6621
  };
6622
+ /** Scale of a hovered heatmap cell (like the DOM host) */
6623
+ var CELL_LIFT = 1.16;
6624
+ /** A rounded rectangle grown by `d` on every side (shrunk when negative) */
6625
+ var grownRect = (ctx, r, radius, d) => {
6626
+ const w = Math.max(0, r.width + d * 2);
6627
+ const h = Math.max(0, r.height + d * 2);
6628
+ roundedRect(ctx, r.x - d, r.y - d, w, h, Math.max(0, Math.min(radius + d, w / 2, h / 2)));
6629
+ };
6630
+ /**
6631
+ * A lifted shape: the lift shadow under a halo of the background, an
6632
+ * optional accent line around the halo, the fill, a ring inside the edge
6633
+ */
6634
+ var drawLifted = (ctx, r, radius, fill, backdrop, ring, halo, accent, theme) => {
6635
+ const background = toCss(theme.background);
6636
+ if (ring) {
6637
+ ctx.save();
6638
+ ctx.shadowColor = "rgba(15, 23, 42, 0.4)";
6639
+ ctx.shadowBlur = 14;
6640
+ ctx.shadowOffsetY = 5;
6641
+ grownRect(ctx, r, radius, halo + (accent?.width ?? 0));
6642
+ ctx.fillStyle = accent ? accent.color : background;
6643
+ ctx.fill();
6644
+ ctx.restore();
6645
+ if (accent) {
6646
+ grownRect(ctx, r, radius, halo);
6647
+ ctx.fillStyle = background;
6648
+ ctx.fill();
6649
+ }
6650
+ } else if (backdrop) {
6651
+ grownRect(ctx, r, radius, 0);
6652
+ ctx.fillStyle = background;
6653
+ ctx.fill();
6654
+ }
6655
+ grownRect(ctx, r, radius, 0);
6656
+ ctx.fillStyle = fill;
6657
+ ctx.fill();
6658
+ if (ring && ring.width > 0) {
6659
+ grownRect(ctx, r, radius, -ring.width / 2);
6660
+ ctx.lineWidth = ring.width;
6661
+ ctx.strokeStyle = ring.color;
6662
+ ctx.stroke();
6663
+ }
6664
+ };
6665
+ /** Hovered bars (thicker, clipped to the plot) and heatmap cells, lifted */
6666
+ var drawLiftedMarks = (ctx, marks, plot, theme) => {
6667
+ for (const mark of marks) if (mark.kind === "bar") {
6668
+ const r = liftedBarRect(mark);
6669
+ ctx.save();
6670
+ ctx.beginPath();
6671
+ ctx.rect(plot.x, plot.y, plot.width, plot.height);
6672
+ ctx.clip();
6673
+ const ring = mark.ring === null ? null : {
6674
+ color: mark.ring,
6675
+ width: Math.min(mark.backdrop ? 2 : 1.5, Math.max(0, (Math.min(r.width, r.height) - 2) / 4))
6676
+ };
6677
+ drawLifted(ctx, r, mark.radius, mark.color, mark.backdrop, ring, 1.5, null, theme);
6678
+ ctx.restore();
6679
+ } else if (mark.kind === "cell") {
6680
+ const w = mark.width * CELL_LIFT;
6681
+ const h = mark.height * CELL_LIFT;
6682
+ drawLifted(ctx, {
6683
+ x: mark.x + (mark.width - w) / 2,
6684
+ y: mark.y + (mark.height - h) / 2,
6685
+ width: w,
6686
+ height: h
6687
+ }, Math.max(2, mark.radius) * CELL_LIFT, mark.color, false, {
6688
+ color: mark.ring,
6689
+ width: Math.min(2, Math.min(w, h) / 5)
6690
+ }, 2, {
6691
+ color: toCss(theme.primary, .6),
6692
+ width: 1.5
6693
+ }, theme);
6694
+ }
6695
+ };
6215
6696
  /** Draws a scene (legend, axes, data, labels, hover) on a 2D context */
6216
6697
  var drawScene = (ctx, scene, items, origin, pixelRatio, painter = createCanvas2dPainter(ctx)) => {
6217
6698
  const { width, height, theme, model, view, layout } = scene;
@@ -6258,10 +6739,11 @@ var drawScene = (ctx, scene, items, origin, pixelRatio, painter = createCanvas2d
6258
6739
  }
6259
6740
  ctx.globalAlpha = 1;
6260
6741
  }
6742
+ const lifted = scene.hover?.marks.some((m) => m.kind === "bar") ?? false;
6261
6743
  for (const mark of scene.hover?.marks ?? []) {
6262
6744
  if (mark.kind !== "band") continue;
6263
6745
  roundedRect(ctx, mark.x, mark.y, mark.width, mark.height, 6);
6264
- ctx.fillStyle = toCss(theme.text, .06);
6746
+ ctx.fillStyle = toCss(theme.text, lifted ? .04 : .06);
6265
6747
  ctx.fill();
6266
6748
  }
6267
6749
  if (scales.horizontal) drawPrimitives(ctx, scenePrimitives(scene), plot);
@@ -6292,6 +6774,11 @@ var drawScene = (ctx, scene, items, origin, pixelRatio, painter = createCanvas2d
6292
6774
  ctx.fillStyle = toCss(theme.text, .06);
6293
6775
  ctx.fillRect(mark.x, mark.y, mark.width, mark.height);
6294
6776
  }
6777
+ if (lifted) {
6778
+ ctx.fillStyle = toCss(theme.background, .42);
6779
+ ctx.fillRect(plot.x, plot.y, plot.width, plot.height);
6780
+ }
6781
+ if (scene.hover) drawLiftedMarks(ctx, scene.hover.marks, plot, theme);
6295
6782
  drawForeground(ctx, {
6296
6783
  width,
6297
6784
  height,
@@ -6353,27 +6840,7 @@ var drawScene = (ctx, scene, items, origin, pixelRatio, painter = createCanvas2d
6353
6840
  ctx.restore();
6354
6841
  }
6355
6842
  for (const mark of hover.marks) {
6356
- if (mark.kind === "band" || mark.kind === "guide") continue;
6357
- if (mark.kind === "cell") {
6358
- const grow = Math.min(3, mark.width * .06, mark.height * .06);
6359
- const r = Math.max(2, mark.radius);
6360
- ctx.save();
6361
- ctx.shadowColor = "rgba(15, 23, 42, 0.4)";
6362
- ctx.shadowBlur = 12;
6363
- ctx.shadowOffsetY = 4;
6364
- roundedRect(ctx, mark.x - grow, mark.y - grow, mark.width + grow * 2, mark.height + grow * 2, r);
6365
- ctx.fillStyle = mark.color;
6366
- ctx.fill();
6367
- ctx.restore();
6368
- const inset = Math.min(2.75, mark.width / 5, mark.height / 5);
6369
- if (mark.width > 8 && mark.height > 8) {
6370
- roundedRect(ctx, mark.x + inset, mark.y + inset, mark.width - inset * 2, mark.height - inset * 2, Math.max(1, r - inset / 2));
6371
- ctx.lineWidth = 1.5;
6372
- ctx.strokeStyle = mark.ring;
6373
- ctx.stroke();
6374
- }
6375
- continue;
6376
- }
6843
+ if (mark.kind === "band" || mark.kind === "guide" || mark.kind === "bar" || mark.kind === "cell") continue;
6377
6844
  ctx.beginPath();
6378
6845
  ctx.arc(mark.x, mark.y, mark.size / 2 + 4, 0, Math.PI * 2);
6379
6846
  ctx.fillStyle = mark.halo;
@@ -6407,7 +6874,7 @@ var drawScene = (ctx, scene, items, origin, pixelRatio, painter = createCanvas2d
6407
6874
  ctx.shadowBlur = 16;
6408
6875
  ctx.shadowOffsetY = 6;
6409
6876
  roundedRect(ctx, box.x, box.y, box.width, box.height, 8);
6410
- ctx.fillStyle = toCss(theme.background);
6877
+ ctx.fillStyle = toCss(theme.background, CANVAS_TOOLTIP_ALPHA);
6411
6878
  ctx.fill();
6412
6879
  ctx.restore();
6413
6880
  roundedRect(ctx, box.x + .5, box.y + .5, box.width - 1, box.height - 1, 8);
@@ -8191,7 +8658,7 @@ var drawShapeTooltip = (ctx, theme, box) => {
8191
8658
  ctx.shadowBlur = 16;
8192
8659
  ctx.shadowOffsetY = 6;
8193
8660
  roundRectPath(ctx, box.x, box.y, box.width, box.height, 8);
8194
- ctx.fillStyle = toCss(theme.background);
8661
+ ctx.fillStyle = toCss(theme.background, CANVAS_TOOLTIP_ALPHA);
8195
8662
  ctx.fill();
8196
8663
  ctx.restore();
8197
8664
  roundRectPath(ctx, box.x + .5, box.y + .5, box.width - 1, box.height - 1, 8);
@@ -13019,40 +13486,163 @@ var layoutShapeLegend = (entries, width, position, chartHeight, theme, measure)
13019
13486
  var TIP_MARGIN = 6;
13020
13487
  /** Distance between a tooltip and its anchor */
13021
13488
  var TIP_GAP = 14;
13489
+ /** Half size of the box around the pointer a tooltip keeps off */
13490
+ var TIP_POINTER = 6;
13022
13491
  var overlapArea = (a, b) => Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
13492
+ /** Box of a circle arc (stroke half width included) */
13493
+ var arcBox = (cx, cy, r0, r1, a0, a1) => {
13494
+ const xs = [];
13495
+ const ys = [];
13496
+ const add = (a) => {
13497
+ for (const r of [r0, r1]) {
13498
+ xs.push(cx + r * Math.cos(a));
13499
+ ys.push(cy + r * Math.sin(a));
13500
+ }
13501
+ };
13502
+ add(a0);
13503
+ add(a1);
13504
+ const start = Math.ceil(a0 / (Math.PI / 2));
13505
+ for (let k = start; k * (Math.PI / 2) <= a1; k++) add(k * (Math.PI / 2));
13506
+ const x = Math.min(...xs);
13507
+ const y = Math.min(...ys);
13508
+ return {
13509
+ x,
13510
+ y,
13511
+ width: Math.max(...xs) - x,
13512
+ height: Math.max(...ys) - y
13513
+ };
13514
+ };
13515
+ /** Box of the marks of an item (slice, tile, arc, polygon, flow, ...) */
13516
+ var itemBox = (scene, index) => {
13517
+ let box = null;
13518
+ const add = (b) => {
13519
+ box = unionRect(box, b);
13520
+ };
13521
+ for (const node of scene.nodes) {
13522
+ if (node.hit !== index) continue;
13523
+ switch (node.kind) {
13524
+ case "rect":
13525
+ add(node);
13526
+ break;
13527
+ case "sector":
13528
+ add(arcBox(node.cx, node.cy, node.r0, node.r1, node.a0, node.a1));
13529
+ break;
13530
+ case "arc":
13531
+ add(arcBox(node.cx, node.cy, node.r - node.width / 2, node.r + node.width / 2, node.a0, node.a1));
13532
+ break;
13533
+ case "polygon": {
13534
+ const p = node.points;
13535
+ let x0 = Infinity;
13536
+ let y0 = Infinity;
13537
+ let x1 = -Infinity;
13538
+ let y1 = -Infinity;
13539
+ for (let i = 0; i + 1 < p.length; i += 2) {
13540
+ x0 = Math.min(x0, p[i]);
13541
+ x1 = Math.max(x1, p[i]);
13542
+ y0 = Math.min(y0, p[i + 1]);
13543
+ y1 = Math.max(y1, p[i + 1]);
13544
+ }
13545
+ if (x1 >= x0) add({
13546
+ x: x0,
13547
+ y: y0,
13548
+ width: x1 - x0,
13549
+ height: y1 - y0
13550
+ });
13551
+ break;
13552
+ }
13553
+ case "circle":
13554
+ add({
13555
+ x: node.cx - node.r,
13556
+ y: node.cy - node.r,
13557
+ width: node.r * 2,
13558
+ height: node.r * 2
13559
+ });
13560
+ break;
13561
+ case "link": {
13562
+ const half = node.width / 2;
13563
+ const y0 = Math.min(node.y0, node.y1) - half;
13564
+ add({
13565
+ x: Math.min(node.x0, node.x1),
13566
+ y: y0,
13567
+ width: Math.abs(node.x1 - node.x0),
13568
+ height: Math.abs(node.y1 - node.y0) + node.width
13569
+ });
13570
+ break;
13571
+ }
13572
+ }
13573
+ }
13574
+ return box;
13575
+ };
13576
+ /** Approximate box of a text node (unrotated) */
13577
+ var textBox = (node) => {
13578
+ const size = Number(/(\d+(?:\.\d+)?)px/.exec(node.font)?.[1] ?? 12);
13579
+ const width = estimateText(node.text, node.font);
13580
+ return {
13581
+ x: node.align === "center" ? node.x - width / 2 : node.align === "right" ? node.x - width : node.x,
13582
+ y: node.baseline === "top" ? node.y : node.baseline === "middle" ? node.y - size / 2 : node.y - size,
13583
+ width,
13584
+ height: size
13585
+ };
13586
+ };
13023
13587
  /**
13024
13588
  * Top-left corner of a `w` x `h` tooltip anchored at `anchor`, inside a
13025
- * `width` x `height` drawing. Beside the anchor (right, else left) by
13026
- * default; radar tooltips go outward, off the hovered vertex and, when
13027
- * possible, off the indicator labels.
13589
+ * `width` x `height` drawing (or overflowing it into `bounds`, see
13590
+ * `placeTooltip`, when the drawing has no room). Candidates around the
13591
+ * pointer and around the hovered item are scored: never over the pointer
13592
+ * (nor the hovered radar vertex), then covering as little as possible of the
13593
+ * hovered item and of the texts, close to the pointer; radar tooltips lean
13594
+ * outward.
13028
13595
  */
13029
- var placeShapeTooltip = (scene, hover, anchor, w, h, width, height) => {
13596
+ var placeShapeTooltip = (scene, hover, anchor, w, h, width, height, bounds = null) => {
13030
13597
  const m = TIP_MARGIN;
13598
+ const { x: ax, y: ay } = anchor;
13599
+ const t = TIP_POINTER;
13600
+ const fallback = () => placeTooltip({
13601
+ anchor,
13602
+ size: {
13603
+ width: w,
13604
+ height: h
13605
+ },
13606
+ stage: {
13607
+ width,
13608
+ height
13609
+ },
13610
+ margin: m,
13611
+ avoid: {
13612
+ x: ax - t,
13613
+ y: ay - t,
13614
+ width: 12,
13615
+ height: 12
13616
+ },
13617
+ bounds,
13618
+ gap: 10
13619
+ });
13620
+ if (!scene || !hover || w + 12 > width || h + 12 > height) return fallback();
13031
13621
  const fit = (x, y) => ({
13032
13622
  x: Math.max(m, Math.min(x, width - w - m)),
13033
13623
  y: Math.max(m, Math.min(y, height - h - m))
13034
13624
  });
13035
- const { x: ax, y: ay } = anchor;
13036
- const radar = scene?.radar;
13037
- if (!radar || !scene || !hover) {
13038
- let x = ax + 16;
13039
- if (x + w > width - m) x = ax - 16 - w;
13040
- return fit(x, ay - h / 2);
13041
- }
13042
- const avoid = [[ax, ay]];
13625
+ const keep = [[ax, ay]];
13043
13626
  const item = scene.items.find((it) => it.index === hover.index);
13044
13627
  const vertex = item?.indicators && hover.indicator !== void 0 ? item.indicators[hover.indicator] : void 0;
13045
- if (vertex) avoid.push([vertex.x, vertex.y]);
13046
- const labels = radar.labels ?? [];
13047
- let dx = ax - radar.cx;
13048
- let dy = ay - radar.cy;
13049
- const len = Math.hypot(dx, dy);
13050
- if (len > 1) {
13051
- dx /= len;
13052
- dy /= len;
13053
- } else {
13054
- dx = 1;
13055
- dy = 0;
13628
+ if (vertex) keep.push([vertex.x, vertex.y]);
13629
+ const target = itemBox(scene, hover.index);
13630
+ const texts = [...scene.radar?.labels ?? []];
13631
+ for (const node of scene.nodes) if (node.kind === "text" && !node.rotate && node.indicator === void 0) texts.push(textBox(node));
13632
+ const radar = scene.radar;
13633
+ let dx = 0;
13634
+ let dy = 0;
13635
+ if (radar) {
13636
+ dx = ax - radar.cx;
13637
+ dy = ay - radar.cy;
13638
+ const len = Math.hypot(dx, dy);
13639
+ if (len > 1) {
13640
+ dx /= len;
13641
+ dy /= len;
13642
+ } else {
13643
+ dx = 1;
13644
+ dy = 0;
13645
+ }
13056
13646
  }
13057
13647
  const g = TIP_GAP;
13058
13648
  const candidates = [
@@ -13065,7 +13655,13 @@ var placeShapeTooltip = (scene, hover, anchor, w, h, width, height) => {
13065
13655
  [ax - g - w, ay - g - h],
13066
13656
  [ax - g - w, ay + g]
13067
13657
  ];
13068
- let best = fit(candidates[0][0], candidates[0][1]);
13658
+ if (target) {
13659
+ const gap = 10;
13660
+ candidates.push([target.x + target.width + gap, ay - h / 2], [target.x - gap - w, ay - h / 2], [ax - w / 2, target.y - gap - h], [ax - w / 2, target.y + target.height + gap]);
13661
+ }
13662
+ candidates.push([m, ay - h / 2], [width - w - m, ay - h / 2], [ax - w / 2, m], [ax - w / 2, height - h - m]);
13663
+ const area = w * h;
13664
+ let best = null;
13069
13665
  let bestScore = Infinity;
13070
13666
  for (const [cx0, cy0] of candidates) {
13071
13667
  const p = fit(cx0, cy0);
@@ -13076,20 +13672,25 @@ var placeShapeTooltip = (scene, hover, anchor, w, h, width, height) => {
13076
13672
  height: h
13077
13673
  };
13078
13674
  let score = 0;
13079
- for (const [px, py] of avoid) if (px > box.x - 8 && px < box.x + w + 8 && py > box.y - 8 && py < box.y + h + 8) score += 1e6;
13080
- for (const label of labels) score += overlapArea(box, label) * 4;
13675
+ for (const [px, py] of keep) if (px > box.x - 8 && px < box.x + w + 8 && py > box.y - 8 && py < box.y + h + 8) score += 1e6;
13676
+ if (target) score += overlapArea(box, target) / area * 400;
13677
+ for (const text of texts) score += overlapArea(box, text) * 4;
13081
13678
  const ox = p.x + w / 2 - ax;
13082
13679
  const oy = p.y + h / 2 - ay;
13083
13680
  const dist = Math.hypot(ox, oy) || 1;
13084
- score += dist * .5;
13085
- score -= (ox * dx + oy * dy) / dist * 40;
13681
+ score += dist * .35;
13682
+ if (radar) score -= (ox * dx + oy * dy) / dist * 40;
13086
13683
  score += Math.abs(p.x - cx0) + Math.abs(p.y - cy0);
13087
13684
  if (score < bestScore) {
13088
13685
  bestScore = score;
13089
13686
  best = p;
13090
13687
  }
13091
13688
  }
13092
- return best;
13689
+ if (!best || bestScore >= 1e6) return fallback();
13690
+ return {
13691
+ ...best,
13692
+ fit: "stage"
13693
+ };
13093
13694
  };
13094
13695
  /** Box of a tooltip drawn on a canvas, next to the anchor and inside the drawing */
13095
13696
  var layoutShapeTooltip = (value, anchorX, anchorY, width, height, theme, measure, scene = null, hover = null) => {
@@ -13706,6 +14307,12 @@ Object.defineProperty(exports, "histogramSeries", {
13706
14307
  return histogramSeries;
13707
14308
  }
13708
14309
  });
14310
+ Object.defineProperty(exports, "liftedBarRect", {
14311
+ enumerable: true,
14312
+ get: function() {
14313
+ return liftedBarRect;
14314
+ }
14315
+ });
13709
14316
  Object.defineProperty(exports, "lineOf", {
13710
14317
  enumerable: true,
13711
14318
  get: function() {
@@ -13793,7 +14400,7 @@ Object.defineProperty(exports, "sparklineOptions", {
13793
14400
  Object.defineProperty(exports, "textBox", {
13794
14401
  enumerable: true,
13795
14402
  get: function() {
13796
- return textBox;
14403
+ return textBox$1;
13797
14404
  }
13798
14405
  });
13799
14406
  Object.defineProperty(exports, "toCss", {