@buildcanada/charts 0.1.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 (404) hide show
  1. package/LICENSE.md +8 -0
  2. package/README.md +113 -0
  3. package/package.json +137 -0
  4. package/src/components/BodyPortal/BodyPortal.tsx +40 -0
  5. package/src/components/Button/Button.scss +110 -0
  6. package/src/components/Button/Button.tsx +101 -0
  7. package/src/components/Checkbox.scss +93 -0
  8. package/src/components/Checkbox.tsx +47 -0
  9. package/src/components/ExpandableToggle/ExpandableToggle.scss +123 -0
  10. package/src/components/ExpandableToggle/ExpandableToggle.tsx +60 -0
  11. package/src/components/GrapherTabIcon.tsx +156 -0
  12. package/src/components/GrapherTrendArrow.scss +16 -0
  13. package/src/components/GrapherTrendArrow.tsx +30 -0
  14. package/src/components/Halo/Halo.tsx +44 -0
  15. package/src/components/LabeledSwitch/LabeledSwitch.scss +109 -0
  16. package/src/components/LabeledSwitch/LabeledSwitch.tsx +62 -0
  17. package/src/components/MarkdownTextWrap/MarkdownTextWrap.tsx +1173 -0
  18. package/src/components/OverlayHeader.scss +18 -0
  19. package/src/components/OverlayHeader.tsx +29 -0
  20. package/src/components/RadioButton.scss +69 -0
  21. package/src/components/RadioButton.tsx +42 -0
  22. package/src/components/SimpleMarkdownText.tsx +89 -0
  23. package/src/components/TextInput.scss +17 -0
  24. package/src/components/TextInput.tsx +19 -0
  25. package/src/components/TextWrap/TextWrap.tsx +361 -0
  26. package/src/components/TextWrap/TextWrapUtils.ts +32 -0
  27. package/src/components/closeButton/CloseButton.scss +40 -0
  28. package/src/components/closeButton/CloseButton.tsx +27 -0
  29. package/src/components/index.ts +70 -0
  30. package/src/components/loadingIndicator/LoadingIndicator.scss +40 -0
  31. package/src/components/loadingIndicator/LoadingIndicator.tsx +28 -0
  32. package/src/components/markdown/remarkPlainLinks.ts +36 -0
  33. package/src/components/reactUtil.ts +20 -0
  34. package/src/components/stubs/CodeSnippet.tsx +19 -0
  35. package/src/components/stubs/DataCitation.tsx +16 -0
  36. package/src/components/stubs/IndicatorKeyData.tsx +45 -0
  37. package/src/components/stubs/IndicatorProcessing.tsx +15 -0
  38. package/src/components/stubs/IndicatorSources.tsx +15 -0
  39. package/src/components/styles/colors.scss +113 -0
  40. package/src/components/styles/mixins.scss +630 -0
  41. package/src/components/styles/typography.scss +579 -0
  42. package/src/components/styles/util.scss +89 -0
  43. package/src/components/styles/variables.scss +208 -0
  44. package/src/config/ChartsConfig.ts +163 -0
  45. package/src/config/ChartsProvider.tsx +157 -0
  46. package/src/config/index.ts +20 -0
  47. package/src/core-table/CoreTable.ts +1355 -0
  48. package/src/core-table/CoreTableColumns.ts +973 -0
  49. package/src/core-table/CoreTableUtils.ts +793 -0
  50. package/src/core-table/ErrorValues.ts +73 -0
  51. package/src/core-table/OwidTable.ts +1175 -0
  52. package/src/core-table/OwidTableSynthesizers.ts +272 -0
  53. package/src/core-table/OwidTableUtil.ts +76 -0
  54. package/src/core-table/Transforms.ts +484 -0
  55. package/src/core-table/index.ts +82 -0
  56. package/src/explorer/ColumnGrammar.ts +217 -0
  57. package/src/explorer/Explorer.sample.ts +212 -0
  58. package/src/explorer/Explorer.scss +148 -0
  59. package/src/explorer/Explorer.tsx +1283 -0
  60. package/src/explorer/ExplorerConstants.ts +85 -0
  61. package/src/explorer/ExplorerControls.scss +156 -0
  62. package/src/explorer/ExplorerControls.tsx +210 -0
  63. package/src/explorer/ExplorerDecisionMatrix.ts +471 -0
  64. package/src/explorer/ExplorerGrammar.ts +161 -0
  65. package/src/explorer/ExplorerProgram.ts +568 -0
  66. package/src/explorer/ExplorerUtils.ts +59 -0
  67. package/src/explorer/GrapherGrammar.ts +387 -0
  68. package/src/explorer/gridLang/GrammarUtils.ts +121 -0
  69. package/src/explorer/gridLang/GridCell.ts +298 -0
  70. package/src/explorer/gridLang/GridLangConstants.ts +255 -0
  71. package/src/explorer/gridLang/GridProgram.ts +311 -0
  72. package/src/explorer/gridLang/readme.md +17 -0
  73. package/src/explorer/index.ts +69 -0
  74. package/src/explorer/readme.md +19 -0
  75. package/src/explorer/urlMigrations/CO2UrlMigration.ts +46 -0
  76. package/src/explorer/urlMigrations/CovidUrlMigration.ts +37 -0
  77. package/src/explorer/urlMigrations/EnergyUrlMigration.ts +41 -0
  78. package/src/explorer/urlMigrations/ExplorerPageUrlMigrationSpec.ts +12 -0
  79. package/src/explorer/urlMigrations/ExplorerUrlMigrationUtils.ts +45 -0
  80. package/src/explorer/urlMigrations/ExplorerUrlMigrations.ts +33 -0
  81. package/src/explorer/urlMigrations/LegacyCovidUrlMigration.ts +144 -0
  82. package/src/explorer/urlMigrations/readme.md +39 -0
  83. package/src/grapher/axis/Axis.ts +973 -0
  84. package/src/grapher/axis/AxisConfig.ts +179 -0
  85. package/src/grapher/axis/AxisViews.tsx +597 -0
  86. package/src/grapher/barCharts/DiscreteBarChart.tsx +728 -0
  87. package/src/grapher/barCharts/DiscreteBarChartConstants.ts +60 -0
  88. package/src/grapher/barCharts/DiscreteBarChartHelpers.ts +338 -0
  89. package/src/grapher/barCharts/DiscreteBarChartState.ts +354 -0
  90. package/src/grapher/barCharts/DiscreteBarChartThumbnail.tsx +34 -0
  91. package/src/grapher/captionedChart/CaptionedChart.scss +61 -0
  92. package/src/grapher/captionedChart/CaptionedChart.tsx +523 -0
  93. package/src/grapher/captionedChart/Logos.tsx +141 -0
  94. package/src/grapher/captionedChart/LogosSVG.tsx +16 -0
  95. package/src/grapher/captionedChart/StaticChartRasterizer.tsx +178 -0
  96. package/src/grapher/captionedChart/assets/buildcanada-logo-square.svg +15 -0
  97. package/src/grapher/captionedChart/assets/buildcanada-logo.svg +15 -0
  98. package/src/grapher/captionedChart/assets/canadaspends.svg +7 -0
  99. package/src/grapher/captionedChart/readme.md +14 -0
  100. package/src/grapher/chart/Chart.tsx +62 -0
  101. package/src/grapher/chart/ChartAreaContent.tsx +172 -0
  102. package/src/grapher/chart/ChartDimension.ts +121 -0
  103. package/src/grapher/chart/ChartInterface.ts +83 -0
  104. package/src/grapher/chart/ChartManager.ts +113 -0
  105. package/src/grapher/chart/ChartTabs.ts +178 -0
  106. package/src/grapher/chart/ChartTypeMap.tsx +158 -0
  107. package/src/grapher/chart/ChartTypeSwitcher.tsx +26 -0
  108. package/src/grapher/chart/ChartUtils.tsx +364 -0
  109. package/src/grapher/chart/DimensionSlot.ts +45 -0
  110. package/src/grapher/chart/StaticChartWrapper.tsx +94 -0
  111. package/src/grapher/chart/guidedChartUtils.ts +82 -0
  112. package/src/grapher/color/BinningStrategies.ts +484 -0
  113. package/src/grapher/color/BinningStrategyEqualSizeBins.ts +132 -0
  114. package/src/grapher/color/BinningStrategyLogarithmic.ts +121 -0
  115. package/src/grapher/color/CategoricalColorAssigner.ts +97 -0
  116. package/src/grapher/color/ColorBrewerSchemes.ts +80 -0
  117. package/src/grapher/color/ColorConstants.ts +20 -0
  118. package/src/grapher/color/ColorScale.ts +339 -0
  119. package/src/grapher/color/ColorScaleBin.ts +147 -0
  120. package/src/grapher/color/ColorScaleConfig.ts +204 -0
  121. package/src/grapher/color/ColorScheme.ts +137 -0
  122. package/src/grapher/color/ColorSchemes.ts +149 -0
  123. package/src/grapher/color/ColorUtils.ts +86 -0
  124. package/src/grapher/color/CustomSchemes.ts +1772 -0
  125. package/src/grapher/color/readme.md +84 -0
  126. package/src/grapher/comparisonLine/ComparisonLine.tsx +31 -0
  127. package/src/grapher/comparisonLine/ComparisonLineConstants.ts +11 -0
  128. package/src/grapher/comparisonLine/ComparisonLineGenerator.ts +60 -0
  129. package/src/grapher/comparisonLine/ComparisonLineHelpers.ts +10 -0
  130. package/src/grapher/comparisonLine/CustomComparisonLine.tsx +159 -0
  131. package/src/grapher/comparisonLine/VerticalComparisonLine.tsx +208 -0
  132. package/src/grapher/controls/ActionButtons.scss +97 -0
  133. package/src/grapher/controls/ActionButtons.tsx +453 -0
  134. package/src/grapher/controls/CommandPalette.scss +50 -0
  135. package/src/grapher/controls/CommandPalette.tsx +74 -0
  136. package/src/grapher/controls/ContentSwitchers.scss +93 -0
  137. package/src/grapher/controls/ContentSwitchers.tsx +238 -0
  138. package/src/grapher/controls/Controls.scss +158 -0
  139. package/src/grapher/controls/DataTableFilterDropdown.scss +7 -0
  140. package/src/grapher/controls/DataTableFilterDropdown.tsx +168 -0
  141. package/src/grapher/controls/DataTableSearchField.scss +3 -0
  142. package/src/grapher/controls/DataTableSearchField.tsx +76 -0
  143. package/src/grapher/controls/Dropdown.scss +252 -0
  144. package/src/grapher/controls/Dropdown.tsx +235 -0
  145. package/src/grapher/controls/EntitySelectionToggle.tsx +135 -0
  146. package/src/grapher/controls/MapRegionDropdown.scss +3 -0
  147. package/src/grapher/controls/MapRegionDropdown.tsx +104 -0
  148. package/src/grapher/controls/MapResetButton.tsx +115 -0
  149. package/src/grapher/controls/MapZoomDropdown.scss +9 -0
  150. package/src/grapher/controls/MapZoomDropdown.tsx +270 -0
  151. package/src/grapher/controls/MapZoomToSelectionButton.tsx +87 -0
  152. package/src/grapher/controls/SearchField.scss +78 -0
  153. package/src/grapher/controls/SearchField.tsx +63 -0
  154. package/src/grapher/controls/SettingsMenu.scss +191 -0
  155. package/src/grapher/controls/SettingsMenu.tsx +399 -0
  156. package/src/grapher/controls/ShareMenu.scss +58 -0
  157. package/src/grapher/controls/ShareMenu.tsx +304 -0
  158. package/src/grapher/controls/SortIcon.tsx +39 -0
  159. package/src/grapher/controls/VerticalScrollContainer.tsx +263 -0
  160. package/src/grapher/controls/controlsRow/ControlsRow.tsx +168 -0
  161. package/src/grapher/controls/dropdown-icons.scss +4 -0
  162. package/src/grapher/controls/entityPicker/EntityPicker.scss +255 -0
  163. package/src/grapher/controls/entityPicker/EntityPicker.tsx +816 -0
  164. package/src/grapher/controls/entityPicker/EntityPickerConstants.ts +23 -0
  165. package/src/grapher/controls/globalEntitySelector/GlobalEntitySelector.scss +129 -0
  166. package/src/grapher/controls/globalEntitySelector/GlobalEntitySelector.tsx +463 -0
  167. package/src/grapher/controls/globalEntitySelector/GlobalEntitySelectorConstants.ts +3 -0
  168. package/src/grapher/controls/globalEntitySelector/readme.md +17 -0
  169. package/src/grapher/controls/settings/AbsRelToggle.tsx +64 -0
  170. package/src/grapher/controls/settings/AxisScaleToggle.tsx +53 -0
  171. package/src/grapher/controls/settings/FacetStrategySelector.tsx +110 -0
  172. package/src/grapher/controls/settings/FacetYDomainToggle.tsx +51 -0
  173. package/src/grapher/controls/settings/NoDataAreaToggle.tsx +38 -0
  174. package/src/grapher/controls/settings/ZoomToggle.tsx +36 -0
  175. package/src/grapher/core/EntitiesByRegionType.ts +174 -0
  176. package/src/grapher/core/EntityCodes.ts +19 -0
  177. package/src/grapher/core/EntityUrlBuilder.ts +200 -0
  178. package/src/grapher/core/FetchingGrapher.tsx +156 -0
  179. package/src/grapher/core/Grapher.tsx +760 -0
  180. package/src/grapher/core/GrapherAnalytics.ts +229 -0
  181. package/src/grapher/core/GrapherConstants.ts +173 -0
  182. package/src/grapher/core/GrapherState.tsx +3659 -0
  183. package/src/grapher/core/GrapherUrl.ts +184 -0
  184. package/src/grapher/core/GrapherUrlMigrations.ts +29 -0
  185. package/src/grapher/core/GrapherUseHelpers.tsx +147 -0
  186. package/src/grapher/core/LegacyToOwidTable.ts +841 -0
  187. package/src/grapher/core/grapher.entry.ts +5 -0
  188. package/src/grapher/core/grapher.scss +257 -0
  189. package/src/grapher/core/loadGrapherTableHelpers.ts +116 -0
  190. package/src/grapher/core/loadVariable.ts +104 -0
  191. package/src/grapher/core/relatedQuestion.ts +12 -0
  192. package/src/grapher/core/typography.scss +206 -0
  193. package/src/grapher/dataTable/DataTable.sample.ts +206 -0
  194. package/src/grapher/dataTable/DataTable.scss +249 -0
  195. package/src/grapher/dataTable/DataTable.tsx +1332 -0
  196. package/src/grapher/dataTable/DataTableConstants.ts +186 -0
  197. package/src/grapher/entitySelector/EntitySelector.scss +255 -0
  198. package/src/grapher/entitySelector/EntitySelector.tsx +1838 -0
  199. package/src/grapher/facet/FacetChart.tsx +943 -0
  200. package/src/grapher/facet/FacetChartConstants.ts +24 -0
  201. package/src/grapher/facet/FacetChartUtils.ts +51 -0
  202. package/src/grapher/facet/FacetMap.tsx +604 -0
  203. package/src/grapher/facet/FacetMapConstants.ts +23 -0
  204. package/src/grapher/facet/readme.md +13 -0
  205. package/src/grapher/focus/FocusArray.ts +79 -0
  206. package/src/grapher/footer/Footer.scss +63 -0
  207. package/src/grapher/footer/Footer.tsx +809 -0
  208. package/src/grapher/footer/FooterManager.ts +44 -0
  209. package/src/grapher/fullScreen/FullScreen.scss +11 -0
  210. package/src/grapher/fullScreen/FullScreen.tsx +61 -0
  211. package/src/grapher/header/Header.scss +35 -0
  212. package/src/grapher/header/Header.tsx +372 -0
  213. package/src/grapher/header/HeaderManager.ts +28 -0
  214. package/src/grapher/index.ts +157 -0
  215. package/src/grapher/interaction/InteractionState.ts +60 -0
  216. package/src/grapher/legend/HorizontalColorLegends.tsx +923 -0
  217. package/src/grapher/legend/LegendInteractionState.ts +40 -0
  218. package/src/grapher/legend/VerticalColorLegend.tsx +295 -0
  219. package/src/grapher/lineCharts/LineChart.tsx +968 -0
  220. package/src/grapher/lineCharts/LineChartConstants.ts +89 -0
  221. package/src/grapher/lineCharts/LineChartHelpers.ts +184 -0
  222. package/src/grapher/lineCharts/LineChartState.ts +394 -0
  223. package/src/grapher/lineCharts/LineChartThumbnail.tsx +437 -0
  224. package/src/grapher/lineCharts/Lines.tsx +258 -0
  225. package/src/grapher/lineLegend/LineLegend.tsx +723 -0
  226. package/src/grapher/lineLegend/LineLegendConstants.ts +9 -0
  227. package/src/grapher/lineLegend/LineLegendFilterAlgorithms.ts +143 -0
  228. package/src/grapher/lineLegend/LineLegendHelpers.ts +253 -0
  229. package/src/grapher/lineLegend/LineLegendTypes.ts +32 -0
  230. package/src/grapher/mapCharts/CanadaTopology.ts +17922 -0
  231. package/src/grapher/mapCharts/ChoroplethGlobe.tsx +949 -0
  232. package/src/grapher/mapCharts/ChoroplethMap.tsx +662 -0
  233. package/src/grapher/mapCharts/GeoFeatures.ts +184 -0
  234. package/src/grapher/mapCharts/GlobeController.ts +496 -0
  235. package/src/grapher/mapCharts/MapAnnotationPlacements.json +1040 -0
  236. package/src/grapher/mapCharts/MapAnnotationPlacements.ts +31 -0
  237. package/src/grapher/mapCharts/MapAnnotations.ts +723 -0
  238. package/src/grapher/mapCharts/MapChart.sample.ts +59 -0
  239. package/src/grapher/mapCharts/MapChart.scss +5 -0
  240. package/src/grapher/mapCharts/MapChart.tsx +720 -0
  241. package/src/grapher/mapCharts/MapChartConstants.ts +260 -0
  242. package/src/grapher/mapCharts/MapChartState.ts +416 -0
  243. package/src/grapher/mapCharts/MapChartThumbnail.tsx +25 -0
  244. package/src/grapher/mapCharts/MapComponents.tsx +338 -0
  245. package/src/grapher/mapCharts/MapConfig.ts +156 -0
  246. package/src/grapher/mapCharts/MapHelpers.ts +181 -0
  247. package/src/grapher/mapCharts/MapProjections.ts +49 -0
  248. package/src/grapher/mapCharts/MapSparkline.tsx +257 -0
  249. package/src/grapher/mapCharts/MapTooltip.scss +49 -0
  250. package/src/grapher/mapCharts/MapTooltip.tsx +409 -0
  251. package/src/grapher/mapCharts/MapTopology.ts +1766 -0
  252. package/src/grapher/mapCharts/d3-bboxCollide.js +204 -0
  253. package/src/grapher/mapCharts/d3-geo-projection.ts +198 -0
  254. package/src/grapher/modal/DownloadIcons.tsx +39 -0
  255. package/src/grapher/modal/DownloadModal.scss +300 -0
  256. package/src/grapher/modal/DownloadModal.tsx +1226 -0
  257. package/src/grapher/modal/EmbedModal.scss +40 -0
  258. package/src/grapher/modal/EmbedModal.tsx +160 -0
  259. package/src/grapher/modal/EntitySelectorModal.tsx +59 -0
  260. package/src/grapher/modal/Modal.scss +31 -0
  261. package/src/grapher/modal/Modal.tsx +90 -0
  262. package/src/grapher/modal/ModalHeader.scss +12 -0
  263. package/src/grapher/modal/ModalHeader.tsx +16 -0
  264. package/src/grapher/modal/SourcesDescriptions.scss +87 -0
  265. package/src/grapher/modal/SourcesDescriptions.tsx +89 -0
  266. package/src/grapher/modal/SourcesKeyDataTable.scss +49 -0
  267. package/src/grapher/modal/SourcesKeyDataTable.tsx +87 -0
  268. package/src/grapher/modal/SourcesModal.scss +301 -0
  269. package/src/grapher/modal/SourcesModal.tsx +568 -0
  270. package/src/grapher/noDataModal/NoDataModal.tsx +125 -0
  271. package/src/grapher/scatterCharts/ConnectedScatterLegend.tsx +143 -0
  272. package/src/grapher/scatterCharts/MultiColorPolyline.tsx +129 -0
  273. package/src/grapher/scatterCharts/NoDataSection.scss +14 -0
  274. package/src/grapher/scatterCharts/NoDataSection.tsx +56 -0
  275. package/src/grapher/scatterCharts/ScatterPlotChart.tsx +792 -0
  276. package/src/grapher/scatterCharts/ScatterPlotChartConstants.ts +157 -0
  277. package/src/grapher/scatterCharts/ScatterPlotChartState.ts +678 -0
  278. package/src/grapher/scatterCharts/ScatterPlotChartThumbnail.tsx +155 -0
  279. package/src/grapher/scatterCharts/ScatterPlotTooltip.tsx +560 -0
  280. package/src/grapher/scatterCharts/ScatterPoints.tsx +153 -0
  281. package/src/grapher/scatterCharts/ScatterPointsWithLabels.tsx +708 -0
  282. package/src/grapher/scatterCharts/ScatterSizeLegend.tsx +327 -0
  283. package/src/grapher/scatterCharts/ScatterUtils.ts +265 -0
  284. package/src/grapher/scatterCharts/Triangle.tsx +41 -0
  285. package/src/grapher/schema/README.md +33 -0
  286. package/src/grapher/schema/defaultGrapherConfig.ts +100 -0
  287. package/src/grapher/schema/grapher-schema.009.yaml +781 -0
  288. package/src/grapher/schema/migrations/helpers.ts +58 -0
  289. package/src/grapher/schema/migrations/migrate.ts +75 -0
  290. package/src/grapher/schema/migrations/migrations.ts +158 -0
  291. package/src/grapher/selection/MapSelectionArray.ts +99 -0
  292. package/src/grapher/selection/SelectionArray.ts +71 -0
  293. package/src/grapher/selection/readme.md +16 -0
  294. package/src/grapher/sidePanel/SidePanel.scss +10 -0
  295. package/src/grapher/sidePanel/SidePanel.tsx +23 -0
  296. package/src/grapher/slideInDrawer/SlideInDrawer.scss +57 -0
  297. package/src/grapher/slideInDrawer/SlideInDrawer.tsx +125 -0
  298. package/src/grapher/slideshowController/SlideShowController.tsx +43 -0
  299. package/src/grapher/slideshowController/readme.md +7 -0
  300. package/src/grapher/slopeCharts/MarkX.tsx +45 -0
  301. package/src/grapher/slopeCharts/Slope.tsx +102 -0
  302. package/src/grapher/slopeCharts/SlopeChart.tsx +1152 -0
  303. package/src/grapher/slopeCharts/SlopeChartConstants.ts +33 -0
  304. package/src/grapher/slopeCharts/SlopeChartHelpers.ts +73 -0
  305. package/src/grapher/slopeCharts/SlopeChartState.ts +392 -0
  306. package/src/grapher/slopeCharts/SlopeChartThumbnail.tsx +368 -0
  307. package/src/grapher/stackedCharts/AbstractStackedChartState.ts +370 -0
  308. package/src/grapher/stackedCharts/MarimekkoBars.tsx +190 -0
  309. package/src/grapher/stackedCharts/MarimekkoBarsForOneEntity.tsx +168 -0
  310. package/src/grapher/stackedCharts/MarimekkoChart.tsx +1144 -0
  311. package/src/grapher/stackedCharts/MarimekkoChartConstants.ts +112 -0
  312. package/src/grapher/stackedCharts/MarimekkoChartHelpers.ts +21 -0
  313. package/src/grapher/stackedCharts/MarimekkoChartState.ts +465 -0
  314. package/src/grapher/stackedCharts/MarimekkoChartThumbnail.tsx +168 -0
  315. package/src/grapher/stackedCharts/MarimekkoInternalLabels.tsx +124 -0
  316. package/src/grapher/stackedCharts/StackedAreaChart.tsx +678 -0
  317. package/src/grapher/stackedCharts/StackedAreaChartState.ts +34 -0
  318. package/src/grapher/stackedCharts/StackedAreaChartThumbnail.tsx +215 -0
  319. package/src/grapher/stackedCharts/StackedAreas.tsx +223 -0
  320. package/src/grapher/stackedCharts/StackedBarChart.tsx +619 -0
  321. package/src/grapher/stackedCharts/StackedBarChartState.ts +80 -0
  322. package/src/grapher/stackedCharts/StackedBarChartThumbnail.tsx +220 -0
  323. package/src/grapher/stackedCharts/StackedBarSegment.tsx +87 -0
  324. package/src/grapher/stackedCharts/StackedBars.tsx +102 -0
  325. package/src/grapher/stackedCharts/StackedConstants.ts +109 -0
  326. package/src/grapher/stackedCharts/StackedDiscreteBarChart.tsx +270 -0
  327. package/src/grapher/stackedCharts/StackedDiscreteBarChartState.ts +296 -0
  328. package/src/grapher/stackedCharts/StackedDiscreteBarChartThumbnail.tsx +27 -0
  329. package/src/grapher/stackedCharts/StackedDiscreteBars.tsx +648 -0
  330. package/src/grapher/stackedCharts/StackedUtils.ts +142 -0
  331. package/src/grapher/tabs/Tabs.scss +169 -0
  332. package/src/grapher/tabs/Tabs.tsx +54 -0
  333. package/src/grapher/tabs/TabsWithDropdown.scss +62 -0
  334. package/src/grapher/tabs/TabsWithDropdown.tsx +114 -0
  335. package/src/grapher/testData/OwidTestData.sample.ts +273 -0
  336. package/src/grapher/testData/OwidTestData.ts +64 -0
  337. package/src/grapher/timeline/TimelineComponent.scss +139 -0
  338. package/src/grapher/timeline/TimelineComponent.tsx +658 -0
  339. package/src/grapher/timeline/TimelineController.ts +368 -0
  340. package/src/grapher/timeline/readme.md +7 -0
  341. package/src/grapher/tooltip/Tooltip.scss +510 -0
  342. package/src/grapher/tooltip/Tooltip.tsx +294 -0
  343. package/src/grapher/tooltip/TooltipContents.tsx +383 -0
  344. package/src/grapher/tooltip/TooltipProps.ts +123 -0
  345. package/src/grapher/tooltip/TooltipState.ts +81 -0
  346. package/src/grapher/verticalLabels/VerticalLabels.tsx +31 -0
  347. package/src/grapher/verticalLabels/VerticalLabelsState.ts +154 -0
  348. package/src/index.ts +226 -0
  349. package/src/styles/charts.scss +15 -0
  350. package/src/types/NominalType.ts +30 -0
  351. package/src/types/OwidOrigin.ts +18 -0
  352. package/src/types/OwidSource.ts +9 -0
  353. package/src/types/OwidVariable.ts +133 -0
  354. package/src/types/OwidVariableDisplayConfigInterface.ts +49 -0
  355. package/src/types/analyticsTypes.ts +54 -0
  356. package/src/types/dbTypes/Tags.ts +11 -0
  357. package/src/types/domainTypes/Archive.ts +139 -0
  358. package/src/types/domainTypes/Author.ts +28 -0
  359. package/src/types/domainTypes/ContentGraph.ts +76 -0
  360. package/src/types/domainTypes/CoreTableTypes.ts +305 -0
  361. package/src/types/domainTypes/DeployStatus.ts +23 -0
  362. package/src/types/domainTypes/Layout.ts +34 -0
  363. package/src/types/domainTypes/Posts.ts +34 -0
  364. package/src/types/domainTypes/Search.ts +299 -0
  365. package/src/types/domainTypes/Site.ts +8 -0
  366. package/src/types/domainTypes/StaticViz.ts +64 -0
  367. package/src/types/domainTypes/Toc.ts +11 -0
  368. package/src/types/domainTypes/Tombstone.ts +19 -0
  369. package/src/types/domainTypes/Various.ts +79 -0
  370. package/src/types/gdocTypes/Gdoc.ts +280 -0
  371. package/src/types/grapherTypes/BinningStrategyTypes.ts +46 -0
  372. package/src/types/grapherTypes/GrapherConstants.ts +53 -0
  373. package/src/types/grapherTypes/GrapherTypes.ts +743 -0
  374. package/src/types/index.ts +316 -0
  375. package/src/types/wordpressTypes/WordpressTypes.ts +9 -0
  376. package/src/utils/Bounds.ts +439 -0
  377. package/src/utils/BrowserUtils.ts +12 -0
  378. package/src/utils/FuzzySearch.ts +74 -0
  379. package/src/utils/MultiDimDataPageConfig.ts +31 -0
  380. package/src/utils/OwidVariable.ts +82 -0
  381. package/src/utils/PointVector.ts +97 -0
  382. package/src/utils/PromiseCache.ts +36 -0
  383. package/src/utils/PromiseSwitcher.ts +52 -0
  384. package/src/utils/TimeBounds.ts +130 -0
  385. package/src/utils/Tippy.tsx +57 -0
  386. package/src/utils/Util.ts +2369 -0
  387. package/src/utils/archival/archivalDate.ts +48 -0
  388. package/src/utils/dayjs.ts +32 -0
  389. package/src/utils/formatValue.ts +242 -0
  390. package/src/utils/grapherConfigUtils.ts +81 -0
  391. package/src/utils/image.ts +225 -0
  392. package/src/utils/index.ts +318 -0
  393. package/src/utils/isPresent.ts +5 -0
  394. package/src/utils/metadataHelpers.ts +329 -0
  395. package/src/utils/persistable/Persistable.ts +82 -0
  396. package/src/utils/persistable/readme.md +50 -0
  397. package/src/utils/regions.json +5635 -0
  398. package/src/utils/regions.ts +463 -0
  399. package/src/utils/serializers.ts +16 -0
  400. package/src/utils/string.ts +42 -0
  401. package/src/utils/urls/Url.ts +195 -0
  402. package/src/utils/urls/UrlMigration.ts +10 -0
  403. package/src/utils/urls/UrlUtils.ts +54 -0
  404. package/src/utils/urls/readme.md +90 -0
@@ -0,0 +1,723 @@
1
+ import { Feature, GeoJsonProperties, Polygon } from "geojson"
2
+ import { GeoProjection } from "d3-geo"
3
+ import { forceSimulation, forceX, forceY, SimulationNodeDatum } from "d3-force"
4
+ // no types available
5
+ import bboxCollide from "./d3-bboxCollide"
6
+ import { booleanIntersects } from "@turf/boolean-intersects"
7
+ import { Bounds, excludeUndefined } from "../../utils/index.js"
8
+ import {
9
+ Direction,
10
+ Ellipse,
11
+ RenderFeature,
12
+ ExternalAnnotation,
13
+ Circle,
14
+ EllipseCoords,
15
+ InternalAnnotation,
16
+ GeoFeature,
17
+ ANNOTATION_FONT_SIZE_INTERNAL_DEFAULT,
18
+ ANNOTATION_FONT_SIZE_INTERNAL_MIN,
19
+ ANNOTATION_FONT_SIZE_EXTERNAL_DEFAULT,
20
+ ANNOTATION_FONT_SIZE_EXTERNAL_MAX,
21
+ ANNOTATION_MARKER_LINE_LENGTH_DEFAULT,
22
+ ANNOTATION_MARKER_LINE_LENGTH_MAX,
23
+ } from "./MapChartConstants"
24
+ import * as R from "remeda"
25
+ import { annotationPlacementsById } from "./MapAnnotationPlacements"
26
+ import { GRAPHER_DARK_TEXT } from "../color/ColorConstants"
27
+ import { getGeoFeaturesForGlobe } from "./GeoFeatures"
28
+
29
+ const MARKER_TEXT_GAP = 2
30
+
31
+ export function makeInternalAnnotationForFeature({
32
+ feature,
33
+ projection,
34
+ formattedValue,
35
+ color,
36
+ fontSizeScale = 1,
37
+ }: {
38
+ feature: RenderFeature
39
+ projection: GeoProjection
40
+ formattedValue: string
41
+ color?: string
42
+ fontSizeScale?: number
43
+ }): InternalAnnotation | undefined {
44
+ const placement = annotationPlacementsById.get(feature.id)
45
+ if (!placement || !placement.ellipse) return
46
+
47
+ // project ellipse onto the map
48
+ const ellipse = makeEllipseForProjection({
49
+ ellipse: placement.ellipse,
50
+ projection,
51
+ paddingFactor: { x: 0.1 },
52
+ })
53
+
54
+ if (!ellipse) return
55
+
56
+ // place label at the center of the ellipse (decreases the font size if necessary)
57
+ const placedLabel = placeLabelAtEllipseCenter({
58
+ text: formattedValue,
59
+ ellipse,
60
+ fontSizeScale,
61
+ })
62
+
63
+ if (placedLabel)
64
+ return {
65
+ type: "internal",
66
+ id: feature.id,
67
+ feature,
68
+ placedBounds: placedLabel.placedBounds,
69
+ text: formattedValue,
70
+ ellipse,
71
+ fontSize: placedLabel.fontSize,
72
+ color: color ?? GRAPHER_DARK_TEXT,
73
+ }
74
+
75
+ return
76
+ }
77
+
78
+ export function makeExternalAnnotationForFeature({
79
+ feature,
80
+ projection,
81
+ formattedValue,
82
+ fontSizeScale = 1,
83
+ }: {
84
+ feature: RenderFeature
85
+ projection: GeoProjection
86
+ formattedValue: string
87
+ fontSizeScale?: number
88
+ }): ExternalAnnotation | undefined {
89
+ const placement = annotationPlacementsById.get(feature.id)
90
+ if (!placement || !placement.anchorPoint || !placement.direction) return
91
+
92
+ const direction = placement.direction
93
+ const anchorPoint = projection(placement.anchorPoint)
94
+ if (!anchorPoint) return
95
+
96
+ const fontSize = Math.min(
97
+ ANNOTATION_FONT_SIZE_EXTERNAL_DEFAULT / fontSizeScale,
98
+ ANNOTATION_FONT_SIZE_EXTERNAL_MAX
99
+ )
100
+ const markerLength = Math.min(
101
+ ANNOTATION_MARKER_LINE_LENGTH_DEFAULT / fontSizeScale,
102
+ ANNOTATION_MARKER_LINE_LENGTH_MAX
103
+ )
104
+
105
+ // places the label outside of the country borders based on the
106
+ // given anchor point and direction
107
+ let placedBounds = placeExternalLabel({
108
+ text: formattedValue,
109
+ anchorPoint,
110
+ direction,
111
+ fontSize,
112
+ markerLength,
113
+ })
114
+
115
+ // make sure the labels of countries like Lesotho and Eswatini that are
116
+ // separated from the ocean by another country are placed in the ocean
117
+ if (placement.bridgeFeatures?.length)
118
+ placedBounds = moveExternalLabelIntoOcean({
119
+ bridgeFeatures: placement.bridgeFeatures,
120
+ placedBounds,
121
+ direction,
122
+ projection,
123
+ step: markerLength / 2,
124
+ maxSteps: 3,
125
+ })
126
+
127
+ return {
128
+ type: "external",
129
+ id: feature.id,
130
+ feature,
131
+ text: formattedValue,
132
+ placedBounds,
133
+ anchor: anchorPoint,
134
+ direction,
135
+ fontSize,
136
+ color: GRAPHER_DARK_TEXT,
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Given a list of naively placed external annotations that might overlap,
142
+ * adjust label positions to minimise collisions and drop labels that can't
143
+ * be rendered.
144
+ */
145
+ export function repositionAndFilterExternalAnnotations({
146
+ annotations,
147
+ projection,
148
+ backgroundFeatureIdSet, // countries ignored for collision detection
149
+ }: {
150
+ annotations: ExternalAnnotation[]
151
+ projection: GeoProjection
152
+ backgroundFeatureIdSet: Set<string>
153
+ }): ExternalAnnotation[] {
154
+ const originalAnnotationsById = new Map(
155
+ annotations.map((annotation) => [annotation.id, annotation])
156
+ )
157
+
158
+ // re-position label annotations so that they're not overlapping
159
+ const nonOverlappingAnnotations = minimiseLabelCollisions(annotations)
160
+
161
+ // the re-positioned label annotations might overlap with other countries
162
+ const filteredAnnotations = nonOverlappingAnnotations.filter(
163
+ (annotation) => {
164
+ // we do these checks only for countries that are proximally close for performance reasons
165
+ let nearbyGeoFeatures = getGeoFeaturesWithinRadius(
166
+ annotation.feature
167
+ )
168
+
169
+ // exclude background countries from collision detection,
170
+ // i.e. showing a label on top of a background country is allowed
171
+ if (backgroundFeatureIdSet.size > 0)
172
+ nearbyGeoFeatures = nearbyGeoFeatures.filter(
173
+ (feature) =>
174
+ !backgroundFeatureIdSet.has(feature.id as string)
175
+ )
176
+
177
+ const nearbyAnnotations = excludeUndefined(
178
+ nearbyGeoFeatures.map((feature) =>
179
+ originalAnnotationsById.get(feature.id as string)
180
+ )
181
+ )
182
+
183
+ return (
184
+ // hide an annotation if the line that connects the annotation
185
+ // with the anchor point crosses through other labels
186
+ !checkAnnotationMarkerCollidesWithSomeLabel({
187
+ annotation,
188
+ nearbyAnnotations,
189
+ }) &&
190
+ // hide an annotation if it overlaps with other countries
191
+ !checkAnnotationCollidesWithLandmass({
192
+ annotation,
193
+ nearbyGeoFeatures,
194
+ projection,
195
+ })
196
+ )
197
+ }
198
+ )
199
+
200
+ if (filteredAnnotations.length < annotations.length) {
201
+ // reset the filtered annotations to their original position and
202
+ // re-position their labels again
203
+ return minimiseLabelCollisions(
204
+ filteredAnnotations.map((annotation) => {
205
+ const origBounds = originalAnnotationsById.get(
206
+ annotation.id
207
+ )!.placedBounds
208
+ return { ...annotation, placedBounds: origBounds }
209
+ })
210
+ )
211
+ }
212
+
213
+ return filteredAnnotations
214
+ }
215
+
216
+ function placeLabelAtEllipseCenter({
217
+ text,
218
+ ellipse,
219
+ fontSizeScale = 1,
220
+ }: {
221
+ text: string
222
+ ellipse: Ellipse
223
+ fontSizeScale?: number
224
+ }): { placedBounds: Bounds; fontSize: number } | undefined {
225
+ const defaultFontSize =
226
+ ANNOTATION_FONT_SIZE_INTERNAL_DEFAULT / fontSizeScale
227
+
228
+ // place label at the center of the ellipse
229
+ const ellipseCenter = { x: ellipse.cx, y: ellipse.cy }
230
+ let placedBounds = makePlacedBoundsForText({
231
+ text,
232
+ fontSize: defaultFontSize,
233
+ fontWeight: 700,
234
+ position: ellipseCenter,
235
+ center: true,
236
+ })
237
+
238
+ // return early if the label fits into the ellipse
239
+ let textFits = checkPointIsInEllipse(placedBounds.topLeft, ellipse)
240
+ if (textFits) return { placedBounds, fontSize: defaultFontSize }
241
+
242
+ // decrease the font size to make the label fit into the ellipse
243
+ const step = 1
244
+ for (
245
+ let fontSize = ANNOTATION_FONT_SIZE_INTERNAL_DEFAULT - step;
246
+ fontSize >= ANNOTATION_FONT_SIZE_INTERNAL_MIN;
247
+ fontSize -= step
248
+ ) {
249
+ const scaledFontSize = fontSize / fontSizeScale
250
+ placedBounds = makePlacedBoundsForText({
251
+ text,
252
+ fontSize: scaledFontSize,
253
+ fontWeight: 700,
254
+ position: ellipseCenter,
255
+ center: true,
256
+ })
257
+
258
+ textFits = checkPointIsInEllipse(placedBounds.topLeft, ellipse)
259
+ if (textFits) return { placedBounds, fontSize: scaledFontSize }
260
+ }
261
+
262
+ // the label text didn't fit into the ellipse
263
+ return undefined
264
+ }
265
+
266
+ function placeExternalLabel({
267
+ text,
268
+ anchorPoint,
269
+ direction,
270
+ fontSize,
271
+ markerLength,
272
+ padding = MARKER_TEXT_GAP,
273
+ }: {
274
+ text: string
275
+ anchorPoint: [number, number]
276
+ direction: Direction
277
+ fontSize: number
278
+ markerLength: number
279
+ padding?: number
280
+ }): Bounds {
281
+ const textBounds = Bounds.forText(text, { fontSize, fontWeight: 700 })
282
+
283
+ const labelPosition = calculateExternalLabelPosition({
284
+ anchorPoint,
285
+ textBounds,
286
+ direction,
287
+ markerLength,
288
+ padding,
289
+ })
290
+
291
+ return textBounds.set({ x: labelPosition[0], y: labelPosition[1] })
292
+ }
293
+
294
+ function checkPointIsInEllipse(
295
+ point: { x: number; y: number },
296
+ ellipse: Ellipse
297
+ ): boolean {
298
+ const dx = point.x - ellipse.cx
299
+ const dy = point.y - ellipse.cy
300
+ return (
301
+ (dx * dx) / (ellipse.rx * ellipse.rx) +
302
+ (dy * dy) / (ellipse.ry * ellipse.ry) <=
303
+ 1
304
+ )
305
+ }
306
+
307
+ function makeEllipseForProjection({
308
+ ellipse,
309
+ projection,
310
+ paddingFactor,
311
+ }: {
312
+ ellipse: EllipseCoords
313
+ projection: GeoProjection
314
+ paddingFactor?: { x?: number; y?: number }
315
+ }): Ellipse | undefined {
316
+ const projCenter = projection([ellipse.cx, ellipse.cy])
317
+ const projLeft = projection([ellipse.left, ellipse.cy])
318
+ const projTop = projection([ellipse.cx, ellipse.top])
319
+
320
+ if (!projCenter || !projLeft || !projTop) return undefined
321
+
322
+ const rx = Math.abs(projCenter[0] - projLeft[0])
323
+ const ry = Math.abs(projCenter[1] - projTop[1])
324
+
325
+ const padX = (paddingFactor?.x ?? 0) * rx
326
+ const padY = (paddingFactor?.y ?? 0) * ry
327
+
328
+ return {
329
+ cx: projCenter[0],
330
+ cy: projCenter[1],
331
+ rx: rx - padX,
332
+ ry: ry - padY,
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Calculate the top-left corner of the externally placed label given the
338
+ * top-left corner of the anchor point and a direction
339
+ */
340
+ function calculateExternalLabelPosition({
341
+ anchorPoint,
342
+ textBounds,
343
+ direction,
344
+ markerLength,
345
+ padding = MARKER_TEXT_GAP,
346
+ }: {
347
+ anchorPoint: [number, number]
348
+ textBounds: Bounds
349
+ direction: Direction
350
+ markerLength: number
351
+ padding?: number
352
+ }): [number, number] {
353
+ const [x, y] = anchorPoint
354
+ const w = textBounds.width,
355
+ h = textBounds.height
356
+ const m = markerLength + padding
357
+
358
+ switch (direction) {
359
+ case "right":
360
+ return [x + m, y - h / 2]
361
+ case "left":
362
+ return [x - m - w, y - h / 2]
363
+ case "bottom":
364
+ return [x - w / 2, y + m]
365
+ case "top":
366
+ return [x - w / 2, y - h / 2 - m]
367
+ case "leftTop":
368
+ return [x - m - w, y - h / 2 - m / 2 - h]
369
+ case "leftBottom":
370
+ return [x - m - w, y - h / 2 + h + m / 2]
371
+ case "rightTop":
372
+ return [x + m, y - h / 2 - m / 2 - h]
373
+ case "rightBottom":
374
+ return [x + m, y - h / 2 + h + m / 2]
375
+ }
376
+ }
377
+
378
+ export function getExternalMarkerEndPosition({
379
+ textBounds,
380
+ direction,
381
+ padding = MARKER_TEXT_GAP,
382
+ }: {
383
+ textBounds: Bounds
384
+ direction: Direction
385
+ padding?: number
386
+ }): [number, number] {
387
+ const { x, y, width, height } = textBounds
388
+
389
+ switch (direction) {
390
+ case "right":
391
+ return [x - padding, y + height / 2]
392
+ case "left":
393
+ return [x + width + padding, y + height / 2]
394
+ case "bottom":
395
+ return [x + width / 2, y - padding]
396
+ case "top":
397
+ return [x + width / 2, y + height + padding]
398
+ case "leftTop":
399
+ return [x + width, y + height]
400
+ case "leftBottom":
401
+ return [x + width, y]
402
+ case "rightTop":
403
+ return [x, y + height]
404
+ case "rightBottom":
405
+ return [x, y]
406
+ }
407
+ }
408
+
409
+ function makePlacedBoundsForText({
410
+ text,
411
+ fontSize,
412
+ fontWeight,
413
+ position,
414
+ center = false,
415
+ }: {
416
+ text: string
417
+ fontSize: number
418
+ fontWeight: number
419
+ position: { x: number; y: number }
420
+ center?: boolean
421
+ }): Bounds {
422
+ // make bounds for text
423
+ const textBounds = Bounds.forText(text, { fontSize, fontWeight })
424
+
425
+ // place bounds at the given position
426
+ const x = center ? position.x - textBounds.width / 2 : position.x
427
+ const y = center ? position.y - textBounds.height / 2 : position.y
428
+ return textBounds.set({ x, y })
429
+ }
430
+
431
+ function moveExternalLabelIntoOcean({
432
+ bridgeFeatures,
433
+ direction,
434
+ placedBounds,
435
+ projection,
436
+ step,
437
+ maxSteps,
438
+ }: {
439
+ bridgeFeatures: GeoFeature[]
440
+ direction: Direction
441
+ placedBounds: Bounds
442
+ projection: any
443
+ step: number
444
+ maxSteps: number
445
+ }): Bounds {
446
+ // polygon in lon/lat coordinates that represents the annotation label
447
+ let annotationLabel = makePolygonFromBounds(placedBounds, (position) =>
448
+ projection.invert(position)
449
+ )
450
+
451
+ let newBounds = placedBounds
452
+ for (let tick = 0; tick < maxSteps; tick++) {
453
+ const intersectsWithSomeFeature = bridgeFeatures.some((bridgeFeature) =>
454
+ booleanIntersects(annotationLabel, bridgeFeature)
455
+ )
456
+ if (!intersectsWithSomeFeature) break
457
+
458
+ // update bounds
459
+ newBounds = newBounds.set(
460
+ moveIntoDirectionByStep({
461
+ position: newBounds.topLeft,
462
+ direction,
463
+ step,
464
+ })
465
+ )
466
+
467
+ // update label
468
+ annotationLabel = makePolygonFromBounds(newBounds, (position) =>
469
+ projection.invert(position)
470
+ )
471
+ }
472
+
473
+ return newBounds
474
+ }
475
+
476
+ function moveIntoDirectionByStep({
477
+ position: { x, y },
478
+ direction,
479
+ step,
480
+ }: {
481
+ position: { x: number; y: number }
482
+ direction: Direction
483
+ step: number
484
+ }): { x: number; y: number } {
485
+ switch (direction) {
486
+ case "right":
487
+ return { x: x + step, y }
488
+ case "left":
489
+ return { x: x - step, y }
490
+ case "top":
491
+ return { x, y: y - step }
492
+ case "bottom":
493
+ return { x, y: y + step }
494
+ case "leftTop":
495
+ return { x: x - step, y: y - step }
496
+ case "rightTop":
497
+ return { x: x + step, y: y - step }
498
+ case "leftBottom":
499
+ return { x: x - step, y: y + step }
500
+ case "rightBottom":
501
+ return { x: x + step, y: y + step }
502
+ }
503
+ }
504
+
505
+ const _featuresWithinRadiusCache = new Map<string, GeoFeature[]>()
506
+ function getGeoFeaturesWithinRadius(feature: RenderFeature): GeoFeature[] {
507
+ if (_featuresWithinRadiusCache.has(feature.id))
508
+ return _featuresWithinRadiusCache.get(feature.id)!
509
+
510
+ const circle = {
511
+ cx: feature.geoCentroid[0],
512
+ cy: feature.geoCentroid[1],
513
+ r: 10,
514
+ }
515
+
516
+ const nearbyGeoFeatures = getGeoFeaturesForGlobe()
517
+ .filter(
518
+ (candidate) =>
519
+ candidate.id !== feature.id &&
520
+ checkRectangleOverlapsWithCircle({
521
+ circle,
522
+ rectangle: candidate.geoBounds,
523
+ })
524
+ )
525
+ .map((feature) => feature.geo)
526
+
527
+ _featuresWithinRadiusCache.set(feature.id, nearbyGeoFeatures)
528
+ return nearbyGeoFeatures
529
+ }
530
+
531
+ function checkRectangleOverlapsWithCircle({
532
+ circle,
533
+ rectangle,
534
+ }: {
535
+ circle: Circle
536
+ rectangle: Bounds
537
+ }): boolean {
538
+ const closestX = R.clamp(circle.cx, {
539
+ min: rectangle.topLeft.x,
540
+ max: rectangle.bottomRight.x,
541
+ })
542
+ const closestY = R.clamp(circle.cy, {
543
+ min: rectangle.topLeft.y,
544
+ max: rectangle.bottomRight.y,
545
+ })
546
+
547
+ const dx = circle.cx - closestX
548
+ const dy = circle.cy - closestY
549
+
550
+ return dx * dx + dy * dy <= circle.r * circle.r
551
+ }
552
+
553
+ interface SimulationNode extends SimulationNodeDatum {
554
+ annotation: ExternalAnnotation
555
+ }
556
+
557
+ function minimiseLabelCollisions(
558
+ annotations: ExternalAnnotation[],
559
+ padding = 1
560
+ ): ExternalAnnotation[] {
561
+ if (annotations.length < 2) return annotations
562
+
563
+ const simulationNodes: SimulationNode[] = annotations.map((annotation) => {
564
+ const { centerX, centerY } = annotation.placedBounds
565
+ const isTopOrBottom =
566
+ annotation.direction === "top" || annotation.direction === "bottom"
567
+ return {
568
+ annotation,
569
+ x: centerX,
570
+ y: centerY,
571
+ // fix the x or y position to make sure the simulation doesn't move
572
+ // a label into its anchor point
573
+ fx: !isTopOrBottom ? centerX : undefined,
574
+ fy: isTopOrBottom ? centerY : undefined,
575
+ }
576
+ })
577
+
578
+ // run force simulation that balances two concerns:
579
+ // - keeping the annotation close to its anchor point (x,y forces)
580
+ // - minimising collisions between labels (collide force)
581
+ forceSimulation(simulationNodes)
582
+ .force(
583
+ "x",
584
+ forceX((d: SimulationNode) => d.annotation.placedBounds.centerX)
585
+ )
586
+ .force(
587
+ "y",
588
+ forceY((d: SimulationNode) => d.annotation.placedBounds.centerY)
589
+ )
590
+ .force(
591
+ "collide",
592
+ bboxCollide((d: SimulationNode) => [
593
+ [
594
+ -d.annotation.placedBounds.width / 2 - padding,
595
+ -d.annotation.placedBounds.height / 2 - padding,
596
+ ],
597
+ [
598
+ d.annotation.placedBounds.width / 2 + padding,
599
+ d.annotation.placedBounds.height / 2 + padding,
600
+ ],
601
+ ])
602
+ // weak force since keeping labels close to their anchor point
603
+ // is more important than minimising collisions
604
+ .strength(0.1)
605
+ )
606
+ // simulating very few ticks is good enough since dramatic position
607
+ // changes are not needed in a typical setup
608
+ .tick(5)
609
+
610
+ // update the bounds of the original annotations
611
+ const updatedAnnotations = annotations.map((annotation, index) => {
612
+ const node = simulationNodes[index]
613
+ const originalBounds = annotation.placedBounds
614
+
615
+ // update placed bounds with the simulated position
616
+ const { x: centerX = 0, y: centerY = 0 } = node
617
+ const placedBounds = originalBounds.set({
618
+ x: centerX - originalBounds.width / 2,
619
+ y: centerY - originalBounds.height / 2,
620
+ })
621
+
622
+ return { ...annotation, placedBounds }
623
+ })
624
+
625
+ return updatedAnnotations
626
+ }
627
+
628
+ function checkAnnotationCollidesWithLandmass({
629
+ annotation,
630
+ nearbyGeoFeatures,
631
+ projection,
632
+ }: {
633
+ annotation: ExternalAnnotation
634
+ nearbyGeoFeatures: GeoFeature[]
635
+ projection: any
636
+ }): boolean {
637
+ const annotationLabel = makePolygonFromBounds(
638
+ annotation.placedBounds,
639
+ (position) => projection.invert(position)
640
+ )
641
+ return nearbyGeoFeatures.some((feature) =>
642
+ booleanIntersects(annotationLabel, feature)
643
+ )
644
+ }
645
+
646
+ function checkAnnotationMarkerCollidesWithSomeLabel({
647
+ annotation,
648
+ nearbyAnnotations,
649
+ }: {
650
+ annotation: ExternalAnnotation
651
+ nearbyAnnotations: ExternalAnnotation[]
652
+ }): boolean {
653
+ // marker line from the anchor point to the label
654
+ const markerStart = annotation.anchor
655
+ const markerEnd = getExternalMarkerEndPosition({
656
+ textBounds: annotation.placedBounds,
657
+ direction: annotation.direction,
658
+ })
659
+ const markerLine = makePolygonForLine(markerStart, markerEnd)
660
+
661
+ // check if the marker line crosses through any of the labels
662
+ return nearbyAnnotations.some((nearbyAnnotation) => {
663
+ const nearbyLabel = makePolygonFromBounds(nearbyAnnotation.placedBounds)
664
+ return booleanIntersects(markerLine, nearbyLabel)
665
+ })
666
+ }
667
+
668
+ function makePolygonFromBounds(
669
+ bounds: Bounds,
670
+ transform?: (position: number[]) => number[]
671
+ ): Feature<Polygon, GeoJsonProperties> {
672
+ let corners = [
673
+ [bounds.topLeft.x, bounds.topLeft.y],
674
+ [bounds.topRight.x, bounds.topRight.y],
675
+ [bounds.bottomRight.x, bounds.bottomRight.y],
676
+ [bounds.bottomLeft.x, bounds.bottomLeft.y],
677
+ [bounds.topLeft.x, bounds.topLeft.y], // close the polygon
678
+ ]
679
+ if (transform) {
680
+ corners = corners.map((position) => transform(position))
681
+ }
682
+ return {
683
+ type: "Feature",
684
+ properties: {},
685
+ geometry: {
686
+ type: "Polygon",
687
+ coordinates: [corners],
688
+ },
689
+ }
690
+ }
691
+
692
+ function makePolygonForLine(
693
+ start: [number, number],
694
+ end: [number, number],
695
+ lineWidth = 4
696
+ ): Feature<Polygon, GeoJsonProperties> {
697
+ // Calculate the vector direction of the line
698
+ const dx = end[0] - start[0]
699
+ const dy = end[1] - start[1]
700
+ const lineLength = Math.sqrt(dx * dx + dy * dy)
701
+
702
+ // Calculate normalized perpendicular vector
703
+ const perpX = (-dy / lineLength) * (lineWidth / 2)
704
+ const perpY = (dx / lineLength) * (lineWidth / 2)
705
+
706
+ // Create four corners of the rectangle
707
+ const corners = [
708
+ [start[0] + perpX, start[1] + perpY],
709
+ [start[0] - perpX, start[1] - perpY],
710
+ [end[0] - perpX, end[1] - perpY],
711
+ [end[0] + perpX, end[1] + perpY],
712
+ [start[0] + perpX, start[1] + perpY], // close the polygon
713
+ ]
714
+
715
+ return {
716
+ type: "Feature",
717
+ properties: {},
718
+ geometry: {
719
+ type: "Polygon",
720
+ coordinates: [corners],
721
+ },
722
+ }
723
+ }