@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,816 @@
1
+ import * as _ from "lodash-es"
2
+ import * as React from "react"
3
+ import * as R from "remeda"
4
+ import {
5
+ action,
6
+ computed,
7
+ observable,
8
+ runInAction,
9
+ reaction,
10
+ makeObservable,
11
+ } from "mobx"
12
+ import { observer } from "mobx-react"
13
+ import { Flipper, Flipped } from "react-flip-toolkit"
14
+ import {
15
+ bind,
16
+ FuzzySearch,
17
+ scrollIntoViewIfNeeded,
18
+ sortByUndefinedLast,
19
+ ColumnSlug,
20
+ getUserCountryInformation,
21
+ regions,
22
+ SortOrder,
23
+ EntityName,
24
+ CoreColumnDef,
25
+ OwidTableSlugs,
26
+ } from "../../../utils/index.js"
27
+ import classnames from "classnames"
28
+ import { scaleLinear, ScaleLinear } from "d3-scale"
29
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
30
+ import { faSearch, faTimes } from "@fortawesome/free-solid-svg-icons"
31
+ import { VerticalScrollContainer } from "../../controls/VerticalScrollContainer"
32
+ import { SortIcon } from "../../controls/SortIcon"
33
+ import { Dropdown, BasicDropdownOption } from "../Dropdown"
34
+ import {
35
+ ColumnTypeMap,
36
+ CoreColumn,
37
+ OwidTable,
38
+ } from "../../../core-table/index.js"
39
+ import { EntityPickerManager } from "./EntityPickerConstants"
40
+ import { SelectionArray } from "../../selection/SelectionArray"
41
+
42
+ const toggleSort = (order: SortOrder): SortOrder =>
43
+ order === SortOrder.desc ? SortOrder.asc : SortOrder.desc
44
+
45
+ enum FocusDirection {
46
+ first = "first",
47
+ last = "last",
48
+ up = "up",
49
+ down = "down",
50
+ }
51
+
52
+ interface EntityOptionWithMetricValue {
53
+ entityName: EntityName
54
+ plotValue: number | string | undefined
55
+ formattedValue: any
56
+ localEntitiesIndex: number | undefined
57
+ }
58
+
59
+ /** Modulo that wraps negative numbers too */
60
+ const mod = (n: number, m: number): number => ((n % m) + m) % m
61
+
62
+ interface EntityPickerProps {
63
+ manager: EntityPickerManager
64
+ selection: SelectionArray
65
+ isDropdownMenu?: boolean
66
+ onSelectEntity?: (entityName: EntityName) => void
67
+ onDeselectEntity?: (entityName: EntityName) => void
68
+ onClearEntities?: () => void
69
+ }
70
+
71
+ @observer
72
+ export class EntityPicker extends React.Component<EntityPickerProps> {
73
+ private searchInput: string | undefined = undefined
74
+ private searchInputRef = React.createRef<HTMLInputElement>()
75
+
76
+ private focusIndex: number | undefined = undefined
77
+ private focusRef = React.createRef<HTMLLabelElement>()
78
+ private scrollFocusedIntoViewOnUpdate = false
79
+
80
+ private blockOptionHover = false
81
+
82
+ private mostRecentlySelectedEntityName: string | null = null
83
+
84
+ private scrollContainerRef = React.createRef<HTMLDivElement>()
85
+
86
+ private isOpen = false
87
+
88
+ private localEntityNames: string[] | undefined = undefined
89
+
90
+ constructor(props: EntityPickerProps) {
91
+ super(props)
92
+
93
+ makeObservable<
94
+ EntityPicker,
95
+ | "searchInput"
96
+ | "searchInputRef"
97
+ | "focusIndex"
98
+ | "focusRef"
99
+ | "scrollFocusedIntoViewOnUpdate"
100
+ | "blockOptionHover"
101
+ | "mostRecentlySelectedEntityName"
102
+ | "scrollContainerRef"
103
+ | "isOpen"
104
+ | "localEntityNames"
105
+ >(this, {
106
+ searchInput: observable,
107
+ searchInputRef: observable,
108
+ focusIndex: observable,
109
+ focusRef: observable,
110
+ scrollFocusedIntoViewOnUpdate: observable,
111
+ blockOptionHover: observable,
112
+ mostRecentlySelectedEntityName: observable,
113
+ scrollContainerRef: observable,
114
+ isOpen: observable,
115
+ localEntityNames: observable,
116
+ })
117
+ }
118
+
119
+ @computed private get isDropdownMenu(): boolean {
120
+ return !!this.props.isDropdownMenu
121
+ }
122
+
123
+ @action.bound private selectEntity(
124
+ name: EntityName,
125
+ checked?: boolean
126
+ ): void {
127
+ this.props.selection.toggleSelection(name)
128
+
129
+ if (this.props.selection.selectedSet.has(name)) {
130
+ this.props.onSelectEntity?.(name)
131
+ } else {
132
+ this.props.onDeselectEntity?.(name)
133
+ }
134
+
135
+ // Clear search input
136
+ this.searchInput = ""
137
+ this.manager.analytics?.logEntityPickerEvent(
138
+ checked ? "select" : "deselect",
139
+ name
140
+ )
141
+
142
+ this.mostRecentlySelectedEntityName = name
143
+ }
144
+
145
+ @computed private get manager(): EntityPickerManager {
146
+ return this.props.manager
147
+ }
148
+
149
+ @computed private get metric(): string | undefined {
150
+ return this.manager.entityPickerMetric
151
+ }
152
+
153
+ @computed private get sortOrder(): SortOrder {
154
+ // On mobile, only allow sorting by entityName (ascending)
155
+ if (this.isDropdownMenu) return SortOrder.asc
156
+ return this.manager.entityPickerSort ?? SortOrder.asc
157
+ }
158
+
159
+ @computed private get pickerColumnDefs(): CoreColumnDef[] {
160
+ return this.manager.entityPickerColumnDefs ?? []
161
+ }
162
+
163
+ @computed private get metricOptions(): {
164
+ label: string
165
+ value: string | undefined
166
+ }[] {
167
+ const entityNameColumn = this.grapherTable?.entityNameColumn
168
+ const entityNameColumnInPickerColumnDefs = !!this.pickerColumnDefs.find(
169
+ (col) => col.slug === entityNameColumn?.slug
170
+ )
171
+ return _.compact([
172
+ { label: "Relevance", value: undefined },
173
+ !entityNameColumnInPickerColumnDefs &&
174
+ entityNameColumn && {
175
+ label: _.upperFirst(this.manager.entityType) || "Name",
176
+ value: entityNameColumn?.slug,
177
+ },
178
+ ...this.pickerColumnDefs.map(
179
+ (
180
+ col
181
+ ): {
182
+ label: string
183
+ value: string
184
+ } => {
185
+ return {
186
+ label: col.name || col.slug,
187
+ value: col.slug,
188
+ }
189
+ }
190
+ ),
191
+ ])
192
+ }
193
+
194
+ @computed private get metricDropdownOptions(): BasicDropdownOption[] {
195
+ return this.metricOptions.map((option) => ({
196
+ // Use a special string value for undefined metric (relevance)
197
+ value: option.value ?? "",
198
+ label: option.label,
199
+ }))
200
+ }
201
+
202
+ @computed
203
+ private get selectedMetricDropdownOption():
204
+ | BasicDropdownOption
205
+ | undefined {
206
+ const targetValue = this.metric ?? ""
207
+ return this.metricDropdownOptions.find(
208
+ (option) => option.value === targetValue
209
+ )
210
+ }
211
+
212
+ @computed private get metricTable(): OwidTable | undefined {
213
+ if (this.metric === undefined) return undefined
214
+
215
+ // If the slug is "entityName", then try to get it from grapherTable first, because it might
216
+ // not be present in pickerTable (for indicator-powered explorers, for example).
217
+ if (
218
+ this.metric === OwidTableSlugs.entityName &&
219
+ this.grapherTable?.has(this.metric)
220
+ )
221
+ return this.grapherTable
222
+ return this.pickerTable
223
+ }
224
+
225
+ @computed private get activePickerMetricColumn(): CoreColumn | undefined {
226
+ return this.metricTable?.get(this.metric)
227
+ }
228
+
229
+ @computed private get availableEntitiesForCurrentView(): Set<string> {
230
+ if (!this.grapherTable)
231
+ return new Set(this.manager.availableEntityNames)
232
+ if (!this.manager.requiredColumnSlugs?.length)
233
+ return this.grapherTable.availableEntityNameSet
234
+ return this.grapherTable.entitiesWith(this.manager.requiredColumnSlugs)
235
+ }
236
+
237
+ @action.bound async populateLocalEntity(): Promise<void> {
238
+ try {
239
+ const localCountryInfo = await getUserCountryInformation()
240
+ if (!localCountryInfo) return
241
+
242
+ const userEntityCodes = [
243
+ localCountryInfo.code,
244
+ ...(localCountryInfo.regions ?? []),
245
+ "OWID_WRL",
246
+ ]
247
+
248
+ const userRegionNames = _.sortBy(
249
+ regions.filter((region) =>
250
+ userEntityCodes.includes(region.code)
251
+ ),
252
+ (region) => userEntityCodes.indexOf(region.code)
253
+ ).map((region) => region.name)
254
+
255
+ if (userRegionNames) this.localEntityNames = userRegionNames
256
+ } catch {
257
+ // ignore
258
+ }
259
+ }
260
+
261
+ @computed
262
+ private get entitiesWithMetricValue(): EntityOptionWithMetricValue[] {
263
+ const { metricTable, localEntityNames } = this
264
+ const col = this.activePickerMetricColumn
265
+ const entityNames = this.manager.availableEntityNames?.toSorted() ?? []
266
+ return entityNames.map((entityName) => {
267
+ const plotValue =
268
+ col && metricTable?.has(col.slug)
269
+ ? (metricTable.getLatestValueForEntity(
270
+ entityName,
271
+ col.slug
272
+ ) as string | number)
273
+ : undefined
274
+
275
+ const formattedValue =
276
+ plotValue !== undefined
277
+ ? col?.formatValueShortWithAbbreviations(plotValue)
278
+ : undefined
279
+
280
+ const localEntitiesIndex = localEntityNames?.indexOf(entityName)
281
+ return {
282
+ entityName,
283
+ plotValue,
284
+ formattedValue,
285
+ localEntitiesIndex:
286
+ localEntitiesIndex !== undefined && localEntitiesIndex >= 0
287
+ ? localEntitiesIndex
288
+ : undefined,
289
+ }
290
+ })
291
+ }
292
+
293
+ @computed private get grapherTable(): OwidTable | undefined {
294
+ return this.manager.grapherTable
295
+ }
296
+
297
+ @computed private get pickerTable(): OwidTable | undefined {
298
+ return this.manager.entityPickerTable
299
+ }
300
+
301
+ @computed get selection(): SelectionArray {
302
+ return this.props.selection
303
+ }
304
+
305
+ @computed get selectionSet(): Set<string> {
306
+ return new Set(this.selection.selectedEntityNames)
307
+ }
308
+
309
+ @computed private get fuzzy(): FuzzySearch<EntityOptionWithMetricValue> {
310
+ return FuzzySearch.withKey(
311
+ this.entitiesWithMetricValue,
312
+ (entity) => entity.entityName
313
+ )
314
+ }
315
+
316
+ @computed private get searchResults(): EntityOptionWithMetricValue[] {
317
+ if (this.searchInput) return this.fuzzy.search(this.searchInput)
318
+ const { entitiesWithMetricValue, sortOrder, selectionSet } = this
319
+
320
+ // Show the selected up top and in order.
321
+ const sorted = sortByUndefinedLast(
322
+ entitiesWithMetricValue,
323
+ (option) => option.plotValue,
324
+ sortOrder
325
+ )
326
+
327
+ let [selected, unselected] = _.partition(sorted, (option) =>
328
+ selectionSet.has(option.entityName)
329
+ )
330
+ if (this.metric === undefined) {
331
+ // only sort local entity names first if we're not sorting by some other metric already
332
+ unselected = sortByUndefinedLast(
333
+ unselected,
334
+ (option) => option.localEntitiesIndex
335
+ )
336
+ }
337
+ return [...selected, ...unselected]
338
+ }
339
+
340
+ private normalizeFocusIndex(index: number): number | undefined {
341
+ if (this.searchResults.length === 0) return undefined
342
+ return mod(index, this.searchResults.length)
343
+ }
344
+
345
+ @computed private get focusedOption(): string | undefined {
346
+ return this.focusIndex !== undefined
347
+ ? this.searchResults[this.focusIndex].entityName
348
+ : undefined
349
+ }
350
+
351
+ @computed private get showDoneButton(): boolean {
352
+ return this.isDropdownMenu && this.isOpen
353
+ }
354
+
355
+ @action.bound private focusOptionDirection(
356
+ direction: FocusDirection
357
+ ): void {
358
+ if (direction === FocusDirection.first)
359
+ this.focusIndex = this.normalizeFocusIndex(0)
360
+ else if (direction === FocusDirection.last)
361
+ this.focusIndex = this.normalizeFocusIndex(-1)
362
+ else if (direction === FocusDirection.up) {
363
+ const newIndex =
364
+ this.focusIndex === undefined ? -1 : this.focusIndex - 1
365
+ this.focusIndex = this.normalizeFocusIndex(newIndex)
366
+ } else if (direction === FocusDirection.down) {
367
+ const newIndex =
368
+ this.focusIndex === undefined ? 0 : this.focusIndex + 1
369
+ this.focusIndex = this.normalizeFocusIndex(newIndex)
370
+ } else return // Exit without updating scroll
371
+ this.scrollFocusedIntoViewOnUpdate = true
372
+ }
373
+
374
+ @action.bound private clearSearchInput(): void {
375
+ if (this.searchInput) this.searchInput = ""
376
+ }
377
+
378
+ @action.bound private onKeyDown(
379
+ event: React.KeyboardEvent<HTMLDivElement>
380
+ ): void {
381
+ // We want to block hover if a key is pressed.
382
+ // The hover will be unblocked iff the user moves the mouse (relative to the menu).
383
+ this.blockHover()
384
+ switch (event.key) {
385
+ case "Enter": {
386
+ if (event.keyCode === 229) {
387
+ // ignore the keydown event from an Input Method Editor(IME)
388
+ // ref. https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode
389
+ break
390
+ }
391
+ if (!this.focusedOption) return
392
+ const name = this.focusedOption
393
+ this.selectEntity(name)
394
+ this.clearSearchInput()
395
+ this.manager.analytics?.logEntityPickerEvent("enter", name)
396
+ break
397
+ }
398
+ case "ArrowUp":
399
+ this.focusOptionDirection(FocusDirection.up)
400
+ break
401
+ case "ArrowDown":
402
+ this.focusOptionDirection(FocusDirection.down)
403
+ break
404
+ default:
405
+ return
406
+ }
407
+ event.preventDefault()
408
+ }
409
+
410
+ @bind private focusSearch(): void {
411
+ this.searchInputRef.current?.focus()
412
+ }
413
+
414
+ @action.bound private onSearchFocus(): void {
415
+ this.isOpen = true
416
+ if (this.focusIndex === undefined)
417
+ this.focusOptionDirection(FocusDirection.first)
418
+ }
419
+
420
+ @action.bound private onSearchBlur(): void {
421
+ // Do not allow focus on elements inside menu; shift focus back to search input.
422
+ if (
423
+ this.scrollContainerRef.current &&
424
+ this.scrollContainerRef.current.contains(document.activeElement)
425
+ ) {
426
+ this.focusSearch()
427
+ return
428
+ }
429
+ this.isOpen = false
430
+ this.focusIndex = undefined
431
+ }
432
+
433
+ @action.bound private onHover(index: number): void {
434
+ if (!this.blockOptionHover) this.focusIndex = index
435
+ }
436
+
437
+ @action.bound private blockHover(): void {
438
+ this.blockOptionHover = true
439
+ }
440
+
441
+ @action.bound private unblockHover(): void {
442
+ this.blockOptionHover = false
443
+ }
444
+
445
+ @action.bound private onMenuMouseDown(
446
+ event: React.MouseEvent<HTMLDivElement, MouseEvent>
447
+ ): void {
448
+ event.stopPropagation()
449
+ event.preventDefault()
450
+ this.focusSearch()
451
+ }
452
+
453
+ @bind private highlightLabel(label: string): string | React.ReactElement {
454
+ if (!this.searchInput) return label
455
+
456
+ const result = this.fuzzy.single(this.searchInput, label)
457
+ if (!result) return label
458
+
459
+ const tokens: { match: boolean; text: string }[] = []
460
+ for (let i = 0; i < result.target.length; i++) {
461
+ const currentToken = R.last(tokens)
462
+ const match = result.indexes.includes(i)
463
+ const char = result.target[i]
464
+ if (!currentToken || currentToken.match !== match) {
465
+ tokens.push({
466
+ match,
467
+ text: char,
468
+ })
469
+ } else currentToken.text += char
470
+ }
471
+ return (
472
+ <span translate="no">
473
+ {tokens.map((token, i) =>
474
+ token.match ? (
475
+ <mark key={i}>{token.text}</mark>
476
+ ) : (
477
+ <span key={i}>{token.text}</span>
478
+ )
479
+ )}
480
+ </span>
481
+ )
482
+ }
483
+
484
+ @computed private get barScale(): ScaleLinear<number, number> {
485
+ const maxValue = _.max(
486
+ this.entitiesWithMetricValue
487
+ .map((option) => option.plotValue)
488
+ .filter(_.isNumber)
489
+ )
490
+ return scaleLinear()
491
+ .domain([0, maxValue ?? 1])
492
+ .range([0, 1])
493
+ }
494
+
495
+ override componentDidMount(): void {
496
+ // Whenever the search term changes, shift focus to first option in the list
497
+ reaction(
498
+ () => this.searchInput,
499
+ () => this.focusOptionDirection(FocusDirection.first)
500
+ )
501
+
502
+ void this.populateLocalEntity()
503
+ }
504
+
505
+ override componentDidUpdate(): void {
506
+ if (
507
+ this.focusIndex !== undefined &&
508
+ this.scrollFocusedIntoViewOnUpdate &&
509
+ this.scrollContainerRef.current &&
510
+ this.focusRef.current
511
+ ) {
512
+ scrollIntoViewIfNeeded(
513
+ this.scrollContainerRef.current,
514
+ this.focusRef.current
515
+ )
516
+ runInAction(() => (this.scrollFocusedIntoViewOnUpdate = false))
517
+ }
518
+ }
519
+
520
+ @action.bound private onMetricSelectionChange(
521
+ option: BasicDropdownOption | null
522
+ ): void {
523
+ if (!option) return
524
+ // Empty string means undefined/relevance option
525
+ const columnSlug = option.value === "" ? undefined : option.value
526
+ this.updateMetric(columnSlug)
527
+ }
528
+
529
+ @action private updateMetric(columnSlug: ColumnSlug | undefined): void {
530
+ const col = this.pickerTable?.get(columnSlug)
531
+
532
+ this.manager.setEntityPicker?.({
533
+ metric: columnSlug,
534
+ sort: this.isColumnTypeNumeric(columnSlug, col)
535
+ ? SortOrder.desc
536
+ : SortOrder.asc,
537
+ })
538
+ this.manager.analytics?.logEntityPickerEvent("sortBy", columnSlug)
539
+ }
540
+
541
+ private isColumnTypeNumeric(
542
+ columnSlug: ColumnSlug | undefined,
543
+ col: CoreColumn | undefined
544
+ ): boolean {
545
+ return (
546
+ // If columnSlug is undefined, we're sorting by relevance, which is (mostly) by country name.
547
+ // If the column is currently missing (not loaded yet), assume it is numeric.
548
+ columnSlug !== undefined &&
549
+ columnSlug !== OwidTableSlugs.entityName &&
550
+ (col === undefined ||
551
+ col.isMissing ||
552
+ col instanceof ColumnTypeMap.Numeric)
553
+ )
554
+ }
555
+
556
+ private get pickerMenu(): React.ReactElement | null {
557
+ if (this.isDropdownMenu) return null
558
+
559
+ return (
560
+ <div className="MetricSettings">
561
+ <label id="metric-dropdown-label" className="mainLabel">
562
+ Sort by
563
+ </label>
564
+ <Dropdown
565
+ className="metricDropdown"
566
+ options={this.metricDropdownOptions}
567
+ value={this.selectedMetricDropdownOption ?? null}
568
+ onChange={this.onMetricSelectionChange}
569
+ isLoading={this.manager.entityPickerTableIsLoading}
570
+ aria-labelledby="metric-dropdown-label"
571
+ />
572
+ <span
573
+ className="sort"
574
+ onClick={(): void => {
575
+ const sortOrder = toggleSort(this.sortOrder)
576
+ this.manager.setEntityPicker?.({
577
+ metric: this.metric,
578
+ sort: sortOrder,
579
+ })
580
+ this.manager.analytics?.logEntityPickerEvent(
581
+ "sortOrder",
582
+ sortOrder
583
+ )
584
+ }}
585
+ >
586
+ <SortIcon
587
+ type={
588
+ this.isColumnTypeNumeric(
589
+ this.metric,
590
+ this.activePickerMetricColumn
591
+ )
592
+ ? "numeric"
593
+ : "text"
594
+ }
595
+ order={this.sortOrder}
596
+ />
597
+ </span>
598
+ </div>
599
+ )
600
+ }
601
+
602
+ override render(): React.ReactElement {
603
+ const { selection } = this
604
+ const { entityType } = this.manager
605
+ const entities = this.searchResults
606
+ const selectedEntityNames = selection.selectedEntityNames
607
+ const availableEntities = this.availableEntitiesForCurrentView
608
+
609
+ const selectedDebugMessage = `${selectedEntityNames.length} selected. ${availableEntities.size} available. ${this.entitiesWithMetricValue.length} options total.`
610
+
611
+ return (
612
+ <div className="EntityPicker">
613
+ <div
614
+ className="EntityPickerSearchInput"
615
+ onKeyDown={this.onKeyDown}
616
+ >
617
+ <input
618
+ className={classnames("input-field", {
619
+ "with-done-button": this.showDoneButton,
620
+ })}
621
+ type="text"
622
+ placeholder={`Type to add a ${entityType}...`}
623
+ value={this.searchInput ?? ""}
624
+ onChange={(e): string =>
625
+ (this.searchInput = e.currentTarget.value)
626
+ }
627
+ onFocus={this.onSearchFocus}
628
+ onBlur={this.onSearchBlur}
629
+ ref={this.searchInputRef}
630
+ data-track-note="entity_picker_search_input"
631
+ />
632
+ <div className="search-icon">
633
+ <FontAwesomeIcon icon={faSearch} />
634
+ </div>
635
+ {this.showDoneButton && (
636
+ <div className="done">
637
+ <button>Done</button>
638
+ </div>
639
+ )}
640
+ </div>
641
+ {this.pickerMenu}
642
+ <div className="EntityListContainer" onKeyDown={this.onKeyDown}>
643
+ {(!this.isDropdownMenu || this.isOpen) && (
644
+ <div
645
+ className={classnames("EntityList", {
646
+ isDropdown: this.isDropdownMenu,
647
+ })}
648
+ onMouseDown={this.onMenuMouseDown}
649
+ >
650
+ <VerticalScrollContainer
651
+ scrollingShadows={true}
652
+ scrollLock={true}
653
+ className="EntitySearchResults"
654
+ contentsId={entities
655
+ .map((c) => c.entityName)
656
+ .join(",")}
657
+ onMouseMove={this.unblockHover}
658
+ ref={this.scrollContainerRef}
659
+ >
660
+ <Flipper
661
+ spring={{
662
+ stiffness: 300,
663
+ damping: 33,
664
+ }}
665
+ // We only want to animate when the selection changes, but not on changes due to
666
+ // searching
667
+ flipKey={selectedEntityNames.join(",")}
668
+ >
669
+ {entities.map((option, index) => (
670
+ <PickerOption
671
+ key={index}
672
+ hasDataForActiveMetric={availableEntities.has(
673
+ option.entityName
674
+ )}
675
+ optionWithMetricValue={option}
676
+ highlight={this.highlightLabel}
677
+ barScale={this.barScale}
678
+ onChange={this.selectEntity}
679
+ onHover={(): void =>
680
+ this.onHover(index)
681
+ }
682
+ isSelected={this.selectionSet.has(
683
+ option.entityName
684
+ )}
685
+ isFocused={
686
+ this.focusIndex === index
687
+ }
688
+ innerRef={
689
+ this.focusIndex === index
690
+ ? this.focusRef
691
+ : undefined
692
+ }
693
+ className={
694
+ this
695
+ .mostRecentlySelectedEntityName ===
696
+ option.entityName
697
+ ? "most-recently-selected"
698
+ : undefined
699
+ }
700
+ />
701
+ ))}
702
+ </Flipper>
703
+ </VerticalScrollContainer>
704
+ <div>
705
+ <div
706
+ title={selectedDebugMessage}
707
+ className="ClearSelectionButton"
708
+ data-track-note="entity_picker_clear_selection"
709
+ onClick={(): void => {
710
+ selection.clearSelection()
711
+ this.props.onClearEntities?.()
712
+ }}
713
+ >
714
+ <FontAwesomeIcon icon={faTimes} /> Clear
715
+ selection
716
+ </div>
717
+ </div>
718
+ </div>
719
+ )}
720
+ </div>
721
+ </div>
722
+ )
723
+ }
724
+ }
725
+
726
+ interface PickerOptionProps {
727
+ optionWithMetricValue: EntityOptionWithMetricValue
728
+ highlight: (label: string) => React.ReactElement | string
729
+ onChange: (name: string, checked: boolean) => void
730
+ onHover?: () => void
731
+ innerRef?: React.RefObject<HTMLLabelElement | null>
732
+ isFocused?: boolean
733
+ isSelected?: boolean
734
+ barScale?: ScaleLinear<number, number>
735
+ hasDataForActiveMetric: boolean
736
+ className?: string
737
+ }
738
+
739
+ class PickerOption extends React.Component<PickerOptionProps> {
740
+ @bind onClick(event: React.MouseEvent<HTMLLabelElement, MouseEvent>): void {
741
+ event.stopPropagation()
742
+ event.preventDefault()
743
+ this.props.onChange(
744
+ this.props.optionWithMetricValue.entityName,
745
+ !this.props.isSelected
746
+ )
747
+ }
748
+
749
+ override render(): React.ReactElement {
750
+ const {
751
+ barScale,
752
+ optionWithMetricValue,
753
+ innerRef,
754
+ isSelected,
755
+ isFocused,
756
+ hasDataForActiveMetric,
757
+ highlight,
758
+ className,
759
+ } = this.props
760
+ const { entityName, plotValue, formattedValue } = optionWithMetricValue
761
+ const metricValue = formattedValue === entityName ? "" : formattedValue // If the user has this entity selected, don't show the name twice.
762
+
763
+ return (
764
+ <Flipped flipId={entityName} translate opacity>
765
+ <label
766
+ className={classnames(
767
+ "EntityPickerOption",
768
+ className,
769
+ {
770
+ selected: isSelected,
771
+ focused: isFocused,
772
+ "local-country":
773
+ optionWithMetricValue.localEntitiesIndex !==
774
+ undefined,
775
+ },
776
+ hasDataForActiveMetric ? undefined : "MissingData"
777
+ )}
778
+ onMouseMove={this.props.onHover}
779
+ onMouseOver={this.props.onHover}
780
+ onClick={this.onClick}
781
+ ref={innerRef}
782
+ >
783
+ <div className="input-container">
784
+ <input
785
+ type="checkbox"
786
+ checked={isSelected}
787
+ value={entityName}
788
+ tabIndex={-1}
789
+ readOnly
790
+ />
791
+ </div>
792
+ <div className="info-container">
793
+ <div className="labels-container">
794
+ <div className="name">{highlight(entityName)}</div>
795
+ {plotValue !== undefined && (
796
+ <div className="metric">{metricValue}</div>
797
+ )}
798
+ </div>
799
+ {barScale && _.isNumber(plotValue) ? (
800
+ <div className="plot">
801
+ <div
802
+ className="bar"
803
+ style={{
804
+ width: `${barScale(plotValue) * 100}%`,
805
+ }}
806
+ />
807
+ </div>
808
+ ) : (
809
+ <div className="plot"></div>
810
+ )}
811
+ </div>
812
+ </label>
813
+ </Flipped>
814
+ )
815
+ }
816
+ }