@voila.dev/chart 4.0.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 (377) hide show
  1. package/README.md +85 -0
  2. package/dist/brush/brush-layer.d.ts +16 -0
  3. package/dist/brush/brush-layer.d.ts.map +1 -0
  4. package/dist/brush/brush-layer.js +137 -0
  5. package/dist/brush/brush-layer.js.map +7 -0
  6. package/dist/brush/index.d.ts +10 -0
  7. package/dist/brush/index.d.ts.map +1 -0
  8. package/dist/brush/index.js +8 -0
  9. package/dist/brush/index.js.map +7 -0
  10. package/dist/canvas/canvas-renderer.d.ts +11 -0
  11. package/dist/canvas/canvas-renderer.d.ts.map +1 -0
  12. package/dist/canvas/canvas-renderer.js +49 -0
  13. package/dist/canvas/canvas-renderer.js.map +7 -0
  14. package/dist/canvas/index.d.ts +2 -0
  15. package/dist/canvas/index.d.ts.map +1 -0
  16. package/dist/canvas/index.js +5 -0
  17. package/dist/canvas/index.js.map +7 -0
  18. package/dist/canvas/paint-scene.d.ts +4 -0
  19. package/dist/canvas/paint-scene.d.ts.map +1 -0
  20. package/dist/canvas/paint-scene.js +130 -0
  21. package/dist/canvas/paint-scene.js.map +7 -0
  22. package/dist/canvas/resolve-color.d.ts +18 -0
  23. package/dist/canvas/resolve-color.d.ts.map +1 -0
  24. package/dist/canvas/resolve-color.js +52 -0
  25. package/dist/canvas/resolve-color.js.map +7 -0
  26. package/dist/contour/density.d.ts +20 -0
  27. package/dist/contour/density.d.ts.map +1 -0
  28. package/dist/contour/density.js +59 -0
  29. package/dist/contour/density.js.map +7 -0
  30. package/dist/contour/index.d.ts +2 -0
  31. package/dist/contour/index.d.ts.map +1 -0
  32. package/dist/contour/index.js +5 -0
  33. package/dist/contour/index.js.map +7 -0
  34. package/dist/core/channel.d.ts +11 -0
  35. package/dist/core/channel.d.ts.map +1 -0
  36. package/dist/core/channel.js +44 -0
  37. package/dist/core/channel.js.map +7 -0
  38. package/dist/core/compile.d.ts +22 -0
  39. package/dist/core/compile.d.ts.map +1 -0
  40. package/dist/core/compile.js +295 -0
  41. package/dist/core/compile.js.map +7 -0
  42. package/dist/core/define-chart.d.ts +96 -0
  43. package/dist/core/define-chart.d.ts.map +1 -0
  44. package/dist/core/define-chart.js +7 -0
  45. package/dist/core/define-chart.js.map +7 -0
  46. package/dist/core/facet.d.ts +7 -0
  47. package/dist/core/facet.d.ts.map +1 -0
  48. package/dist/core/facet.js +134 -0
  49. package/dist/core/facet.js.map +7 -0
  50. package/dist/core/format.d.ts +37 -0
  51. package/dist/core/format.d.ts.map +1 -0
  52. package/dist/core/format.js +108 -0
  53. package/dist/core/format.js.map +7 -0
  54. package/dist/core/guides/axes.d.ts +25 -0
  55. package/dist/core/guides/axes.d.ts.map +1 -0
  56. package/dist/core/guides/axes.js +204 -0
  57. package/dist/core/guides/axes.js.map +7 -0
  58. package/dist/core/marks/area.d.ts +23 -0
  59. package/dist/core/marks/area.d.ts.map +1 -0
  60. package/dist/core/marks/area.js +187 -0
  61. package/dist/core/marks/area.js.map +7 -0
  62. package/dist/core/marks/bar.d.ts +26 -0
  63. package/dist/core/marks/bar.d.ts.map +1 -0
  64. package/dist/core/marks/bar.js +200 -0
  65. package/dist/core/marks/bar.js.map +7 -0
  66. package/dist/core/marks/cell.d.ts +16 -0
  67. package/dist/core/marks/cell.d.ts.map +1 -0
  68. package/dist/core/marks/cell.js +98 -0
  69. package/dist/core/marks/cell.js.map +7 -0
  70. package/dist/core/marks/dot.d.ts +17 -0
  71. package/dist/core/marks/dot.d.ts.map +1 -0
  72. package/dist/core/marks/dot.js +125 -0
  73. package/dist/core/marks/dot.js.map +7 -0
  74. package/dist/core/marks/funnel-layout.d.ts +37 -0
  75. package/dist/core/marks/funnel-layout.d.ts.map +1 -0
  76. package/dist/core/marks/funnel-layout.js +61 -0
  77. package/dist/core/marks/funnel-layout.js.map +7 -0
  78. package/dist/core/marks/funnel.d.ts +19 -0
  79. package/dist/core/marks/funnel.d.ts.map +1 -0
  80. package/dist/core/marks/funnel.js +143 -0
  81. package/dist/core/marks/funnel.js.map +7 -0
  82. package/dist/core/marks/line.d.ts +17 -0
  83. package/dist/core/marks/line.d.ts.map +1 -0
  84. package/dist/core/marks/line.js +150 -0
  85. package/dist/core/marks/line.js.map +7 -0
  86. package/dist/core/marks/polar.d.ts +61 -0
  87. package/dist/core/marks/polar.d.ts.map +1 -0
  88. package/dist/core/marks/polar.js +436 -0
  89. package/dist/core/marks/polar.js.map +7 -0
  90. package/dist/core/marks/rect.d.ts +22 -0
  91. package/dist/core/marks/rect.d.ts.map +1 -0
  92. package/dist/core/marks/rect.js +119 -0
  93. package/dist/core/marks/rect.js.map +7 -0
  94. package/dist/core/marks/rule.d.ts +17 -0
  95. package/dist/core/marks/rule.d.ts.map +1 -0
  96. package/dist/core/marks/rule.js +73 -0
  97. package/dist/core/marks/rule.js.map +7 -0
  98. package/dist/core/marks/shared.d.ts +41 -0
  99. package/dist/core/marks/shared.d.ts.map +1 -0
  100. package/dist/core/marks/shared.js +74 -0
  101. package/dist/core/marks/shared.js.map +7 -0
  102. package/dist/core/marks/stack.d.ts +13 -0
  103. package/dist/core/marks/stack.d.ts.map +1 -0
  104. package/dist/core/marks/stack.js +28 -0
  105. package/dist/core/marks/stack.js.map +7 -0
  106. package/dist/core/marks/stats/difference.d.ts +20 -0
  107. package/dist/core/marks/stats/difference.d.ts.map +1 -0
  108. package/dist/core/marks/stats/difference.js +168 -0
  109. package/dist/core/marks/stats/difference.js.map +7 -0
  110. package/dist/core/marks/stats/distribution.d.ts +41 -0
  111. package/dist/core/marks/stats/distribution.d.ts.map +1 -0
  112. package/dist/core/marks/stats/distribution.js +354 -0
  113. package/dist/core/marks/stats/distribution.js.map +7 -0
  114. package/dist/core/marks/stats/dodge.d.ts +16 -0
  115. package/dist/core/marks/stats/dodge.d.ts.map +1 -0
  116. package/dist/core/marks/stats/dodge.js +104 -0
  117. package/dist/core/marks/stats/dodge.js.map +7 -0
  118. package/dist/core/marks/stats/hexbin.d.ts +26 -0
  119. package/dist/core/marks/stats/hexbin.d.ts.map +1 -0
  120. package/dist/core/marks/stats/hexbin.js +113 -0
  121. package/dist/core/marks/stats/hexbin.js.map +7 -0
  122. package/dist/core/marks/stats/histogram.d.ts +27 -0
  123. package/dist/core/marks/stats/histogram.d.ts.map +1 -0
  124. package/dist/core/marks/stats/histogram.js +70 -0
  125. package/dist/core/marks/stats/histogram.js.map +7 -0
  126. package/dist/core/marks/stats/regression.d.ts +13 -0
  127. package/dist/core/marks/stats/regression.d.ts.map +1 -0
  128. package/dist/core/marks/stats/regression.js +80 -0
  129. package/dist/core/marks/stats/regression.js.map +7 -0
  130. package/dist/core/marks/stats/statistics.d.ts +34 -0
  131. package/dist/core/marks/stats/statistics.d.ts.map +1 -0
  132. package/dist/core/marks/stats/statistics.js +111 -0
  133. package/dist/core/marks/stats/statistics.js.map +7 -0
  134. package/dist/core/marks/stats/waffle.d.ts +18 -0
  135. package/dist/core/marks/stats/waffle.d.ts.map +1 -0
  136. package/dist/core/marks/stats/waffle.js +117 -0
  137. package/dist/core/marks/stats/waffle.js.map +7 -0
  138. package/dist/core/marks/text.d.ts +19 -0
  139. package/dist/core/marks/text.d.ts.map +1 -0
  140. package/dist/core/marks/text.js +60 -0
  141. package/dist/core/marks/text.js.map +7 -0
  142. package/dist/core/motion/tween.d.ts +6 -0
  143. package/dist/core/motion/tween.d.ts.map +1 -0
  144. package/dist/core/motion/tween.js +98 -0
  145. package/dist/core/motion/tween.js.map +7 -0
  146. package/dist/core/nearest.d.ts +25 -0
  147. package/dist/core/nearest.d.ts.map +1 -0
  148. package/dist/core/nearest.js +151 -0
  149. package/dist/core/nearest.js.map +7 -0
  150. package/dist/core/paths.d.ts +65 -0
  151. package/dist/core/paths.d.ts.map +1 -0
  152. package/dist/core/paths.js +192 -0
  153. package/dist/core/paths.js.map +7 -0
  154. package/dist/core/scales/color.d.ts +11 -0
  155. package/dist/core/scales/color.d.ts.map +1 -0
  156. package/dist/core/scales/color.js +35 -0
  157. package/dist/core/scales/color.js.map +7 -0
  158. package/dist/core/scales/continuous.d.ts +18 -0
  159. package/dist/core/scales/continuous.d.ts.map +1 -0
  160. package/dist/core/scales/continuous.js +82 -0
  161. package/dist/core/scales/continuous.js.map +7 -0
  162. package/dist/core/scales/discrete.d.ts +22 -0
  163. package/dist/core/scales/discrete.d.ts.map +1 -0
  164. package/dist/core/scales/discrete.js +103 -0
  165. package/dist/core/scales/discrete.js.map +7 -0
  166. package/dist/core/scales/resolve.d.ts +18 -0
  167. package/dist/core/scales/resolve.d.ts.map +1 -0
  168. package/dist/core/scales/resolve.js +112 -0
  169. package/dist/core/scales/resolve.js.map +7 -0
  170. package/dist/core/scales/time.d.ts +4 -0
  171. package/dist/core/scales/time.d.ts.map +1 -0
  172. package/dist/core/scales/time.js +140 -0
  173. package/dist/core/scales/time.js.map +7 -0
  174. package/dist/core/text.d.ts +3 -0
  175. package/dist/core/text.d.ts.map +1 -0
  176. package/dist/core/text.js +29 -0
  177. package/dist/core/text.js.map +7 -0
  178. package/dist/core/theme.d.ts +8 -0
  179. package/dist/core/theme.d.ts.map +1 -0
  180. package/dist/core/theme.js +21 -0
  181. package/dist/core/theme.js.map +7 -0
  182. package/dist/core/ticks.d.ts +27 -0
  183. package/dist/core/ticks.d.ts.map +1 -0
  184. package/dist/core/ticks.js +76 -0
  185. package/dist/core/ticks.js.map +7 -0
  186. package/dist/core/types.d.ts +305 -0
  187. package/dist/core/types.d.ts.map +1 -0
  188. package/dist/core/types.js +1 -0
  189. package/dist/core/types.js.map +7 -0
  190. package/dist/force/force.d.ts +30 -0
  191. package/dist/force/force.d.ts.map +1 -0
  192. package/dist/force/force.js +143 -0
  193. package/dist/force/force.js.map +7 -0
  194. package/dist/force/index.d.ts +2 -0
  195. package/dist/force/index.d.ts.map +1 -0
  196. package/dist/force/index.js +7 -0
  197. package/dist/force/index.js.map +7 -0
  198. package/dist/geo/geo.d.ts +59 -0
  199. package/dist/geo/geo.d.ts.map +1 -0
  200. package/dist/geo/geo.js +231 -0
  201. package/dist/geo/geo.js.map +7 -0
  202. package/dist/geo/index.d.ts +2 -0
  203. package/dist/geo/index.d.ts.map +1 -0
  204. package/dist/geo/index.js +13 -0
  205. package/dist/geo/index.js.map +7 -0
  206. package/dist/hierarchy/hierarchy.d.ts +24 -0
  207. package/dist/hierarchy/hierarchy.d.ts.map +1 -0
  208. package/dist/hierarchy/hierarchy.js +279 -0
  209. package/dist/hierarchy/hierarchy.js.map +7 -0
  210. package/dist/hierarchy/index.d.ts +2 -0
  211. package/dist/hierarchy/index.d.ts.map +1 -0
  212. package/dist/hierarchy/index.js +11 -0
  213. package/dist/hierarchy/index.js.map +7 -0
  214. package/dist/index.d.ts +29 -0
  215. package/dist/index.d.ts.map +1 -0
  216. package/dist/index.js +96 -0
  217. package/dist/index.js.map +7 -0
  218. package/dist/react/brush-contract.d.ts +23 -0
  219. package/dist/react/brush-contract.d.ts.map +1 -0
  220. package/dist/react/brush-contract.js +7 -0
  221. package/dist/react/brush-contract.js.map +7 -0
  222. package/dist/react/chart-data-table.d.ts +18 -0
  223. package/dist/react/chart-data-table.d.ts.map +1 -0
  224. package/dist/react/chart-data-table.js +55 -0
  225. package/dist/react/chart-data-table.js.map +7 -0
  226. package/dist/react/chart-legend.d.ts +17 -0
  227. package/dist/react/chart-legend.d.ts.map +1 -0
  228. package/dist/react/chart-legend.js +126 -0
  229. package/dist/react/chart-legend.js.map +7 -0
  230. package/dist/react/chart-tooltip.d.ts +18 -0
  231. package/dist/react/chart-tooltip.d.ts.map +1 -0
  232. package/dist/react/chart-tooltip.js +95 -0
  233. package/dist/react/chart-tooltip.js.map +7 -0
  234. package/dist/react/chart.d.ts +45 -0
  235. package/dist/react/chart.d.ts.map +1 -0
  236. package/dist/react/chart.js +383 -0
  237. package/dist/react/chart.js.map +7 -0
  238. package/dist/react/focus-overlay.d.ts +14 -0
  239. package/dist/react/focus-overlay.d.ts.map +1 -0
  240. package/dist/react/focus-overlay.js +147 -0
  241. package/dist/react/focus-overlay.js.map +7 -0
  242. package/dist/react/index.d.ts +7 -0
  243. package/dist/react/index.d.ts.map +1 -0
  244. package/dist/react/index.js +11 -0
  245. package/dist/react/index.js.map +7 -0
  246. package/dist/react/messages.d.ts +16 -0
  247. package/dist/react/messages.d.ts.map +1 -0
  248. package/dist/react/messages.js +33 -0
  249. package/dist/react/messages.js.map +7 -0
  250. package/dist/react/renderer.d.ts +14 -0
  251. package/dist/react/renderer.d.ts.map +1 -0
  252. package/dist/react/renderer.js +1 -0
  253. package/dist/react/renderer.js.map +7 -0
  254. package/dist/react/sr-only.d.ts +4 -0
  255. package/dist/react/sr-only.d.ts.map +1 -0
  256. package/dist/react/sr-only.js +15 -0
  257. package/dist/react/sr-only.js.map +7 -0
  258. package/dist/react/svg-renderer.d.ts +4 -0
  259. package/dist/react/svg-renderer.d.ts.map +1 -0
  260. package/dist/react/svg-renderer.js +20 -0
  261. package/dist/react/svg-renderer.js.map +7 -0
  262. package/dist/react/svg-scene.d.ts +8 -0
  263. package/dist/react/svg-scene.d.ts.map +1 -0
  264. package/dist/react/svg-scene.js +121 -0
  265. package/dist/react/svg-scene.js.map +7 -0
  266. package/dist/react/use-animated-scene.d.ts +10 -0
  267. package/dist/react/use-animated-scene.d.ts.map +1 -0
  268. package/dist/react/use-animated-scene.js +41 -0
  269. package/dist/react/use-animated-scene.js.map +7 -0
  270. package/dist/react/use-chart-focus.d.ts +29 -0
  271. package/dist/react/use-chart-focus.d.ts.map +1 -0
  272. package/dist/react/use-chart-focus.js +180 -0
  273. package/dist/react/use-chart-focus.js.map +7 -0
  274. package/dist/react/use-chart-width.d.ts +12 -0
  275. package/dist/react/use-chart-width.d.ts.map +1 -0
  276. package/dist/react/use-chart-width.js +23 -0
  277. package/dist/react/use-chart-width.js.map +7 -0
  278. package/dist/react/use-chart-zoom.d.ts +13 -0
  279. package/dist/react/use-chart-zoom.d.ts.map +1 -0
  280. package/dist/react/use-chart-zoom.js +52 -0
  281. package/dist/react/use-chart-zoom.js.map +7 -0
  282. package/dist/react/use-text-measurer.d.ts +8 -0
  283. package/dist/react/use-text-measurer.d.ts.map +1 -0
  284. package/dist/react/use-text-measurer.js +44 -0
  285. package/dist/react/use-text-measurer.js.map +7 -0
  286. package/dist/sankey/index.d.ts +2 -0
  287. package/dist/sankey/index.d.ts.map +1 -0
  288. package/dist/sankey/index.js +7 -0
  289. package/dist/sankey/index.js.map +7 -0
  290. package/dist/sankey/sankey.d.ts +29 -0
  291. package/dist/sankey/sankey.d.ts.map +1 -0
  292. package/dist/sankey/sankey.js +127 -0
  293. package/dist/sankey/sankey.js.map +7 -0
  294. package/dist/voronoi/index.d.ts +2 -0
  295. package/dist/voronoi/index.d.ts.map +1 -0
  296. package/dist/voronoi/index.js +5 -0
  297. package/dist/voronoi/index.js.map +7 -0
  298. package/dist/voronoi/voronoi.d.ts +16 -0
  299. package/dist/voronoi/voronoi.d.ts.map +1 -0
  300. package/dist/voronoi/voronoi.js +62 -0
  301. package/dist/voronoi/voronoi.js.map +7 -0
  302. package/package.json +74 -0
  303. package/src/brush/brush-layer.tsx +174 -0
  304. package/src/brush/index.ts +14 -0
  305. package/src/canvas/canvas-renderer.tsx +56 -0
  306. package/src/canvas/index.ts +1 -0
  307. package/src/canvas/paint-scene.ts +152 -0
  308. package/src/canvas/resolve-color.ts +65 -0
  309. package/src/contour/density.ts +90 -0
  310. package/src/contour/index.ts +1 -0
  311. package/src/core/channel.ts +53 -0
  312. package/src/core/compile.ts +436 -0
  313. package/src/core/define-chart.ts +128 -0
  314. package/src/core/facet.ts +189 -0
  315. package/src/core/format.ts +163 -0
  316. package/src/core/guides/axes.ts +283 -0
  317. package/src/core/marks/area.ts +238 -0
  318. package/src/core/marks/bar.ts +278 -0
  319. package/src/core/marks/cell.ts +123 -0
  320. package/src/core/marks/dot.ts +157 -0
  321. package/src/core/marks/funnel-layout.ts +113 -0
  322. package/src/core/marks/funnel.ts +172 -0
  323. package/src/core/marks/line.ts +206 -0
  324. package/src/core/marks/polar.ts +568 -0
  325. package/src/core/marks/rect.ts +156 -0
  326. package/src/core/marks/rule.ts +104 -0
  327. package/src/core/marks/shared.ts +134 -0
  328. package/src/core/marks/stack.ts +42 -0
  329. package/src/core/marks/stats/difference.ts +205 -0
  330. package/src/core/marks/stats/distribution.ts +425 -0
  331. package/src/core/marks/stats/dodge.ts +136 -0
  332. package/src/core/marks/stats/hexbin.ts +152 -0
  333. package/src/core/marks/stats/histogram.ts +108 -0
  334. package/src/core/marks/stats/regression.ts +108 -0
  335. package/src/core/marks/stats/statistics.ts +153 -0
  336. package/src/core/marks/stats/waffle.ts +141 -0
  337. package/src/core/marks/text.ts +97 -0
  338. package/src/core/motion/tween.ts +131 -0
  339. package/src/core/nearest.ts +223 -0
  340. package/src/core/paths.ts +323 -0
  341. package/src/core/scales/color.ts +51 -0
  342. package/src/core/scales/continuous.ts +111 -0
  343. package/src/core/scales/discrete.ts +156 -0
  344. package/src/core/scales/resolve.ts +158 -0
  345. package/src/core/scales/time.ts +180 -0
  346. package/src/core/text.ts +42 -0
  347. package/src/core/theme.ts +26 -0
  348. package/src/core/ticks.ts +118 -0
  349. package/src/core/types.ts +358 -0
  350. package/src/force/force.ts +200 -0
  351. package/src/force/index.ts +6 -0
  352. package/src/geo/geo.ts +346 -0
  353. package/src/geo/index.ts +10 -0
  354. package/src/hierarchy/hierarchy.ts +377 -0
  355. package/src/hierarchy/index.ts +6 -0
  356. package/src/index.ts +70 -0
  357. package/src/react/brush-contract.ts +35 -0
  358. package/src/react/chart-data-table.tsx +98 -0
  359. package/src/react/chart-legend.tsx +130 -0
  360. package/src/react/chart-tooltip.tsx +111 -0
  361. package/src/react/chart.tsx +489 -0
  362. package/src/react/focus-overlay.tsx +164 -0
  363. package/src/react/index.ts +9 -0
  364. package/src/react/messages.ts +49 -0
  365. package/src/react/renderer.ts +15 -0
  366. package/src/react/sr-only.ts +14 -0
  367. package/src/react/svg-renderer.tsx +18 -0
  368. package/src/react/svg-scene.tsx +146 -0
  369. package/src/react/use-animated-scene.ts +57 -0
  370. package/src/react/use-chart-focus.ts +245 -0
  371. package/src/react/use-chart-width.ts +27 -0
  372. package/src/react/use-chart-zoom.ts +67 -0
  373. package/src/react/use-text-measurer.ts +49 -0
  374. package/src/sankey/index.ts +6 -0
  375. package/src/sankey/sankey.ts +179 -0
  376. package/src/voronoi/index.ts +1 -0
  377. package/src/voronoi/voronoi.ts +84 -0
package/src/geo/geo.ts ADDED
@@ -0,0 +1,346 @@
1
+ import {
2
+ type GeoPermissibleObjects,
3
+ type GeoProjection,
4
+ geoAlbersUsa,
5
+ geoArea,
6
+ geoConicConformal,
7
+ geoEqualEarth,
8
+ geoEquirectangular,
9
+ geoMercator,
10
+ geoNaturalEarth1,
11
+ geoOrthographic,
12
+ geoPath,
13
+ } from "d3-geo";
14
+ import { isPlaceable, numericValue, readChannel } from "#/core/channel.ts";
15
+ import { markId, paint } from "#/core/marks/shared.ts";
16
+ import type {
17
+ ChartAccessor,
18
+ ChartFittedProjection,
19
+ ChartMark,
20
+ ChartPoint,
21
+ ChartProjection,
22
+ ChartValue,
23
+ SceneNode,
24
+ } from "#/core/types.ts";
25
+
26
+ export type ProjectionName =
27
+ | "equal-earth"
28
+ | "natural-earth"
29
+ | "mercator"
30
+ | "equirectangular"
31
+ | "orthographic"
32
+ | "albers-usa"
33
+ /** Conformal conic on France's own standard parallels (Lambert 93's). */
34
+ | "france";
35
+
36
+ const PROJECTIONS: Record<ProjectionName, () => GeoProjection> = {
37
+ "equal-earth": geoEqualEarth,
38
+ "natural-earth": geoNaturalEarth1,
39
+ mercator: geoMercator,
40
+ equirectangular: geoEquirectangular,
41
+ orthographic: geoOrthographic,
42
+ "albers-usa": geoAlbersUsa as unknown as () => GeoProjection,
43
+ france: () => geoConicConformal().parallels([44, 49]).rotate([-3, 0]),
44
+ };
45
+
46
+ type Ring = ReadonlyArray<ReadonlyArray<number>>;
47
+
48
+ function reverseRings(geometry: {
49
+ type: string;
50
+ coordinates?: unknown;
51
+ }): unknown {
52
+ if (geometry.type === "Polygon") {
53
+ return {
54
+ ...geometry,
55
+ coordinates: (geometry.coordinates as Ring[]).map((ring) =>
56
+ [...ring].reverse(),
57
+ ),
58
+ };
59
+ }
60
+ if (geometry.type === "MultiPolygon") {
61
+ return {
62
+ ...geometry,
63
+ coordinates: (geometry.coordinates as Ring[][]).map((polygon) =>
64
+ polygon.map((ring) => [...ring].reverse()),
65
+ ),
66
+ };
67
+ }
68
+ return geometry;
69
+ }
70
+
71
+ /**
72
+ * GeoJSON as RFC 7946 writes it winds rings counter-clockwise; d3-geo reads
73
+ * that as "the whole sphere except this region". A shape covering more than
74
+ * half the globe is that mistake, so its rings are turned round.
75
+ */
76
+ export function rewind<T>(object: T): T {
77
+ const value = object as {
78
+ type?: string;
79
+ features?: unknown[];
80
+ geometry?: { type: string };
81
+ };
82
+ if (value.type === "FeatureCollection" && Array.isArray(value.features)) {
83
+ return {
84
+ ...value,
85
+ features: value.features.map((feature) => rewind(feature)),
86
+ } as T;
87
+ }
88
+ if (value.type === "Feature" && value.geometry) {
89
+ return geoArea(value as GeoPermissibleObjects) > 2 * Math.PI
90
+ ? ({ ...value, geometry: reverseRings(value.geometry) } as T)
91
+ : object;
92
+ }
93
+ return object;
94
+ }
95
+
96
+ export interface ProjectionOptions {
97
+ /** The GeoJSON the map should frame: usually the shapes it draws. */
98
+ readonly domain: GeoPermissibleObjects;
99
+ /** Pixels kept clear around the framed shape. */
100
+ readonly inset?: number;
101
+ }
102
+
103
+ /**
104
+ * How longitude and latitude become pixels, fitted so `domain` fills the
105
+ * plot. A name picks one of the usual projections; any d3-geo projection
106
+ * factory works too.
107
+ */
108
+ export function projection(
109
+ kind: ProjectionName | (() => GeoProjection),
110
+ options: ProjectionOptions,
111
+ ): ChartProjection {
112
+ return {
113
+ fit(plot): ChartFittedProjection {
114
+ const inset = options.inset ?? 4;
115
+ const projected = (
116
+ typeof kind === "function" ? kind : PROJECTIONS[kind]
117
+ )().fitExtent(
118
+ [
119
+ [plot.x + inset, plot.y + inset],
120
+ [plot.x + plot.width - inset, plot.y + plot.height - inset],
121
+ ],
122
+ rewind(options.domain),
123
+ );
124
+ const path = geoPath(projected);
125
+ return {
126
+ project(longitude, latitude) {
127
+ const point = projected([longitude, latitude]);
128
+ return point ? { x: point[0], y: point[1] } : undefined;
129
+ },
130
+ path: (geometry) => path(geometry as GeoPermissibleObjects) ?? "",
131
+ centroid(geometry) {
132
+ const [x, y] = path.centroid(geometry as GeoPermissibleObjects);
133
+ return { x, y };
134
+ },
135
+ bounds(geometry) {
136
+ const [[x0, y0], [x1, y1]] = path.bounds(
137
+ geometry as GeoPermissibleObjects,
138
+ );
139
+ return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
140
+ },
141
+ };
142
+ },
143
+ };
144
+ }
145
+
146
+ interface Feature {
147
+ readonly type: "Feature";
148
+ readonly properties?: Record<string, unknown> | null;
149
+ readonly geometry: unknown;
150
+ }
151
+
152
+ export interface GeoShapeOptions<TFeature> {
153
+ readonly id?: string;
154
+ /** A region's name: tooltip, table, live region. */
155
+ readonly name: ChartAccessor<TFeature, unknown>;
156
+ /** A value per region: a choropleth on a sequential (numbers) or ordinal (categories) color scale. */
157
+ readonly color?: ChartAccessor<TFeature, ChartValue>;
158
+ readonly fill?: string;
159
+ readonly stroke?: string;
160
+ /** What the colour values are, for the table heading. */
161
+ readonly label?: string;
162
+ readonly tip?: boolean;
163
+ }
164
+
165
+ /** Regions, countries, départements: one shape per GeoJSON feature. */
166
+ export function geoShape<TFeature extends Feature>(
167
+ features: ReadonlyArray<TFeature>,
168
+ options: GeoShapeOptions<TFeature>,
169
+ ): ChartMark {
170
+ const names = readChannel(features, options.name);
171
+ const colors = readChannel(features, options.color);
172
+ return {
173
+ id: options.id,
174
+ coordinate: "frame",
175
+ focusOrder: "point",
176
+ titles: { value: options.label },
177
+ channels:
178
+ options.color === undefined
179
+ ? {}
180
+ : { color: { values: colors.filter(isPlaceable) } },
181
+ render(context) {
182
+ const id = markId(options.id, "geo", context);
183
+ const fitted = context.projection;
184
+ if (fitted === undefined) {
185
+ return { nodes: [] };
186
+ }
187
+ const nodes: SceneNode[] = [];
188
+ const points: ChartPoint[] = [];
189
+ for (const [index, feature] of features.entries()) {
190
+ const name = String(names[index] ?? index);
191
+ const value = colors[index];
192
+ const fill =
193
+ options.fill ??
194
+ (value === undefined || options.color === undefined
195
+ ? context.theme.grid
196
+ : context.colorOf(value));
197
+ const shape = rewind(feature);
198
+ const d = fitted.path(shape);
199
+ if (d === "") continue;
200
+ nodes.push({
201
+ kind: "path",
202
+ key: `${id}:${name}`,
203
+ role: "mark",
204
+ d,
205
+ paint: paint({
206
+ fill,
207
+ stroke: options.stroke ?? context.theme.background,
208
+ strokeWidth: 0.75,
209
+ }),
210
+ });
211
+ if (options.tip !== false) {
212
+ const center = fitted.centroid(shape);
213
+ const number = numericValue(value);
214
+ points.push({
215
+ key: `${id}:${name}`,
216
+ markId: id,
217
+ index,
218
+ datum: feature,
219
+ x: center.x,
220
+ y: center.y,
221
+ xValue: name,
222
+ yValue: value,
223
+ seriesLabel: options.label,
224
+ color: fill,
225
+ title: name,
226
+ value:
227
+ value === undefined
228
+ ? ""
229
+ : number === undefined
230
+ ? String(value)
231
+ : context.formatY(number),
232
+ hit: { kind: "rect", rect: fitted.bounds(shape) },
233
+ });
234
+ }
235
+ }
236
+ return { nodes, points };
237
+ },
238
+ };
239
+ }
240
+
241
+ export interface GeoDotOptions<TDatum> {
242
+ readonly id?: string;
243
+ readonly longitude: ChartAccessor<TDatum, ChartValue>;
244
+ readonly latitude: ChartAccessor<TDatum, ChartValue>;
245
+ readonly name: ChartAccessor<TDatum, unknown>;
246
+ /** A radius in pixels, or a field whose values set each dot's area. */
247
+ readonly r?: number | ChartAccessor<TDatum, ChartValue>;
248
+ readonly maxRadius?: number;
249
+ readonly fill?: string;
250
+ readonly label?: string;
251
+ readonly value?: ChartAccessor<TDatum, ChartValue>;
252
+ readonly tip?: boolean;
253
+ }
254
+
255
+ /** Places on the map: a dot at each longitude and latitude, sized by a value if asked. */
256
+ export function geoDot<TDatum>(
257
+ data: ReadonlyArray<TDatum>,
258
+ options: GeoDotOptions<TDatum>,
259
+ ): ChartMark {
260
+ const longitudes = readChannel(data, options.longitude).map(numericValue);
261
+ const latitudes = readChannel(data, options.latitude).map(numericValue);
262
+ const names = readChannel(data, options.name);
263
+ const values = readChannel(data, options.value).map(numericValue);
264
+ const sized = options.r !== undefined && typeof options.r !== "number";
265
+ const sizes = sized
266
+ ? readChannel(data, options.r as ChartAccessor<TDatum, ChartValue>).map(
267
+ numericValue,
268
+ )
269
+ : [];
270
+ const largest = Math.max(0, ...sizes.map((size) => Math.abs(size ?? 0)));
271
+ return {
272
+ id: options.id,
273
+ coordinate: "frame",
274
+ focusOrder: "point",
275
+ titles: { value: options.label },
276
+ channels: {},
277
+ render(context) {
278
+ const id = markId(options.id, "geoDot", context);
279
+ const fitted = context.projection;
280
+ if (fitted === undefined) {
281
+ return { nodes: [] };
282
+ }
283
+ const color = options.fill ?? context.paletteColor(context.markIndex);
284
+ const maxRadius = options.maxRadius ?? 16;
285
+ const nodes: SceneNode[] = [];
286
+ const points: ChartPoint[] = [];
287
+ for (const [index, datum] of data.entries()) {
288
+ const longitude = longitudes[index];
289
+ const latitude = latitudes[index];
290
+ if (longitude === undefined || latitude === undefined) continue;
291
+ const at = fitted.project(longitude, latitude);
292
+ if (at === undefined) continue;
293
+ const r =
294
+ typeof options.r === "number"
295
+ ? options.r
296
+ : sized
297
+ ? largest === 0
298
+ ? 0
299
+ : Math.sqrt(Math.abs(sizes[index] ?? 0) / largest) * maxRadius
300
+ : 4;
301
+ const name = String(names[index] ?? index);
302
+ nodes.push({
303
+ kind: "circle",
304
+ key: `${id}:${index}`,
305
+ role: "mark",
306
+ cx: at.x,
307
+ cy: at.y,
308
+ r,
309
+ paint: {
310
+ fill: color,
311
+ fillOpacity: 0.75,
312
+ stroke: context.theme.background,
313
+ strokeWidth: 1,
314
+ },
315
+ });
316
+ if (options.tip !== false) {
317
+ const value = values[index] ?? sizes[index];
318
+ points.push({
319
+ key: `${id}:${index}`,
320
+ markId: id,
321
+ index,
322
+ datum,
323
+ x: at.x,
324
+ y: at.y,
325
+ xValue: name,
326
+ yValue: value,
327
+ seriesLabel: options.label,
328
+ color,
329
+ title: name,
330
+ value: value === undefined ? "" : context.formatY(value),
331
+ hit: {
332
+ kind: "rect",
333
+ rect: {
334
+ x: at.x - r - 2,
335
+ y: at.y - r - 2,
336
+ width: r * 2 + 4,
337
+ height: r * 2 + 4,
338
+ },
339
+ },
340
+ });
341
+ }
342
+ }
343
+ return { nodes, points };
344
+ },
345
+ };
346
+ }
@@ -0,0 +1,10 @@
1
+ export {
2
+ type GeoDotOptions,
3
+ type GeoShapeOptions,
4
+ geoDot,
5
+ geoShape,
6
+ type ProjectionName,
7
+ type ProjectionOptions,
8
+ projection,
9
+ rewind,
10
+ } from "#/geo/geo.ts";