minerva-design 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +21 -19
  3. package/dist/charts/canvas.d.cts +7 -0
  4. package/dist/charts/canvas.d.ts +7 -0
  5. package/dist/charts/core.cjs +1 -0
  6. package/dist/charts/core.d.cts +7 -0
  7. package/dist/charts/core.d.ts +7 -0
  8. package/dist/charts/core.js +6 -0
  9. package/dist/charts/engine/axis.cjs +2 -0
  10. package/dist/charts/engine/axis.cjs.map +1 -0
  11. package/dist/charts/engine/axis.d.cts +13 -0
  12. package/dist/charts/engine/axis.d.ts +13 -0
  13. package/dist/charts/engine/axis.js +38 -0
  14. package/dist/charts/engine/axis.js.map +1 -0
  15. package/dist/charts/engine/canvas.cjs +2 -0
  16. package/dist/charts/engine/canvas.cjs.map +1 -0
  17. package/dist/charts/engine/canvas.d.cts +85 -0
  18. package/dist/charts/engine/canvas.d.ts +85 -0
  19. package/dist/charts/engine/canvas.js +165 -0
  20. package/dist/charts/engine/canvas.js.map +1 -0
  21. package/dist/charts/engine/chart.cjs +3 -0
  22. package/dist/charts/engine/chart.cjs.map +1 -0
  23. package/dist/charts/engine/chart.d.cts +6 -0
  24. package/dist/charts/engine/chart.d.ts +6 -0
  25. package/dist/charts/engine/chart.js +566 -0
  26. package/dist/charts/engine/chart.js.map +1 -0
  27. package/dist/charts/engine/color.cjs +2 -0
  28. package/dist/charts/engine/color.cjs.map +1 -0
  29. package/dist/charts/engine/color.d.cts +33 -0
  30. package/dist/charts/engine/color.d.ts +33 -0
  31. package/dist/charts/engine/color.js +167 -0
  32. package/dist/charts/engine/color.js.map +1 -0
  33. package/dist/charts/engine/controller.cjs +3 -0
  34. package/dist/charts/engine/controller.cjs.map +1 -0
  35. package/dist/charts/engine/controller.d.cts +182 -0
  36. package/dist/charts/engine/controller.d.ts +182 -0
  37. package/dist/charts/engine/controller.js +528 -0
  38. package/dist/charts/engine/controller.js.map +1 -0
  39. package/dist/charts/engine/data.cjs +2 -0
  40. package/dist/charts/engine/data.cjs.map +1 -0
  41. package/dist/charts/engine/data.d.cts +29 -0
  42. package/dist/charts/engine/data.d.ts +29 -0
  43. package/dist/charts/engine/data.js +104 -0
  44. package/dist/charts/engine/data.js.map +1 -0
  45. package/dist/charts/engine/decimate.cjs +2 -0
  46. package/dist/charts/engine/decimate.cjs.map +1 -0
  47. package/dist/charts/engine/decimate.d.cts +13 -0
  48. package/dist/charts/engine/decimate.d.ts +13 -0
  49. package/dist/charts/engine/decimate.js +28 -0
  50. package/dist/charts/engine/decimate.js.map +1 -0
  51. package/dist/charts/engine/dom.cjs +2 -0
  52. package/dist/charts/engine/dom.cjs.map +1 -0
  53. package/dist/charts/engine/dom.d.cts +21 -0
  54. package/dist/charts/engine/dom.d.ts +21 -0
  55. package/dist/charts/engine/dom.js +125 -0
  56. package/dist/charts/engine/dom.js.map +1 -0
  57. package/dist/charts/engine/format.cjs +2 -0
  58. package/dist/charts/engine/format.cjs.map +1 -0
  59. package/dist/charts/engine/format.d.cts +9 -0
  60. package/dist/charts/engine/format.d.ts +9 -0
  61. package/dist/charts/engine/format.js +34 -0
  62. package/dist/charts/engine/format.js.map +1 -0
  63. package/dist/charts/engine/items.cjs +2 -0
  64. package/dist/charts/engine/items.cjs.map +1 -0
  65. package/dist/charts/engine/items.d.cts +8 -0
  66. package/dist/charts/engine/items.d.ts +8 -0
  67. package/dist/charts/engine/items.js +91 -0
  68. package/dist/charts/engine/items.js.map +1 -0
  69. package/dist/charts/engine/layout.cjs +2 -0
  70. package/dist/charts/engine/layout.cjs.map +1 -0
  71. package/dist/charts/engine/layout.d.cts +37 -0
  72. package/dist/charts/engine/layout.d.ts +37 -0
  73. package/dist/charts/engine/layout.js +26 -0
  74. package/dist/charts/engine/layout.js.map +1 -0
  75. package/dist/charts/engine/model.cjs +2 -0
  76. package/dist/charts/engine/model.cjs.map +1 -0
  77. package/dist/charts/engine/model.d.cts +74 -0
  78. package/dist/charts/engine/model.d.ts +74 -0
  79. package/dist/charts/engine/model.js +175 -0
  80. package/dist/charts/engine/model.js.map +1 -0
  81. package/dist/charts/engine/overlay.cjs +2 -0
  82. package/dist/charts/engine/overlay.cjs.map +1 -0
  83. package/dist/charts/engine/overlay.d.cts +78 -0
  84. package/dist/charts/engine/overlay.d.ts +78 -0
  85. package/dist/charts/engine/overlay.js +137 -0
  86. package/dist/charts/engine/overlay.js.map +1 -0
  87. package/dist/charts/engine/presets.cjs +3 -0
  88. package/dist/charts/engine/presets.cjs.map +1 -0
  89. package/dist/charts/engine/presets.d.cts +103 -0
  90. package/dist/charts/engine/presets.d.ts +103 -0
  91. package/dist/charts/engine/presets.js +153 -0
  92. package/dist/charts/engine/presets.js.map +1 -0
  93. package/dist/charts/engine/renderers/canvas2d.cjs +2 -0
  94. package/dist/charts/engine/renderers/canvas2d.cjs.map +1 -0
  95. package/dist/charts/engine/renderers/canvas2d.d.cts +20 -0
  96. package/dist/charts/engine/renderers/canvas2d.d.ts +20 -0
  97. package/dist/charts/engine/renderers/canvas2d.js +193 -0
  98. package/dist/charts/engine/renderers/canvas2d.js.map +1 -0
  99. package/dist/charts/engine/renderers/detect.cjs +2 -0
  100. package/dist/charts/engine/renderers/detect.cjs.map +1 -0
  101. package/dist/charts/engine/renderers/detect.d.cts +12 -0
  102. package/dist/charts/engine/renderers/detect.d.ts +12 -0
  103. package/dist/charts/engine/renderers/detect.js +33 -0
  104. package/dist/charts/engine/renderers/detect.js.map +1 -0
  105. package/dist/charts/engine/renderers/range.cjs +2 -0
  106. package/dist/charts/engine/renderers/range.cjs.map +1 -0
  107. package/dist/charts/engine/renderers/range.d.cts +5 -0
  108. package/dist/charts/engine/renderers/range.d.ts +5 -0
  109. package/dist/charts/engine/renderers/range.js +32 -0
  110. package/dist/charts/engine/renderers/range.js.map +1 -0
  111. package/dist/charts/engine/renderers/shaders.cjs +266 -0
  112. package/dist/charts/engine/renderers/shaders.cjs.map +1 -0
  113. package/dist/charts/engine/renderers/shaders.d.cts +20 -0
  114. package/dist/charts/engine/renderers/shaders.d.ts +20 -0
  115. package/dist/charts/engine/renderers/shaders.js +251 -0
  116. package/dist/charts/engine/renderers/shaders.js.map +1 -0
  117. package/dist/charts/engine/renderers/types.cjs +2 -0
  118. package/dist/charts/engine/renderers/types.cjs.map +1 -0
  119. package/dist/charts/engine/renderers/types.d.cts +83 -0
  120. package/dist/charts/engine/renderers/types.d.ts +83 -0
  121. package/dist/charts/engine/renderers/types.js +6 -0
  122. package/dist/charts/engine/renderers/types.js.map +1 -0
  123. package/dist/charts/engine/renderers/webgl2.cjs +2 -0
  124. package/dist/charts/engine/renderers/webgl2.cjs.map +1 -0
  125. package/dist/charts/engine/renderers/webgl2.d.cts +5 -0
  126. package/dist/charts/engine/renderers/webgl2.d.ts +5 -0
  127. package/dist/charts/engine/renderers/webgl2.js +134 -0
  128. package/dist/charts/engine/renderers/webgl2.js.map +1 -0
  129. package/dist/charts/engine/renderers/webgpu.cjs +2 -0
  130. package/dist/charts/engine/renderers/webgpu.cjs.map +1 -0
  131. package/dist/charts/engine/renderers/webgpu.d.cts +6 -0
  132. package/dist/charts/engine/renderers/webgpu.d.ts +6 -0
  133. package/dist/charts/engine/renderers/webgpu.js +220 -0
  134. package/dist/charts/engine/renderers/webgpu.js.map +1 -0
  135. package/dist/charts/engine/scale.cjs +2 -0
  136. package/dist/charts/engine/scale.cjs.map +1 -0
  137. package/dist/charts/engine/scale.d.cts +13 -0
  138. package/dist/charts/engine/scale.d.ts +13 -0
  139. package/dist/charts/engine/scale.js +37 -0
  140. package/dist/charts/engine/scale.js.map +1 -0
  141. package/dist/charts/engine/scene.cjs +2 -0
  142. package/dist/charts/engine/scene.cjs.map +1 -0
  143. package/dist/charts/engine/scene.d.cts +89 -0
  144. package/dist/charts/engine/scene.d.ts +89 -0
  145. package/dist/charts/engine/scene.js +241 -0
  146. package/dist/charts/engine/scene.js.map +1 -0
  147. package/dist/charts/engine/spatial.cjs +2 -0
  148. package/dist/charts/engine/spatial.cjs.map +1 -0
  149. package/dist/charts/engine/spatial.d.cts +18 -0
  150. package/dist/charts/engine/spatial.d.ts +18 -0
  151. package/dist/charts/engine/spatial.js +53 -0
  152. package/dist/charts/engine/spatial.js.map +1 -0
  153. package/dist/charts/engine/theme.cjs +2 -0
  154. package/dist/charts/engine/theme.cjs.map +1 -0
  155. package/dist/charts/engine/theme.d.cts +45 -0
  156. package/dist/charts/engine/theme.d.ts +45 -0
  157. package/dist/charts/engine/theme.js +86 -0
  158. package/dist/charts/engine/theme.js.map +1 -0
  159. package/dist/charts/engine/time.cjs +2 -0
  160. package/dist/charts/engine/time.cjs.map +1 -0
  161. package/dist/charts/engine/time.d.cts +10 -0
  162. package/dist/charts/engine/time.d.ts +10 -0
  163. package/dist/charts/engine/time.js +189 -0
  164. package/dist/charts/engine/time.js.map +1 -0
  165. package/dist/charts/engine/types.d.cts +356 -0
  166. package/dist/charts/engine/types.d.ts +356 -0
  167. package/dist/charts/engine/viewport.cjs +2 -0
  168. package/dist/charts/engine/viewport.cjs.map +1 -0
  169. package/dist/charts/engine/viewport.d.cts +12 -0
  170. package/dist/charts/engine/viewport.d.ts +12 -0
  171. package/dist/charts/engine/viewport.js +15 -0
  172. package/dist/charts/engine/viewport.js.map +1 -0
  173. package/dist/charts/index.cjs +1 -0
  174. package/dist/charts/index.d.cts +4 -0
  175. package/dist/charts/index.d.ts +4 -0
  176. package/dist/charts/index.js +10 -0
  177. package/dist/charts/react/Chart.cjs +2 -0
  178. package/dist/charts/react/Chart.cjs.map +1 -0
  179. package/dist/charts/react/Chart.d.cts +35 -0
  180. package/dist/charts/react/Chart.d.ts +35 -0
  181. package/dist/charts/react/Chart.js +117 -0
  182. package/dist/charts/react/Chart.js.map +1 -0
  183. package/dist/charts/react/charts.cjs +2 -0
  184. package/dist/charts/react/charts.cjs.map +1 -0
  185. package/dist/charts/react/charts.d.cts +13 -0
  186. package/dist/charts/react/charts.d.ts +13 -0
  187. package/dist/charts/react/charts.js +133 -0
  188. package/dist/charts/react/charts.js.map +1 -0
  189. package/dist/charts/react/types.d.cts +136 -0
  190. package/dist/charts/react/types.d.ts +136 -0
  191. package/dist/native/charts/src/canvas.cjs +1 -0
  192. package/dist/native/charts/src/canvas.js +6 -0
  193. package/dist/native/charts/src/engine/axis.cjs +2 -0
  194. package/dist/native/charts/src/engine/axis.cjs.map +1 -0
  195. package/dist/native/charts/src/engine/axis.js +38 -0
  196. package/dist/native/charts/src/engine/axis.js.map +1 -0
  197. package/dist/native/charts/src/engine/canvas.cjs +1 -0
  198. package/dist/native/charts/src/engine/canvas.js +1 -0
  199. package/dist/native/charts/src/engine/color.cjs +2 -0
  200. package/dist/native/charts/src/engine/color.cjs.map +1 -0
  201. package/dist/native/charts/src/engine/color.js +130 -0
  202. package/dist/native/charts/src/engine/color.js.map +1 -0
  203. package/dist/native/charts/src/engine/controller.cjs +3 -0
  204. package/dist/native/charts/src/engine/controller.cjs.map +1 -0
  205. package/dist/native/charts/src/engine/controller.js +528 -0
  206. package/dist/native/charts/src/engine/controller.js.map +1 -0
  207. package/dist/native/charts/src/engine/data.cjs +2 -0
  208. package/dist/native/charts/src/engine/data.cjs.map +1 -0
  209. package/dist/native/charts/src/engine/data.js +104 -0
  210. package/dist/native/charts/src/engine/data.js.map +1 -0
  211. package/dist/native/charts/src/engine/decimate.cjs +2 -0
  212. package/dist/native/charts/src/engine/decimate.cjs.map +1 -0
  213. package/dist/native/charts/src/engine/decimate.js +28 -0
  214. package/dist/native/charts/src/engine/decimate.js.map +1 -0
  215. package/dist/native/charts/src/engine/format.cjs +2 -0
  216. package/dist/native/charts/src/engine/format.cjs.map +1 -0
  217. package/dist/native/charts/src/engine/format.js +34 -0
  218. package/dist/native/charts/src/engine/format.js.map +1 -0
  219. package/dist/native/charts/src/engine/layout.cjs +2 -0
  220. package/dist/native/charts/src/engine/layout.cjs.map +1 -0
  221. package/dist/native/charts/src/engine/layout.js +26 -0
  222. package/dist/native/charts/src/engine/layout.js.map +1 -0
  223. package/dist/native/charts/src/engine/model.cjs +2 -0
  224. package/dist/native/charts/src/engine/model.cjs.map +1 -0
  225. package/dist/native/charts/src/engine/model.js +175 -0
  226. package/dist/native/charts/src/engine/model.js.map +1 -0
  227. package/dist/native/charts/src/engine/overlay.cjs +2 -0
  228. package/dist/native/charts/src/engine/overlay.cjs.map +1 -0
  229. package/dist/native/charts/src/engine/overlay.js +49 -0
  230. package/dist/native/charts/src/engine/overlay.js.map +1 -0
  231. package/dist/native/charts/src/engine/presets.cjs +3 -0
  232. package/dist/native/charts/src/engine/presets.cjs.map +1 -0
  233. package/dist/native/charts/src/engine/presets.js +147 -0
  234. package/dist/native/charts/src/engine/presets.js.map +1 -0
  235. package/dist/native/charts/src/engine/scale.cjs +2 -0
  236. package/dist/native/charts/src/engine/scale.cjs.map +1 -0
  237. package/dist/native/charts/src/engine/scale.js +37 -0
  238. package/dist/native/charts/src/engine/scale.js.map +1 -0
  239. package/dist/native/charts/src/engine/scene.cjs +2 -0
  240. package/dist/native/charts/src/engine/scene.cjs.map +1 -0
  241. package/dist/native/charts/src/engine/scene.js +241 -0
  242. package/dist/native/charts/src/engine/scene.js.map +1 -0
  243. package/dist/native/charts/src/engine/spatial.cjs +2 -0
  244. package/dist/native/charts/src/engine/spatial.cjs.map +1 -0
  245. package/dist/native/charts/src/engine/spatial.js +53 -0
  246. package/dist/native/charts/src/engine/spatial.js.map +1 -0
  247. package/dist/native/charts/src/engine/theme.cjs +2 -0
  248. package/dist/native/charts/src/engine/theme.cjs.map +1 -0
  249. package/dist/native/charts/src/engine/theme.js +62 -0
  250. package/dist/native/charts/src/engine/theme.js.map +1 -0
  251. package/dist/native/charts/src/engine/time.cjs +2 -0
  252. package/dist/native/charts/src/engine/time.cjs.map +1 -0
  253. package/dist/native/charts/src/engine/time.js +189 -0
  254. package/dist/native/charts/src/engine/time.js.map +1 -0
  255. package/dist/native/charts/src/engine/viewport.cjs +2 -0
  256. package/dist/native/charts/src/engine/viewport.cjs.map +1 -0
  257. package/dist/native/charts/src/engine/viewport.js +12 -0
  258. package/dist/native/charts/src/engine/viewport.js.map +1 -0
  259. package/dist/native/components/Chart/ChartBase.cjs +2 -0
  260. package/dist/native/components/Chart/ChartBase.cjs.map +1 -0
  261. package/dist/native/components/Chart/ChartBase.d.cts +29 -0
  262. package/dist/native/components/Chart/ChartBase.d.ts +29 -0
  263. package/dist/native/components/Chart/ChartBase.js +287 -0
  264. package/dist/native/components/Chart/ChartBase.js.map +1 -0
  265. package/dist/native/components/Chart/charts.cjs +2 -0
  266. package/dist/native/components/Chart/charts.cjs.map +1 -0
  267. package/dist/native/components/Chart/charts.d.cts +19 -0
  268. package/dist/native/components/Chart/charts.d.ts +19 -0
  269. package/dist/native/components/Chart/charts.js +173 -0
  270. package/dist/native/components/Chart/charts.js.map +1 -0
  271. package/dist/native/components/Chart/index.cjs +1 -0
  272. package/dist/native/components/Chart/index.d.cts +2 -0
  273. package/dist/native/components/Chart/index.d.ts +2 -0
  274. package/dist/native/components/Chart/index.js +2 -0
  275. package/dist/native/components/Chart/measure.cjs +2 -0
  276. package/dist/native/components/Chart/measure.cjs.map +1 -0
  277. package/dist/native/components/Chart/measure.d.cts +5 -0
  278. package/dist/native/components/Chart/measure.d.ts +5 -0
  279. package/dist/native/components/Chart/measure.js +11 -0
  280. package/dist/native/components/Chart/measure.js.map +1 -0
  281. package/dist/native/components/Chart/scene.cjs +2 -0
  282. package/dist/native/components/Chart/scene.cjs.map +1 -0
  283. package/dist/native/components/Chart/scene.d.cts +41 -0
  284. package/dist/native/components/Chart/scene.d.ts +41 -0
  285. package/dist/native/components/Chart/scene.js +419 -0
  286. package/dist/native/components/Chart/scene.js.map +1 -0
  287. package/dist/native/components/Chart/theme.cjs +2 -0
  288. package/dist/native/components/Chart/theme.cjs.map +1 -0
  289. package/dist/native/components/Chart/theme.d.cts +10 -0
  290. package/dist/native/components/Chart/theme.d.ts +10 -0
  291. package/dist/native/components/Chart/theme.js +10 -0
  292. package/dist/native/components/Chart/theme.js.map +1 -0
  293. package/dist/native/components/Chart/types.d.cts +129 -0
  294. package/dist/native/components/Chart/types.d.ts +129 -0
  295. package/dist/native/index.cjs +1 -1
  296. package/dist/native/index.d.cts +1 -0
  297. package/dist/native/index.d.ts +1 -0
  298. package/dist/native/index.js +6 -4
  299. package/dist/native/manifest.d.cts +7 -0
  300. package/dist/native/manifest.d.ts +7 -0
  301. package/dist/native/source/components/Chart/ChartBase.tsx +545 -0
  302. package/dist/native/source/components/Chart/charts.tsx +291 -0
  303. package/dist/native/source/components/Chart/index.ts +8 -0
  304. package/dist/native/source/components/Chart/measure.ts +50 -0
  305. package/dist/native/source/components/Chart/scene.tsx +987 -0
  306. package/dist/native/source/components/Chart/theme.ts +16 -0
  307. package/dist/native/source/components/Chart/types.ts +194 -0
  308. package/dist/native/source/index.ts +1 -0
  309. package/dist/native/source/manifest.ts +14 -0
  310. package/dist/taro/charts.d.ts +144 -0
  311. package/dist/taro/index.d.ts +1 -0
  312. package/dist/taro/index.js +3064 -618
  313. package/dist/uni/BarChart.vue +75 -0
  314. package/dist/uni/BarChart.vue.d.ts +111 -0
  315. package/dist/uni/Chart.vue +68 -0
  316. package/dist/uni/Chart.vue.d.ts +100 -0
  317. package/dist/uni/LineChart.vue +75 -0
  318. package/dist/uni/LineChart.vue.d.ts +111 -0
  319. package/dist/uni/ProgressIndicator.vue.d.ts +1 -1
  320. package/dist/uni/QuadrantChart.vue +184 -0
  321. package/dist/uni/QuadrantChart.vue.d.ts +173 -0
  322. package/dist/uni/ScatterChart.vue +69 -0
  323. package/dist/uni/ScatterChart.vue.d.ts +100 -0
  324. package/dist/uni/chart-canvas.d.ts +68 -0
  325. package/dist/uni/chart-canvas.js +363 -0
  326. package/dist/uni/chart-types.d.ts +135 -0
  327. package/dist/uni/chart-types.js +84 -0
  328. package/dist/uni/charts-canvas.d.ts +1 -0
  329. package/dist/uni/charts-canvas.js +8 -0
  330. package/dist/uni/index.d.ts +6 -0
  331. package/dist/uni/index.js +6 -0
  332. package/dist/weapp/bar-chart/index.js +1 -0
  333. package/dist/weapp/bar-chart/index.json +1 -0
  334. package/dist/weapp/bar-chart/index.wxml +1 -0
  335. package/dist/weapp/bar-chart/index.wxss +1 -0
  336. package/dist/weapp/chart/index.js +1 -0
  337. package/dist/weapp/chart/index.json +1 -0
  338. package/dist/weapp/chart/index.wxml +1 -0
  339. package/dist/weapp/chart/index.wxss +1 -0
  340. package/dist/weapp/controls.js +4019 -266
  341. package/dist/weapp/line-chart/index.js +1 -0
  342. package/dist/weapp/line-chart/index.json +1 -0
  343. package/dist/weapp/line-chart/index.wxml +1 -0
  344. package/dist/weapp/line-chart/index.wxss +1 -0
  345. package/dist/weapp/quadrant-chart/index.js +1 -0
  346. package/dist/weapp/quadrant-chart/index.json +1 -0
  347. package/dist/weapp/quadrant-chart/index.wxml +1 -0
  348. package/dist/weapp/quadrant-chart/index.wxss +1 -0
  349. package/dist/weapp/scatter-chart/index.js +1 -0
  350. package/dist/weapp/scatter-chart/index.json +1 -0
  351. package/dist/weapp/scatter-chart/index.wxml +1 -0
  352. package/dist/weapp/scatter-chart/index.wxss +1 -0
  353. package/dist/weapp/types.d.ts +295 -0
  354. package/package.json +48 -2
@@ -0,0 +1,987 @@
1
+ // Draws a chart scene (`ChartController.getScene()`) with plain views: no
2
+ // canvas, SVG or native drawing module. The engine already resolves every
3
+ // position in pixels; this file only turns them into absolutely positioned
4
+ // <View> / <Text> elements, keeping the number of views bounded by the size
5
+ // of the chart (lines decimated per column, dots merged per cell, bars
6
+ // merged per pixel column), whatever the size of the data.
7
+
8
+ import type { ReactElement, ReactNode } from "react";
9
+ import {
10
+ StyleSheet,
11
+ Text,
12
+ View,
13
+ type TextStyle,
14
+ type ViewStyle,
15
+ } from "react-native";
16
+ import {
17
+ scenePrimitives,
18
+ toCss,
19
+ type ChartPrimitive,
20
+ type ChartRect,
21
+ type ChartScene,
22
+ } from "../../../charts/src/canvas.js";
23
+ import { part } from "../../internal/parts";
24
+ import { measureChartText } from "./measure";
25
+
26
+ type Rgba = ChartScene["theme"]["text"];
27
+ type Annotation = ChartScene["annotations"][number];
28
+ type RectAnnotation = Extract<Annotation, { type: "rect" }>;
29
+ type TextAnnotation = Extract<Annotation, { type: "text" }>;
30
+ type Scales = ChartScene["layout"]["scales"];
31
+
32
+ /** Upper bounds of the views drawn for the data of a chart */
33
+ export const SHAPE_BUDGET = {
34
+ /** Points of all the lines (one view per segment) */
35
+ linePoints: 1200,
36
+ /** Dots of one scatter series before they are merged per cell */
37
+ dotsPerSeries: 800,
38
+ /** Dots of all the series */
39
+ dots: 2000,
40
+ /** Bars of one series before neighbors are merged per pixel column */
41
+ barsPerSeries: 600,
42
+ /** Bars of all the series */
43
+ bars: 1500,
44
+ /** Vertical strips filling one area */
45
+ areaColumns: 200,
46
+ /** Dashes of one dashed line */
47
+ dashes: 200,
48
+ } as const;
49
+
50
+ const C = "chart";
51
+
52
+ // ------------------------------------------------------------------ text
53
+
54
+ interface TextOptions {
55
+ size: number;
56
+ color: string;
57
+ weight?: number;
58
+ family?: string;
59
+ /** Halo color: keeps the text readable over the data */
60
+ halo?: string;
61
+ }
62
+
63
+ type Align = "left" | "center" | "right";
64
+ type VAlign = "top" | "middle" | "bottom";
65
+
66
+ const lineHeightOf = (size: number) => Math.ceil(size * 1.35);
67
+
68
+ /** Box wide enough for the estimated width (the real one may differ) */
69
+ const slack = (text: string, o: TextOptions) =>
70
+ Math.ceil(
71
+ measureChartText(text, `${o.weight ?? 400} ${o.size}px sans-serif`) * 1.4 +
72
+ o.size * 2,
73
+ );
74
+
75
+ /**
76
+ * A one-line text anchored at (x, y) of a container `frameWidth` wide:
77
+ * `left` starts there, `right` ends there, `center` is centered on it;
78
+ * `top` / `bottom` place the em box like the canvas text baselines
79
+ */
80
+ function label(
81
+ key: string,
82
+ text: string,
83
+ x: number,
84
+ y: number,
85
+ align: Align,
86
+ valign: VAlign,
87
+ o: TextOptions,
88
+ frameWidth: number,
89
+ extra?: TextStyle,
90
+ ): ReactElement {
91
+ const lh = lineHeightOf(o.size);
92
+ const pad = (lh - o.size) / 2;
93
+ const w = slack(text, o);
94
+ const top =
95
+ valign === "top"
96
+ ? y - pad
97
+ : valign === "bottom"
98
+ ? y - lh + pad
99
+ : y - lh / 2;
100
+ const horizontal: TextStyle =
101
+ align === "left"
102
+ ? { left: x, width: w }
103
+ : align === "right"
104
+ ? { right: frameWidth - x, width: w }
105
+ : { left: x - w / 2, width: w };
106
+ return (
107
+ <Text
108
+ key={key}
109
+ numberOfLines={1}
110
+ style={[
111
+ {
112
+ position: "absolute",
113
+ top,
114
+ ...horizontal,
115
+ textAlign: align,
116
+ fontSize: o.size,
117
+ lineHeight: lh,
118
+ color: o.color,
119
+ fontWeight: String(o.weight ?? 400) as TextStyle["fontWeight"],
120
+ includeFontPadding: false,
121
+ ...(o.family ? { fontFamily: o.family } : null),
122
+ ...(o.halo
123
+ ? {
124
+ textShadowColor: o.halo,
125
+ textShadowRadius: 3,
126
+ textShadowOffset: { width: 0, height: 0 },
127
+ }
128
+ : null),
129
+ },
130
+ extra,
131
+ ]}
132
+ >
133
+ {text}
134
+ </Text>
135
+ );
136
+ }
137
+
138
+ // ---------------------------------------------------------------- shapes
139
+
140
+ const box = (
141
+ key: string,
142
+ x: number,
143
+ y: number,
144
+ width: number,
145
+ height: number,
146
+ color: string,
147
+ extra?: ViewStyle,
148
+ ): ReactElement => (
149
+ <View
150
+ key={key}
151
+ style={[
152
+ {
153
+ position: "absolute",
154
+ left: x,
155
+ top: y,
156
+ width,
157
+ height,
158
+ backgroundColor: color,
159
+ },
160
+ extra,
161
+ ]}
162
+ />
163
+ );
164
+
165
+ const dot = (
166
+ key: string,
167
+ cx: number,
168
+ cy: number,
169
+ d: number,
170
+ color: string,
171
+ extra?: ViewStyle,
172
+ ) =>
173
+ box(key, cx - d / 2, cy - d / 2, d, d, color, {
174
+ borderRadius: d / 2,
175
+ ...extra,
176
+ });
177
+
178
+ /** A straight stroke: one view, rotated around its center */
179
+ const segment = (
180
+ key: string,
181
+ x1: number,
182
+ y1: number,
183
+ x2: number,
184
+ y2: number,
185
+ width: number,
186
+ color: string,
187
+ ) => {
188
+ const dx = x2 - x1;
189
+ const dy = y2 - y1;
190
+ const length = Math.hypot(dx, dy) + width;
191
+ return box(
192
+ key,
193
+ (x1 + x2) / 2 - length / 2,
194
+ (y1 + y2) / 2 - width / 2,
195
+ length,
196
+ width,
197
+ color,
198
+ {
199
+ borderRadius: width / 2,
200
+ transform: [{ rotate: `${Math.atan2(dy, dx)}rad` }],
201
+ },
202
+ );
203
+ };
204
+
205
+ /** Dash runs `[start, length]` along a line (canvas `setLineDash` pattern) */
206
+ export function dashRuns(
207
+ length: number,
208
+ dash: readonly number[],
209
+ max: number = SHAPE_BUDGET.dashes,
210
+ ): [number, number][] {
211
+ let pattern = dash.filter((v) => Number.isFinite(v) && v >= 0);
212
+ // odd patterns repeat twice, like the canvas
213
+ if (pattern.length % 2) pattern = [...pattern, ...pattern];
214
+ const period = pattern.reduce((a, b) => a + b, 0);
215
+ if (!pattern.length || period <= 0) return [[0, length]];
216
+ const runs = (length / period) * (pattern.length / 2);
217
+ const scale = runs > max ? runs / max : 1;
218
+ const out: [number, number][] = [];
219
+ let at = 0;
220
+ for (let i = 0; at < length; i = (i + 1) % pattern.length) {
221
+ const step = pattern[i] * scale;
222
+ if (i % 2 === 0 && step > 0) out.push([at, Math.min(step, length - at)]);
223
+ at += Math.max(step, 0.5);
224
+ }
225
+ return out;
226
+ }
227
+
228
+ const dashedLine = (
229
+ key: string,
230
+ axis: "x" | "y",
231
+ at: number,
232
+ from: number,
233
+ length: number,
234
+ width: number,
235
+ color: string,
236
+ dash: readonly number[],
237
+ ) =>
238
+ dashRuns(length, dash).map(([start, run], i) =>
239
+ axis === "y"
240
+ ? box(`${key}-${i}`, from + start, at, run, width, color)
241
+ : box(`${key}-${i}`, at, from + start, width, run, color),
242
+ );
243
+
244
+ const crisp = (v: number, width = 1) => Math.round(v) + 0.5 - width / 2;
245
+
246
+ // ------------------------------------------------------------------ data
247
+
248
+ /** Thins a run of interleaved points to `max` points and drops sub-pixel steps */
249
+ function thin(run: readonly number[], max: number): number[] {
250
+ const n = run.length / 2;
251
+ const stride = n > max ? Math.ceil(n / max) : 1;
252
+ const out: number[] = [];
253
+ for (let i = 0; i < n; i += stride) {
254
+ const x = run[i * 2];
255
+ const y = run[i * 2 + 1];
256
+ const k = out.length;
257
+ if (k && Math.hypot(x - out[k - 2], y - out[k - 1]) < 1.5) continue;
258
+ out.push(x, y);
259
+ }
260
+ const lx = run[n * 2 - 2];
261
+ const ly = run[n * 2 - 1];
262
+ const k = out.length;
263
+ if (n > 1 && (out[k - 2] !== lx || out[k - 1] !== ly)) out.push(lx, ly);
264
+ return out;
265
+ }
266
+
267
+ /** Vertical strips between the line and the baseline, one per column */
268
+ function areaStrips(
269
+ key: string,
270
+ run: readonly number[],
271
+ baseline: number,
272
+ color: string,
273
+ opacity: number,
274
+ plot: ChartRect,
275
+ ): ReactElement[] {
276
+ const n = run.length / 2;
277
+ if (n < 2) return [];
278
+ // left to right (reversed axes list their points right to left)
279
+ const forward = run[0] <= run[n * 2 - 2];
280
+ const xs = (i: number) => run[(forward ? i : n - 1 - i) * 2];
281
+ const ys = (i: number) => run[(forward ? i : n - 1 - i) * 2 + 1];
282
+ const from = Math.max(plot.x, xs(0));
283
+ const to = Math.min(plot.x + plot.width, xs(n - 1));
284
+ if (to <= from) return [];
285
+ const column = Math.max(2, plot.width / SHAPE_BUDGET.areaColumns);
286
+ const out: ReactElement[] = [];
287
+ let j = 0;
288
+ for (let left = Math.floor(from), c = 0; left < to; c++) {
289
+ const right = Math.min(Math.ceil(to), Math.round(left + column));
290
+ const mid = (left + right) / 2;
291
+ while (j < n - 2 && xs(j + 1) < mid) j++;
292
+ const x0 = xs(j);
293
+ const x1 = xs(j + 1);
294
+ const t = x1 === x0 ? 0 : Math.max(0, Math.min(1, (mid - x0) / (x1 - x0)));
295
+ const y = ys(j) + (ys(j + 1) - ys(j)) * t;
296
+ if (Number.isFinite(y)) {
297
+ const top = Math.min(y, baseline);
298
+ out.push(
299
+ box(
300
+ `${key}-${c}`,
301
+ left - plot.x,
302
+ top - plot.y,
303
+ right - left,
304
+ Math.abs(baseline - y),
305
+ color,
306
+ {
307
+ opacity,
308
+ },
309
+ ),
310
+ );
311
+ }
312
+ left = right;
313
+ }
314
+ return out;
315
+ }
316
+
317
+ /** Limits of `scenePrimitives` for the views budget */
318
+ export function primitiveLimits(scene: ChartScene) {
319
+ const { plot } = scene.layout;
320
+ const visible = scene.model.series.filter((s) => !s.hidden);
321
+ const lines = visible.filter(
322
+ (s) => s.type === "line" || s.type === "area",
323
+ ).length;
324
+ const scatters = visible.filter((s) => s.type === "scatter").length;
325
+ const perLine = Math.max(150, SHAPE_BUDGET.linePoints / Math.max(1, lines));
326
+ const maxDots = Math.max(
327
+ 100,
328
+ Math.min(
329
+ SHAPE_BUDGET.dotsPerSeries,
330
+ SHAPE_BUDGET.dots / Math.max(1, scatters),
331
+ ),
332
+ );
333
+ return {
334
+ perLine,
335
+ options: {
336
+ // decimation keeps up to 4 points per column
337
+ columnWidth: Math.max(3, (plot.width * 4) / perLine),
338
+ maxDots,
339
+ maxBars: SHAPE_BUDGET.barsPerSeries,
340
+ dotCell: Math.max(
341
+ 4,
342
+ Math.sqrt((plot.width * plot.height) / maxDots) * 1.15 + 1,
343
+ ),
344
+ },
345
+ };
346
+ }
347
+
348
+ function drawData(scene: ChartScene): ReactElement[] {
349
+ const { plot } = scene.layout;
350
+ const { perLine, options } = primitiveLimits(scene);
351
+ const primitives: ChartPrimitive[] = scenePrimitives(scene, options);
352
+ const ox = plot.x;
353
+ const oy = plot.y;
354
+ const out: ReactElement[] = [];
355
+ let dotsLeft: number = SHAPE_BUDGET.dots;
356
+ let barsLeft: number = SHAPE_BUDGET.bars;
357
+ primitives.forEach((p, k) => {
358
+ const key = `${p.kind}${p.seriesIndex}-${k}`;
359
+ if (p.kind === "area") {
360
+ p.segments.forEach((run, r) =>
361
+ out.push(
362
+ ...areaStrips(
363
+ `${key}-${r}`,
364
+ run,
365
+ p.baseline,
366
+ p.color,
367
+ p.opacity,
368
+ plot,
369
+ ),
370
+ ),
371
+ );
372
+ return;
373
+ }
374
+ if (p.kind === "line") {
375
+ let total = 0;
376
+ for (const run of p.segments) total += run.length / 2;
377
+ p.segments.forEach((run, r) => {
378
+ const pts = thin(
379
+ run,
380
+ Math.max(
381
+ 2,
382
+ Math.round((perLine * run.length) / 2 / Math.max(1, total)),
383
+ ),
384
+ );
385
+ if (pts.length === 2) {
386
+ out.push(
387
+ dot(
388
+ `${key}-${r}`,
389
+ pts[0] - ox,
390
+ pts[1] - oy,
391
+ p.width * 1.5,
392
+ p.color,
393
+ ),
394
+ );
395
+ return;
396
+ }
397
+ for (let i = 0; i + 3 < pts.length; i += 2) {
398
+ out.push(
399
+ segment(
400
+ `${key}-${r}-${i}`,
401
+ pts[i] - ox,
402
+ pts[i + 1] - oy,
403
+ pts[i + 2] - ox,
404
+ pts[i + 3] - oy,
405
+ p.width,
406
+ p.color,
407
+ ),
408
+ );
409
+ }
410
+ });
411
+ return;
412
+ }
413
+ if (p.kind === "bars") {
414
+ const count = p.rects.length / 4;
415
+ const stride =
416
+ count > barsLeft ? Math.ceil(count / Math.max(1, barsLeft)) : 1;
417
+ for (let i = 0; i < count; i += stride) {
418
+ const [x, y, w, h] = p.rects.slice(i * 4, i * 4 + 4);
419
+ out.push(
420
+ box(`${key}-${i}`, x - ox, y - oy, Math.max(w, 0.5), h, p.color),
421
+ );
422
+ }
423
+ barsLeft = Math.max(0, barsLeft - Math.ceil(count / stride));
424
+ return;
425
+ }
426
+ const count = p.dots.length / 3;
427
+ const stride =
428
+ count > dotsLeft ? Math.ceil(count / Math.max(1, dotsLeft)) : 1;
429
+ for (let i = 0; i < count; i += stride) {
430
+ out.push(
431
+ dot(
432
+ `${key}-${i}`,
433
+ p.dots[i * 3] - ox,
434
+ p.dots[i * 3 + 1] - oy,
435
+ p.dots[i * 3 + 2],
436
+ p.colors[i],
437
+ {
438
+ opacity: p.opacity,
439
+ },
440
+ ),
441
+ );
442
+ }
443
+ dotsLeft = Math.max(0, dotsLeft - Math.ceil(count / stride));
444
+ });
445
+ return out;
446
+ }
447
+
448
+ // ------------------------------------------------------------ background
449
+
450
+ const rectOf = (a: RectAnnotation, s: Scales): ChartRect => {
451
+ const { plot } = s;
452
+ const xa = a.x0 === undefined ? -Infinity : s.xToPx(a.x0);
453
+ const xb = a.x1 === undefined ? Infinity : s.xToPx(a.x1);
454
+ const ya = a.y0 === undefined ? Infinity : s.yToPx(a.y0);
455
+ const yb = a.y1 === undefined ? -Infinity : s.yToPx(a.y1);
456
+ const left = Math.max(plot.x, Math.min(xa, xb));
457
+ const right = Math.min(plot.x + plot.width, Math.max(xa, xb));
458
+ const top = Math.max(plot.y, Math.min(ya, yb));
459
+ const bottom = Math.min(plot.y + plot.height, Math.max(ya, yb));
460
+ return { x: left, y: top, width: right - left, height: bottom - top };
461
+ };
462
+
463
+ /** Box of a text annotation, kept inside the plot (engine `textBox`) */
464
+ const textBox = (
465
+ a: TextAnnotation,
466
+ s: Scales,
467
+ width: number,
468
+ height: number,
469
+ ): ChartRect => {
470
+ const { plot } = s;
471
+ const px = a.x === undefined ? plot.x + plot.width / 2 : s.xToPx(a.x);
472
+ const py = a.y === undefined ? plot.y + plot.height / 2 : s.yToPx(a.y);
473
+ const gap = 6;
474
+ let x = px - width / 2;
475
+ let y = py - height / 2;
476
+ if (a.anchor === "top") y = py + gap;
477
+ else if (a.anchor === "bottom") y = py - gap - height;
478
+ else if (a.anchor === "left") x = px + gap;
479
+ else if (a.anchor === "right") x = px - gap - width;
480
+ x = Math.max(plot.x + gap, Math.min(x, plot.x + plot.width - gap - width));
481
+ y = Math.max(plot.y + 2, Math.min(y, plot.y + plot.height - 2 - height));
482
+ return { x, y, width, height };
483
+ };
484
+
485
+ const css = (c: Rgba, alpha?: number) => toCss(c, alpha);
486
+
487
+ function drawBackground(scene: ChartScene, family?: string): ReactElement[] {
488
+ const { width, theme, model, layout, annotations } = scene;
489
+ const { plot, scales, xTicks, yTicks } = layout;
490
+ const fs = theme.fontSize;
491
+ const out: ReactElement[] = [];
492
+
493
+ annotations.forEach((a, i) => {
494
+ if (a.type !== "rect") return;
495
+ const r = rectOf(a, scales);
496
+ if (r.width <= 0 || r.height <= 0) return;
497
+ out.push(
498
+ box(`area-${i}`, r.x, r.y, r.width, r.height, css(a.color, a.opacity)),
499
+ );
500
+ });
501
+
502
+ const grid = css(theme.grid);
503
+ if (model.y.grid) {
504
+ yTicks.values.forEach((v, i) => {
505
+ const py = scales.yToPx(v);
506
+ if (py < plot.y - 1 || py > plot.y + plot.height + 1) return;
507
+ out.push(box(`ygrid-${i}`, plot.x, crisp(py), plot.width, 1, grid));
508
+ });
509
+ }
510
+ if (model.x.grid) {
511
+ xTicks.values.forEach((v, i) => {
512
+ const px = scales.xToPx(v);
513
+ if (px < plot.x - 1 || px > plot.x + plot.width + 1) return;
514
+ out.push(box(`xgrid-${i}`, crisp(px), plot.y, 1, plot.height, grid));
515
+ });
516
+ }
517
+
518
+ const line = Math.ceil(fs * 1.4);
519
+ const tick: TextOptions = { size: fs, color: css(theme.textMuted), family };
520
+ const title: TextOptions = {
521
+ size: fs + 1,
522
+ weight: 500,
523
+ color: css(theme.text, 0.8),
524
+ family,
525
+ };
526
+ const bottom = plot.y + plot.height;
527
+
528
+ if (model.x.visible) {
529
+ out.push(
530
+ box("xaxis", plot.x, crisp(bottom), plot.width, 1, css(theme.axis)),
531
+ );
532
+ let lastRight = -Infinity;
533
+ xTicks.values
534
+ .map((v, i) => ({ px: scales.xToPx(v), text: xTicks.labels[i] ?? "" }))
535
+ .sort((a, b) => a.px - b.px)
536
+ .forEach(({ px, text }, i) => {
537
+ if (!text || px < plot.x - 0.5 || px > plot.x + plot.width + 0.5)
538
+ return;
539
+ const w = measureChartText(text, `400 ${fs}px sans-serif`);
540
+ const left = Math.max(0, Math.min(width - w, px - w / 2));
541
+ if (left < lastRight + 6) return;
542
+ lastRight = left + w;
543
+ out.push(
544
+ label(
545
+ `xtick-${i}`,
546
+ text,
547
+ left + w / 2,
548
+ bottom + 7,
549
+ "center",
550
+ "top",
551
+ tick,
552
+ width,
553
+ ),
554
+ );
555
+ });
556
+ if (model.x.title)
557
+ out.push(
558
+ label(
559
+ "xtitle",
560
+ model.x.title,
561
+ plot.x + plot.width / 2,
562
+ bottom + line + 10,
563
+ "center",
564
+ "top",
565
+ title,
566
+ width,
567
+ ),
568
+ );
569
+ }
570
+
571
+ if (model.y.visible) {
572
+ yTicks.values.forEach((v, i) => {
573
+ const py = scales.yToPx(v);
574
+ const text = yTicks.labels[i];
575
+ if (!text || py < plot.y - 0.5 || py > plot.y + plot.height + 0.5) return;
576
+ out.push(
577
+ label(
578
+ `ytick-${i}`,
579
+ text,
580
+ plot.x - 8,
581
+ py,
582
+ "right",
583
+ "middle",
584
+ tick,
585
+ width,
586
+ ),
587
+ );
588
+ });
589
+ if (model.y.title)
590
+ out.push(
591
+ label(
592
+ "ytitle",
593
+ model.y.title,
594
+ line / 2 + 4,
595
+ plot.y + plot.height / 2,
596
+ "center",
597
+ "middle",
598
+ title,
599
+ width,
600
+ {
601
+ transform: [{ rotate: "-90deg" }],
602
+ },
603
+ ),
604
+ );
605
+ }
606
+
607
+ if (scene.legend) {
608
+ for (const e of scene.legend.entries) {
609
+ // coordinates relative to the entry box
610
+ const cy = e.box.height / 2;
611
+ const swatch =
612
+ e.shape === "dot"
613
+ ? dot("swatch", 5, cy, 8, e.color)
614
+ : e.shape === "line"
615
+ ? box("swatch", 0, cy - 1, 10, 2, e.color)
616
+ : box("swatch", 1, cy - 4, 8, 8, e.color);
617
+ out.push(
618
+ <View
619
+ key={`legend-${e.id}`}
620
+ style={{
621
+ position: "absolute",
622
+ left: e.box.x,
623
+ top: e.box.y,
624
+ width: e.box.width,
625
+ height: e.box.height,
626
+ opacity: e.hidden ? 0.45 : 1,
627
+ }}
628
+ {...part(C, "legend-item", { hidden: e.hidden })}
629
+ >
630
+ {swatch}
631
+ {label(
632
+ "name",
633
+ e.name,
634
+ 14,
635
+ cy,
636
+ "left",
637
+ "middle",
638
+ { size: 12, color: css(theme.textMuted), family },
639
+ e.box.width,
640
+ e.hidden ? { textDecorationLine: "line-through" } : undefined,
641
+ )}
642
+ </View>,
643
+ );
644
+ }
645
+ }
646
+ return out;
647
+ }
648
+
649
+ // ------------------------------------------------------------ foreground
650
+
651
+ function drawForeground(scene: ChartScene, family?: string): ReactElement[] {
652
+ const { theme, annotations, layout } = scene;
653
+ const { plot, scales } = layout;
654
+ const fs = theme.fontSize;
655
+ const ox = plot.x;
656
+ const oy = plot.y;
657
+ const fw = plot.width;
658
+ const halo = css(theme.background, 0.9);
659
+ const labelHeight = fs + 4;
660
+ const out: ReactElement[] = [];
661
+
662
+ annotations.forEach((a, i) => {
663
+ if (a.type !== "line") return;
664
+ const color = css(a.color);
665
+ if (a.axis === "y") {
666
+ const py = scales.yToPx(a.value);
667
+ out.push(
668
+ ...dashedLine(
669
+ `line-${i}`,
670
+ "y",
671
+ crisp(py, a.width) - oy,
672
+ 0,
673
+ plot.width,
674
+ a.width,
675
+ color,
676
+ a.dash,
677
+ ),
678
+ );
679
+ if (a.label)
680
+ out.push(
681
+ label(
682
+ `line-label-${i}`,
683
+ a.label,
684
+ plot.width - 6,
685
+ py - 3 - oy,
686
+ "right",
687
+ "bottom",
688
+ { size: fs, weight: 500, color, halo, family },
689
+ fw,
690
+ ),
691
+ );
692
+ } else {
693
+ const px = scales.xToPx(a.value);
694
+ out.push(
695
+ ...dashedLine(
696
+ `line-${i}`,
697
+ "x",
698
+ crisp(px, a.width) - ox,
699
+ 0,
700
+ plot.height,
701
+ a.width,
702
+ color,
703
+ a.dash,
704
+ ),
705
+ );
706
+ if (a.label)
707
+ out.push(
708
+ label(
709
+ `line-label-${i}`,
710
+ a.label,
711
+ px + 5 - ox,
712
+ 5,
713
+ "left",
714
+ "top",
715
+ { size: fs, weight: 500, color, halo, family },
716
+ fw,
717
+ ),
718
+ );
719
+ }
720
+ });
721
+
722
+ annotations.forEach((a, i) => {
723
+ if (a.type !== "rect" || !a.label) return;
724
+ const r = rectOf(a, scales);
725
+ if (r.width <= 0 || r.height <= 0) return;
726
+ const pad = 10;
727
+ const pos = a.labelPosition;
728
+ const align: Align = pos.endsWith("left")
729
+ ? "left"
730
+ : pos.endsWith("right")
731
+ ? "right"
732
+ : "center";
733
+ const tx =
734
+ align === "left"
735
+ ? r.x + pad
736
+ : align === "right"
737
+ ? r.x + r.width - pad
738
+ : r.x + r.width / 2;
739
+ const ty = pos.startsWith("top")
740
+ ? r.y + pad
741
+ : pos.startsWith("bottom")
742
+ ? r.y + r.height - pad - labelHeight
743
+ : r.y + (r.height - labelHeight) / 2;
744
+ out.push(
745
+ label(
746
+ `area-label-${i}`,
747
+ a.label,
748
+ tx - ox,
749
+ ty - oy,
750
+ align,
751
+ "top",
752
+ { size: fs + 1, weight: 600, color: css(a.labelColor), family },
753
+ fw,
754
+ ),
755
+ );
756
+ });
757
+
758
+ annotations.forEach((a, i) => {
759
+ if (a.type !== "text" || !a.text) return;
760
+ const w = measureChartText(a.text, `${a.weight} ${fs}px sans-serif`);
761
+ const b = textBox(a, scales, w, labelHeight);
762
+ out.push(
763
+ label(
764
+ `text-${i}`,
765
+ a.text,
766
+ b.x - ox,
767
+ b.y + b.height / 2 - oy,
768
+ "left",
769
+ "middle",
770
+ { size: fs, weight: a.weight, color: css(a.color), halo, family },
771
+ fw,
772
+ ),
773
+ );
774
+ });
775
+
776
+ const pointColor = css(theme.text, 0.85);
777
+ scene.pointLabels.forEach(({ label: l, box: b }, i) => {
778
+ out.push(
779
+ label(
780
+ `point-label-${i}`,
781
+ l.text,
782
+ b.x - ox,
783
+ b.y + b.height / 2 - oy,
784
+ "left",
785
+ "middle",
786
+ { size: fs, color: pointColor, halo, family },
787
+ fw,
788
+ ),
789
+ );
790
+ });
791
+ return out;
792
+ }
793
+
794
+ const clip = (plot: ChartRect): ViewStyle => ({
795
+ position: "absolute",
796
+ left: plot.x,
797
+ top: plot.y,
798
+ width: plot.width,
799
+ height: plot.height,
800
+ overflow: "hidden",
801
+ });
802
+
803
+ /**
804
+ * Everything but the hover state: axes, grid, legend, annotations, data and
805
+ * labels (cached by the caller while the model, view and size are the same)
806
+ */
807
+ export function drawStatic(scene: ChartScene, family?: string): ReactNode {
808
+ const { plot } = scene.layout;
809
+ return (
810
+ <>
811
+ {drawBackground(scene, family)}
812
+ <View key="data" style={clip(plot)} {...part(C, "data")}>
813
+ {drawData(scene)}
814
+ </View>
815
+ <View key="labels" style={clip(plot)} {...part(C, "labels")}>
816
+ {drawForeground(scene, family)}
817
+ </View>
818
+ </>
819
+ );
820
+ }
821
+
822
+ /** Crosshair, highlighted points, dragged point and the default tooltip */
823
+ export function drawHover(
824
+ scene: ChartScene,
825
+ family: string | undefined,
826
+ customTooltip: boolean,
827
+ ): ReactNode {
828
+ const { theme, layout, hover, dragging } = scene;
829
+ const { plot } = layout;
830
+ const background = css(theme.background);
831
+ const out: ReactElement[] = [];
832
+ if (hover && hover.crosshairX !== null) {
833
+ out.push(
834
+ <View
835
+ key="crosshair"
836
+ style={StyleSheet.absoluteFill}
837
+ {...part(C, "crosshair")}
838
+ >
839
+ {dashedLine(
840
+ "dash",
841
+ "x",
842
+ Math.round(hover.crosshairX),
843
+ plot.y,
844
+ plot.height,
845
+ 1,
846
+ css(theme.textMuted, 0.55),
847
+ [3, 3],
848
+ )}
849
+ </View>,
850
+ );
851
+ }
852
+ hover?.marks.forEach((m, i) => {
853
+ out.push(
854
+ m.kind === "bar"
855
+ ? box(
856
+ `mark-${i}`,
857
+ m.x - 2,
858
+ m.y - 2,
859
+ m.width + 4,
860
+ m.height + 2,
861
+ css(theme.text, 0.08),
862
+ )
863
+ : dot(`mark-${i}`, m.x, m.y, m.size + 2, m.color, {
864
+ borderWidth: 2,
865
+ borderColor: background,
866
+ }),
867
+ );
868
+ });
869
+ if (dragging) {
870
+ out.push(
871
+ <View key="drag" {...part(C, "drag")} style={StyleSheet.absoluteFill}>
872
+ {dot(
873
+ "ghost",
874
+ dragging.x,
875
+ dragging.y,
876
+ dragging.size + 2,
877
+ dragging.color,
878
+ {
879
+ opacity: 0.85,
880
+ borderWidth: 2,
881
+ borderColor: background,
882
+ },
883
+ )}
884
+ </View>,
885
+ );
886
+ }
887
+ const tip = hover?.tooltip;
888
+ if (tip && !customTooltip) {
889
+ const { box: b, lineHeight, padding, content } = tip;
890
+ const text = css(theme.text);
891
+ const muted = css(theme.textMuted);
892
+ const font = (weight: number, color: string): TextStyle => ({
893
+ fontSize: 12,
894
+ lineHeight,
895
+ color,
896
+ fontWeight: String(weight) as TextStyle["fontWeight"],
897
+ includeFontPadding: false,
898
+ ...(family ? { fontFamily: family } : null),
899
+ });
900
+ const swatch = (color: string) => (
901
+ <View
902
+ style={{
903
+ width: 8,
904
+ height: 8,
905
+ borderRadius: 4,
906
+ marginRight: 6,
907
+ backgroundColor: color,
908
+ }}
909
+ />
910
+ );
911
+ const row: ViewStyle = {
912
+ height: lineHeight,
913
+ flexDirection: "row",
914
+ alignItems: "center",
915
+ };
916
+ out.push(
917
+ <View
918
+ key="tooltip"
919
+ {...part(C, "tooltip")}
920
+ style={[
921
+ tooltipCard(scene),
922
+ {
923
+ left: b.x,
924
+ top: b.y,
925
+ width: b.width,
926
+ height: b.height,
927
+ paddingHorizontal: padding + 1,
928
+ paddingVertical: padding - 1,
929
+ },
930
+ ]}
931
+ >
932
+ {tip.text
933
+ ? tip.text.map((t, i) => (
934
+ <Text key={i} numberOfLines={1} style={font(400, text)}>
935
+ {t}
936
+ </Text>
937
+ ))
938
+ : [
939
+ <View key="title" style={row}>
940
+ {content.titleColor ? swatch(content.titleColor) : null}
941
+ <Text
942
+ numberOfLines={1}
943
+ style={[font(600, text), { flexShrink: 1 }]}
944
+ >
945
+ {content.title}
946
+ </Text>
947
+ </View>,
948
+ ...content.rows.map((r, i) => (
949
+ <View key={i} style={row}>
950
+ {r.color ? swatch(r.color) : null}
951
+ <Text
952
+ numberOfLines={1}
953
+ style={[font(400, muted), { flexShrink: 1 }]}
954
+ >
955
+ {r.name}
956
+ </Text>
957
+ <Text
958
+ numberOfLines={1}
959
+ style={[
960
+ font(600, text),
961
+ { flexGrow: 1, marginLeft: 16, textAlign: "right" },
962
+ ]}
963
+ >
964
+ {r.value}
965
+ </Text>
966
+ </View>
967
+ )),
968
+ ]}
969
+ </View>,
970
+ );
971
+ }
972
+ return out;
973
+ }
974
+
975
+ /** Look of the tooltips (default and `renderTooltip` content) */
976
+ export const tooltipCard = (scene: ChartScene): ViewStyle => ({
977
+ position: "absolute",
978
+ backgroundColor: css(scene.theme.background),
979
+ borderColor: css(scene.theme.axis),
980
+ borderWidth: 1,
981
+ borderRadius: 8,
982
+ shadowColor: "#0f172a",
983
+ shadowOpacity: 0.18,
984
+ shadowRadius: 8,
985
+ shadowOffset: { width: 0, height: 6 },
986
+ elevation: 6,
987
+ });