@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,841 @@
1
+ // todo: Remove this file when we've migrated OWID data and OWID charts to next version
2
+
3
+ import * as _ from "lodash-es"
4
+ import {
5
+ ColumnTypeNames,
6
+ CoreColumnDef,
7
+ StandardOwidColumnDefs,
8
+ OwidTableSlugs,
9
+ OwidColumnDef,
10
+ OwidVariableDimensions,
11
+ OwidVariableDataMetadataDimensions,
12
+ ErrorValue,
13
+ OwidChartDimensionInterfaceWithMandatorySlug,
14
+ OwidChartDimensionInterface,
15
+ EntityName,
16
+ } from "../../types/index.js"
17
+ import {
18
+ OwidTable,
19
+ ErrorValueTypes,
20
+ makeKeyFn,
21
+ } from "../../core-table/index.js"
22
+ import {
23
+ diffDateISOStringInDays,
24
+ getYearFromISOStringAndDayOffset,
25
+ intersection,
26
+ makeAnnotationsSlug,
27
+ trimObject,
28
+ OwidEntityKey,
29
+ MultipleOwidVariableDataDimensionsMap,
30
+ OwidVariableWithSource,
31
+ OwidVariableMixedData,
32
+ OwidVariableWithSourceAndDimension,
33
+ ColumnSlug,
34
+ EPOCH_DATE,
35
+ OwidVariableType,
36
+ } from "../../utils/index.js"
37
+ import { isContinentsVariableId } from "./GrapherConstants"
38
+ import * as R from "remeda"
39
+ import { getDimensionColumnSlug } from "../chart/ChartDimension.js"
40
+
41
+ export const legacyToOwidTableAndDimensionsWithMandatorySlug = (
42
+ json: MultipleOwidVariableDataDimensionsMap,
43
+ dimensions: OwidChartDimensionInterface[],
44
+ selectedEntityColors:
45
+ | { [entityName: string]: string | undefined }
46
+ | undefined
47
+ ): OwidTable => {
48
+ const dimensionsWithSlug = dimensions?.map((dimension) => ({
49
+ ...dimension,
50
+ slug:
51
+ dimension.slug ??
52
+ getDimensionColumnSlug(dimension.variableId, dimension.targetYear),
53
+ }))
54
+ return legacyToOwidTableAndDimensions(
55
+ json,
56
+ dimensionsWithSlug,
57
+ selectedEntityColors
58
+ )
59
+ }
60
+
61
+ export const legacyToOwidTableAndDimensions = (
62
+ json: MultipleOwidVariableDataDimensionsMap,
63
+ dimensions: OwidChartDimensionInterfaceWithMandatorySlug[],
64
+ selectedEntityColors:
65
+ | { [entityName: string]: string | undefined }
66
+ | undefined
67
+ ): OwidTable => {
68
+ // Entity meta map
69
+
70
+ const entityMeta = [...json.values()].flatMap(
71
+ (value) => value.metadata.dimensions.entities.values
72
+ )
73
+ const entityMetaById: OwidEntityKey = Object.fromEntries(
74
+ entityMeta.map((entity) => [entity.id.toString(), entity])
75
+ )
76
+
77
+ // Base column defs, shared by all variable tables
78
+
79
+ const baseColumnDefs: Map<ColumnSlug, CoreColumnDef> = new Map()
80
+ StandardOwidColumnDefs.forEach((def) => {
81
+ baseColumnDefs.set(def.slug, def)
82
+ })
83
+
84
+ // We need to create a column for each unique [variable, targetTime] pair. So there can be
85
+ // multiple columns for a single variable.
86
+ const dimensionColumns = _.uniqBy(dimensions, (dim) => dim.slug)
87
+
88
+ const variableTablesToJoinByYear: OwidTable[] = []
89
+ const variableTablesToJoinByDay: OwidTable[] = []
90
+ const variableTablesWithYearToJoinByEntityOnly: OwidTable[] = []
91
+ for (const dimension of dimensionColumns) {
92
+ const variable = json.get(dimension.variableId)
93
+
94
+ // TODO: this shouldn't happen but it does sometimes
95
+ // when adding dimensions in the chart editor
96
+ if (!variable) continue
97
+
98
+ // Copy the base columnDef
99
+ const columnDefs = new Map(baseColumnDefs)
100
+
101
+ // Time column
102
+ const timeColumnDef = timeColumnDefFromOwidVariable(variable.metadata)
103
+ columnDefs.set(timeColumnDef.slug, timeColumnDef)
104
+
105
+ // Value column
106
+ const valueColumnDef = columnDefFromOwidVariable(variable.metadata)
107
+ const valueColumnColor = dimension.display?.color
108
+ // Ensure the column slug is unique by copying it from the dimensions
109
+ // (there can be two columns of the same variable with different targetTimes)
110
+ if (dimension.slug) valueColumnDef.slug = dimension.slug
111
+ else throw new Error("Dimension slug was undefined")
112
+ // Because database columns can contain mixed types, we want to avoid
113
+ // parsing for Grapher data until we fix that.
114
+ valueColumnDef.skipParsing = true
115
+ if (valueColumnColor) {
116
+ valueColumnDef.color = valueColumnColor
117
+ }
118
+ if (dimension) {
119
+ valueColumnDef.display = {
120
+ ...trimObject(valueColumnDef.display),
121
+ ...trimObject(dimension.display),
122
+ }
123
+ }
124
+ if (dimension.targetYear !== undefined)
125
+ valueColumnDef.targetTime = dimension.targetYear
126
+ columnDefs.set(valueColumnDef.slug, valueColumnDef)
127
+
128
+ // Annotations column
129
+ const [annotationMap, annotationColumnDef] =
130
+ annotationMapAndDefFromOwidVariable(variable.metadata)
131
+
132
+ // Column values
133
+
134
+ const times = timeColumnValuesFromOwidVariable(
135
+ variable.metadata,
136
+ variable.data
137
+ )
138
+ const entityIds = variable.data.entities ?? []
139
+ const entityNames = entityIds.map(
140
+ // if entityMetaById[id] does not exist, then we don't have entity
141
+ // from variable metadata in MySQL. This can happen because we take
142
+ // data from S3 and metadata from MySQL. After we unify it, it should
143
+ // no longer be a problem
144
+ (id) => entityMetaById[id]?.name ?? id.toString()
145
+ )
146
+ // see comment above about entityMetaById[id]
147
+ const entityCodes = entityIds.map((id) => entityMetaById[id]?.code)
148
+
149
+ // If there is a conversionFactor, apply it.
150
+ let values = variable.data.values || []
151
+ const conversionFactor = valueColumnDef.display?.conversionFactor
152
+ if (conversionFactor !== undefined) {
153
+ values = values.map((value) =>
154
+ _.isNumber(value) ? value * conversionFactor : value
155
+ )
156
+
157
+ // If a non-int conversion factor is applied to an integer column,
158
+ // we end up with a numeric column.
159
+ if (
160
+ valueColumnDef.type === ColumnTypeNames.Integer &&
161
+ !_.isInteger(conversionFactor)
162
+ )
163
+ valueColumnDef.type = ColumnTypeNames.Numeric
164
+ }
165
+
166
+ const columnStore: { [key: string]: any[] } = {
167
+ [OwidTableSlugs.entityId]: entityIds,
168
+ [OwidTableSlugs.entityCode]: entityCodes,
169
+ [OwidTableSlugs.entityName]: entityNames,
170
+ [timeColumnDef.slug]: times,
171
+ [valueColumnDef.slug]: values,
172
+ }
173
+
174
+ if (annotationColumnDef) {
175
+ columnStore[annotationColumnDef.slug] = entityNames.map(
176
+ (entityName) => annotationMap!.get(entityName)
177
+ )
178
+ columnDefs.set(annotationColumnDef.slug, annotationColumnDef)
179
+ }
180
+ // Build the tables
181
+
182
+ let variableTable = new OwidTable(
183
+ columnStore,
184
+ Array.from(columnDefs.values())
185
+ )
186
+
187
+ // If there is a targetTime set on the dimension, we need to perform the join on the
188
+ // entities columns only, excluding any time columns.
189
+ // We do this by dropping the column. We interpolate before which adds an originalTime
190
+ // column which can be used to recover the time.
191
+ const targetTime = dimension?.targetYear
192
+ if (_.isNumber(targetTime)) {
193
+ variableTable = variableTable
194
+ // interpolateColumnWithTolerance() won't handle injecting times beyond the current
195
+ // allTimes. So if targetYear is 2018, and we have data up to 2017, the
196
+ // interpolation won't add the 2018 rows (unless we apply the interpolation after
197
+ // the big join).
198
+ // This is why we use filterByTargetTimes() which handles that case.
199
+ .filterByTargetTimes(
200
+ [targetTime],
201
+ valueColumnDef.display?.tolerance
202
+ )
203
+ // Interpolate with 0 to add originalTimes column
204
+ .interpolateColumnWithTolerance(valueColumnDef.slug, {
205
+ toleranceOverride: 0,
206
+ })
207
+ .dropColumns([timeColumnDef.slug])
208
+ // We keep variables that have a targetTime set in a special bucket and will join them
209
+ // on entity only (disregarding the year since we already filtered all other years out for
210
+ // those variables)
211
+ variableTablesWithYearToJoinByEntityOnly.push(variableTable)
212
+ } else if (variable.metadata.display?.yearIsDay)
213
+ variableTablesToJoinByDay.push(variableTable)
214
+ else variableTablesToJoinByYear.push(variableTable)
215
+ }
216
+
217
+ // If we only had years then all we would need to do is a single fullJoinTables call and
218
+ // we'd be done with it. But since we also have days this is a bit trickier. The
219
+ // basic approach is to say that if we have day variables then we should join those internally
220
+ // on day+entity first and day+entity becomes the primary index for our final table.
221
+ // We then join this merged days table with all the year based variables.
222
+ // The multi table join iterates over all the unique index values (day+entity).
223
+ // To join this with years we derive the year from the day. The fullJoinTables then uses a number
224
+ // of fallbacks when trying to find matching rows in the various tables: we first try to join by day+entity,
225
+ // then by year+entity and finally entity only.
226
+ // This last join by entity only is important so that variables that don't have values for years
227
+ // that come up in the day+entity index we still retain the values. E.g. our continents table
228
+ // has values for all countries but only for the year 2015. If we join that with covid era days
229
+ // we still want to retain the continents so we have the fallback to entity only (this was also
230
+ // the only behaviour prior to July 2022). Remember that tolerance will only be applied much later -
231
+ // here we are only concerned with merging multiple variables into an inputTable that retains information.
232
+ // Another approach would be to convert years into days when we have days - then we could simplify the fallback
233
+ // join key logic described above.
234
+ // Another caveat is that by switching to day+entity as the primary index that we use to join we can drop some entities.
235
+ // This happens e.g. with Antarctica if the continents table is used. Continents contains an entry for the entity Antarctica for 2015
236
+ // that maps it (and 3 other territories) to the Antarctica continent. If the days variables don't have values for any of the
237
+ // Antarctica entities then they will not be enumerated for the final join table and thus they will be dropped from the final table.
238
+ // This is maybe counter to what you would expect from a full join but is simply an artifact of making days+entities the primary
239
+ // index and not backporting years to days. We might want to revisit this in the future and/or also apply tolerance already
240
+ // at this level here.
241
+
242
+ // Merge all day based variables together (returns an empty table if there are none)
243
+ const variablesJoinedByDay = fullJoinTables(variableTablesToJoinByDay, [
244
+ OwidTableSlugs.day,
245
+ OwidTableSlugs.entityId,
246
+ ])
247
+
248
+ let joinedVariablesTable: OwidTable
249
+ // If we have both day and year based variables we need to do some special logic as described above
250
+ if (
251
+ variableTablesToJoinByYear.length > 0 &&
252
+ variableTablesToJoinByDay.length > 0
253
+ ) {
254
+ // Derive the year from the day column and add it to the joined days table
255
+ const daysColumn = variablesJoinedByDay.getColumns([
256
+ OwidTableSlugs.day,
257
+ ])[0]
258
+ const getYearFromISOStringMemoized = _.memoize((dayValue: number) =>
259
+ getYearFromISOStringAndDayOffset(EPOCH_DATE, dayValue)
260
+ )
261
+ const yearsForDaysValues = daysColumn.values.map((dayValue) =>
262
+ getYearFromISOStringMemoized(dayValue as number)
263
+ )
264
+
265
+ const newYearColumn = {
266
+ ...daysColumn,
267
+ slug: OwidTableSlugs.year,
268
+ name: OwidTableSlugs.year,
269
+ values: yearsForDaysValues,
270
+ } as OwidColumnDef
271
+ const variablesJoinedByDayWithYearFilled =
272
+ variablesJoinedByDay.appendColumns([newYearColumn])
273
+
274
+ // Now join the already merged days table with all the years. It is important
275
+ // to not join the years together into one table already before so that each
276
+ // table lookup for fallback values is looked at individually.
277
+ // See the longer comment above for the idea behind the fallback cascade here of
278
+ // trying to merge first by day+entity, then year+entity and finally entity only
279
+ joinedVariablesTable = fullJoinTables(
280
+ [variablesJoinedByDayWithYearFilled, ...variableTablesToJoinByYear],
281
+ [OwidTableSlugs.day, OwidTableSlugs.entityId],
282
+ [
283
+ [OwidTableSlugs.year, OwidTableSlugs.entityId],
284
+ [OwidTableSlugs.entityId],
285
+ ]
286
+ )
287
+ // If we have scatter/marimekko variables that had a targetTime set
288
+ // then these are now joined in by matching entity only
289
+ if (variableTablesWithYearToJoinByEntityOnly.length > 0)
290
+ joinedVariablesTable = fullJoinTables(
291
+ [
292
+ joinedVariablesTable,
293
+ ...variableTablesWithYearToJoinByEntityOnly,
294
+ ],
295
+ [OwidTableSlugs.day, OwidTableSlugs.entityId],
296
+ [[OwidTableSlugs.entityId]]
297
+ )
298
+ } else if (variableTablesToJoinByYear.length > 0) {
299
+ // If we only have year based variables then life is easy and we just join
300
+ // those together without any special cases
301
+ joinedVariablesTable = fullJoinTables(variableTablesToJoinByYear, [
302
+ OwidTableSlugs.year,
303
+ OwidTableSlugs.entityId,
304
+ ])
305
+
306
+ // If we have scatter/marimekko variables that had a targetTime set
307
+ // then these are now joined in by matching entity only
308
+ if (variableTablesWithYearToJoinByEntityOnly.length > 0)
309
+ joinedVariablesTable = fullJoinTables(
310
+ [
311
+ joinedVariablesTable,
312
+ ...variableTablesWithYearToJoinByEntityOnly,
313
+ ],
314
+ [OwidTableSlugs.year, OwidTableSlugs.entityId],
315
+ [[OwidTableSlugs.entityId]]
316
+ )
317
+ } else {
318
+ // If we only have day variables life is also easy but this case is rare
319
+ joinedVariablesTable = variablesJoinedByDay
320
+
321
+ // If we have scatter/marimekko variables that had a targetTime set
322
+ // then these are now joined in by matching entity only
323
+ if (variableTablesWithYearToJoinByEntityOnly.length > 0)
324
+ joinedVariablesTable = fullJoinTables(
325
+ [
326
+ joinedVariablesTable,
327
+ ...variableTablesWithYearToJoinByEntityOnly,
328
+ ],
329
+ [OwidTableSlugs.day, OwidTableSlugs.entityId],
330
+ [[OwidTableSlugs.entityId]]
331
+ )
332
+ }
333
+
334
+ // Inject a common "time" column that is used as the main time column for the table
335
+ // e.g. for the timeline.
336
+ for (const dayOrYearSlug of [OwidTableSlugs.day, OwidTableSlugs.year]) {
337
+ if (joinedVariablesTable.columnSlugs.includes(dayOrYearSlug)) {
338
+ joinedVariablesTable = joinedVariablesTable.duplicateColumn(
339
+ dayOrYearSlug,
340
+ {
341
+ slug: OwidTableSlugs.time,
342
+ name: OwidTableSlugs.time,
343
+ }
344
+ )
345
+ // Do not inject multiple columns, terminate after one is successful
346
+ break
347
+ }
348
+ }
349
+
350
+ // Append the entity color column if we have selected entity colors
351
+ if (!_.isEmpty(selectedEntityColors)) {
352
+ const entityColorColumnSlug = OwidTableSlugs.entityColor
353
+
354
+ const valueFn = (
355
+ entityName: EntityName | undefined
356
+ ): string | ErrorValue => {
357
+ if (!entityName) return ErrorValueTypes.UndefinedButShouldBeString
358
+ return entityName && selectedEntityColors
359
+ ? (selectedEntityColors[entityName] ??
360
+ ErrorValueTypes.UndefinedButShouldBeString)
361
+ : ErrorValueTypes.UndefinedButShouldBeString
362
+ }
363
+
364
+ const values =
365
+ joinedVariablesTable.entityNameColumn.valuesIncludingErrorValues.map(
366
+ (entityName) => valueFn(entityName as EntityName)
367
+ )
368
+
369
+ joinedVariablesTable = joinedVariablesTable.appendColumns([
370
+ {
371
+ slug: entityColorColumnSlug,
372
+ name: entityColorColumnSlug,
373
+ type: ColumnTypeNames.Color,
374
+ values: values,
375
+ },
376
+ ])
377
+ }
378
+ return joinedVariablesTable
379
+ }
380
+
381
+ const fullJoinTables = (
382
+ tables: OwidTable[],
383
+ indexColumnNames: OwidTableSlugs[],
384
+ mergeFallbackLookupColumns?: OwidTableSlugs[][]
385
+ ): OwidTable => {
386
+ // This function merges a number of OwidTables together using a given list of columns
387
+ // to be used as the merge key. The merge key columns are used to construct a full set
388
+ // of index values from the various tables - all tables are enumerated, we create
389
+ // a merged string value from the index column values for each row and then we create
390
+ // a set from all these string values.
391
+ // Note that not every table has to have values for all columns - not even all the index
392
+ // columns have to exist on all tables (the index columns have to exist on the first table though)!
393
+ // The reason for this and how this can possibly work
394
+ // is that we also have a list of fallback merge columns. This is required so we can handle
395
+ // not just the easy case where we have year+entity for every table to be merged (which in our
396
+ // data model is by far the most common default), but also handle cases where we merge year and
397
+ // day based variables together. For this latter case we need to still construct the set of index
398
+ // values for the final table, but then when we try to look up values in the various tables to
399
+ // merge together we will not find values by day+entity for the year based tables. So for this
400
+ // case we get a series of fallback column tuples that we try in turn if the main index lookup
401
+ // fails. These fallback tuples are year+entity first and then entity only. The reasoning here is
402
+ // that e.g. when merging population to a day variable we want to merge the values from the year
403
+ // matching the day from the population variable (year+entity lookup) but for variables that don't
404
+ // have overlapping years (e.g. continents that only has 2015 as the single year) we want to fall back
405
+ // to merging by entity alone as a last resort
406
+ if (tables.length === 0) return new OwidTable()
407
+ else if (tables.length === 1) return tables[0]
408
+
409
+ // Get all the index values per table and then figure out the full set of all stringified index values
410
+ const indexValuesPerTable: Map<string, number[]>[] = tables.map((table) =>
411
+ // When we get a mergeFallbackLookupColumn then it can happen that a table does not have all the
412
+ // columns of the main index. In this case, just return an empty map because that will lead all
413
+ // lookups by main index to fail and we'll try the fallback index
414
+ mergeFallbackLookupColumns &&
415
+ _.difference(indexColumnNames, table.columnSlugs).length > 0
416
+ ? new Map()
417
+ : table.rowIndex(indexColumnNames)
418
+ )
419
+
420
+ // Construct all the fallback index lookup values for all tables. mergeFallbackLookupColumns is an
421
+ // array of array that is supposed to be treated as a sequence of tuples of column names
422
+ const mergeFallbackLookupValuesPerTable = mergeFallbackLookupColumns
423
+ ? mergeFallbackLookupColumns.map((fallbackColumns) =>
424
+ tables.map((table) => table.rowIndex(fallbackColumns))
425
+ )
426
+ : undefined
427
+
428
+ // Figure out which column names are shared. Shared columns (the index columns but also in our
429
+ // data model stuff like entityCode and entityName that we usually have in addition to entityId)
430
+ // will end up only once in the final table. This is a bit of a footgun for arbitrary data models
431
+ // as we don't make sure that the values are equal for the same index values (we only assume that
432
+ // this is true) - but this is how we have handled it in the past and it works with the setup we
433
+ // have.
434
+ const sharedColumnNames = intersection(
435
+ ...tables.map((table) => table.columnSlugs)
436
+ )
437
+ const allIndexValuesArray = indexValuesPerTable.flatMap((index) => [
438
+ ...index.keys(),
439
+ ])
440
+ const allIndexValues = new Set(allIndexValuesArray)
441
+
442
+ // Now identify for each table which columns should be copied (i.e. all non-index columns).
443
+ const columnsToAddPerTable = tables.map((table) =>
444
+ _.difference(table.columnSlugs, sharedColumnNames)
445
+ )
446
+ // Prepare a special entry for the Table + column names tuple that we will zip and
447
+ // map in the next step. This special entry is the first table and contains only the
448
+ // unique columns (index + other tables that are shared among all tables).
449
+ // We will preferentially get the unique values from the first table but because
450
+ // the first table is not guaranteed to contain all index values we'll later on
451
+ // try other tables for these shared columns if the given row index does
452
+ // not exist in the first table
453
+ const firstTableDuplicateForIndices: [
454
+ OwidTable | undefined,
455
+ string[] | undefined,
456
+ ] = [tables[0], sharedColumnNames]
457
+ const defsToAddPerTable = [firstTableDuplicateForIndices]
458
+ .concat(R.zip(tables, columnsToAddPerTable))
459
+ .map(
460
+ (tableAndColumns) =>
461
+ tableAndColumns[0]!
462
+ .getColumns(tableAndColumns[1]!)
463
+ .map((col) => {
464
+ const def = { ...col.def }
465
+ def.values = []
466
+ return def
467
+ }) as OwidColumnDef[]
468
+ )
469
+ // Now loop over all unique index values and for each assemble as full a row as we can manage by looking
470
+ // up the values in the different source tables
471
+ for (const index of allIndexValues.values()) {
472
+ // First we handle the unique/index columns (defsToAddPerTable[0])
473
+ for (const def of defsToAddPerTable[0]) {
474
+ // The index columns are special - the first table might not have a value for each of the index columns
475
+ // at the current index (e.g. a year that does not exist in the first table). We therefore have to keep
476
+ // looking in the other tables until we find a table that has the values. Because the index values were
477
+ // generated from the combined set of all index values we are guaranteed to find a value eventually before
478
+ // we exceed the length of the tables array
479
+ let tableIndex = 0
480
+ let indexHits = indexValuesPerTable[tableIndex].get(index)
481
+ while (indexHits === undefined) {
482
+ tableIndex++
483
+ indexHits = indexValuesPerTable[tableIndex].get(index)
484
+ }
485
+ def.values?.push(
486
+ tables[tableIndex].columnStore[def.slug][indexHits[0]]
487
+ )
488
+ }
489
+ // Now figure out the fallback merge lookup index value from the first table.
490
+ // This is the fallback index we use when looking up values and we don't find a value in the normal index.
491
+ // This is the case when we join year and day variables and then use day+entityid as the key but the year
492
+ // variables don't have those - so for the year variables we then check if there is a match using year+entityId
493
+ // where the year to use comes from the first table that by convention HAS TO contain a year column with the
494
+ // value to merge years on.
495
+ const indexHits = indexValuesPerTable[0].get(index)
496
+ const fallbackMergeIndices =
497
+ mergeFallbackLookupColumns && indexHits
498
+ ? mergeFallbackLookupColumns.map((columnSet) =>
499
+ makeKeyFn(tables[0].columnStore, columnSet)(indexHits![0])
500
+ )
501
+ : undefined
502
+ // now add all the nonindex value columns. We now loop over all tables and for each non-shared column
503
+ // we look up the value for the current row. We find the value for the current row by first trying to
504
+ // look up a match based on the shared index values, indexValuesPerTable[i]. If we don't have a hit
505
+ // for this row in this table then we try the fallbackMergeIndices in turn to see if we can merge
506
+ // based on these fallback indexes. If no option leads to a match we store ErrorValueTypes.NoMatchingValueAfterJoin
507
+ // in the cell.
508
+
509
+ // note that defsToAddPerTable has one more element than tables (the one duplicate of the
510
+ // first table that we added in the beginning that we use as the primary source for the shared columns)
511
+ for (let i = 0; i < tables.length; i++) {
512
+ let indexHits = indexValuesPerTable[i].get(index)
513
+
514
+ if (indexHits !== undefined && indexHits.length > 1)
515
+ // This case should be rare but it can come up. The old algorithm ran into this often because it
516
+ // joined a lot more stuff on entity only and then when you look up by entity into a table that has
517
+ // several years you end up with multiple matches. The old algorithm used to just pick one value.
518
+ // We still do this ultimately but because we try to match even day and year variables by year+entity
519
+ // first we should usually be able to find a unique match. The error output is here so that when
520
+ // something is weird in an edge case then this shows up as a debugging hint in the console.
521
+ console.error(
522
+ `Found more than one matching row in table ${tables[i].tableSlug}`
523
+ )
524
+ for (const def of defsToAddPerTable[i + 1]) {
525
+ // If the main index led to a hit then we just copy the value into the new row from the source table
526
+ if (indexHits !== undefined)
527
+ def.values?.push(
528
+ tables[i].columnStore[def.slug][indexHits[0]]
529
+ )
530
+ else {
531
+ // If the main index did not lead to a hit then we try the fallback indices in turn
532
+ indexHits = undefined
533
+ for (
534
+ let fallbackIndex = 0;
535
+ fallbackMergeIndices &&
536
+ mergeFallbackLookupValuesPerTable &&
537
+ indexHits === undefined &&
538
+ fallbackIndex < fallbackMergeIndices!.length;
539
+ fallbackIndex++
540
+ ) {
541
+ indexHits = mergeFallbackLookupValuesPerTable[
542
+ fallbackIndex
543
+ ][i].get(fallbackMergeIndices[fallbackIndex])
544
+ }
545
+ if (indexHits !== undefined)
546
+ // If any of the fallbacks led to a hit then we use this hit
547
+ // Note that we choose the last of the indexHits entries to look up the row in the columnstore here.
548
+ // In the usual case the fallback index should still have enough information to match just one row (e.g.
549
+ // year+entity). But for cases when we join day and year variables we have as the ultimate fallback a match
550
+ // by entity only and this can then of course result in many indexHits when looking up an entity like Germany
551
+ // in e.g. the population variable. If this join here were properly time and tolerance aware then we could
552
+ // avoid matching on entity alone as the ultimate fallback but for now this function is not that clever.
553
+ // For the cases when we do get multiple hits for an index we could thus choose any data point. Pre July 2022
554
+ // the old code always chose the first datapoint, which is often the first year that the variable has data for
555
+ // for the given entity. This is not great, e.g. when using a covid date based variable and joining it to
556
+ // population or similar. What we do here now is to use indexHits.length - 1 as the index, i.e. the last
557
+ // row that this variable has data for for this entity. This could in theory be pretty wrong as well but in
558
+ // practice it often means a very close match.
559
+ // The proper solution as mentioned above would be to never fall back to entity matches only and move
560
+ // the tolerance matching into this function as well instead.
561
+ def.values?.push(
562
+ tables[i].columnStore[def.slug][
563
+ indexHits[indexHits.length - 1]
564
+ ]
565
+ )
566
+ // If none of the fallback values worked either we write ErrorValueTypes.NoMatchingValueAfterJoin into the cell
567
+ else
568
+ def.values?.push(
569
+ ErrorValueTypes.NoMatchingValueAfterJoin as any
570
+ )
571
+ }
572
+ }
573
+ }
574
+ }
575
+ return new OwidTable(
576
+ [],
577
+ defsToAddPerTable.flatMap((defs) => defs)
578
+ )
579
+ }
580
+
581
+ const variableTypeToColumnType = (type: OwidVariableType): ColumnTypeNames => {
582
+ switch (type) {
583
+ case "ordinal":
584
+ return ColumnTypeNames.Ordinal
585
+ case "string":
586
+ return ColumnTypeNames.String
587
+ case "int":
588
+ return ColumnTypeNames.Integer
589
+ case "float":
590
+ return ColumnTypeNames.Numeric
591
+ case "mixed":
592
+ default:
593
+ return ColumnTypeNames.NumberOrString
594
+ }
595
+ }
596
+
597
+ const getSortFromDimensions = (
598
+ dimensions: OwidVariableDimensions
599
+ ): string[] | undefined => {
600
+ const values = dimensions.values?.values
601
+ if (!values) return
602
+
603
+ const sort = values
604
+ .map((value) => value.name)
605
+ .filter((name) => name !== undefined)
606
+
607
+ if (sort.length === 0) return
608
+
609
+ return sort
610
+ }
611
+
612
+ const columnDefFromOwidVariable = (
613
+ variable: OwidVariableWithSourceAndDimension
614
+ ): OwidColumnDef => {
615
+ const slug = variable.id.toString() // For now, the variableId will be the column slug
616
+ const {
617
+ unit,
618
+ shortUnit,
619
+ description,
620
+ coverage,
621
+ datasetId,
622
+ datasetName,
623
+ descriptionShort,
624
+ descriptionProcessing,
625
+ descriptionKey,
626
+ descriptionFromProducer,
627
+ source,
628
+ origins,
629
+ display,
630
+ timespan,
631
+ nonRedistributable,
632
+ presentation,
633
+ catalogPath,
634
+ updatePeriodDays,
635
+ shortName,
636
+ } = variable
637
+
638
+ const isContinent = isContinentsVariableId(variable.id)
639
+ const name = variable.name
640
+
641
+ // The column's type
642
+ const parsedType = variable.type
643
+ ? variableTypeToColumnType(variable.type)
644
+ : ColumnTypeNames.NumberOrString
645
+
646
+ // Override the column type for the special Continents variable
647
+ const type = isContinent ? ColumnTypeNames.Continent : parsedType
648
+
649
+ // Extract the sort order for ordinal variables from their dimension metadata.
650
+ // This preserves the author-specified ordering of categorical values
651
+ // (e.g., "Low", "Medium", "High").
652
+ const sort =
653
+ parsedType === ColumnTypeNames.Ordinal
654
+ ? getSortFromDimensions(variable.dimensions)
655
+ : undefined
656
+
657
+ return {
658
+ name,
659
+ slug,
660
+ isDailyMeasurement: display?.yearIsDay,
661
+ unit,
662
+ shortUnit,
663
+ description,
664
+ descriptionShort,
665
+ descriptionProcessing,
666
+ descriptionKey,
667
+ descriptionFromProducer,
668
+ coverage,
669
+ datasetId,
670
+ datasetName,
671
+ display,
672
+ color: display?.color,
673
+ nonRedistributable,
674
+ sourceLink: source?.link,
675
+ sourceName: source?.name,
676
+ dataPublishedBy: source?.dataPublishedBy,
677
+ dataPublisherSource: source?.dataPublisherSource,
678
+ retrievedDate: source?.retrievedDate,
679
+ additionalInfo: source?.additionalInfo,
680
+ timespan,
681
+ origins,
682
+ presentation,
683
+ catalogPath,
684
+ updatePeriodDays,
685
+ owidVariableId: variable.id,
686
+ owidProcessingLevel: variable.processingLevel,
687
+ owidSchemaVersion: variable.schemaVersion,
688
+ type,
689
+ sort,
690
+ shortName,
691
+ }
692
+ }
693
+
694
+ const timeColumnDefFromOwidVariable = (
695
+ variableMetadata: OwidVariableWithSource
696
+ ): OwidColumnDef => {
697
+ return variableMetadata.display?.yearIsDay
698
+ ? {
699
+ slug: OwidTableSlugs.day,
700
+ type: ColumnTypeNames.Day,
701
+ name: "Day",
702
+ }
703
+ : {
704
+ slug: OwidTableSlugs.year,
705
+ type: ColumnTypeNames.Year,
706
+ name: "Year",
707
+ }
708
+ }
709
+
710
+ const timeColumnValuesFromOwidVariable = (
711
+ variableMetadata: OwidVariableWithSource,
712
+ variableData: OwidVariableMixedData
713
+ ): number[] => {
714
+ const { display } = variableMetadata
715
+ const { years } = variableData
716
+ const yearsNeedTransform =
717
+ display &&
718
+ display.yearIsDay &&
719
+ display.zeroDay !== undefined &&
720
+ display.zeroDay !== EPOCH_DATE
721
+ const yearsRaw = years || []
722
+ return yearsNeedTransform
723
+ ? convertLegacyYears(yearsRaw, display!.zeroDay!)
724
+ : yearsRaw
725
+ }
726
+
727
+ const convertLegacyYears = (years: number[], zeroDay: string): number[] => {
728
+ // Only shift years if the variable zeroDay is different from EPOCH_DATE
729
+ // When the dataset uses days (`yearIsDay == true`), the days are expressed as integer
730
+ // days since the specified `zeroDay`, which can be different for different variables.
731
+ // In order to correctly join variables with different `zeroDay`s in a single chart, we
732
+ // normalize all days to be in reference to a single epoch date.
733
+ const diff = diffDateISOStringInDays(zeroDay, EPOCH_DATE)
734
+ return years.map((y) => y + diff)
735
+ }
736
+
737
+ const annotationMapAndDefFromOwidVariable = (
738
+ variable: OwidVariableWithSourceAndDimension
739
+ ): [Map<string, string>, OwidColumnDef] | [] => {
740
+ if (variable.display?.entityAnnotationsMap) {
741
+ const slug = makeAnnotationsSlug(variable.id.toString())
742
+ const annotationMap = annotationsToMap(
743
+ variable.display.entityAnnotationsMap
744
+ )
745
+ const columnDef = {
746
+ slug,
747
+ type: ColumnTypeNames.SeriesAnnotation,
748
+ name: slug,
749
+ display: {
750
+ includeInTable: false,
751
+ },
752
+ }
753
+ return [annotationMap, columnDef]
754
+ }
755
+ return []
756
+ }
757
+
758
+ const annotationsToMap = (annotations: string): Map<string, string> => {
759
+ // Todo: let's delete this and switch to traditional columns
760
+ const entityAnnotationsMap = new Map<string, string>()
761
+ const delimiter = ":"
762
+ annotations.split("\n").forEach((line) => {
763
+ const [key, ...words] = line.split(delimiter)
764
+ entityAnnotationsMap.set(key.trim(), words.join(delimiter).trim())
765
+ })
766
+ return entityAnnotationsMap
767
+ }
768
+
769
+ /**
770
+ * Loads a single variable into an OwidTable.
771
+ */
772
+ export function buildVariableTable(
773
+ variable: OwidVariableDataMetadataDimensions
774
+ ): OwidTable {
775
+ const entityMeta = variable.metadata.dimensions.entities.values
776
+ const entityMetaById: OwidEntityKey = Object.fromEntries(
777
+ entityMeta.map((entity) => [entity.id.toString(), entity])
778
+ )
779
+
780
+ // Base column defs, present in all OwidTables
781
+ const baseColumnDefs: Map<ColumnSlug, CoreColumnDef> = new Map(
782
+ StandardOwidColumnDefs.map((def) => [def.slug, def])
783
+ )
784
+
785
+ const columnDefs = new Map(baseColumnDefs)
786
+
787
+ // Time column
788
+ const timeColumnDef = timeColumnDefFromOwidVariable(variable.metadata)
789
+ columnDefs.set(timeColumnDef.slug, timeColumnDef)
790
+
791
+ // Value column
792
+ const valueColumnDef = columnDefFromOwidVariable(variable.metadata)
793
+ // Because database columns can contain mixed types, we want to avoid
794
+ // parsing for Grapher data until we fix that.
795
+ valueColumnDef.skipParsing = true
796
+ columnDefs.set(valueColumnDef.slug, valueColumnDef)
797
+
798
+ // Column values
799
+
800
+ const times = timeColumnValuesFromOwidVariable(
801
+ variable.metadata,
802
+ variable.data
803
+ )
804
+ const entityIds = variable.data.entities ?? []
805
+ const entityNames = entityIds.map(
806
+ // if entityMetaById[id] does not exist, then we don't have entity
807
+ // from variable metadata in MySQL. This can happen because we take
808
+ // data from S3 and metadata from MySQL. After we unify it, it should
809
+ // no longer be a problem
810
+ (id) => entityMetaById[id]?.name ?? id.toString()
811
+ )
812
+ // see comment above about entityMetaById[id]
813
+ const entityCodes = entityIds.map((id) => entityMetaById[id]?.code)
814
+
815
+ // If there is a conversionFactor, apply it.
816
+ let values = variable.data.values || []
817
+ const conversionFactor = valueColumnDef.display?.conversionFactor
818
+ if (conversionFactor !== undefined) {
819
+ values = values.map((value) =>
820
+ _.isNumber(value) ? value * conversionFactor : value
821
+ )
822
+
823
+ // If a non-int conversion factor is applied to an integer column,
824
+ // we end up with a numeric column.
825
+ if (
826
+ valueColumnDef.type === ColumnTypeNames.Integer &&
827
+ !_.isInteger(conversionFactor)
828
+ )
829
+ valueColumnDef.type = ColumnTypeNames.Numeric
830
+ }
831
+
832
+ const columnStore: { [key: string]: any[] } = {
833
+ [OwidTableSlugs.entityId]: entityIds,
834
+ [OwidTableSlugs.entityCode]: entityCodes,
835
+ [OwidTableSlugs.entityName]: entityNames,
836
+ [timeColumnDef.slug]: times,
837
+ [valueColumnDef.slug]: values,
838
+ }
839
+
840
+ return new OwidTable(columnStore, Array.from(columnDefs.values()))
841
+ }