@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,463 @@
1
+ import * as _ from "lodash-es"
2
+ import entities from "./regions.json"
3
+ import { lazy } from "./Util.js"
4
+ import { EntityName } from "../types/index.js"
5
+
6
+ export enum RegionType {
7
+ Country = "country",
8
+ Other = "other",
9
+ Aggregate = "aggregate",
10
+ Continent = "continent",
11
+ IncomeGroup = "income_group",
12
+ Province = "province",
13
+ }
14
+
15
+ export interface BaseRegion {
16
+ regionType: RegionType
17
+ name: string
18
+ code: string
19
+ slug: string
20
+ shortName?: string
21
+ }
22
+
23
+ export interface Country extends BaseRegion {
24
+ regionType: RegionType.Country | RegionType.Other
25
+ shortCode?: string
26
+ isMappable?: boolean
27
+ isHistorical?: boolean
28
+ isUnlisted?: boolean
29
+ variantNames?: string[]
30
+ article?: string
31
+ }
32
+
33
+ export interface Aggregate extends BaseRegion {
34
+ regionType: RegionType.Aggregate
35
+ definedBy?: AggregateSource
36
+ translationCodes?: string[]
37
+ members: string[]
38
+ }
39
+
40
+ export interface Continent extends BaseRegion {
41
+ name: OwidContinentName
42
+ regionType: RegionType.Continent
43
+ translationCodes?: string[]
44
+ members: string[]
45
+ }
46
+
47
+ export interface IncomeGroup extends BaseRegion {
48
+ name: OwidIncomeGroupName
49
+ regionType: RegionType.IncomeGroup
50
+ members: string[]
51
+ }
52
+
53
+ export interface Province extends BaseRegion {
54
+ regionType: RegionType.Province
55
+ parentCountry: string
56
+ shortCode?: string
57
+ isMappable?: boolean
58
+ }
59
+
60
+ export type Region = Country | Aggregate | Continent | IncomeGroup | Province
61
+
62
+ export const regions: Region[] = entities as Region[]
63
+
64
+ type OwidContinentName =
65
+ | "Africa"
66
+ | "Asia"
67
+ | "Europe"
68
+ | "North America"
69
+ | "Oceania"
70
+ | "South America"
71
+
72
+ export type OwidIncomeGroupName =
73
+ | "OWID_LIC"
74
+ | "OWID_LMC"
75
+ | "OWID_UMC"
76
+ | "OWID_HIC"
77
+
78
+ export const aggregateSources = [
79
+ "pew", // Pew Research Center
80
+ "unm49", // UN M49 (UN country codes for statistical use)
81
+ "unsd", // UN Statistical Division
82
+ "unsdg", // UN SDG
83
+ "wb", // World Bank
84
+ "who", // World Health Organization
85
+ ] as const
86
+ export type AggregateSource = (typeof aggregateSources)[number]
87
+
88
+ export function checkIsOwidIncomeGroupName(
89
+ name: string
90
+ ): name is OwidIncomeGroupName {
91
+ return (
92
+ name === "OWID_LIC" ||
93
+ name === "OWID_LMC" ||
94
+ name === "OWID_UMC" ||
95
+ name === "OWID_HIC"
96
+ )
97
+ }
98
+
99
+ export function checkIsCountry(region: Region): region is Country {
100
+ return (
101
+ region.regionType === RegionType.Country ||
102
+ region.regionType === RegionType.Other
103
+ )
104
+ }
105
+
106
+ export function checkIsOwidContinent(region: Region): region is Continent {
107
+ return region.regionType === RegionType.Continent
108
+ }
109
+
110
+ export function checkIsIncomeGroup(region: Region): region is IncomeGroup {
111
+ return region.regionType === RegionType.IncomeGroup
112
+ }
113
+
114
+ export function checkIsProvince(region: Region): region is Province {
115
+ return region.regionType === RegionType.Province
116
+ }
117
+
118
+ export function checkHasMembers(
119
+ region?: Region
120
+ ): region is Aggregate | Continent | IncomeGroup {
121
+ return region !== undefined && "members" in region
122
+ }
123
+
124
+ export const countries: Country[] = regions.filter(
125
+ (entity) =>
126
+ entity.regionType === RegionType.Country &&
127
+ !entity.isUnlisted &&
128
+ !entity.isHistorical
129
+ ) as Country[]
130
+
131
+ export const listedRegionsNames = lazy(() =>
132
+ regions
133
+ .filter((entity) => checkIsCountry(entity) && !entity.isUnlisted)
134
+ .map((entity) => entity.name)
135
+ )
136
+
137
+ export const mappableCountries: Country[] = regions.filter(
138
+ (country): country is Country =>
139
+ checkIsCountry(country) && !!country.isMappable
140
+ )
141
+
142
+ export const getOthers = lazy(
143
+ () =>
144
+ entities.filter(
145
+ (entity) => entity.regionType === RegionType.Other
146
+ ) as Country[]
147
+ )
148
+
149
+ export const getAggregates = lazy(
150
+ () =>
151
+ entities.filter(
152
+ (entity) => entity.regionType === RegionType.Aggregate
153
+ ) as Aggregate[]
154
+ )
155
+
156
+ export const getContinents = lazy(
157
+ () =>
158
+ entities.filter(
159
+ (entity) => entity.regionType === RegionType.Continent
160
+ ) as Continent[]
161
+ )
162
+
163
+ export const getIncomeGroups = lazy(
164
+ () =>
165
+ entities.filter(
166
+ (entity) => entity.regionType === RegionType.IncomeGroup
167
+ ) as IncomeGroup[]
168
+ )
169
+
170
+ export const getProvinces = lazy(
171
+ () =>
172
+ entities.filter(
173
+ (entity) => entity.regionType === RegionType.Province
174
+ ) as Province[]
175
+ )
176
+
177
+ export const mappableProvinces: Province[] = regions.filter(
178
+ (region): region is Province =>
179
+ checkIsProvince(region) && !!region.isMappable
180
+ )
181
+
182
+ const regionsByName = lazy(() =>
183
+ Object.fromEntries(regions.map((region) => [region.name, region]))
184
+ )
185
+
186
+ const regionsBySlug = lazy(() =>
187
+ Object.fromEntries(regions.map((region) => [region.slug, region]))
188
+ )
189
+
190
+ const regionsByCode = lazy(() =>
191
+ Object.fromEntries(regions.map((region) => [region.code, region]))
192
+ )
193
+
194
+ export const countriesByName = lazy(() =>
195
+ Object.fromEntries(countries.map((country) => [country.name, country]))
196
+ )
197
+
198
+ export const incomeGroupsByName = lazy(
199
+ () =>
200
+ Object.fromEntries(
201
+ regions
202
+ .filter((region) => checkIsOwidIncomeGroupName(region.code))
203
+ .map((region) => [region.code, region])
204
+ ) as Record<OwidIncomeGroupName, IncomeGroup>
205
+ )
206
+
207
+ const countriesBySlug = lazy(() =>
208
+ Object.fromEntries(countries.map((country) => [country.slug, country]))
209
+ )
210
+
211
+ const provincesByName = lazy(() =>
212
+ Object.fromEntries(
213
+ mappableProvinces.map((province) => [province.name, province])
214
+ )
215
+ )
216
+
217
+ export const getProvinceByName = (name: string): Province | undefined =>
218
+ provincesByName()[name]
219
+
220
+ /**
221
+ * Lazy-loaded map of region names to their parent regions.
222
+ *
223
+ * This creates a reverse lookup from child regions to all the parent regions
224
+ * that contain them. For example, if "France" is a member of both "Europe"
225
+ * and "European Union", then parentRegions.get("France") would return
226
+ * both regions.
227
+ */
228
+ const parentRegions = lazy(() => {
229
+ const parentRegions = new Map<string, Region[]>()
230
+ for (const region of regions) {
231
+ // Only process regions that can contain other regions
232
+ if (!checkHasMembers(region)) continue
233
+
234
+ for (const memberCode of region.members) {
235
+ const subRegion = getRegionByCode(memberCode)
236
+ if (!subRegion) continue
237
+ if (!parentRegions.has(subRegion.name))
238
+ parentRegions.set(subRegion.name, [])
239
+ parentRegions.get(subRegion.name)!.push(region)
240
+ }
241
+ }
242
+ return parentRegions
243
+ })
244
+
245
+ /**
246
+ * Get all parent regions that contain the specified region as a member.
247
+ *
248
+ * For example, France's parent regions are 'Europe', 'European Union',
249
+ * 'High-income countries', etc.
250
+ */
251
+ export const getParentRegions = (regionName: string): Region[] => {
252
+ return parentRegions().get(regionName) ?? []
253
+ }
254
+
255
+ /**
256
+ * Get all sibling regions that share the same parent(s) as the specified region.
257
+ *
258
+ * For example, returns other European countries for 'Germany' or other
259
+ * continents for 'Europe'.
260
+ */
261
+ export const getSiblingRegions = (regionName: string): Region[] => {
262
+ const parentRegions = getParentRegions(regionName)
263
+ const siblingCodes = _.uniq(
264
+ parentRegions.flatMap((region) =>
265
+ checkHasMembers(region) ? region.members : []
266
+ )
267
+ )
268
+ return siblingCodes
269
+ .map(getRegionByCode)
270
+ .filter((region) => region && region.name !== regionName) as Region[]
271
+ }
272
+
273
+ const getCountryNamesForRegionRecursive = (region: Region): string[] => {
274
+ if (!checkHasMembers(region)) return [region.name]
275
+ return region.members.reduce<string[]>((countryNames, memberCode) => {
276
+ const subRegion = getRegionByCode(memberCode)
277
+ if (!subRegion) return countryNames
278
+ return [
279
+ ...countryNames,
280
+ ...getCountryNamesForRegionRecursive(subRegion),
281
+ ]
282
+ }, [])
283
+ }
284
+
285
+ export const getCountryNamesForRegion = (
286
+ region: Exclude<Region, Country>
287
+ ): string[] => {
288
+ return getCountryNamesForRegionRecursive(region)
289
+ }
290
+
291
+ const regionsByNameOrVariantNameLowercase = lazy(
292
+ () =>
293
+ new Map(
294
+ regions.flatMap((region) => {
295
+ const names = [region.name.toLowerCase()]
296
+ if ("variantNames" in region && region.variantNames) {
297
+ names.push(
298
+ ...region.variantNames.map((variant) =>
299
+ variant.toLowerCase()
300
+ )
301
+ )
302
+ }
303
+ return names.map((name) => [name, region])
304
+ })
305
+ )
306
+ )
307
+
308
+ const currentAndHistoricalCountryNames = lazy(() =>
309
+ regions
310
+ .filter(({ regionType }) => regionType === RegionType.Country)
311
+ .map(({ name }) => name.toLowerCase())
312
+ )
313
+
314
+ export const isCountryName = (name: string): boolean =>
315
+ currentAndHistoricalCountryNames().includes(name.toLowerCase())
316
+
317
+ export const getCountryByName = (name: string): Country | undefined =>
318
+ countriesByName()[name]
319
+
320
+ export const getCountryBySlug = (slug: string): Country | undefined =>
321
+ countriesBySlug()[slug]
322
+
323
+ export const getRegionByName = (name: string): Region | undefined =>
324
+ regionsByName()[name]
325
+
326
+ export const getRegionBySlug = (slug: string): Region | undefined =>
327
+ regionsBySlug()[slug]
328
+
329
+ const getRegionByCode = (code: string): Region | undefined =>
330
+ regionsByCode()[code]
331
+
332
+ export const getRegionByNameOrVariantName = (
333
+ nameOrVariantName: string
334
+ ): Region | undefined =>
335
+ regionsByNameOrVariantNameLowercase().get(nameOrVariantName.toLowerCase())
336
+
337
+ const _IntlDisplayNamesInstances = new Map<string, Intl.DisplayNames>()
338
+ const getRegionTranslation = (
339
+ regionCode: string,
340
+ languageCode: string
341
+ ): string | undefined => {
342
+ try {
343
+ if (!_IntlDisplayNamesInstances.has(languageCode)) {
344
+ _IntlDisplayNamesInstances.set(
345
+ languageCode,
346
+ new Intl.DisplayNames([languageCode], {
347
+ type: "region",
348
+ fallback: "none",
349
+ })
350
+ )
351
+ }
352
+ return _IntlDisplayNamesInstances.get(languageCode)!.of(regionCode)
353
+ } catch {
354
+ return undefined
355
+ }
356
+ }
357
+
358
+ const _regionAlternativeNames = new Map<string, string[] | undefined>()
359
+ export const getRegionAlternativeNames = (
360
+ regionName: string,
361
+ languages: readonly string[]
362
+ ): string[] | undefined => {
363
+ if (!_regionAlternativeNames.has(regionName)) {
364
+ const region = getRegionByNameOrVariantName(regionName)
365
+ if (region) {
366
+ const names = new Set<string>()
367
+ if ("variantNames" in region && region.variantNames) {
368
+ for (const variant of region.variantNames) {
369
+ names.add(variant)
370
+ }
371
+ }
372
+
373
+ const codesForTranslation =
374
+ ("translationCodes" in region && region.translationCodes) ||
375
+ ("shortCode" in region && region.shortCode)
376
+ if (codesForTranslation) {
377
+ const translations = languages
378
+ .flatMap((lang) => {
379
+ if (Array.isArray(codesForTranslation))
380
+ return codesForTranslation.map((code) =>
381
+ getRegionTranslation(code, lang)
382
+ )
383
+ else
384
+ return getRegionTranslation(
385
+ codesForTranslation,
386
+ lang
387
+ )
388
+ })
389
+ .filter((name) => name !== undefined)
390
+
391
+ translations.forEach((translation) => names.add(translation))
392
+ }
393
+ _regionAlternativeNames.set(regionName, Array.from(names))
394
+ } else {
395
+ _regionAlternativeNames.set(regionName, undefined)
396
+ }
397
+ }
398
+ return _regionAlternativeNames.get(regionName)!
399
+ }
400
+
401
+ // Regions that require the definite article "the" before their name
402
+ // Maintained here instead of in the ETL because it's such a specific piece of metadata.
403
+ const regionsWithArticles = new Set([
404
+ "Aland Islands",
405
+ "Netherlands Antilles",
406
+ "United Arab Emirates",
407
+ "French Southern Territories",
408
+ "Bahrain",
409
+ "Bahamas",
410
+ "Central African Republic",
411
+ "Cocos Islands",
412
+ "Democratic Republic of Congo",
413
+ "Congo",
414
+ "Cook Islands",
415
+ "Comoros",
416
+ "Cayman Islands",
417
+ "Dominican Republic",
418
+ "Western Sahara",
419
+ "Falkland Islands",
420
+ "Faroe Islands",
421
+ "United Kingdom",
422
+ "Gambia",
423
+ "Heard Island and McDonald Islands",
424
+ "Isle of Man",
425
+ "British Indian Ocean Territory",
426
+ "Maldives",
427
+ "Marshall Islands",
428
+ "Northern Mariana Islands",
429
+ "Netherlands",
430
+ "Grand Duchy of Baden",
431
+ "Kingdom of Bavaria",
432
+ "Democratic Republic of Vietnam",
433
+ "Kingdom of the Two Sicilies",
434
+ "Duchy of Modena and Reggio",
435
+ "Orange Free State",
436
+ "Duchy of Parma and Piacenza",
437
+ "Federal Republic of Central America",
438
+ "Republic of Vietnam",
439
+ "Kingdom of Sardinia",
440
+ "Kingdom of Saxony",
441
+ "Sudan (former)",
442
+ "Grand Duchy of Tuscany",
443
+ "USSR",
444
+ "Kingdom of Wurttemberg",
445
+ "Yemen Arab Republic",
446
+ "Yemen People's Republic",
447
+ "Philippines",
448
+ "Gaza Strip",
449
+ "South Georgia and the South Sandwich Islands",
450
+ "Solomon Islands",
451
+ "Seychelles",
452
+ "Turks and Caicos Islands",
453
+ "United States",
454
+ "Vatican",
455
+ "British Virgin Islands",
456
+ "United States Virgin Islands",
457
+ ])
458
+
459
+ export const articulateEntity = (entityName: EntityName): string => {
460
+ return regionsWithArticles.has(entityName)
461
+ ? `the ${entityName}`
462
+ : entityName
463
+ }
@@ -0,0 +1,16 @@
1
+ const jsonCommentDelimiter = "\n//EMBEDDED_JSON\n"
2
+ // Stringifies JSON for placing into an arbitrary doc, for later extraction without parsing the whole doc
3
+ export const serializeJSONForHTML = (
4
+ obj: unknown,
5
+ delimiter = jsonCommentDelimiter
6
+ ): string =>
7
+ `${delimiter}${
8
+ obj === undefined ? "" : JSON.stringify(obj, null, 2)
9
+ }${delimiter}`
10
+ export const deserializeJSONFromHTML = (
11
+ html: string,
12
+ delimiter = jsonCommentDelimiter
13
+ ): any => {
14
+ const json = html.split(delimiter)[1]
15
+ return json === undefined || json === "" ? undefined : JSON.parse(json)
16
+ }
@@ -0,0 +1,42 @@
1
+ const snakeToCamel = (str: string): string =>
2
+ str.replace(/(_\w)/g, (char) => char[1].toUpperCase())
3
+
4
+ export const camelCaseProperties = (
5
+ obj: Record<string, unknown>
6
+ ): Record<string, unknown> => {
7
+ const newObj: any = {}
8
+ for (const key in obj) {
9
+ newObj[snakeToCamel(key)] = obj[key]
10
+ }
11
+ return newObj
12
+ }
13
+
14
+ /**
15
+ * Converts a string to title case, with support for hyphenated words
16
+ * e.g. 'WELCOME to jean-édouard' -> 'Welcome To Jean-Édouard!'
17
+ */
18
+ export const titleCase = (str: string): string => {
19
+ return str
20
+ .split(" ")
21
+ .map(function (word) {
22
+ return word
23
+ .split("-")
24
+ .map(function (subWord) {
25
+ return (
26
+ subWord.charAt(0).toUpperCase() +
27
+ subWord.substring(1).toLowerCase()
28
+ )
29
+ })
30
+ .join("-")
31
+ })
32
+ .join(" ")
33
+ }
34
+
35
+ export function toAsciiQuotes(str: string): string {
36
+ return str.replace(/[“”]/g, '"').replace(/[‘’]/g, "'")
37
+ }
38
+
39
+ // https://stackoverflow.com/a/37511463/9846837
40
+ export function removeDiacritics(str: string): string {
41
+ return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
42
+ }
@@ -0,0 +1,195 @@
1
+ import urlParseLib from "url-parse"
2
+ import { gdocUrlRegex, QueryParams } from "../../types/index.js"
3
+
4
+ import {
5
+ detailOnDemandRegex,
6
+ guidedChartRegex,
7
+ excludeUndefined,
8
+ omitUndefinedValues,
9
+ } from "../Util.js"
10
+
11
+ import { queryParamsToStr, strToQueryParams } from "./UrlUtils.js"
12
+ import * as R from "remeda"
13
+
14
+ const parseUrl = (url: string): urlParseLib<string> => urlParseLib(url, {})
15
+
16
+ const ensureStartsWith = (str: string, start: string): string => {
17
+ if (str.startsWith(start)) return str
18
+ return `${start}${str}`
19
+ }
20
+
21
+ const ensureQueryStrFormat = (queryStr: string): string =>
22
+ ensureStartsWith(queryStr, "?")
23
+ const ensureHashFormat = (queryStr: string): string =>
24
+ ensureStartsWith(queryStr, "#")
25
+
26
+ interface UrlProps {
27
+ readonly origin?: string // https://ourworldindata.org
28
+ readonly pathname?: string // /grapher/abc
29
+ readonly queryStr?: string // ?stackMode=relative
30
+ readonly hash?: string // #articles
31
+ }
32
+
33
+ export class Url {
34
+ private props: UrlProps
35
+
36
+ /**
37
+ * @param url Absolute or relative URL
38
+ */
39
+ static fromURL(url: string): Url {
40
+ const { origin, pathname, query, hash } = parseUrl(url)
41
+ return new Url({
42
+ origin:
43
+ origin !== undefined && origin !== "null" ? origin : undefined,
44
+ pathname,
45
+ queryStr: query,
46
+ hash,
47
+ })
48
+ }
49
+
50
+ static fromQueryStr(queryStr: string): Url {
51
+ return new Url({
52
+ queryStr: ensureQueryStrFormat(queryStr),
53
+ })
54
+ }
55
+
56
+ static fromQueryParams(queryParams: QueryParams): Url {
57
+ return new Url({
58
+ queryStr: queryParamsToStr(queryParams),
59
+ })
60
+ }
61
+
62
+ private constructor(props: UrlProps = {}) {
63
+ this.props = {
64
+ ...props,
65
+ pathname:
66
+ props.pathname !== undefined
67
+ ? props.pathname
68
+ : props.origin
69
+ ? ""
70
+ : undefined,
71
+ }
72
+ }
73
+
74
+ get origin(): string | undefined {
75
+ return this.props.origin
76
+ }
77
+
78
+ get pathname(): string | undefined {
79
+ return this.props.pathname
80
+ }
81
+
82
+ get slug(): string | undefined {
83
+ return this.props.pathname?.split("/").pop()
84
+ }
85
+
86
+ get originAndPath(): string | undefined {
87
+ const strings = excludeUndefined([this.origin, this.pathname])
88
+ if (strings.length === 0) return undefined
89
+ return strings.join("")
90
+ }
91
+
92
+ get queryStr(): string {
93
+ const { queryStr } = this.props
94
+ // Drop a single trailing `?`, if there is one
95
+ return queryStr && queryStr !== "?"
96
+ ? ensureQueryStrFormat(queryStr)
97
+ : ""
98
+ }
99
+
100
+ get hash(): string {
101
+ const { hash } = this.props
102
+ return hash ? ensureHashFormat(hash) : ""
103
+ }
104
+
105
+ get fullUrl(): string {
106
+ return excludeUndefined([
107
+ this.origin,
108
+ this.pathname,
109
+ this.queryStr,
110
+ this.hash,
111
+ ]).join("")
112
+ }
113
+
114
+ get fullUrlNoTrailingSlash(): string {
115
+ return this.fullUrl.replace(/\/$/, "") || "/"
116
+ }
117
+
118
+ get queryParams(): QueryParams {
119
+ return strToQueryParams(this.queryStr)
120
+ }
121
+
122
+ get encodedQueryParams(): QueryParams {
123
+ return strToQueryParams(this.queryStr, true)
124
+ }
125
+
126
+ get isGoogleDoc(): boolean {
127
+ return !!(this.pathname && gdocUrlRegex.test(this.fullUrl))
128
+ }
129
+
130
+ get isGrapher(): boolean {
131
+ return !!(this.pathname && /^\/grapher\/[\w]+/.test(this.pathname))
132
+ }
133
+
134
+ get isDod(): boolean {
135
+ return !!(this.fullUrl && detailOnDemandRegex.test(this.fullUrl))
136
+ }
137
+ get isGuidedChart(): boolean {
138
+ return !!(this.fullUrl && guidedChartRegex.test(this.fullUrl))
139
+ }
140
+
141
+ get isUpload(): boolean {
142
+ return !!(this.pathname && /^\/uploads\/[\w]+/.test(this.pathname))
143
+ }
144
+
145
+ // todo(refactor): move outisde of generic Url class
146
+ // see EXPLORERS_ROUTE_FOLDER
147
+ get isExplorer(): boolean {
148
+ return !!(this.pathname && /^\/explorers\/[\w]+/.test(this.pathname))
149
+ }
150
+
151
+ update(props: UrlProps): Url {
152
+ return new Url({
153
+ ...this.props,
154
+ ...props,
155
+ })
156
+ }
157
+
158
+ setQueryParams(queryParams: QueryParams): Url {
159
+ return new Url({
160
+ ...this.props,
161
+ queryStr: queryParamsToStr(queryParams),
162
+ })
163
+ }
164
+
165
+ updateQueryParams(queryParams: QueryParams): Url {
166
+ return this.update({
167
+ queryStr: queryParamsToStr(
168
+ omitUndefinedValues({
169
+ ...this.queryParams,
170
+ ...queryParams,
171
+ })
172
+ ),
173
+ })
174
+ }
175
+
176
+ areQueryParamsEqual(otherUrl: Url): boolean {
177
+ const thisParams = this.queryParams
178
+ const otherParams = otherUrl.queryParams
179
+
180
+ return R.isShallowEqual(thisParams, otherParams)
181
+ }
182
+ }
183
+
184
+ export const setWindowUrl = (url: Url): void => {
185
+ const pathname = url.pathname ?? window.location.pathname
186
+ window.history.replaceState(
187
+ null,
188
+ document.title,
189
+ excludeUndefined([pathname, url.queryStr, url.hash]).join("")
190
+ )
191
+ }
192
+
193
+ export const getWindowUrl = (): Url => {
194
+ return Url.fromURL(window.location.href)
195
+ }