@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,968 @@
1
+ import * as _ from "lodash-es"
2
+ import React from "react"
3
+ import * as R from "remeda"
4
+ import {
5
+ guid,
6
+ excludeNullish,
7
+ getRelativeMouse,
8
+ exposeInstanceOnWindow,
9
+ excludeUndefined,
10
+ isMobile,
11
+ Bounds,
12
+ HorizontalAlign,
13
+ isTouchDevice,
14
+ } from "../../utils/index.js"
15
+ import { computed, action, observable, makeObservable } from "mobx"
16
+ import { observer } from "mobx-react"
17
+ import { select, type Selection, type BaseType } from "d3-selection"
18
+ import { easeLinear } from "d3-ease"
19
+ import { DualAxisComponent } from "../axis/AxisViews"
20
+ import { DualAxis, HorizontalAxis, VerticalAxis } from "../axis/Axis"
21
+ import { LineLegend } from "../lineLegend/LineLegend"
22
+ import { TooltipFooterIcon } from "../tooltip/TooltipProps.js"
23
+ import {
24
+ Tooltip,
25
+ TooltipState,
26
+ TooltipTable,
27
+ makeTooltipRoundingNotice,
28
+ toTooltipTableColumns,
29
+ } from "../tooltip/Tooltip"
30
+ import { NoDataModal } from "../noDataModal/NoDataModal"
31
+ import { extent } from "d3-array"
32
+ import { SeriesName, VerticalAlign, Time } from "../../types/index.js"
33
+ import {
34
+ BASE_FONT_SIZE,
35
+ DEFAULT_GRAPHER_BOUNDS,
36
+ GRAPHER_OPACITY_MUTE,
37
+ } from "../core/GrapherConstants"
38
+ import { ChartInterface } from "../chart/ChartInterface"
39
+ import {
40
+ LineChartSeries,
41
+ LineChartManager,
42
+ LinePoint,
43
+ PlacedLineChartSeries,
44
+ RenderLineChartSeries,
45
+ CATEGORICAL_LEGEND_STYLE,
46
+ NUMERIC_LEGEND_STYLE,
47
+ LEGEND_PADDING,
48
+ VARIABLE_COLOR_STROKE_WIDTH,
49
+ DEFAULT_STROKE_WIDTH,
50
+ VARIABLE_COLOR_LINE_OUTLINE_WIDTH,
51
+ DEFAULT_LINE_OUTLINE_WIDTH,
52
+ DISCONNECTED_DOTS_MARKER_RADIUS,
53
+ VARIABLE_COLOR_MARKER_RADIUS,
54
+ STATIC_SMALL_MARKER_RADIUS,
55
+ DEFAULT_MARKER_RADIUS,
56
+ LINE_CHART_CLASS_NAME,
57
+ } from "./LineChartConstants"
58
+ import { CoreColumn } from "../../core-table/index.js"
59
+ import {
60
+ ClipPath,
61
+ getHoverStateForSeries,
62
+ getSeriesKey,
63
+ isTargetOutsideElement,
64
+ makeClipPath,
65
+ } from "../chart/ChartUtils"
66
+ import { CategoricalBin, ColorScaleBin } from "../color/ColorScaleBin"
67
+ import { ColorScale } from "../color/ColorScale"
68
+ import { GRAPHER_BACKGROUND_DEFAULT, GRAY_50 } from "../color/ColorConstants"
69
+ import { darkenColorForLine } from "../color/ColorUtils"
70
+ import {
71
+ HorizontalColorLegendManager,
72
+ HorizontalNumericColorLegend,
73
+ } from "../legend/HorizontalColorLegends"
74
+ import {
75
+ AnnotationsMap,
76
+ getAnnotationsForSeries,
77
+ getAnnotationsMap,
78
+ getYAxisConfigDefaults,
79
+ toPlacedLineChartSeries,
80
+ toRenderLineChartSeries,
81
+ } from "./LineChartHelpers"
82
+ import { LineLabelSeries } from "../lineLegend/LineLegendTypes"
83
+ import { Lines } from "./Lines"
84
+ import { LineChartState } from "./LineChartState.js"
85
+ import { AxisConfig, AxisManager } from "../axis/AxisConfig"
86
+ import { ChartComponentProps } from "../chart/ChartTypeMap.js"
87
+ import { InteractionState } from "../interaction/InteractionState"
88
+ import { LegendStyleConfig } from "../legend/LegendInteractionState"
89
+
90
+ export type LineChartProps = ChartComponentProps<LineChartState>
91
+
92
+ @observer
93
+ export class LineChart
94
+ extends React.Component<LineChartProps>
95
+ implements ChartInterface, HorizontalColorLegendManager, AxisManager
96
+ {
97
+ private base = React.createRef<SVGGElement>()
98
+
99
+ constructor(props: LineChartProps) {
100
+ super(props)
101
+
102
+ makeObservable<
103
+ LineChart,
104
+ "tooltipState" | "lineLegendHoveredSeriesName" | "hoverTimer"
105
+ >(this, {
106
+ tooltipState: observable,
107
+ lineLegendHoveredSeriesName: observable,
108
+ hoverTimer: observable,
109
+ })
110
+ }
111
+
112
+ @computed get chartState(): LineChartState {
113
+ return this.props.chartState
114
+ }
115
+
116
+ @computed get isStatic(): boolean {
117
+ return this.manager.isStatic ?? false
118
+ }
119
+
120
+ @computed get detailsOrderedByReference(): string[] {
121
+ return this.manager.detailsOrderedByReference ?? []
122
+ }
123
+
124
+ @computed get annotationsMap(): AnnotationsMap | undefined {
125
+ return getAnnotationsMap(
126
+ this.chartState.inputTable,
127
+ this.yColumnSlugs[0]
128
+ )
129
+ }
130
+
131
+ @action.bound private dismissTooltip(): void {
132
+ this.tooltipState.target = null
133
+ }
134
+
135
+ @action.bound private onCursorLeave(): void {
136
+ if (!this.manager.shouldPinTooltipToBottom) {
137
+ this.dismissTooltip()
138
+ }
139
+ this.clearHighlightedSeries()
140
+ }
141
+
142
+ @computed private get allValues(): LinePoint[] {
143
+ return this.placedSeries.flatMap((series) => series.points)
144
+ }
145
+
146
+ private tooltipState = new TooltipState<{ time: Time }>({
147
+ fade: "immediate",
148
+ })
149
+
150
+ @action.bound private onCursorMove(
151
+ ev: React.MouseEvent | React.TouchEvent
152
+ ): void {
153
+ const ref = this.base.current,
154
+ parentRef = this.manager.base?.current
155
+
156
+ // the tooltip's origin needs to be in the parent's coordinates
157
+ if (parentRef) {
158
+ this.tooltipState.position = getRelativeMouse(parentRef, ev)
159
+ }
160
+
161
+ if (!ref) return
162
+
163
+ const mouse = getRelativeMouse(ref, ev)
164
+ const boxPadding = isMobile() ? 44 : 25
165
+
166
+ // expand the box width, so it's easier to see the tooltip for the first & last timepoints
167
+ const boundedBox = this.dualAxis.innerBounds.expand({
168
+ left: boxPadding,
169
+ right: boxPadding,
170
+ })
171
+
172
+ let hoverTime
173
+ if (boundedBox.contains(mouse)) {
174
+ const invertedX = this.dualAxis.horizontalAxis.invert(mouse.x)
175
+
176
+ const closestValue = _.minBy(this.allValues, (point) =>
177
+ Math.abs(invertedX - point.x)
178
+ )
179
+ hoverTime = closestValue?.x
180
+ }
181
+
182
+ // be sure all lines are un-dimmed if the cursor is above the graph itself
183
+ if (this.dualAxis.innerBounds.contains(mouse)) {
184
+ this.clearLineLegendHover()
185
+ }
186
+
187
+ this.tooltipState.target =
188
+ hoverTime === undefined ? null : { time: hoverTime }
189
+ }
190
+
191
+ @computed private get manager(): LineChartManager {
192
+ return this.chartState.manager
193
+ }
194
+
195
+ @computed private get bounds(): Bounds {
196
+ return this.props.bounds ?? DEFAULT_GRAPHER_BOUNDS
197
+ }
198
+
199
+ @computed private get boundsWithoutColorLegend(): Bounds {
200
+ return this.bounds.padTop(
201
+ this.hasColorLegend ? this.legendHeight + LEGEND_PADDING : 0
202
+ )
203
+ }
204
+
205
+ @computed private get maxLineLegendWidth(): number {
206
+ return this.bounds.width / 3
207
+ }
208
+
209
+ @computed private get lineStrokeWidth(): number {
210
+ if (this.manager.lineStrokeWidth) return this.manager.lineStrokeWidth
211
+ const factor = this.manager.isStaticAndSmall ? 2 : 1
212
+ return this.hasColorScale
213
+ ? factor * VARIABLE_COLOR_STROKE_WIDTH
214
+ : factor * DEFAULT_STROKE_WIDTH
215
+ }
216
+
217
+ @computed private get lineOutlineWidth(): number {
218
+ return this.hasColorScale
219
+ ? VARIABLE_COLOR_LINE_OUTLINE_WIDTH
220
+ : DEFAULT_LINE_OUTLINE_WIDTH
221
+ }
222
+
223
+ @computed private get markerRadius(): number {
224
+ if (this.hasMarkersOnlySeries) return DISCONNECTED_DOTS_MARKER_RADIUS
225
+ if (this.hasColorScale) return VARIABLE_COLOR_MARKER_RADIUS
226
+ if (this.manager.isStaticAndSmall) return STATIC_SMALL_MARKER_RADIUS
227
+ return DEFAULT_MARKER_RADIUS
228
+ }
229
+
230
+ @computed get activeTimes(): Time[] {
231
+ const { highlightedTimesInLineChart = [] } = this.manager
232
+ return _.uniq(
233
+ this.tooltipState.target?.time
234
+ ? [
235
+ this.tooltipState.target.time,
236
+ ...highlightedTimesInLineChart,
237
+ ]
238
+ : highlightedTimesInLineChart
239
+ )
240
+ }
241
+
242
+ @computed private get activeXVerticalLines(): React.ReactElement | null {
243
+ const { activeTimes, dualAxis } = this
244
+ const { horizontalAxis, verticalAxis } = dualAxis
245
+
246
+ if (!activeTimes) return null
247
+
248
+ return (
249
+ <>
250
+ {activeTimes.map((time) => (
251
+ <g className="hoverIndicator" key={time}>
252
+ <line
253
+ x1={horizontalAxis.place(time)}
254
+ y1={verticalAxis.range[0]}
255
+ x2={horizontalAxis.place(time)}
256
+ y2={verticalAxis.range[1]}
257
+ stroke="rgba(180,180,180,.4)"
258
+ />
259
+ {this.renderSeries.map((series, index) => {
260
+ const point = series.points.find(
261
+ (point) => point.x === time
262
+ )
263
+ if (!point || series.hover.background) return null
264
+
265
+ const valueColor = this.hasColorScale
266
+ ? darkenColorForLine(
267
+ this.chartState.getColorScaleColor(
268
+ point.colorValue
269
+ )
270
+ )
271
+ : series.color
272
+ const color =
273
+ !series.focus.background || series.hover.active
274
+ ? valueColor
275
+ : GRAY_50
276
+
277
+ return (
278
+ <circle
279
+ key={getSeriesKey(series, index)}
280
+ cx={horizontalAxis.place(point.x)}
281
+ cy={verticalAxis.place(point.y)}
282
+ r={this.lineStrokeWidth / 2 + 3.5}
283
+ fill={color}
284
+ stroke={
285
+ this.manager.backgroundColor ??
286
+ GRAPHER_BACKGROUND_DEFAULT
287
+ }
288
+ strokeWidth={0.5}
289
+ />
290
+ )
291
+ })}
292
+ </g>
293
+ ))}
294
+ </>
295
+ )
296
+ }
297
+
298
+ @computed private get tooltipId(): number {
299
+ return this.renderUid
300
+ }
301
+
302
+ @computed private get isTooltipActive(): boolean {
303
+ return this.manager.tooltip?.get()?.id === this.tooltipId
304
+ }
305
+
306
+ @computed private get tooltip(): React.ReactElement | undefined {
307
+ const { formatColumn, colorColumn, hasColorScale } = this
308
+ const { target, position, fading } = this.tooltipState
309
+
310
+ if (!target) return undefined
311
+
312
+ // Duplicate seriesNames will be present if there is a projected-values line
313
+ const seriesSegments = _.mapValues(
314
+ _.groupBy(this.series, "seriesName"),
315
+ (segments) =>
316
+ segments.find((series) =>
317
+ // Ideally pick series with a defined value at the target time
318
+ series.points.find((point) => point.x === target.time)
319
+ ) ??
320
+ segments.find((series): boolean | void => {
321
+ // Otherwise pick the series whose start & end contains the target time
322
+ // and display a "No data" notice.
323
+ const [startX, endX] = extent(series.points, ({ x }) => x)
324
+ return (
325
+ _.isNumber(startX) &&
326
+ _.isNumber(endX) &&
327
+ startX < target.time &&
328
+ target.time < endX
329
+ )
330
+ }) ??
331
+ null // If neither series matches, exclude the entity from the tooltip altogether
332
+ )
333
+
334
+ const sortedData = _.sortBy(
335
+ excludeNullish(R.values(seriesSegments)),
336
+ (series) => {
337
+ const value = series.points.find(
338
+ (point) => point.x === target.time
339
+ )
340
+ return value !== undefined ? -value.y : Infinity
341
+ }
342
+ )
343
+
344
+ const formattedTime = formatColumn.formatTime(target.time),
345
+ { displayUnit: unitLabel } = formatColumn,
346
+ { isRelativeMode, startTime } = this.manager
347
+
348
+ const title = formattedTime
349
+ const titleAnnotation = this.xAxis.label ? `(${this.xAxis.label})` : ""
350
+
351
+ const columns = [formatColumn]
352
+ if (hasColorScale && colorColumn.slug !== formatColumn.slug)
353
+ columns.push(colorColumn)
354
+
355
+ const subtitle =
356
+ isRelativeMode && startTime
357
+ ? `% change since ${formatColumn.formatTime(startTime)}`
358
+ : unitLabel
359
+ const subtitleFormat = subtitle === unitLabel ? "unit" : undefined
360
+
361
+ const projectionNotice = sortedData.some(
362
+ (series) => series.isProjection
363
+ )
364
+ ? { icon: TooltipFooterIcon.Stripes, text: "Projected data" }
365
+ : undefined
366
+ const roundingNotice = formatColumn.roundsToSignificantFigures
367
+ ? {
368
+ icon: TooltipFooterIcon.None,
369
+ text: makeTooltipRoundingNotice([
370
+ formatColumn.numSignificantFigures,
371
+ ]),
372
+ }
373
+ : undefined
374
+ const footer = excludeUndefined([projectionNotice, roundingNotice])
375
+
376
+ return (
377
+ <Tooltip
378
+ id={this.tooltipId}
379
+ tooltipManager={this.manager}
380
+ x={position.x}
381
+ y={position.y}
382
+ style={{ maxWidth: "400px" }}
383
+ offsetXDirection="left"
384
+ offsetX={20}
385
+ offsetY={-16}
386
+ title={title}
387
+ titleAnnotation={titleAnnotation}
388
+ subtitle={subtitle}
389
+ subtitleFormat={subtitleFormat}
390
+ footer={footer}
391
+ dissolve={fading}
392
+ dismiss={this.dismissTooltip}
393
+ >
394
+ <TooltipTable
395
+ columns={toTooltipTableColumns(columns)}
396
+ rows={sortedData.map((series) => {
397
+ const {
398
+ seriesName,
399
+ displayName,
400
+ isProjection: striped,
401
+ } = series
402
+ const annotation = getAnnotationsForSeries(
403
+ this.annotationsMap,
404
+ seriesName
405
+ )
406
+
407
+ const point = series.points.find(
408
+ (point) => point.x === target.time
409
+ )
410
+
411
+ const blurred =
412
+ this.hoverStateForSeries(series).background ||
413
+ series.focus.background ||
414
+ point === undefined
415
+
416
+ const color = this.hasColorScale
417
+ ? darkenColorForLine(
418
+ this.chartState.getColorScaleColor(
419
+ point?.colorValue
420
+ )
421
+ )
422
+ : series.color
423
+ const opacity = blurred ? GRAPHER_OPACITY_MUTE : 1
424
+ const swatch = { color, opacity }
425
+
426
+ const values = excludeUndefined([
427
+ point?.y,
428
+ point?.colorValue as undefined | number,
429
+ ])
430
+
431
+ return {
432
+ name: displayName,
433
+ annotation,
434
+ swatch,
435
+ blurred,
436
+ striped,
437
+ values,
438
+ }
439
+ })}
440
+ />
441
+ </Tooltip>
442
+ )
443
+ }
444
+
445
+ private defaultRightPadding = 1
446
+
447
+ private lineLegendHoveredSeriesName: SeriesName | undefined = undefined
448
+ private hoverTimer: number | undefined = undefined
449
+
450
+ @action.bound private onLineLegendMouseOver(seriesName: SeriesName): void {
451
+ clearTimeout(this.hoverTimer)
452
+ this.lineLegendHoveredSeriesName = seriesName
453
+ }
454
+
455
+ @action.bound private clearLineLegendHover(): void {
456
+ this.lineLegendHoveredSeriesName = undefined
457
+ }
458
+
459
+ @action.bound private clearHighlightedSeries(): void {
460
+ clearTimeout(this.hoverTimer)
461
+
462
+ // Wait before clearing selection in case the mouse is moving
463
+ // quickly over neighboring labels
464
+ this.hoverTimer = window.setTimeout(() => {
465
+ this.clearLineLegendHover()
466
+ }, 200)
467
+ }
468
+
469
+ @action.bound private onLineLegendMouseLeave(): void {
470
+ this.clearHighlightedSeries()
471
+ }
472
+
473
+ @action.bound private onLineLegendClick(seriesName: SeriesName): void {
474
+ this.chartState.focusArray.toggle(seriesName)
475
+ }
476
+
477
+ @computed private get hoveredSeriesNames(): string[] {
478
+ const { externalLegendHoverBin } = this.manager
479
+ const hoveredSeriesNames = excludeUndefined([
480
+ this.lineLegendHoveredSeriesName,
481
+ ])
482
+ if (externalLegendHoverBin) {
483
+ hoveredSeriesNames.push(
484
+ ...this.series
485
+ .map((s) => s.seriesName)
486
+ .filter((name) => externalLegendHoverBin.contains(name))
487
+ )
488
+ }
489
+ return hoveredSeriesNames
490
+ }
491
+
492
+ @computed private get isHoverModeActive(): boolean {
493
+ return (
494
+ this.hoveredSeriesNames.length > 0 ||
495
+ // if the external legend is hovered, we want to mute
496
+ // all non-hovered series even if the chart doesn't plot
497
+ // the currently hovered series
498
+ (!!this.manager.externalLegendHoverBin && !this.hasColorScale)
499
+ )
500
+ }
501
+
502
+ @computed private get canToggleFocusMode(): boolean {
503
+ return !isTouchDevice() && this.series.length > 1
504
+ }
505
+
506
+ @computed private get hasTimeHighlights(): boolean {
507
+ const { highlightedTimesInLineChart = [] } = this.manager
508
+ return highlightedTimesInLineChart.length > 0
509
+ }
510
+
511
+ @action.bound private onDocumentClick(e: MouseEvent): void {
512
+ // only dismiss the tooltip if the click is outside of the chart area
513
+ // and outside of the chart areas of neighbouring facets
514
+ const chartContainer = this.manager.base?.current
515
+ if (!chartContainer) return
516
+ const chartAreas = chartContainer.getElementsByClassName(
517
+ LINE_CHART_CLASS_NAME
518
+ )
519
+ const isTargetOutsideChartAreas = Array.from(chartAreas).every(
520
+ (chartArea) => isTargetOutsideElement(e.target!, chartArea)
521
+ )
522
+ if (isTargetOutsideChartAreas) {
523
+ this.dismissTooltip()
524
+ }
525
+ }
526
+
527
+ private animSelection?: Selection<
528
+ BaseType,
529
+ unknown,
530
+ SVGGElement | null,
531
+ unknown
532
+ >
533
+ override componentDidMount(): void {
534
+ if (!this.manager.disableIntroAnimation) {
535
+ this.runFancyIntroAnimation()
536
+ }
537
+ exposeInstanceOnWindow(this)
538
+ document.addEventListener("click", this.onDocumentClick, {
539
+ capture: true,
540
+ })
541
+ }
542
+
543
+ override componentWillUnmount(): void {
544
+ if (this.animSelection) this.animSelection.interrupt()
545
+ document.removeEventListener("click", this.onDocumentClick, {
546
+ capture: true,
547
+ })
548
+ }
549
+
550
+ @computed private get renderUid(): number {
551
+ return guid()
552
+ }
553
+
554
+ @computed get fontSize(): number {
555
+ return this.manager.fontSize ?? BASE_FONT_SIZE
556
+ }
557
+
558
+ @computed private get fontWeight(): number {
559
+ return this.hasColorScale ? 700 : 400
560
+ }
561
+
562
+ @computed private get hidePoints(): boolean {
563
+ return !!this.manager.hidePoints
564
+ }
565
+
566
+ @computed private get lineLegendX(): number {
567
+ return this.bounds.right - this.lineLegendWidth
568
+ }
569
+
570
+ @computed private get lineLegendY(): [number, number] {
571
+ return [
572
+ this.boundsWithoutColorLegend.top,
573
+ this.boundsWithoutColorLegend.bottom,
574
+ ]
575
+ }
576
+
577
+ @computed private get clipPathBounds(): Bounds {
578
+ const { dualAxis, boundsWithoutColorLegend } = this
579
+ return boundsWithoutColorLegend
580
+ .set({ x: dualAxis.innerBounds.x })
581
+ .expand(10)
582
+ }
583
+
584
+ @computed private get clipPath(): ClipPath {
585
+ return makeClipPath({
586
+ renderUid: this.renderUid,
587
+ box: this.clipPathBounds,
588
+ })
589
+ }
590
+
591
+ private runFancyIntroAnimation(): void {
592
+ this.animSelection = select(this.base.current)
593
+ .selectAll("clipPath > rect")
594
+ .attr("width", 0)
595
+ this.animSelection
596
+ .transition()
597
+ .duration(800)
598
+ .ease(easeLinear)
599
+ .attr("width", this.clipPathBounds.width)
600
+ .on("end", () => this.forceUpdate()) // Important in case bounds changes during transition
601
+ }
602
+
603
+ @computed private get lineLegendWidth(): number {
604
+ if (!this.manager.showLegend) return 0
605
+
606
+ // only pass props that are required to calculate
607
+ // the width to avoid circular dependencies
608
+ return LineLegend.stableWidth({
609
+ series: this.lineLegendSeries,
610
+ maxWidth: this.maxLineLegendWidth,
611
+ fontSize: this.fontSize,
612
+ fontWeight: this.fontWeight,
613
+ verticalAlign: VerticalAlign.top,
614
+ })
615
+ }
616
+
617
+ private renderDualAxis(): React.ReactElement {
618
+ const { manager, dualAxis } = this
619
+
620
+ return (
621
+ <DualAxisComponent
622
+ dualAxis={dualAxis}
623
+ showTickMarks={true}
624
+ detailsMarker={manager.detailsMarkerInSvg}
625
+ backgroundColor={manager.backgroundColor}
626
+ />
627
+ )
628
+ }
629
+
630
+ private renderColorLegend(): React.ReactElement | null {
631
+ if (!this.hasColorLegend) return null
632
+ return <HorizontalNumericColorLegend manager={this} />
633
+ }
634
+
635
+ /**
636
+ * Render the lines themselves and their labels
637
+ */
638
+ private renderChartElements(): React.ReactElement {
639
+ const { manager } = this
640
+ return (
641
+ <>
642
+ {manager.showLegend && (
643
+ <LineLegend
644
+ series={this.lineLegendSeries}
645
+ yAxis={this.yAxis}
646
+ x={this.lineLegendX}
647
+ yRange={this.lineLegendY}
648
+ maxWidth={this.maxLineLegendWidth}
649
+ verticalAlign={VerticalAlign.top}
650
+ fontSize={this.fontSize}
651
+ fontWeight={this.fontWeight}
652
+ isStatic={this.isStatic}
653
+ onMouseOver={this.onLineLegendMouseOver}
654
+ onMouseLeave={this.onLineLegendMouseLeave}
655
+ onClick={
656
+ this.canToggleFocusMode
657
+ ? this.onLineLegendClick
658
+ : undefined
659
+ }
660
+ />
661
+ )}
662
+ <Lines
663
+ dualAxis={this.dualAxis}
664
+ series={this.renderSeries}
665
+ multiColor={this.hasColorScale}
666
+ hidePoints={this.hidePoints}
667
+ lineStrokeWidth={this.lineStrokeWidth}
668
+ lineOutlineWidth={this.lineOutlineWidth}
669
+ backgroundColor={this.manager.backgroundColor}
670
+ markerRadius={this.markerRadius}
671
+ isStatic={manager.isStatic}
672
+ />
673
+ </>
674
+ )
675
+ }
676
+
677
+ private renderStatic(): React.ReactElement {
678
+ return (
679
+ <>
680
+ {this.renderColorLegend()}
681
+ {this.renderDualAxis()}
682
+ {this.renderChartElements()}
683
+ </>
684
+ )
685
+ }
686
+
687
+ private renderInteractive(): React.ReactElement {
688
+ return (
689
+ <g
690
+ ref={this.base}
691
+ className={LINE_CHART_CLASS_NAME}
692
+ onMouseLeave={this.onCursorLeave}
693
+ onTouchEnd={this.onCursorLeave}
694
+ onTouchCancel={this.onCursorLeave}
695
+ onMouseMove={this.onCursorMove}
696
+ onTouchStart={this.onCursorMove}
697
+ onTouchMove={this.onCursorMove}
698
+ >
699
+ {/* The tiny bit of extra space in the clippath is to ensure circles
700
+ centered on the very edge are still fully visible */}
701
+ {this.clipPath.element}
702
+ <rect {...this.bounds.toProps()} fillOpacity="0">
703
+ {/* This <rect> ensures that the parent <g> is big enough such that
704
+ we get mouse hover events for the whole charting area, including
705
+ the axis, the entity labels, and the whitespace next to them.
706
+ We need these to be able to show the tooltip for the first/last
707
+ year even if the mouse is outside the charting area. */}
708
+ </rect>
709
+ {this.renderColorLegend()}
710
+ {this.renderDualAxis()}
711
+ <g clipPath={this.clipPath.id}>{this.renderChartElements()}</g>
712
+
713
+ {(this.isTooltipActive || this.hasTimeHighlights) &&
714
+ this.activeXVerticalLines}
715
+ {this.tooltip}
716
+ </g>
717
+ )
718
+ }
719
+
720
+ override render(): React.ReactElement {
721
+ const { manager, dualAxis } = this
722
+
723
+ if (this.chartState.errorInfo.reason)
724
+ return (
725
+ <g>
726
+ {this.renderDualAxis()}
727
+ <NoDataModal
728
+ manager={manager}
729
+ bounds={dualAxis.innerBounds}
730
+ message={this.chartState.errorInfo.reason}
731
+ />
732
+ </g>
733
+ )
734
+
735
+ return manager.isStatic ? this.renderStatic() : this.renderInteractive()
736
+ }
737
+
738
+ @computed protected get yColumnSlugs(): string[] {
739
+ return this.chartState.yColumnSlugs
740
+ }
741
+
742
+ @computed private get colorColumn(): CoreColumn {
743
+ return this.chartState.colorColumn
744
+ }
745
+
746
+ @computed private get formatColumn(): CoreColumn {
747
+ return this.chartState.formatColumn
748
+ }
749
+
750
+ @computed private get hasColorScale(): boolean {
751
+ return this.chartState.hasColorScale
752
+ }
753
+
754
+ @computed private get hasColorLegend(): boolean {
755
+ return (
756
+ this.hasColorScale &&
757
+ !!this.manager.showLegend &&
758
+ !this.manager.isDisplayedAlongsideComplementaryTable
759
+ )
760
+ }
761
+
762
+ @computed get legendX(): number {
763
+ return this.bounds.x
764
+ }
765
+
766
+ @computed get legendMaxWidth(): number {
767
+ return this.bounds.width
768
+ }
769
+
770
+ @computed get legendAlign(): HorizontalAlign {
771
+ return HorizontalAlign.center
772
+ }
773
+
774
+ @computed private get colorScale(): ColorScale {
775
+ return this.chartState.colorScale
776
+ }
777
+
778
+ // TODO just pass colorScale to legend and let it figure it out?
779
+ @computed get numericLegendData(): ColorScaleBin[] {
780
+ // Move CategoricalBins to end
781
+ return _.sortBy(
782
+ this.colorScale.legendBins,
783
+ (bin) => bin instanceof CategoricalBin
784
+ )
785
+ }
786
+
787
+ numericBinSize = 6
788
+ legendTickSize = 1
789
+
790
+ // Used when faceted
791
+ categoricalLegendStyleConfig: LegendStyleConfig = CATEGORICAL_LEGEND_STYLE
792
+
793
+ // Used when the lines are colored by a numeric scale
794
+ numericLegendStyleConfig = NUMERIC_LEGEND_STYLE
795
+
796
+ @computed private get numericLegend():
797
+ | HorizontalNumericColorLegend
798
+ | undefined {
799
+ return this.hasColorScale && this.manager.showLegend
800
+ ? new HorizontalNumericColorLegend({ manager: this })
801
+ : undefined
802
+ }
803
+
804
+ @computed get numericLegendY(): number {
805
+ return this.bounds.top
806
+ }
807
+
808
+ @computed get legendTitle(): string | undefined {
809
+ return this.hasColorScale
810
+ ? this.colorScale.legendDescription
811
+ : undefined
812
+ }
813
+
814
+ @computed get legendHeight(): number {
815
+ return this.numericLegend?.height ?? 0
816
+ }
817
+
818
+ // End of color legend props
819
+
820
+ @computed get series(): readonly LineChartSeries[] {
821
+ return this.chartState.series
822
+ }
823
+
824
+ @computed private get hasMarkersOnlySeries(): boolean {
825
+ return this.series.some((series) => series.plotMarkersOnly)
826
+ }
827
+
828
+ @computed get placedSeries(): PlacedLineChartSeries[] {
829
+ return toPlacedLineChartSeries(this.series, {
830
+ chartState: this.chartState,
831
+ dualAxis: this.dualAxis,
832
+ })
833
+ }
834
+
835
+ private hoverStateForSeries(series: LineChartSeries): InteractionState {
836
+ return getHoverStateForSeries(series, {
837
+ isHoverModeActive: this.isHoverModeActive,
838
+ hoveredSeriesNames: this.hoveredSeriesNames,
839
+ })
840
+ }
841
+
842
+ @computed private get renderSeries(): RenderLineChartSeries[] {
843
+ return toRenderLineChartSeries(this.placedSeries, {
844
+ isFocusModeActive: this.chartState.isFocusModeActive,
845
+ isHoverModeActive: this.isHoverModeActive,
846
+ hoveredSeriesNames: this.hoveredSeriesNames,
847
+ })
848
+ }
849
+
850
+ // Order of the legend items on a line chart should visually correspond
851
+ // to the order of the lines as the approach the legend
852
+ @computed private get lineLegendSeries(): LineLabelSeries[] {
853
+ // If there are any projections, ignore non-projection legends (bit of a hack)
854
+ let series = this.series
855
+ if (series.some((series) => !!series.isProjection))
856
+ series = series.filter((series) => series.isProjection)
857
+
858
+ // Deduplicate series by seriesName to avoid showing the same label multiple times
859
+ const deduplicatedSeries: LineChartSeries[] = []
860
+ const seriesGroupedByName = _.groupBy(series, "seriesName")
861
+ for (const duplicates of Object.values(seriesGroupedByName)) {
862
+ // keep only the label for the series with the most recent data
863
+ // (series are sorted by time, so we can just take the last one)
864
+ deduplicatedSeries.push(R.last(duplicates)!)
865
+ }
866
+
867
+ return deduplicatedSeries.map((series) => {
868
+ const { seriesName, displayName, color } = series
869
+ const lastValue = R.last(series.points)!.y
870
+ return {
871
+ color,
872
+ seriesName,
873
+ // E.g. https://ourworldindata.org/grapher/size-poverty-gap-world
874
+ label: !this.manager.showLegend ? "" : displayName,
875
+ annotation: getAnnotationsForSeries(
876
+ this.annotationsMap,
877
+ seriesName
878
+ ),
879
+ yValue: lastValue,
880
+ focus: series.focus,
881
+ hover: this.hoverStateForSeries(series),
882
+ } satisfies LineLabelSeries
883
+ })
884
+ }
885
+
886
+ @computed private get yAxisConfig(): AxisConfig {
887
+ const { yAxisConfig } = this.manager
888
+ const defaults = getYAxisConfigDefaults(yAxisConfig)
889
+ return new AxisConfig({ ...defaults, ...yAxisConfig }, this)
890
+ }
891
+
892
+ @computed private get xAxisConfig(): AxisConfig {
893
+ const { xAxisConfig } = this.manager
894
+ const custom = { hideGridlines: true }
895
+ return new AxisConfig({ ...custom, ...xAxisConfig }, this)
896
+ }
897
+
898
+ @computed private get horizontalAxisPart(): HorizontalAxis {
899
+ return this.chartState.toHorizontalAxis(this.xAxisConfig)
900
+ }
901
+
902
+ @computed private get verticalAxisPart(): VerticalAxis {
903
+ return this.chartState.toVerticalAxis(this.yAxisConfig)
904
+ }
905
+
906
+ @computed private get innerBounds(): Bounds {
907
+ return (
908
+ this.boundsWithoutColorLegend
909
+ .padRight(
910
+ this.manager.showLegend
911
+ ? this.lineLegendWidth
912
+ : this.defaultRightPadding
913
+ )
914
+ // The top padding leaves room for tick labels.
915
+ // No padding is needed when plotted on a log axis because the
916
+ // log scale notice leaves enough space for tick labels.
917
+ .padTop(this.chartState.isLogScale ? 0 : 6)
918
+ // The bottom padding avoids axis labels to be cut off at some resolutions
919
+ .padBottom(2)
920
+ )
921
+ }
922
+
923
+ @computed get dualAxis(): DualAxis {
924
+ return new DualAxis({
925
+ bounds: this.innerBounds,
926
+ verticalAxis: this.verticalAxisPart,
927
+ horizontalAxis: this.horizontalAxisPart,
928
+ comparisonLines: this.manager.comparisonLines,
929
+ })
930
+ }
931
+
932
+ @computed get yAxis(): VerticalAxis {
933
+ return this.dualAxis.verticalAxis
934
+ }
935
+
936
+ @computed get xAxis(): HorizontalAxis {
937
+ return this.dualAxis.horizontalAxis
938
+ }
939
+
940
+ @computed get externalLegend(): HorizontalColorLegendManager | undefined {
941
+ if (!this.manager.showLegend) {
942
+ const numericLegendData = this.hasColorScale
943
+ ? this.numericLegendData
944
+ : []
945
+ const categoricalLegendData = this.hasColorScale
946
+ ? []
947
+ : this.series.map(
948
+ (series, index) =>
949
+ new CategoricalBin({
950
+ index,
951
+ value: series.seriesName,
952
+ label: series.displayName,
953
+ color: series.color,
954
+ })
955
+ )
956
+ return {
957
+ legendTitle: this.legendTitle,
958
+ legendTickSize: this.legendTickSize,
959
+ numericBinSize: this.numericBinSize,
960
+ numericLegendData,
961
+ categoricalLegendData,
962
+ categoricalLegendStyleConfig: this.categoricalLegendStyleConfig,
963
+ numericLegendStyleConfig: this.numericLegendStyleConfig,
964
+ }
965
+ }
966
+ return undefined
967
+ }
968
+ }