@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,943 @@
1
+ import * as _ from "lodash-es"
2
+ import React from "react"
3
+ import * as R from "remeda"
4
+ import { observer } from "mobx-react"
5
+ import {
6
+ Bounds,
7
+ excludeUndefined,
8
+ getIdealGridParams,
9
+ IDEAL_PLOT_ASPECT_RATIO,
10
+ GridParameters,
11
+ Position,
12
+ PositionMap,
13
+ HorizontalAlign,
14
+ Color,
15
+ makeIdForHumanConsumption,
16
+ exposeInstanceOnWindow,
17
+ SplitBoundsPadding,
18
+ } from "../../utils/index.js"
19
+ import { shortenWithEllipsis } from "../../components/index.js"
20
+ import { action, computed, makeObservable, observable } from "mobx"
21
+ import {
22
+ BASE_FONT_SIZE,
23
+ DEFAULT_GRAPHER_BOUNDS,
24
+ } from "../core/GrapherConstants"
25
+ import {
26
+ GRAPHER_CHART_TYPES,
27
+ GrapherChartType,
28
+ FacetAxisDomain,
29
+ FacetStrategy,
30
+ SeriesColorMap,
31
+ SeriesStrategy,
32
+ AxisConfigInterface,
33
+ ChartErrorInfo,
34
+ } from "../../types/index.js"
35
+ import { ChartComponent, makeChartInstance } from "../chart/ChartTypeMap"
36
+ import { ChartManager } from "../chart/ChartManager"
37
+ import { ChartInterface, ChartState } from "../chart/ChartInterface"
38
+ import {
39
+ calculateAspectRatio,
40
+ getFacetGridPadding,
41
+ getFontSize,
42
+ getLabelPadding,
43
+ } from "./FacetChartUtils"
44
+ import {
45
+ FacetSeries,
46
+ FacetChartProps,
47
+ PlacedFacetSeries,
48
+ FacetChartManager,
49
+ } from "./FacetChartConstants"
50
+ import { OwidTable, CoreColumn } from "../../core-table/index.js"
51
+ import { autoDetectYColumnSlugs, makeSelectionArray } from "../chart/ChartUtils"
52
+ import { SelectionArray } from "../selection/SelectionArray"
53
+ import { AxisConfig } from "../axis/AxisConfig"
54
+ import { HorizontalAxis, VerticalAxis } from "../axis/Axis"
55
+ import {
56
+ HorizontalCategoricalColorLegend,
57
+ HorizontalColorLegend,
58
+ HorizontalColorLegendManager,
59
+ HorizontalNumericColorLegend,
60
+ } from "../legend/HorizontalColorLegends"
61
+ import {
62
+ CategoricalBin,
63
+ ColorScaleBin,
64
+ NumericBin,
65
+ } from "../color/ColorScaleBin"
66
+ import { GRAPHER_DARK_TEXT } from "../color/ColorConstants"
67
+ import { FocusArray } from "../focus/FocusArray"
68
+ import {
69
+ LegendInteractionState,
70
+ LegendStyleConfig,
71
+ } from "../legend/LegendInteractionState"
72
+
73
+ const SHARED_X_AXIS_MIN_FACET_COUNT = 12
74
+
75
+ const facetBackgroundColor = "none" // we don't use color yet but may use it for background later
76
+
77
+ const getContentBounds = (
78
+ containerBounds: Bounds,
79
+ manager: ChartManager,
80
+ chartInstance: ChartInterface
81
+ ): Bounds => {
82
+ let bounds = containerBounds
83
+ const axes = [
84
+ { config: manager.xAxisConfig, axis: chartInstance.xAxis },
85
+ { config: manager.yAxisConfig, axis: chartInstance.yAxis },
86
+ ]
87
+ for (const { config, axis } of axes) {
88
+ if (!config || !axis) continue
89
+ if (!config.hideAxis && config.minSize !== undefined) {
90
+ bounds = bounds.pad({ [axis.orient]: config.minSize })
91
+ }
92
+ }
93
+ return bounds
94
+ }
95
+
96
+ const shouldHideFacetAxis = (
97
+ axis: HorizontalAxis | VerticalAxis | undefined,
98
+ edges: Set<Position>,
99
+ sharedAxesSizes: PositionMap<number>
100
+ ): boolean => {
101
+ if (!axis) return false
102
+ if (axis.hideAxis) return true
103
+ return axis.orient in sharedAxesSizes && !edges.has(axis.orient)
104
+ }
105
+
106
+ interface AxisInfo {
107
+ config: AxisConfigInterface
108
+ axisAccessor: (
109
+ chartInstance: ChartInterface
110
+ ) => HorizontalAxis | VerticalAxis | undefined
111
+ uniform: boolean
112
+ /** only considered when `uniform` is `true`, otherwise ignored */
113
+ shared: boolean
114
+ }
115
+
116
+ interface AxesInfo {
117
+ x: AxisInfo
118
+ y: AxisInfo
119
+ }
120
+
121
+ @observer
122
+ export class FacetChart
123
+ extends React.Component<FacetChartProps>
124
+ implements ChartState, HorizontalColorLegendManager
125
+ {
126
+ constructor(props: FacetChartProps) {
127
+ super(props)
128
+
129
+ makeObservable<FacetChart, "legendHoverBin">(this, {
130
+ legendHoverBin: observable.ref,
131
+ })
132
+ }
133
+
134
+ transformTable(table: OwidTable): OwidTable {
135
+ return table
136
+ }
137
+
138
+ @computed get inputTable(): OwidTable {
139
+ return this.manager.table
140
+ }
141
+
142
+ @computed get transformedTable(): OwidTable {
143
+ return (
144
+ this.manager.transformedTable ??
145
+ this.transformTable(this.inputTable)
146
+ )
147
+ }
148
+
149
+ @computed private get manager(): FacetChartManager {
150
+ return this.props.manager
151
+ }
152
+
153
+ @computed private get chartTypeName(): GrapherChartType {
154
+ return this.props.chartTypeName ?? GRAPHER_CHART_TYPES.LineChart
155
+ }
156
+
157
+ @computed get isStatic(): boolean {
158
+ return !!this.manager.isStatic
159
+ }
160
+
161
+ @computed get errorInfo(): ChartErrorInfo {
162
+ return { reason: "" }
163
+ }
164
+
165
+ @computed private get bounds(): Bounds {
166
+ return this.props.bounds ?? DEFAULT_GRAPHER_BOUNDS
167
+ }
168
+
169
+ @computed private get legendPadding(): number {
170
+ const { isNumericLegend, fontSize } = this
171
+ return isNumericLegend ? 1.5 * fontSize : 0.875 * fontSize
172
+ }
173
+
174
+ @computed private get facetsContainerBounds(): Bounds {
175
+ const legendHeightWithPadding =
176
+ this.showLegend && this.legend.height > 0
177
+ ? this.legend.height + this.legendPadding
178
+ : 0
179
+ return this.bounds.padTop(
180
+ legendHeightWithPadding + 1.25 * this.facetFontSize
181
+ )
182
+ }
183
+
184
+ @computed get fontSize(): number {
185
+ return this.manager.fontSize ?? BASE_FONT_SIZE
186
+ }
187
+
188
+ @computed private get facetFontSize(): number {
189
+ return getFontSize(this.bounds.width, this.series.length, this.fontSize)
190
+ }
191
+
192
+ @computed private get yAxisConfig(): AxisConfig {
193
+ return new AxisConfig(this.manager.yAxisConfig, {
194
+ fontSize: this.facetFontSize,
195
+ })
196
+ }
197
+
198
+ @computed private get uniformYAxis(): boolean {
199
+ // default to shared
200
+ const facetDomain =
201
+ this.yAxisConfig.facetDomain || FacetAxisDomain.shared
202
+ return facetDomain === FacetAxisDomain.shared
203
+ }
204
+
205
+ @computed private get uniformXAxis(): boolean {
206
+ // TODO: maybe should not be the default for ScatterPlot?
207
+ return true
208
+ }
209
+
210
+ @computed private get facetCount(): number {
211
+ return this.series.length
212
+ }
213
+
214
+ @computed private get gridParams(): GridParameters {
215
+ const count = this.facetCount
216
+ const { width, height } = this.bounds
217
+
218
+ const containerAspectRatio = calculateAspectRatio(width, height)
219
+
220
+ return getIdealGridParams({
221
+ count,
222
+ containerAspectRatio,
223
+ idealAspectRatio: IDEAL_PLOT_ASPECT_RATIO,
224
+ })
225
+ }
226
+
227
+ @computed private get facetGridPadding(): SplitBoundsPadding {
228
+ const { isSharedXAxis, facetFontSize } = this
229
+ return getFacetGridPadding({
230
+ baseFontSize: facetFontSize,
231
+ shouldAddRowPadding: !isSharedXAxis,
232
+ })
233
+ }
234
+
235
+ @computed private get hideFacetLegends(): boolean {
236
+ return true
237
+ }
238
+
239
+ // Passing this color map is important to ensure that all facets use the same entity colors
240
+ seriesColorMap: SeriesColorMap = this.manager?.seriesColorMap ?? new Map()
241
+
242
+ /**
243
+ * Holds the intermediate render properties for chart views, before axes are synchronized,
244
+ * collapsed, aligned, etc.
245
+ *
246
+ * An example: a StackedArea has a Y axis domain that is the largest sum of all columns.
247
+ * In order to avoid replicating that logic here (stacking values), we initialize StackedArea
248
+ * instances, without rendering them. In a later method, we use those intermediate chart views to
249
+ * determine the final axes for facets, e.g. for a uniform axis, we would iterate through all
250
+ * instances to find the full extent of the domain.
251
+ *
252
+ * @danielgavrilov, 2021-07-13
253
+ */
254
+ @computed private get intermediatePlacedSeries(): PlacedFacetSeries[] {
255
+ const { manager, series, facetCount, seriesColorMap, legendHoverBin } =
256
+ this
257
+
258
+ // Copy properties from manager to facets
259
+ const fontSize = this.facetFontSize
260
+ // We are using `bounds` instead of `facetsContainerBounds` because the legend
261
+ // is not yet created, and it is derived from the intermediate chart series.
262
+ const gridBoundsArr = this.bounds.grid(
263
+ this.gridParams,
264
+ this.facetGridPadding
265
+ )
266
+
267
+ const {
268
+ yColumnSlug,
269
+ xColumnSlug,
270
+ yColumnSlugs,
271
+ colorColumnSlug,
272
+ numericColorColumnSlug,
273
+ categoricalColorColumnSlug,
274
+ sizeColumnSlug,
275
+ isRelativeMode,
276
+ colorScale,
277
+ sortConfig,
278
+ startHandleTimeBound,
279
+ startTime,
280
+ endTime,
281
+ missingDataStrategy,
282
+ backgroundColor,
283
+ focusArray,
284
+ isStatic,
285
+ base,
286
+ tooltip,
287
+ shouldPinTooltipToBottom,
288
+ } = manager
289
+
290
+ // Use compact labels, e.g. 50k instead of 50,000.
291
+ const numberAbbreviation = facetCount > 2 ? "short" : "long"
292
+ const globalXAxisConfig: AxisConfigInterface = {
293
+ tickFormattingOptions: { numberAbbreviation },
294
+ }
295
+ const globalYAxisConfig: AxisConfigInterface = {
296
+ tickFormattingOptions: { numberAbbreviation },
297
+ }
298
+
299
+ // We infer that the user cares about the trend if the axis is not uniform
300
+ // and the metrics on all facets are the same
301
+ const careAboutTrend = !this.uniformYAxis
302
+ if (careAboutTrend) {
303
+ // Force disable nice axes if we care about the trend,
304
+ // because nice axes misrepresent trends.
305
+ globalYAxisConfig.nice = false
306
+ }
307
+
308
+ const table = this.transformedTable
309
+
310
+ // In order to produce consistent color scales across facets, we need to pass
311
+ // all possible color values from `inputTable`.
312
+ const colorScaleColumnOverride = this.inputTable.get(colorColumnSlug)
313
+
314
+ return series.map((series, index) => {
315
+ const { bounds } = gridBoundsArr[index]
316
+ const showLegend = !this.hideFacetLegends
317
+
318
+ const hidePoints = true
319
+ const hideNoDataSection = true
320
+
321
+ // NOTE: The order of overrides is important!
322
+ // We need to preserve most config coming in.
323
+ const manager: ChartManager = {
324
+ table,
325
+ fontSize,
326
+ showLegend,
327
+ hidePoints,
328
+ yColumnSlug,
329
+ xColumnSlug,
330
+ yColumnSlugs,
331
+ colorColumnSlug,
332
+ categoricalColorColumnSlug,
333
+ numericColorColumnSlug,
334
+ sizeColumnSlug,
335
+ isRelativeMode,
336
+ seriesColorMap,
337
+ colorScale,
338
+ colorScaleColumnOverride,
339
+ sortConfig,
340
+ startHandleTimeBound,
341
+ startTime,
342
+ endTime,
343
+ missingDataStrategy,
344
+ backgroundColor,
345
+ hideNoDataSection,
346
+ focusArray,
347
+ isStatic,
348
+ base,
349
+ tooltip,
350
+ shouldPinTooltipToBottom,
351
+ externalLegendHoverBin: legendHoverBin,
352
+ ...series.manager,
353
+ xAxisConfig: {
354
+ ...globalXAxisConfig,
355
+ ...this.manager.xAxisConfig,
356
+ ...series.manager.xAxisConfig,
357
+ },
358
+ yAxisConfig: {
359
+ ...globalYAxisConfig,
360
+ ...this.manager.yAxisConfig,
361
+ ...series.manager.yAxisConfig,
362
+ },
363
+ }
364
+ return {
365
+ bounds,
366
+ contentBounds: bounds,
367
+ manager,
368
+ seriesName: series.seriesName,
369
+ color: series.color,
370
+ }
371
+ })
372
+ }
373
+
374
+ @computed get intermediateChartInstances(): ChartInterface[] {
375
+ return this.intermediatePlacedSeries.map(({ bounds, manager }) => {
376
+ return makeChartInstance({
377
+ manager,
378
+ bounds,
379
+ chartType: this.chartTypeName,
380
+ variant: this.manager.variant,
381
+ })
382
+ })
383
+ }
384
+
385
+ @computed private get isSharedYAxis(): boolean {
386
+ // When the Y axis is uniform for all facets:
387
+ // - for most charts, we want to only show the axis on the left-most facet charts, and omit
388
+ // it on the others
389
+ // - for bar charts the Y axis is plotted horizontally, so we don't want to omit it
390
+ return (
391
+ this.uniformYAxis &&
392
+ ![
393
+ GRAPHER_CHART_TYPES.StackedDiscreteBar,
394
+ GRAPHER_CHART_TYPES.DiscreteBar,
395
+ ].includes(this.chartTypeName as any)
396
+ )
397
+ }
398
+
399
+ @computed private get isSharedXAxis(): boolean {
400
+ return (
401
+ this.uniformXAxis &&
402
+ // TODO: do this for stacked area charts and line charts as well?
403
+ this.chartTypeName === GRAPHER_CHART_TYPES.StackedBar &&
404
+ this.facetCount >= SHARED_X_AXIS_MIN_FACET_COUNT
405
+ )
406
+ }
407
+
408
+ // Only made public for testing
409
+ @computed get placedSeries(): PlacedFacetSeries[] {
410
+ const { intermediateChartInstances } = this
411
+
412
+ // If one of the charts should use a value-based color scheme,
413
+ // switch them all over for consistency
414
+ const useValueBasedColorScheme = intermediateChartInstances.some(
415
+ (instance) => instance.shouldUseValueBasedColorScheme
416
+ )
417
+
418
+ // Define the global axis config, shared between all facets
419
+ const sharedAxesSizes: PositionMap<number> = {}
420
+
421
+ const axes: AxesInfo = {
422
+ x: {
423
+ config: {},
424
+ axisAccessor: (instance) => instance.xAxis,
425
+ uniform: this.uniformXAxis,
426
+ shared: this.isSharedXAxis,
427
+ },
428
+ y: {
429
+ config: {},
430
+ axisAccessor: (instance) => instance.yAxis,
431
+ uniform: this.uniformYAxis,
432
+ shared: this.isSharedYAxis,
433
+ },
434
+ }
435
+ R.values(axes).forEach(({ config, axisAccessor, uniform, shared }) => {
436
+ // max size is the width (if vertical axis) or height (if horizontal axis)
437
+ const axisWithMaxSize = _.maxBy(
438
+ intermediateChartInstances.map(axisAccessor),
439
+ (axis) => axis?.size
440
+ )
441
+ if (uniform) {
442
+ const axes = excludeUndefined(
443
+ intermediateChartInstances.map(axisAccessor)
444
+ )
445
+
446
+ // If the axes are uniform, we want to find the full domain extent across all facets
447
+ const domains = axes.map((axis) => axis.domain)
448
+ config.min = _.min(domains.map((d) => d[0]))
449
+ config.max = _.max(domains.map((d) => d[1]))
450
+
451
+ // Find domain values across all facets
452
+ const domainValues = _.uniq(
453
+ axes.flatMap((axis) => axis.config.domainValues ?? [])
454
+ )
455
+ if (domainValues.length > 0) config.domainValues = domainValues
456
+
457
+ // Find ticks across all facets
458
+ const ticks = _.uniq(
459
+ axes.flatMap((axis) => axis.config.ticks ?? [])
460
+ )
461
+ if (ticks.length > 0) config.ticks = ticks
462
+
463
+ // If there was at least one chart with a non-undefined axis,
464
+ // this variable will be populated
465
+ if (axisWithMaxSize) {
466
+ // Create a new axis object with the full domain extent
467
+ const axis = axisWithMaxSize.clone()
468
+ const { size } = axis.updateDomainPreservingUserSettings([
469
+ config.min,
470
+ config.max,
471
+ ])
472
+ config.minSize = size
473
+ if (shared) {
474
+ const sharedAxisSize =
475
+ axis.orient === Position.bottom ? 0 : size
476
+ sharedAxesSizes[axis.orient] = sharedAxisSize
477
+ }
478
+ }
479
+ } else if (axisWithMaxSize) {
480
+ config.minSize = axisWithMaxSize.size
481
+ }
482
+ })
483
+
484
+ // Allocate space for shared axes, so that the content areas of charts are all equal.
485
+ // If the axes are "shared", then an axis will only plotted on the facets that are on the
486
+ // same side as the axis.
487
+ // For example, a vertical Y axis would be plotted on the left-most charts only.
488
+ // An exception is the bottom axis, which gets plotted on the top row of charts, instead of
489
+ // the bottom row of charts.
490
+ const fullBounds = this.facetsContainerBounds.pad(sharedAxesSizes)
491
+ const gridBoundsArr = fullBounds.grid(
492
+ this.gridParams,
493
+ this.facetGridPadding
494
+ )
495
+ return this.intermediatePlacedSeries.map((series, i) => {
496
+ const chartInstance = intermediateChartInstances[i]
497
+ const { xAxis, yAxis } = chartInstance
498
+ const { bounds: initialGridBounds, cellEdges } = gridBoundsArr[i]
499
+ let bounds = initialGridBounds
500
+ // Only expand bounds if the facet is on the same edge as the shared axes
501
+ for (const edge of cellEdges) {
502
+ bounds = bounds.expand({
503
+ [edge]: sharedAxesSizes[edge],
504
+ })
505
+ }
506
+ // NOTE: The order of overrides is important!
507
+ // We need to preserve most config coming in.
508
+ const manager = {
509
+ ...series.manager,
510
+ useValueBasedColorScheme,
511
+ xAxisConfig: {
512
+ // For now, sharing an x axis means hiding the tick labels of inner facets.
513
+ // This means that none of the x axes are actually hidden (we just don't plot their tick labels).
514
+ // If we ever allow shared x axes to be actually hidden, we need to be careful with how we determine
515
+ // the `minSize` – in the intermediate series (at this time) all axes are shown in
516
+ // order to find the one with maximum size, but in the placed series, some axes are
517
+ // hidden. This expands the available area for the chart, which can in turn increase
518
+ // the number of ticks shown, which can make the size of the axis in the placed
519
+ // series greater than the one in the intermediate series.
520
+ hideTickLabels: shouldHideFacetAxis(
521
+ xAxis,
522
+ cellEdges,
523
+ sharedAxesSizes
524
+ ),
525
+ ...series.manager.xAxisConfig,
526
+ ...axes.x.config,
527
+ },
528
+ yAxisConfig: {
529
+ hideAxis: shouldHideFacetAxis(
530
+ yAxis,
531
+ cellEdges,
532
+ sharedAxesSizes
533
+ ),
534
+ ...series.manager.yAxisConfig,
535
+ ...axes.y.config,
536
+ },
537
+ }
538
+ const contentBounds = getContentBounds(
539
+ bounds,
540
+ manager,
541
+ chartInstance
542
+ )
543
+ return {
544
+ ...series,
545
+ bounds,
546
+ contentBounds,
547
+ manager,
548
+ }
549
+ })
550
+ }
551
+
552
+ @computed get selectionArray(): SelectionArray {
553
+ return makeSelectionArray(this.manager.selection)
554
+ }
555
+
556
+ @computed get focusArray(): FocusArray {
557
+ return this.manager.focusArray ?? new FocusArray()
558
+ }
559
+
560
+ @computed private get entityFacets(): FacetSeries[] {
561
+ const table = this.transformedTable.filterByEntityNames(
562
+ this.selectionArray.selectedEntityNames
563
+ )
564
+ return this.selectionArray.selectedEntityNames.map((seriesName) => {
565
+ const seriesTable = table.filterByEntityNames([seriesName])
566
+ // Only set overrides for this facet strategy.
567
+ // Default properties are set elsewhere.
568
+ const manager: ChartManager = {
569
+ table: seriesTable,
570
+ selection: [seriesName],
571
+ seriesStrategy: SeriesStrategy.column,
572
+ }
573
+ return {
574
+ seriesName,
575
+ color: facetBackgroundColor,
576
+ manager,
577
+ }
578
+ })
579
+ }
580
+
581
+ @computed private get columnFacets(): FacetSeries[] {
582
+ return this.yColumns.map((col) => ({
583
+ seriesName: col.displayName,
584
+ color: facetBackgroundColor,
585
+ // Only set overrides for this facet strategy.
586
+ // Default properties are set elsewhere.
587
+ manager: {
588
+ selection: this.selectionArray,
589
+ yColumnSlug: col.slug,
590
+ yColumnSlugs: [col.slug],
591
+ seriesStrategy: SeriesStrategy.entity,
592
+ },
593
+ }))
594
+ }
595
+
596
+ @computed private get yColumns(): CoreColumn[] {
597
+ return this.yColumnSlugs.map((slug) => this.inputTable.get(slug))
598
+ }
599
+
600
+ @computed private get yColumnSlugs(): string[] {
601
+ return autoDetectYColumnSlugs(this.manager)
602
+ }
603
+
604
+ @computed private get facetStrategy(): FacetStrategy {
605
+ return this.manager.facetStrategy ?? FacetStrategy.none
606
+ }
607
+
608
+ @computed get series(): FacetSeries[] {
609
+ return this.facetStrategy === FacetStrategy.metric
610
+ ? this.columnFacets
611
+ : this.entityFacets
612
+ }
613
+
614
+ // legend utils
615
+
616
+ @computed private get externalLegends(): HorizontalColorLegendManager[] {
617
+ return excludeUndefined(
618
+ this.intermediateChartInstances.map(
619
+ (instance) => instance.externalLegend
620
+ )
621
+ )
622
+ }
623
+
624
+ @computed private get isNumericLegend(): boolean {
625
+ return this.externalLegends.some((legend) =>
626
+ legend.numericLegendData?.some((bin) => bin instanceof NumericBin)
627
+ )
628
+ }
629
+
630
+ @computed private get LegendClass():
631
+ | typeof HorizontalNumericColorLegend
632
+ | typeof HorizontalCategoricalColorLegend {
633
+ return this.isNumericLegend
634
+ ? HorizontalNumericColorLegend
635
+ : HorizontalCategoricalColorLegend
636
+ }
637
+
638
+ @computed private get showLegend(): boolean {
639
+ const { isNumericLegend, categoricalLegendData, numericLegendData } =
640
+ this
641
+ if (this.manager.isDisplayedAlongsideComplementaryTable) return false
642
+ const hasBins =
643
+ categoricalLegendData.length > 0 || numericLegendData.length > 0
644
+ if (!hasBins) return false
645
+ if (isNumericLegend) return true
646
+ if (
647
+ this.props.chartTypeName ===
648
+ GRAPHER_CHART_TYPES.StackedDiscreteBar &&
649
+ this.facetStrategy === FacetStrategy.metric
650
+ ) {
651
+ return false
652
+ }
653
+ if (
654
+ categoricalLegendData.length > 1 ||
655
+ // If the facetStrategy is metric, then the legend (probably?) shows entity items.
656
+ // If the user happens to select only a single entity, we don't want to collapse the
657
+ // legend, because it's (probably?) the only information about what is selected.
658
+ // This is fragile and ideally we shouldn't be making assumptions about what type of
659
+ // items are shown on the legend, but it works for now...
660
+ // -@danielgavrilov, 2021-09-28
661
+ (this.facetStrategy === FacetStrategy.metric &&
662
+ this.props.manager.canSelectMultipleEntities)
663
+ ) {
664
+ return true
665
+ }
666
+ return false
667
+ }
668
+
669
+ private getExternalLegendProp<
670
+ Prop extends keyof HorizontalColorLegendManager,
671
+ >(prop: Prop): HorizontalColorLegendManager[Prop] | undefined {
672
+ for (const externalLegend of this.externalLegends) {
673
+ if (externalLegend[prop] !== undefined) {
674
+ return externalLegend[prop]
675
+ }
676
+ }
677
+ return undefined
678
+ }
679
+
680
+ private getUniqBins<Bin extends ColorScaleBin>(bins: Bin[]): Bin[] {
681
+ return _.uniqWith(bins, (binA, binB): boolean => {
682
+ // For categorical bins, the `.equals()` method isn't good enough,
683
+ // because it only compares `.index`, which in this case can be
684
+ // identical even when the bins are not, because they are coming
685
+ // from different charts (facets).
686
+ if (binA instanceof CategoricalBin) {
687
+ return binA.text === binB.text
688
+ }
689
+ return binA.equals(binB)
690
+ })
691
+ }
692
+
693
+ // legend props
694
+
695
+ @computed get legendX(): number {
696
+ return this.bounds.x
697
+ }
698
+
699
+ @computed get numericLegendY(): number {
700
+ return this.bounds.top
701
+ }
702
+
703
+ @computed get categoryLegendY(): number {
704
+ return this.bounds.top
705
+ }
706
+
707
+ @computed get legendMaxWidth(): number {
708
+ return this.bounds.width
709
+ }
710
+
711
+ @computed get legendAlign(): HorizontalAlign {
712
+ return this.isNumericLegend
713
+ ? HorizontalAlign.center
714
+ : HorizontalAlign.left
715
+ }
716
+
717
+ @computed get legendTitle(): string | undefined {
718
+ return this.getExternalLegendProp("legendTitle")
719
+ }
720
+
721
+ @computed get legendHeight(): number | undefined {
722
+ return this.getExternalLegendProp("legendHeight")
723
+ }
724
+
725
+ @computed get legendTickSize(): number | undefined {
726
+ return this.getExternalLegendProp("legendTickSize")
727
+ }
728
+
729
+ @computed get numericBinSize(): number | undefined {
730
+ return this.getExternalLegendProp("numericBinSize")
731
+ }
732
+
733
+ @computed get hoverColors(): Color[] | undefined {
734
+ if (!this.legendHoverBin) return undefined
735
+ return [this.legendHoverBin.color]
736
+ }
737
+
738
+ @computed get activeColors(): Color[] | undefined {
739
+ if (!this.focusArray) return undefined
740
+
741
+ // find colours of all currently focused series
742
+ const activeColors = _.uniq(
743
+ this.intermediateChartInstances.flatMap((chartInstance) =>
744
+ chartInstance.chartState.series
745
+ .filter((series) => this.focusArray.has(series.seriesName))
746
+ .map((series) => series.color)
747
+ )
748
+ )
749
+
750
+ return activeColors.length > 0 ? activeColors : undefined
751
+ }
752
+
753
+ @computed get numericLegendData(): ColorScaleBin[] {
754
+ if (!this.isNumericLegend || !this.hideFacetLegends) return []
755
+ const allBins: ColorScaleBin[] = this.externalLegends.flatMap(
756
+ (legend) => [
757
+ ...(legend.numericLegendData ?? []),
758
+ ...(legend.categoricalLegendData ?? []),
759
+ ]
760
+ )
761
+ const uniqBins = this.getUniqBins(allBins)
762
+ const sortedBins = _.sortBy(
763
+ uniqBins,
764
+ (bin) => bin instanceof CategoricalBin
765
+ )
766
+ return sortedBins
767
+ }
768
+
769
+ @computed get categoricalLegendData(): CategoricalBin[] {
770
+ if (this.isNumericLegend || !this.hideFacetLegends) return []
771
+
772
+ const allBins: CategoricalBin[] = this.externalLegends
773
+ .flatMap((legend) => [
774
+ ...(legend.numericLegendData ?? []),
775
+ ...(legend.categoricalLegendData ?? []),
776
+ ])
777
+ .filter((bin) => bin instanceof CategoricalBin) as CategoricalBin[]
778
+
779
+ const uniqBins = this.getUniqBins(allBins).map(
780
+ // Remap index to ensure it's unique (the above procedure can lead to duplicates)
781
+ (bin, index) => new CategoricalBin({ ...bin.props, index })
782
+ )
783
+
784
+ // Hide single-item legends for metric facets
785
+ if (this.facetStrategy === FacetStrategy.metric && uniqBins.length <= 1)
786
+ return []
787
+
788
+ // Stacked area and bar charts reverse the stacking order
789
+ if (
790
+ this.chartTypeName === GRAPHER_CHART_TYPES.StackedArea ||
791
+ this.chartTypeName === GRAPHER_CHART_TYPES.StackedBar
792
+ )
793
+ return _.reverse(uniqBins)
794
+
795
+ return uniqBins
796
+ }
797
+
798
+ private legendHoverBin: ColorScaleBin | undefined = undefined
799
+
800
+ @action.bound onLegendMouseOver(bin: ColorScaleBin): void {
801
+ this.legendHoverBin = bin
802
+ }
803
+
804
+ @action.bound onLegendMouseLeave(): void {
805
+ this.legendHoverBin = undefined
806
+ }
807
+
808
+ @action.bound onLegendClick(bin: ColorScaleBin): void {
809
+ if (!this.manager.focusArray || !this.isFocusModeSupported) return
810
+
811
+ // find all series (of all facets) that are contained in the bin
812
+ const seriesNames = _.uniq(
813
+ this.intermediateChartInstances.flatMap((chartInstance) =>
814
+ chartInstance.chartState.series
815
+ .filter((series) => bin.contains(series.seriesName))
816
+ .map((series) => series.seriesName)
817
+ )
818
+ )
819
+
820
+ this.manager.focusArray.toggle(...seriesNames)
821
+ }
822
+
823
+ getLegendBinState(bin: ColorScaleBin): LegendInteractionState {
824
+ if (!this.activeColors && !this.hoverColors)
825
+ return LegendInteractionState.Default
826
+
827
+ const isHovered = this.hoverColors?.includes(bin.color)
828
+ if (isHovered) return LegendInteractionState.Focused
829
+
830
+ const isActive = this.activeColors?.includes(bin.color)
831
+ return isActive
832
+ ? LegendInteractionState.Focused
833
+ : LegendInteractionState.Muted
834
+ }
835
+
836
+ @computed get legendStyleConfig(): LegendStyleConfig | undefined {
837
+ return this.externalLegends[0]?.legendStyleConfig
838
+ }
839
+
840
+ @computed get numericLegendStyleConfig(): LegendStyleConfig | undefined {
841
+ return this.externalLegends[0]?.numericLegendStyleConfig
842
+ }
843
+
844
+ @computed get categoricalLegendStyleConfig():
845
+ | LegendStyleConfig
846
+ | undefined {
847
+ return this.externalLegends[0]?.categoricalLegendStyleConfig
848
+ }
849
+
850
+ // end of legend props
851
+
852
+ @computed private get legend(): HorizontalColorLegend {
853
+ return new this.LegendClass({ manager: this })
854
+ }
855
+
856
+ @computed private get isFocusModeSupported(): boolean {
857
+ return (
858
+ this.chartTypeName === GRAPHER_CHART_TYPES.LineChart ||
859
+ this.chartTypeName === GRAPHER_CHART_TYPES.SlopeChart
860
+ )
861
+ }
862
+
863
+ /**
864
+ * In order to display a potentially long facet label in the potentially tight space, we
865
+ * shrink and shorten the label as follows to prevent overlap between neighbouring labels:
866
+ * - If the label already fits, we're happy :)
867
+ * - Otherwise, we calculate the ideal font size where it would fit perfectly.
868
+ * However, in order to not make the label tiny, we cap the font size at 0.7 * baseFontSize
869
+ * - If the label still doesn't fit, we shorten it to the number of characters that fit and append an ellispsis (…)
870
+ * -@MarcelGerber, 2021-10-28
871
+ */
872
+ private shrinkAndShortenFacetLabel(
873
+ label: string,
874
+ availableWidth: number,
875
+ baseFontSize: number // font size to use when we're not shrinking
876
+ ): { fontSize: number; shortenedLabel: string } {
877
+ // How much width would we need if we were to render the text at font size 1?
878
+ // We calculate this to compute the ideal font size from the available width.
879
+ const textBounds = Bounds.forText(label, {
880
+ fontSize: 1,
881
+ })
882
+ const idealFontSize = availableWidth / textBounds.width
883
+
884
+ // Clamp the ideal font size: 0.7 * baseFontSize <= fontSize <= baseFontSize
885
+ const fontSize = Math.min(
886
+ baseFontSize,
887
+ Math.max(0.7 * baseFontSize, idealFontSize)
888
+ )
889
+
890
+ if (fontSize > idealFontSize) {
891
+ label = shortenWithEllipsis(label, availableWidth, { fontSize })
892
+ }
893
+
894
+ return { fontSize, shortenedLabel: label }
895
+ }
896
+
897
+ override componentDidMount(): void {
898
+ exposeInstanceOnWindow(this, "facetChart")
899
+ }
900
+
901
+ override render(): React.ReactElement {
902
+ const { facetFontSize, LegendClass, showLegend } = this
903
+ return (
904
+ <React.Fragment>
905
+ {showLegend && <LegendClass manager={this} />}
906
+ {this.placedSeries.map((facetChart, index: number) => {
907
+ const { bounds, contentBounds, seriesName } = facetChart
908
+ const labelPadding = getLabelPadding(facetFontSize)
909
+
910
+ const { fontSize, shortenedLabel } =
911
+ this.shrinkAndShortenFacetLabel(
912
+ seriesName,
913
+ contentBounds.width,
914
+ facetFontSize
915
+ )
916
+
917
+ return (
918
+ <React.Fragment key={index}>
919
+ <text
920
+ x={contentBounds.x}
921
+ y={contentBounds.top - labelPadding}
922
+ fill={GRAPHER_DARK_TEXT}
923
+ fontSize={fontSize}
924
+ style={{ fontWeight: 700 }}
925
+ >
926
+ {shortenedLabel}
927
+ <title>{seriesName}</title>
928
+ </text>
929
+ <g id={makeIdForHumanConsumption(seriesName)}>
930
+ <ChartComponent
931
+ manager={facetChart.manager}
932
+ chartType={this.chartTypeName}
933
+ variant={this.manager.variant}
934
+ bounds={bounds}
935
+ />
936
+ </g>
937
+ </React.Fragment>
938
+ )
939
+ })}
940
+ </React.Fragment>
941
+ )
942
+ }
943
+ }