@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,82 @@
1
+ import * as React from "react"
2
+ import { GrapherProgrammaticInterface } from "../core/Grapher.js"
3
+ import { GrapherState } from "../core/GrapherState.js"
4
+ import { MultiDimDataPageConfig, Url } from "../../utils/index.js"
5
+ import {
6
+ GrapherQueryParams,
7
+ MultiDimDimensionChoices,
8
+ ChartConfigType,
9
+ } from "../../types/index.js"
10
+
11
+ export interface ArchiveGuidedChartRegistration {
12
+ iframeRef: React.RefObject<HTMLIFrameElement | null>
13
+ baseUrl: string
14
+ defaultQueryParams: Record<string, string | undefined>
15
+ chartConfigType: ChartConfigType
16
+ }
17
+
18
+ export interface GuidedChartContextValue {
19
+ grapherStateRef: React.RefObject<GrapherState>
20
+ chartRef?: React.RefObject<HTMLDivElement>
21
+ onGuidedChartLinkClick?: (href: string) => void
22
+ registerArchiveChart?: (
23
+ registration: ArchiveGuidedChartRegistration
24
+ ) => () => void
25
+ registerMultiDim?: (registrationData: {
26
+ config: MultiDimDataPageConfig
27
+ onSettingsChange: (
28
+ newSettings: MultiDimDimensionChoices,
29
+ queryParams: GrapherQueryParams
30
+ ) => void
31
+ grapherContainerRef: React.RefObject<HTMLDivElement | null>
32
+ }) => void
33
+ }
34
+
35
+ export const GuidedChartContext =
36
+ React.createContext<GuidedChartContextValue | null>(null)
37
+
38
+ /**
39
+ * If called within a `GuidedChartContext`, sets the context's `grapherStateRef`
40
+ * to a new `GrapherState` instance initialized with the provided config.
41
+ * If no context is available, returns a local ref initialized with the config.
42
+ * This is so the `GrapherState` can be controlled from a GuidedChart,
43
+ * but also allows for local usage when not
44
+ */
45
+ export function useMaybeGlobalGrapherStateRef(
46
+ config: GrapherProgrammaticInterface
47
+ ): React.RefObject<GrapherState> {
48
+ const context = React.useContext(GuidedChartContext)
49
+ const localRef = React.useRef<GrapherState | null>(null)
50
+
51
+ // If a context is provided, use it; otherwise, use the local ref
52
+ const refToUse = context?.grapherStateRef || localRef
53
+
54
+ // Only initialize if the ref is empty
55
+ if (!refToUse.current) {
56
+ refToUse.current = new GrapherState(config)
57
+ }
58
+
59
+ return refToUse as React.RefObject<GrapherState>
60
+ }
61
+
62
+ export function useGuidedChartLinkHandler():
63
+ | ((href: string) => void)
64
+ | undefined {
65
+ const context = React.useContext(GuidedChartContext)
66
+ return context?.onGuidedChartLinkClick
67
+ }
68
+
69
+ export const buildArchiveGuidedChartSrc = (
70
+ registration: ArchiveGuidedChartRegistration,
71
+ guidedUrl: Url
72
+ ): string => {
73
+ const baseUrl = Url.fromURL(registration.baseUrl)
74
+ const mergedQuery = {
75
+ ...registration.defaultQueryParams,
76
+ ...guidedUrl.queryParams,
77
+ }
78
+ const updatedUrl = baseUrl.setQueryParams(mergedQuery)
79
+
80
+ const hash = guidedUrl.hash || baseUrl.hash
81
+ return hash ? updatedUrl.update({ hash }).fullUrl : updatedUrl.fullUrl
82
+ }
@@ -0,0 +1,484 @@
1
+ import {
2
+ automaticBinningStrategies,
3
+ AutomaticBinningStrategy,
4
+ MidpointMode,
5
+ ResolvedBinningStrategy,
6
+ } from "../../types/index.js"
7
+ import {
8
+ firstOfNonEmptyArray,
9
+ lastOfNonEmptyArray,
10
+ } from "../../utils/index.js"
11
+ import { quantile } from "d3"
12
+ import { sortedUniq } from "lodash-es"
13
+ import * as R from "remeda"
14
+ import { match, P } from "ts-pattern"
15
+ import {
16
+ isLogBinningStrategy,
17
+ runLogBinningStrategy,
18
+ } from "./BinningStrategyLogarithmic.js"
19
+ import {
20
+ isEqualSizeBinningStrategy,
21
+ runEqualSizeBinningStrategy,
22
+ } from "./BinningStrategyEqualSizeBins.js"
23
+
24
+ /**
25
+ * Strategies:
26
+ * - The log scales result in log-like steps, e.g. 1, 2, 5, 10, ...
27
+ * They are fully defined given a minValue, maxValue and steps, and
28
+ * then generate as many bins as needed to cover the range.
29
+ * `log-auto` chooses the step size automatically in order to get a decent number of bins.
30
+ * - The equal size bins result in evenly spaced steps, e.g. 0, 1, 2, 3, ...
31
+ * They are defined by a minValue, maxValue, and a rough target number of bins.
32
+ * `equalSizeBins-few-bins` chooses a small number of bins, while
33
+ * `equalSizeBins-many-bins` chooses a large number of bins.
34
+ * They then generate nice round bin thresholds given the input data.
35
+ * - `equalSizeBins-percent` is a special case, where for data that looks like percent
36
+ * from 0% to 100% we want to mostly use 0%, 10%, 20%, etc. bins.
37
+ *
38
+ * Midpoints:
39
+ * Sometimes, we do have a midpoint in our data. In many cases, a natural midpoint is zero
40
+ * (e.g. for year-over-year change, net migration, temperature anomaly, etc.), but other
41
+ * midpoints also make sense (e.g. for sex ratio, or survey responses ranging 0-10).
42
+ * If we have a midpoint, then we want to account for it when binning, and generate bins
43
+ * that are centered or symmetric around the midpoint.
44
+ */
45
+
46
+ export interface BinningStrategyConfig {
47
+ strategy: AutomaticBinningStrategy
48
+ minValue?: number
49
+ maxValue?: number
50
+ sortedValues: number[]
51
+ isPercent?: boolean
52
+ numDecimalPlaces?: number
53
+ midpointMode?: MidpointMode
54
+ midpoint?: number
55
+ createBinForMidpoint?: boolean
56
+ }
57
+
58
+ export interface ResolvedBinningStrategyConfig {
59
+ strategy: ResolvedBinningStrategy
60
+ minValue?: number
61
+ maxValue?: number
62
+ numDecimalPlaces?: number
63
+ sortedValues: number[]
64
+ midpointMode: MidpointMode
65
+ midpoint: number
66
+ }
67
+
68
+ interface BinningStrategyOutput {
69
+ bins: number[]
70
+ midpoint?: number
71
+ }
72
+
73
+ // If the log10 difference between the min and max values is less than or equal to 1.2,
74
+ // the data is considered to be close enough in magnitude to use equal-size bins.
75
+ // This threshold was chosen empirically.
76
+ const AUTO_EQUAL_BINS_MAX_MAGNITUDE_DIFF = 1.2
77
+
78
+ /**
79
+ * Calculates the log10 difference between two numbers, i.e. compute x such that lowerValue * 10^x = upperValue.
80
+ */
81
+ export const calcMagnitudeDiff = (
82
+ lowerValue: number,
83
+ upperValue: number
84
+ ): number => {
85
+ if (lowerValue > upperValue)
86
+ throw new Error("lowerValue must be less than upperValue")
87
+
88
+ return Math.log10(upperValue) - Math.log10(lowerValue)
89
+ }
90
+
91
+ export const pruneUnusedBins = (
92
+ bins: number[],
93
+ { minValue, maxValue }: { minValue?: number; maxValue?: number }
94
+ ): number[] => {
95
+ if (minValue !== undefined) {
96
+ bins = R.dropWhile(
97
+ bins,
98
+ (v, i) => bins[i + 1] !== undefined && bins[i + 1] <= minValue
99
+ )
100
+ }
101
+
102
+ if (maxValue !== undefined) {
103
+ bins = R.dropLastWhile(
104
+ bins,
105
+ (v, i) => bins[i - 1] !== undefined && bins[i - 1] >= maxValue
106
+ )
107
+ }
108
+
109
+ return bins
110
+ }
111
+
112
+ export const runBinningStrategy = (
113
+ conf: BinningStrategyConfig
114
+ ): BinningStrategyOutput => {
115
+ if (conf.sortedValues.length === 0) {
116
+ return { bins: [0] }
117
+ }
118
+
119
+ conf.midpoint ??= 0
120
+
121
+ const hasValuesBelowMidpoint = conf.sortedValues[0] < conf.midpoint
122
+ const hasValuesAboveMidpoint =
123
+ lastOfNonEmptyArray(conf.sortedValues) > conf.midpoint
124
+ const hasValuesBelowAndAboveMidpoint =
125
+ hasValuesBelowMidpoint && hasValuesAboveMidpoint
126
+
127
+ if (hasValuesBelowAndAboveMidpoint && conf.midpointMode === undefined) {
128
+ // Default to symmetric midpoint if there are negative and positive values
129
+ conf.midpointMode ??= "symmetric"
130
+ }
131
+
132
+ conf.midpointMode ??= "none"
133
+
134
+ let resolvedStrategy: ResolvedBinningStrategy
135
+ if (conf.strategy === "auto") {
136
+ resolvedStrategy = autoChooseBinningStrategy(conf)
137
+ } else {
138
+ resolvedStrategy = conf.strategy as ResolvedBinningStrategy
139
+ }
140
+
141
+ const validationResult = hasValidConfigForBinningStrategy(
142
+ conf.strategy,
143
+ conf
144
+ )
145
+ if (!validationResult.valid) return { bins: [0] } // Placeholder binning for invalid configurations
146
+
147
+ let bins = runBinningStrategyAroundMidpoint({
148
+ ...conf,
149
+ midpointMode: conf.midpointMode,
150
+ midpoint: conf.midpoint,
151
+ strategy: resolvedStrategy,
152
+ })
153
+
154
+ if (conf.createBinForMidpoint) {
155
+ const midpointIdx = R.sortedIndex(bins, conf.midpoint)
156
+ bins = bins.toSpliced(midpointIdx, 0, conf.midpoint)
157
+ }
158
+
159
+ return {
160
+ bins,
161
+ midpoint: conf.midpointMode !== "none" ? conf.midpoint : undefined,
162
+ }
163
+ }
164
+
165
+ type MinMaxValueResult =
166
+ | { valid: true }
167
+ | { valid: false; reason: string; field: string }
168
+
169
+ export const hasValidConfigForBinningStrategy = (
170
+ strategy: AutomaticBinningStrategy,
171
+ config: { minValue?: number; maxValue?: number; midpoint?: number }
172
+ ): MinMaxValueResult => {
173
+ const { minValue, maxValue, midpoint } = config
174
+
175
+ if (!automaticBinningStrategies.includes(strategy))
176
+ return {
177
+ valid: false,
178
+ reason: `Binning: Invalid strategy '${strategy}'`,
179
+ field: "binningStrategy",
180
+ }
181
+
182
+ if (
183
+ minValue !== undefined &&
184
+ maxValue !== undefined &&
185
+ maxValue < minValue
186
+ ) {
187
+ return {
188
+ valid: false,
189
+ reason: "Binning: minValue is greater than maxValue",
190
+ field: "minValue",
191
+ }
192
+ }
193
+
194
+ return match(strategy)
195
+ .with("auto", () => {
196
+ return { valid: true } as const
197
+ })
198
+ .when(isEqualSizeBinningStrategy, () => {
199
+ return { valid: true } as const
200
+ })
201
+ .when(isLogBinningStrategy, () => {
202
+ if (minValue !== undefined && minValue <= 0) {
203
+ return {
204
+ valid: false,
205
+ reason: "Binning: Logarithmic binning requires non-zero positive minValue",
206
+ field: "minValue",
207
+ } as const
208
+ }
209
+ if (maxValue !== undefined && maxValue <= 0) {
210
+ return {
211
+ valid: false,
212
+ reason: "Binning: Logarithmic binning requires non-zero positive maxValue",
213
+ field: "maxValue",
214
+ } as const
215
+ }
216
+ if (midpoint !== undefined && midpoint !== 0) {
217
+ return {
218
+ valid: false,
219
+ reason: "Binning: Logarithmic binning does not support midpoints other than 0",
220
+ field: "midpoint",
221
+ } as const
222
+ }
223
+ return { valid: true } as const
224
+ })
225
+ .exhaustive()
226
+ }
227
+
228
+ /**
229
+ * minValue and maxValue may either be explicitly given, or automatically computed from the data.
230
+ * If auto-computed, they are based on some very much heuristic rules, based on quantiles etc.
231
+ *
232
+ * This also means that these lines contain a bunch of magic numbers, which have been chosen like so
233
+ * because they work well in practice for most cases.
234
+ */
235
+ export const computeMinMaxForStrategy = (
236
+ strategy: ResolvedBinningStrategy,
237
+ sortedValues: number[],
238
+ conf?: Partial<ResolvedBinningStrategyConfig>
239
+ ): { minValue: number; maxValue: number } => {
240
+ let { minValue, maxValue } = conf || {}
241
+ if (
242
+ conf?.midpointMode !== "none" &&
243
+ minValue !== undefined &&
244
+ conf?.midpoint !== undefined
245
+ ) {
246
+ // Ensure that the midpoint is within the min/max range, otherwise we'll get very weird results when mirroring around the midpoint
247
+ minValue = Math.max(minValue, conf.midpoint)
248
+ }
249
+ if (minValue !== undefined && maxValue !== undefined) {
250
+ maxValue = Math.max(minValue, maxValue)
251
+ return { minValue, maxValue }
252
+ }
253
+
254
+ match(strategy)
255
+ .with("equalSizeBins-percent", () => {
256
+ // Percent strategy always uses 0 to 100
257
+ minValue ??= 0
258
+ maxValue ??= 100
259
+ })
260
+ .when(isEqualSizeBinningStrategy, () => {
261
+ const uniqValues = sortedUniq(sortedValues)
262
+ minValue ??= quantile(uniqValues, 0.05)
263
+ maxValue ??= quantile(uniqValues, 0.95)
264
+ })
265
+ .when(isLogBinningStrategy, () => {
266
+ const posValues = R.dropWhile(sortedValues, (v) => v <= 0)
267
+ if (posValues.length === 0) {
268
+ throw new Error("Log binning strategy requires positive values")
269
+ }
270
+
271
+ const uniqValues = sortedUniq(posValues)
272
+ minValue ??= quantile(uniqValues, 0.15)
273
+ maxValue ??= quantile(uniqValues, 0.995)
274
+
275
+ if (minValue === undefined || maxValue === undefined)
276
+ throw new Error("Couldn't obtain minValue or maxValue")
277
+
278
+ // Ensure that the min/max values are at least as high as the number of decimals precision.
279
+ // E.g. if we have 1 decimal place, the minimum value should be at least 0.1
280
+ if (conf?.numDecimalPlaces !== undefined) {
281
+ if (minValue > 0)
282
+ minValue = Math.max(
283
+ minValue,
284
+ Math.pow(10, -conf.numDecimalPlaces)
285
+ )
286
+ if (maxValue < 0)
287
+ maxValue = Math.min(
288
+ maxValue,
289
+ -Math.pow(10, -conf.numDecimalPlaces)
290
+ )
291
+ }
292
+ })
293
+ .exhaustive()
294
+
295
+ if (minValue === undefined || maxValue === undefined)
296
+ throw new Error("Couldn't obtain minValue or maxValue")
297
+
298
+ // Ensure that we have minValue <= maxValue
299
+ maxValue = Math.max(minValue, maxValue)
300
+ return { minValue, maxValue }
301
+ }
302
+
303
+ const runBinningStrategyAroundMidpoint = (
304
+ conf: ResolvedBinningStrategyConfig
305
+ ): number[] => {
306
+ if (
307
+ isLogBinningStrategy(conf.strategy) &&
308
+ conf.midpointMode !== "none" &&
309
+ conf.midpoint !== 0
310
+ ) {
311
+ throw new Error(
312
+ "Log binning strategy does not support midpoints other than 0"
313
+ )
314
+ }
315
+
316
+ const minValue = firstOfNonEmptyArray(conf.sortedValues)
317
+ const maxValue = lastOfNonEmptyArray(conf.sortedValues)
318
+
319
+ const bins = match(conf.midpointMode)
320
+ .with("none", () => {
321
+ return runResolvedBinningStrategy(conf, { hasMidpoint: false })
322
+ })
323
+ .with(P.union("symmetric", "asymmetric"), () => {
324
+ const leftRange = Math.max(conf.midpoint - minValue, 0)
325
+ const rightRange = Math.max(maxValue - conf.midpoint, 0)
326
+ const biggerRange = Math.max(leftRange, rightRange)
327
+
328
+ let posValues: number[]
329
+ if (biggerRange === leftRange) {
330
+ posValues = R.takeWhile(
331
+ conf.sortedValues,
332
+ (v) => v < conf.midpoint
333
+ )
334
+ .map((v) => conf.midpoint - v)
335
+ .reverse()
336
+ } else {
337
+ posValues = R.dropWhile(
338
+ conf.sortedValues,
339
+ (v) => v <= conf.midpoint
340
+ ).map((v) => v - conf.midpoint)
341
+ }
342
+
343
+ const binsRight = runResolvedBinningStrategy(
344
+ { ...conf, sortedValues: posValues },
345
+ { hasMidpoint: true }
346
+ )
347
+ const bins = mirrorBinsAroundMidpoint(binsRight, conf.midpoint)
348
+
349
+ if (conf.midpointMode === "symmetric") {
350
+ return bins
351
+ } else {
352
+ // asymmetric: create bins the same way, but then prune any unused ones
353
+
354
+ return pruneUnusedBins(bins, {
355
+ minValue,
356
+ maxValue,
357
+ })
358
+ }
359
+ })
360
+ .with("same-num-bins", () => {
361
+ const leftValues = R.takeWhile(
362
+ conf.sortedValues,
363
+ (v) => v < conf.midpoint
364
+ )
365
+ .map((v) => conf.midpoint - v)
366
+ .reverse()
367
+
368
+ const rightValues = R.dropWhile(
369
+ conf.sortedValues,
370
+ (v) => v <= conf.midpoint
371
+ ).map((v) => v - conf.midpoint)
372
+
373
+ const binsRight = runResolvedBinningStrategy(
374
+ { ...conf, sortedValues: rightValues },
375
+ { hasMidpoint: true }
376
+ ).filter((v) => v !== 0)
377
+ const binsLeft = runResolvedBinningStrategy(
378
+ { ...conf, sortedValues: leftValues },
379
+ { hasMidpoint: true }
380
+ ).filter((v) => v !== 0)
381
+
382
+ return [
383
+ ...binsLeft.map((v) => conf.midpoint - v).reverse(),
384
+ conf.midpoint,
385
+ ...binsRight.map((v) => v + conf.midpoint),
386
+ ]
387
+ })
388
+ .with(undefined, () => {
389
+ throw new Error("Invalid unresolved midpoint mode")
390
+ })
391
+ .exhaustive()
392
+
393
+ return bins
394
+ }
395
+
396
+ const runResolvedBinningStrategy = (
397
+ conf: ResolvedBinningStrategyConfig,
398
+ { hasMidpoint }: { hasMidpoint: boolean }
399
+ ): number[] => {
400
+ const { minValue, maxValue } = computeMinMaxForStrategy(
401
+ conf.strategy,
402
+ conf.sortedValues,
403
+ conf
404
+ )
405
+ return match(conf.strategy)
406
+ .when(isEqualSizeBinningStrategy, () =>
407
+ runEqualSizeBinningStrategy(
408
+ { ...conf, minValue, maxValue },
409
+ { hasMidpoint }
410
+ )
411
+ )
412
+ .when(isLogBinningStrategy, () =>
413
+ runLogBinningStrategy(
414
+ { ...conf, minValue, maxValue },
415
+ { hasMidpoint }
416
+ )
417
+ )
418
+ .exhaustive()
419
+ }
420
+
421
+ const autoChooseBinningStrategy = (
422
+ conf: BinningStrategyConfig
423
+ ): ResolvedBinningStrategy => {
424
+ if (conf.midpointMode !== "none") {
425
+ return "equalSizeBins-normal"
426
+ }
427
+
428
+ const posValuesOnly = R.dropWhile(conf.sortedValues, (v) => v <= 0)
429
+ if (posValuesOnly.length === 0) {
430
+ // All values are negative or zero, use equal size bins as a simple fallback (this is rare anyways)
431
+ return "equalSizeBins-normal"
432
+ }
433
+
434
+ // If either minValue or maxValue is non-positive, we cannot use log bins
435
+ const hasNegativeMinOrMaxValue =
436
+ (conf.minValue !== undefined && conf.minValue <= 0) ||
437
+ (conf.maxValue !== undefined && conf.maxValue <= 0)
438
+
439
+ let minValueForLog, maxValueForLog
440
+ if (hasNegativeMinOrMaxValue) {
441
+ minValueForLog = maxValueForLog = 0
442
+ } else {
443
+ const { minValue, maxValue } = computeMinMaxForStrategy(
444
+ "log-auto",
445
+ posValuesOnly,
446
+ { minValue: conf.minValue, maxValue: conf.maxValue }
447
+ )
448
+ minValueForLog = minValue
449
+ maxValueForLog = maxValue
450
+ }
451
+
452
+ const magnitudeDiff = calcMagnitudeDiff(minValueForLog, maxValueForLog)
453
+
454
+ if (
455
+ magnitudeDiff < AUTO_EQUAL_BINS_MAX_MAGNITUDE_DIFF ||
456
+ hasNegativeMinOrMaxValue
457
+ ) {
458
+ if (conf.isPercent) {
459
+ const lastValue = lastOfNonEmptyArray(posValuesOnly)
460
+ const percentile99 = quantile(posValuesOnly, 0.99)
461
+ if (
462
+ lastValue <= 100 &&
463
+ percentile99 !== undefined &&
464
+ percentile99 >= 60
465
+ ) {
466
+ return "equalSizeBins-percent"
467
+ }
468
+ }
469
+
470
+ return "equalSizeBins-normal"
471
+ }
472
+
473
+ return "log-auto"
474
+ }
475
+
476
+ export const mirrorBinsAroundMidpoint = (
477
+ binOffsets: number[],
478
+ midpoint: number
479
+ ): number[] => {
480
+ const filteredRightBins = binOffsets.filter((v) => v !== 0)
481
+ const leftBins = filteredRightBins.map((v) => midpoint - v).reverse()
482
+ const rightBins = filteredRightBins.map((v) => v + midpoint)
483
+ return [...leftBins, midpoint, ...rightBins]
484
+ }
@@ -0,0 +1,132 @@
1
+ import {
2
+ BinningStrategyIncludingManual,
3
+ EqualSizeBinningStrategy,
4
+ ResolvedBinningStrategy,
5
+ } from "../../types/index.js"
6
+ import {
7
+ normaliseToSingleDigitNumber,
8
+ RequiredBy,
9
+ roundSigFig,
10
+ } from "../../utils/index.js"
11
+ import * as R from "remeda"
12
+ import { match, P } from "ts-pattern"
13
+ import { ResolvedBinningStrategyConfig } from "./BinningStrategies.js"
14
+
15
+ export const isEqualSizeBinningStrategy = (
16
+ strategy: BinningStrategyIncludingManual | ResolvedBinningStrategy
17
+ ): strategy is EqualSizeBinningStrategy => strategy.startsWith("equalSizeBins-")
18
+
19
+ export const runEqualSizeBinningStrategy = (
20
+ conf: RequiredBy<ResolvedBinningStrategyConfig, "minValue" | "maxValue">,
21
+ { hasMidpoint }: { hasMidpoint?: boolean } = {}
22
+ ): number[] => {
23
+ if (!isEqualSizeBinningStrategy(conf.strategy)) {
24
+ throw new Error("Invalid strategy")
25
+ }
26
+
27
+ const { minValue, maxValue } = conf
28
+ const targetBinCount = getTargetBinCountForEqualSizeBinsStrategy(
29
+ conf.strategy,
30
+ { hasMidpoint }
31
+ )
32
+ return createEqualSizeBins({ minValue, maxValue, targetBinCount })
33
+ }
34
+
35
+ /**
36
+ * The equal-size binning strategies all operate with a range of target bins depending on the strategy.
37
+ * For example, [5, 9] means that there should be at least 5 bins and at most 9.
38
+ * If there is a midpoint, the number of bins needs to be adjusted downward, because we are potentially
39
+ * creating up to twice as many bins overall.
40
+ */
41
+ const getTargetBinCountForEqualSizeBinsStrategy = (
42
+ strategy: EqualSizeBinningStrategy,
43
+ { hasMidpoint }: { hasMidpoint?: boolean } = {}
44
+ ): readonly [number, number] => {
45
+ return match(strategy)
46
+ .with("equalSizeBins-few-bins", () =>
47
+ hasMidpoint ? ([1, 3] as const) : ([2, 5] as const)
48
+ )
49
+ .with("equalSizeBins-normal", () =>
50
+ hasMidpoint ? ([3, 6] as const) : ([5, 9] as const)
51
+ )
52
+ .with(
53
+ P.union("equalSizeBins-many-bins", "equalSizeBins-percent"),
54
+ () => (hasMidpoint ? ([4, 8] as const) : ([8, 12] as const))
55
+ )
56
+ .exhaustive()
57
+ }
58
+
59
+ export const createEqualSizeBins = ({
60
+ minValue,
61
+ maxValue,
62
+ targetBinCount,
63
+ }: {
64
+ minValue: number
65
+ maxValue: number
66
+ targetBinCount: readonly number[]
67
+ }): number[] => {
68
+ if (minValue > maxValue) {
69
+ throw new Error("minValue must be less than maxValue")
70
+ }
71
+
72
+ const range = maxValue - minValue
73
+
74
+ // Normalise the range to be within 1 and 10
75
+ const { normalised: normalisedRange, factor } =
76
+ normaliseToSingleDigitNumber(range)
77
+
78
+ // These are all common and "good" step sizes
79
+ const stepSizeCandidates = [1, 0.1, 0.5, 0.2, 2, 3, 0.3, 0.75, 0.25]
80
+
81
+ // Compute the number of bins we would get for each candidate step size
82
+ const stepSizeInfo = stepSizeCandidates.map((candidateStepSize) => {
83
+ // if (normalisedRange === 0) return true
84
+
85
+ const numSteps = Math.ceil(normalisedRange / candidateStepSize)
86
+ return { stepSize: candidateStepSize, numSteps }
87
+ // return numSteps >= targetBinCount[0] && numSteps <= targetBinCount[1]
88
+ })
89
+
90
+ // Find the first step size that gives us the target bin count
91
+ let stepSize = stepSizeInfo.find(
92
+ (info) =>
93
+ info.numSteps >= targetBinCount[0] &&
94
+ info.numSteps <= targetBinCount[1]
95
+ )?.stepSize
96
+
97
+ if (stepSize === undefined) {
98
+ // It could be interesting to see which charts cause this
99
+ console.warn(
100
+ "Equal size binning: Couldn't find a step size fitting targetBinCount; instead using next-best candidate",
101
+ { targetBinCount, minValue, maxValue }
102
+ )
103
+
104
+ // prefer too many bins over too few (by weighing "too many" less highly than "too few")
105
+ const stepSizeDifference = (candidate: number): number =>
106
+ Math.max(candidate - targetBinCount[1], 0) * 1 + // this is how many more bins there are than targetBinCount specifies
107
+ Math.max(targetBinCount[0] - candidate, 0) * 2 // and this is how many fewer there are
108
+
109
+ // Find the step size that minimizes the distance function, i.e. such that we are not much above/below what targetBinCount requests
110
+ stepSize = R.firstBy(stepSizeInfo, (info) =>
111
+ stepSizeDifference(info.numSteps)
112
+ )?.stepSize
113
+ }
114
+
115
+ if (stepSize === undefined) {
116
+ // This should never happen
117
+ throw new Error("Could not find a valid step size")
118
+ }
119
+
120
+ const actualStepSize = stepSize * factor
121
+
122
+ // Round min value to step size, e.g. if the step size is 20 then this would round 50 down to 40
123
+ const minValueRounded =
124
+ Math.floor(minValue / actualStepSize) * actualStepSize
125
+
126
+ const steps = Math.ceil((maxValue - minValueRounded) / actualStepSize)
127
+
128
+ return R.range(0, steps + 1).map((i) => {
129
+ const value = i * actualStepSize
130
+ return minValueRounded + roundSigFig(value, 3) // to avoid floating point issues
131
+ })
132
+ }