@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,48 @@
1
+ import {
2
+ ARCHIVE_DATE_TIME_FORMAT,
3
+ ArchivalDateString,
4
+ } from "../../types/index.js"
5
+ import dayjs from "../dayjs.js"
6
+
7
+ export interface ArchivalTimestamp {
8
+ date: Date
9
+ formattedDate: ArchivalDateString
10
+ }
11
+
12
+ export type DateInput = Date | ArchivalDateString | string | dayjs.Dayjs
13
+
14
+ export const parseArchivalDate = (dateInput: DateInput): dayjs.Dayjs => {
15
+ if (typeof dateInput === "string") {
16
+ if (dateInput.length === ARCHIVE_DATE_TIME_FORMAT.length)
17
+ return dayjs.utc(dateInput, ARCHIVE_DATE_TIME_FORMAT)
18
+ else return dayjs.utc(dateInput)
19
+ }
20
+ return dayjs.utc(dateInput)
21
+ }
22
+
23
+ export const formatAsArchivalDate = (date: dayjs.Dayjs): ArchivalDateString =>
24
+ date.format(ARCHIVE_DATE_TIME_FORMAT) as ArchivalDateString
25
+
26
+ export const convertToArchivalDateStringIfNecessary = (
27
+ dateInput: DateInput
28
+ ): ArchivalDateString => {
29
+ if (
30
+ typeof dateInput === "string" &&
31
+ dateInput.length === ARCHIVE_DATE_TIME_FORMAT.length
32
+ )
33
+ return dateInput as ArchivalDateString
34
+
35
+ return formatAsArchivalDate(parseArchivalDate(dateInput))
36
+ }
37
+
38
+ export const getDateForArchival = (): ArchivalTimestamp => {
39
+ const date = dayjs
40
+ .utc()
41
+ // it's important here that we explicitly set the milliseconds to 0 -
42
+ // otherwise we run the risk of MySQL rounding up to the next second,
43
+ // which then breaks the archival URL
44
+ .millisecond(0)
45
+ const formattedDate = formatAsArchivalDate(date)
46
+
47
+ return { date: date.toDate(), formattedDate }
48
+ }
@@ -0,0 +1,32 @@
1
+ // Imports dayjs and loads the plugins we need. Then exports it with the correct types.
2
+
3
+ import dayjs, { Dayjs } from "dayjs"
4
+ import customParseFormat from "dayjs/plugin/customParseFormat.js"
5
+ import isToday from "dayjs/plugin/isToday.js"
6
+ import isYesterday from "dayjs/plugin/isYesterday.js"
7
+ import relativeTime from "dayjs/plugin/relativeTime.js"
8
+ import utc from "dayjs/plugin/utc.js"
9
+
10
+ dayjs.extend(customParseFormat)
11
+ dayjs.extend(isToday)
12
+ dayjs.extend(isYesterday)
13
+ dayjs.extend(relativeTime)
14
+ dayjs.extend(utc)
15
+
16
+ export default dayjs
17
+
18
+ // We need these explicit plugin type imports _and exports_ to get the right Dayjs type down the line
19
+ import type customParseFormatType from "dayjs/plugin/customParseFormat.js"
20
+ import type isTodayType from "dayjs/plugin/isToday.js"
21
+ import type isYesterdayType from "dayjs/plugin/isYesterday.js"
22
+ import type relativeTimeType from "dayjs/plugin/relativeTime.js"
23
+ import type utcType from "dayjs/plugin/utc.js"
24
+
25
+ export type {
26
+ Dayjs,
27
+ customParseFormatType,
28
+ isTodayType,
29
+ isYesterdayType,
30
+ relativeTimeType,
31
+ utcType,
32
+ }
@@ -0,0 +1,242 @@
1
+ import { FormatSpecifier } from "d3-format"
2
+ import { createFormatter } from "./Util.js"
3
+ import {
4
+ OwidVariableRoundingMode,
5
+ TickFormattingOptions,
6
+ } from "../types/index.js"
7
+
8
+ // Used outside this module to figure out if the unit will be joined with the number.
9
+ export function checkIsVeryShortUnit(unit: string): unit is "$" | "£" | "%" {
10
+ return ["%", "$", "£"].includes(unit)
11
+ }
12
+
13
+ function checkIsUnitCurrency(unit: string): unit is "$" | "£" {
14
+ return ["$", "£"].includes(unit)
15
+ }
16
+
17
+ function checkIsUnitPercent(unit: string): unit is "%" {
18
+ return unit[0] === "%"
19
+ }
20
+
21
+ function getTrim({
22
+ roundingMode,
23
+ trailingZeroes,
24
+ }: {
25
+ roundingMode: OwidVariableRoundingMode
26
+ trailingZeroes: boolean
27
+ }): "~" | "" {
28
+ // always show trailing zeroes when rounding to significant figures
29
+ return roundingMode === OwidVariableRoundingMode.significantFigures
30
+ ? ""
31
+ : trailingZeroes
32
+ ? ""
33
+ : "~"
34
+ }
35
+
36
+ function getSign({ showPlus }: { showPlus: boolean }): "+" | "" {
37
+ return showPlus ? "+" : ""
38
+ }
39
+
40
+ function getSymbol({ unit }: { unit: string }): "$" | "" {
41
+ return checkIsUnitCurrency(unit) ? "$" : ""
42
+ }
43
+
44
+ function getType({
45
+ roundingMode,
46
+ numberAbbreviation,
47
+ value,
48
+ unit,
49
+ }: {
50
+ roundingMode: OwidVariableRoundingMode
51
+ numberAbbreviation: "long" | "short" | false
52
+ value: number
53
+ unit: string
54
+ }): "f" | "s" | "r" {
55
+ // f: fixed-point notation (i.e. fixed number of decimal points)
56
+ // r: decimal notation, rounded to significant digits
57
+ // s: decimal notation with an SI prefix, rounded to significant digits
58
+
59
+ const typeMap: Record<OwidVariableRoundingMode, "f" | "r"> = {
60
+ [OwidVariableRoundingMode.decimalPlaces]: "f",
61
+ [OwidVariableRoundingMode.significantFigures]: "r",
62
+ }
63
+ const type = typeMap[roundingMode]
64
+
65
+ if (checkIsUnitPercent(unit)) {
66
+ return type
67
+ }
68
+ if (numberAbbreviation === "long") {
69
+ // do not abbreviate until 1 million
70
+ return Math.abs(value) < 1e6 ? type : "s"
71
+ }
72
+ if (numberAbbreviation === "short") {
73
+ // do not abbreviate until 1 thousand
74
+ return Math.abs(value) < 1e3 ? type : "s"
75
+ }
76
+
77
+ return type
78
+ }
79
+
80
+ function getPrecision({
81
+ value,
82
+ roundingMode,
83
+ numDecimalPlaces,
84
+ numSignificantFigures,
85
+ type,
86
+ }: {
87
+ value: number
88
+ roundingMode: OwidVariableRoundingMode
89
+ numDecimalPlaces: number
90
+ numSignificantFigures: number
91
+ type: "f" | "s" | "r"
92
+ }): string {
93
+ if (roundingMode === OwidVariableRoundingMode.significantFigures) {
94
+ return `${numSignificantFigures}`
95
+ }
96
+
97
+ if (type === "f") {
98
+ return `${numDecimalPlaces}`
99
+ }
100
+
101
+ // when dealing with abbreviated numbers, adjust precision so we get 12.84 million instead of 13 million
102
+ // the modulo one-liner counts the "place columns" of the number, resetting every 3
103
+ // 1 -> 1, 48 -> 2, 981 -> 3, 7222 -> 1
104
+ const numberOfDigits = String(Math.floor(Math.abs(value))).length
105
+ const precisionPadding = ((numberOfDigits - 1) % 3) + 1
106
+
107
+ // hard-coded 2 decimal places for abbreviated numbers
108
+ return `${precisionPadding + 2}`
109
+ }
110
+
111
+ function replaceSIPrefixes({
112
+ string,
113
+ numberAbbreviation,
114
+ }: {
115
+ string: string
116
+ numberAbbreviation: "short" | "long"
117
+ }): string {
118
+ const prefix = string[string.length - 1]
119
+
120
+ const prefixMap: Record<string, Record<string, string>> = {
121
+ short: {
122
+ k: "k",
123
+ M: "M",
124
+ G: "B",
125
+ T: "T",
126
+ P: "quad",
127
+ E: "quint",
128
+ Z: "sext",
129
+ Y: "sept",
130
+ },
131
+ long: {
132
+ k: "k",
133
+ M: " million",
134
+ G: " billion",
135
+ T: " trillion",
136
+ P: " quadrillion",
137
+ E: " quintillion",
138
+ Z: " sextillion",
139
+ Y: " septillion",
140
+ },
141
+ }
142
+
143
+ if (prefixMap[numberAbbreviation][prefix]) {
144
+ return string.replace(prefix, prefixMap[numberAbbreviation][prefix])
145
+ }
146
+ return string
147
+ }
148
+
149
+ function postprocessString({
150
+ string,
151
+ roundingMode,
152
+ numberAbbreviation,
153
+ spaceBeforeUnit,
154
+ useNoBreakSpace,
155
+ unit,
156
+ value,
157
+ numDecimalPlaces,
158
+ }: {
159
+ string: string
160
+ roundingMode: OwidVariableRoundingMode
161
+ numberAbbreviation: "long" | "short" | false
162
+ spaceBeforeUnit: boolean
163
+ useNoBreakSpace: boolean
164
+ unit: string
165
+ value: number
166
+ numDecimalPlaces: number
167
+ }): string {
168
+ let output = string
169
+
170
+ // handling infinitesimal values
171
+ if (roundingMode !== OwidVariableRoundingMode.significantFigures) {
172
+ const tooSmallThreshold = Math.pow(10, -numDecimalPlaces).toPrecision(1)
173
+ if (numberAbbreviation && 0 < value && value < +tooSmallThreshold) {
174
+ output = "<" + output.replace(/0\.?(\d+)?/, tooSmallThreshold)
175
+ }
176
+ }
177
+
178
+ if (numberAbbreviation) {
179
+ output = replaceSIPrefixes({
180
+ string: output,
181
+ numberAbbreviation,
182
+ })
183
+ }
184
+
185
+ if (unit && !checkIsUnitCurrency(unit)) {
186
+ const spaceCharacter = useNoBreakSpace ? "\u00a0" : " "
187
+ const appendage = spaceBeforeUnit ? spaceCharacter + unit : unit
188
+ output += appendage
189
+ }
190
+
191
+ return output
192
+ }
193
+
194
+ export function formatValue(
195
+ value: number,
196
+ {
197
+ roundingMode = OwidVariableRoundingMode.decimalPlaces,
198
+ trailingZeroes = false, // only applies to fixed-point notation
199
+ unit = "",
200
+ spaceBeforeUnit = !checkIsUnitPercent(unit),
201
+ useNoBreakSpace = false,
202
+ showPlus = false,
203
+ numDecimalPlaces = 2, // only applies to fixed-point notation
204
+ numSignificantFigures = 3, // only applies to sig fig rounding
205
+ numberAbbreviation = "long",
206
+ }: TickFormattingOptions
207
+ ): string {
208
+ const formatter = createFormatter(unit)
209
+
210
+ // Explore how specifiers work here
211
+ // https://observablehq.com/@ikesau/d3-format-interactive-demo
212
+ const specifier = new FormatSpecifier({
213
+ zero: "0",
214
+ trim: getTrim({ roundingMode, trailingZeroes }),
215
+ sign: getSign({ showPlus }),
216
+ symbol: getSymbol({ unit }),
217
+ comma: ",",
218
+ precision: getPrecision({
219
+ roundingMode,
220
+ value,
221
+ numDecimalPlaces,
222
+ numSignificantFigures,
223
+ type: getType({ roundingMode, numberAbbreviation, value, unit }),
224
+ }),
225
+ type: getType({ roundingMode, numberAbbreviation, value, unit }),
226
+ }).toString()
227
+
228
+ const formattedString = formatter(specifier)(value)
229
+
230
+ const postprocessedString = postprocessString({
231
+ string: formattedString,
232
+ roundingMode,
233
+ numberAbbreviation,
234
+ spaceBeforeUnit,
235
+ useNoBreakSpace,
236
+ unit,
237
+ value,
238
+ numDecimalPlaces,
239
+ })
240
+
241
+ return postprocessedString
242
+ }
@@ -0,0 +1,81 @@
1
+ import { GrapherInterface } from "../types/index.js"
2
+ import * as _ from "lodash-es"
3
+ import {
4
+ excludeUndefined,
5
+ omitUndefinedValuesRecursive,
6
+ omitEmptyObjectsRecursive,
7
+ traverseObjects,
8
+ merge,
9
+ } from "./Util"
10
+
11
+ const REQUIRED_KEYS = ["$schema", "dimensions"]
12
+
13
+ const KEYS_EXCLUDED_FROM_INHERITANCE = [
14
+ "$schema",
15
+ "id",
16
+ "slug",
17
+ "version",
18
+ "isPublished",
19
+ ]
20
+
21
+ export function mergeGrapherConfigs(
22
+ ...grapherConfigs: GrapherInterface[]
23
+ ): GrapherInterface {
24
+ const configsToMerge = grapherConfigs.filter((c) => !_.isEmpty(c))
25
+
26
+ // return early if there are no configs to merge
27
+ if (configsToMerge.length === 0) return {}
28
+ if (configsToMerge.length === 1) return configsToMerge[0]
29
+
30
+ // warn if one of the configs is missing a schema version
31
+ const configsWithoutSchema = configsToMerge.filter(
32
+ (c) => c["$schema"] === undefined
33
+ )
34
+ if (configsWithoutSchema.length > 0) {
35
+ const configsJson = JSON.stringify(configsWithoutSchema, null, 2)
36
+ console.warn(
37
+ `About to merge Grapher configs with missing schema information: ${configsJson}`
38
+ )
39
+ }
40
+
41
+ // abort if the grapher configs have different schema versions
42
+ const uniqueSchemas = _.uniq(
43
+ excludeUndefined(configsToMerge.map((c) => c["$schema"]))
44
+ )
45
+ if (uniqueSchemas.length > 1) {
46
+ const message = `Merging Grapher configs with different schema versions. This may lead to unexpected behavior. Found: ${uniqueSchemas.join(
47
+ ", "
48
+ )}`
49
+ console.warn(message)
50
+ }
51
+
52
+ // keys that should not be inherited are removed from all but the last config
53
+ const cleanedConfigs = configsToMerge.map((config, index) => {
54
+ if (index === configsToMerge.length - 1) return config
55
+ return _.omit(config, KEYS_EXCLUDED_FROM_INHERITANCE)
56
+ })
57
+
58
+ return merge(
59
+ ...(cleanedConfigs as [GrapherInterface, ...GrapherInterface[]])
60
+ )
61
+ }
62
+
63
+ export function diffGrapherConfigs(
64
+ config: GrapherInterface,
65
+ reference: GrapherInterface
66
+ ): GrapherInterface {
67
+ const keepKeys = [...REQUIRED_KEYS, ...KEYS_EXCLUDED_FROM_INHERITANCE]
68
+ const keep = _.pick(config, keepKeys)
69
+
70
+ const diffed = omitEmptyObjectsRecursive(
71
+ omitUndefinedValuesRecursive(
72
+ traverseObjects(config, reference, (value, refValue) => {
73
+ if (refValue === undefined) return value
74
+ if (!_.isEqual(value, refValue)) return value
75
+ return undefined
76
+ })
77
+ )
78
+ )
79
+
80
+ return { ...diffed, ...keep }
81
+ }
@@ -0,0 +1,225 @@
1
+ // @ts-nocheck
2
+ /*
3
+ Common utlities for deriving properties from image metadata.
4
+ */
5
+
6
+ import { traverseEnrichedBlock } from "./Util.js"
7
+ import {
8
+ AssetMap,
9
+ OwidGdoc,
10
+ OwidGdocType,
11
+ ImageMetadata,
12
+ } from "../types/index.js"
13
+ import { match, P } from "ts-pattern"
14
+
15
+ export const AUTHOR_BYLINE_WIDTH = 48
16
+ export const THUMBNAIL_WIDTH = 100
17
+ export const LARGE_THUMBNAIL_WIDTH = 350
18
+ export const LARGEST_IMAGE_WIDTH = 1350
19
+
20
+ export function getArchivalSizes(
21
+ originalWidth: ImageMetadata["originalWidth"]
22
+ ): number[] {
23
+ if (!originalWidth) return []
24
+ const widths: number[] = []
25
+ if (originalWidth > LARGE_THUMBNAIL_WIDTH) {
26
+ widths.push(LARGE_THUMBNAIL_WIDTH)
27
+ }
28
+ if (originalWidth > LARGEST_IMAGE_WIDTH) {
29
+ widths.push(LARGEST_IMAGE_WIDTH)
30
+ }
31
+ widths.push(originalWidth)
32
+ return widths
33
+ }
34
+
35
+ export function getSizes(
36
+ originalWidth: ImageMetadata["originalWidth"]
37
+ ): number[] {
38
+ if (!originalWidth) return []
39
+ // ensure a thumbnail and author byline is generated
40
+ const widths = [AUTHOR_BYLINE_WIDTH, THUMBNAIL_WIDTH]
41
+ // start at large thumbnail and go up by 500 to a max of 1350 before we just show the original image
42
+ let width = LARGE_THUMBNAIL_WIDTH
43
+ while (width < originalWidth && width <= LARGEST_IMAGE_WIDTH) {
44
+ widths.push(width)
45
+ width += 500
46
+ }
47
+ widths.push(originalWidth)
48
+ return widths
49
+ }
50
+
51
+ export function generateSrcSet(
52
+ sizes: number[],
53
+ id: ImageMetadata["cloudflareId"],
54
+ absoluteUrl: string = ""
55
+ ): string {
56
+ return sizes
57
+ .map((size) => {
58
+ const path = `${absoluteUrl}/${id}/w=${size}`
59
+ return `${path} ${size}w`
60
+ })
61
+ .join(", ")
62
+ }
63
+
64
+ export function getFilenameWithoutExtension(
65
+ filename: ImageMetadata["filename"]
66
+ ): string {
67
+ return filename.slice(0, filename.indexOf("."))
68
+ }
69
+
70
+ export function getFilenameExtension(
71
+ filename: ImageMetadata["filename"]
72
+ ): string {
73
+ return filename.slice(filename.lastIndexOf(".") + 1)
74
+ }
75
+
76
+ export function getFilenameAsPng(filename: ImageMetadata["filename"]): string {
77
+ return `${getFilenameWithoutExtension(filename)}.png`
78
+ }
79
+
80
+ export function getFilenameMIMEType(filename: string): string | undefined {
81
+ const fileExtension = getFilenameExtension(filename)
82
+ const MIMEType = {
83
+ png: "image/png",
84
+ svg: "image/svg+xml",
85
+ jpg: "image/jpg",
86
+ jpeg: "image/jpeg",
87
+ webp: "image/webp",
88
+ }[fileExtension]
89
+
90
+ return MIMEType
91
+ }
92
+
93
+ export type SourceProps = {
94
+ media: string | undefined
95
+ srcSet: string
96
+ }
97
+
98
+ export function appendImageSizeSuffix(filename: string, size: number): string {
99
+ const extIndex = filename.lastIndexOf(".")
100
+ if (extIndex === -1) return `${filename}-${size}w`
101
+ const name = filename.slice(0, extIndex)
102
+ const ext = filename.slice(extIndex)
103
+ return `${name}-${size}w${ext}`
104
+ }
105
+
106
+ function generateArchivalSrcSet(
107
+ image: ImageMetadata,
108
+ sizes: number[],
109
+ assetMap: AssetMap
110
+ ): string {
111
+ return sizes
112
+ .map((size) => {
113
+ const filename =
114
+ size === image.originalWidth
115
+ ? image.filename
116
+ : appendImageSizeSuffix(image.filename, size)
117
+ const path = assetMap[filename]
118
+ if (!path) {
119
+ throw new Error(
120
+ `Image ${image.filename} not found in asset map`
121
+ )
122
+ }
123
+ return `${path} ${size}w`
124
+ })
125
+ .filter(Boolean)
126
+ .join(", ")
127
+ }
128
+
129
+ /**
130
+ * When we have a small and large image, we want to generate two <source> elements.
131
+ * The first will only be active at small screen sizes, due to its `media` property.
132
+ * The second will be active at all screen sizes.
133
+ * These props work in conjuction with a `sizes` attribute on the <source> element.
134
+ */
135
+ export function generateSourceProps(
136
+ smallImage: ImageMetadata | undefined,
137
+ regularImage: ImageMetadata,
138
+ absoluteUrl: string = "",
139
+ assetMap?: AssetMap
140
+ ): SourceProps[] {
141
+ const props: SourceProps[] = []
142
+
143
+ function buildProps(image: ImageMetadata, media: string | undefined): void {
144
+ const sizes = assetMap
145
+ ? getArchivalSizes(image.originalWidth)
146
+ : getSizes(image.originalWidth)
147
+ if (sizes.length === 0) return
148
+
149
+ let srcSet: string | undefined
150
+ if (assetMap) {
151
+ srcSet = generateArchivalSrcSet(image, sizes, assetMap)
152
+ } else if (image.cloudflareId) {
153
+ const encodedId = encodeURIComponent(image.cloudflareId)
154
+ srcSet = generateSrcSet(sizes, encodedId, absoluteUrl)
155
+ }
156
+
157
+ if (!srcSet) return
158
+
159
+ props.push({
160
+ media,
161
+ srcSet,
162
+ })
163
+ }
164
+
165
+ if (smallImage) {
166
+ buildProps(smallImage, "(max-width: 768px)")
167
+ }
168
+ if (regularImage) {
169
+ buildProps(regularImage, undefined)
170
+ }
171
+
172
+ return props
173
+ }
174
+
175
+ export function getFeaturedImageFilename(gdoc: OwidGdoc): string | undefined {
176
+ return match(gdoc)
177
+ .with(
178
+ {
179
+ content: {
180
+ type: P.union(
181
+ OwidGdocType.Article,
182
+ OwidGdocType.TopicPage,
183
+ OwidGdocType.LinearTopicPage,
184
+ OwidGdocType.AboutPage,
185
+ OwidGdocType.Author,
186
+ OwidGdocType.Announcement
187
+ ),
188
+ },
189
+ },
190
+ (match) => {
191
+ const featuredImageFilename = match.content["featured-image"]
192
+ return featuredImageFilename
193
+ }
194
+ )
195
+ .with({ content: { type: OwidGdocType.DataInsight } }, (gdoc) => {
196
+ // Use the first image in the document as the featured image
197
+ let filename: string | undefined = undefined
198
+ for (const block of gdoc.content.body) {
199
+ traverseEnrichedBlock(block, (block) => {
200
+ if (!filename && block.type === "image") {
201
+ filename = block.smallFilename || block.filename
202
+ }
203
+ })
204
+ }
205
+ return filename
206
+ })
207
+ .with(
208
+ {
209
+ content: { type: OwidGdocType.Profile },
210
+ },
211
+ (match) => match.content["featured-image"]
212
+ )
213
+ .with(
214
+ {
215
+ content: {
216
+ type: P.optional(
217
+ P.union(OwidGdocType.Fragment, OwidGdocType.Homepage)
218
+ ),
219
+ },
220
+ },
221
+ // This will fallback to DEFAULT_THUMBNAIL_FILENAME in Head.tsx
222
+ () => undefined
223
+ )
224
+ .exhaustive()
225
+ }