@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,793 @@
1
+ // @ts-nocheck
2
+ import * as _ from "lodash-es"
3
+ import * as Papa from "papaparse"
4
+ import * as R from "remeda"
5
+ import { sampleFrom, slugifySameCase, ColumnSlug } from "../utils/index.js"
6
+ import {
7
+ CoreColumnStore,
8
+ CoreRow,
9
+ CoreMatrix,
10
+ Time,
11
+ CoreValueType,
12
+ ColumnTypeNames,
13
+ CoreColumnDef,
14
+ ErrorValue,
15
+ OwidEntityCodeColumnDef,
16
+ OwidEntityIdColumnDef,
17
+ OwidEntityNameColumnDef,
18
+ OwidTableSlugs,
19
+ } from "../types/index.js"
20
+ import { ErrorValueTypes, DroppedForTesting } from "./ErrorValues.js"
21
+
22
+ export const columnStoreToRows = (
23
+ columnStore: CoreColumnStore
24
+ ): Record<string, CoreValueType>[] => {
25
+ const firstCol = Object.values(columnStore)[0]
26
+ if (!firstCol) return []
27
+ const slugs = Object.keys(columnStore)
28
+ return firstCol.map((val, index) => {
29
+ const newRow: Record<string, CoreValueType> = {}
30
+ slugs.forEach((slug) => {
31
+ newRow[slug] = columnStore[slug][index]
32
+ })
33
+ return newRow
34
+ })
35
+ }
36
+
37
+ // If string exceeds maxLength, will replace the end char with a ... and drop the rest
38
+ export const truncate = (str: string, maxLength: number): string =>
39
+ str.length > maxLength ? `${str.substr(0, maxLength - 3)}...` : str
40
+
41
+ // Picks a type for each column from the first row then autotypes all rows after that so all values in
42
+ // a column will have the same type. Only chooses between strings and numbers.
43
+ const numberOnly = /^-?\d+\.?\d*$/
44
+ type RawRow = Record<string, unknown> | undefined
45
+ type ParsedRow = Record<string, string | number | ErrorValue> | undefined | null
46
+ export const makeAutoTypeFn = (
47
+ numericSlugs?: ColumnSlug[]
48
+ ): ((object?: RawRow) => ParsedRow) => {
49
+ const slugToType: any = {}
50
+ numericSlugs?.forEach((slug) => {
51
+ slugToType[slug] = "number"
52
+ })
53
+ return (object: RawRow): ParsedRow => {
54
+ for (const columnSlug in object) {
55
+ const value = object[columnSlug]
56
+ const type = slugToType[columnSlug]
57
+ if (type === "string") {
58
+ object[columnSlug] = value
59
+ continue
60
+ }
61
+
62
+ const number = parseFloat(value as string) // The "+" type casting that d3 does for perf converts "" to 0, so use parseFloat.
63
+ if (type === "number") {
64
+ object[columnSlug] = isNaN(number)
65
+ ? ErrorValueTypes.NaNButShouldBeNumber
66
+ : number
67
+ continue
68
+ }
69
+
70
+ if (isNaN(number) || !numberOnly.test(value as string)) {
71
+ object[columnSlug] = value
72
+ slugToType[columnSlug] = "string"
73
+ continue
74
+ }
75
+
76
+ object[columnSlug] = number
77
+ slugToType[columnSlug] = "number"
78
+ }
79
+ return object as ParsedRow
80
+ }
81
+ }
82
+
83
+ // Removes whitespace and non-word characters from column slugs if any exist.
84
+ // The original names are moved to the name property on the column def.
85
+ export const standardizeSlugs = (
86
+ rows: CoreRow[]
87
+ ): { rows: CoreRow[]; defs: { name: string; slug: string }[] } | undefined => {
88
+ const firstRow = rows[0] ?? {}
89
+ const colsToRename = Object.keys(firstRow)
90
+ .map((name) => {
91
+ return {
92
+ name,
93
+ slug: slugifySameCase(name),
94
+ }
95
+ })
96
+ .filter((col) => col.name !== col.slug)
97
+ if (!colsToRename.length) return undefined
98
+
99
+ rows.forEach((row: CoreRow) => {
100
+ colsToRename.forEach((col) => {
101
+ row[col.slug] = row[col.name]
102
+ delete row[col.name]
103
+ })
104
+ })
105
+
106
+ return { rows, defs: colsToRename }
107
+ }
108
+
109
+ export const guessColumnDefFromSlugAndRow = (
110
+ slug: string,
111
+ sampleValue: unknown
112
+ ): CoreColumnDef => {
113
+ const valueType = typeof sampleValue
114
+
115
+ const name = slug
116
+
117
+ if (slug === "Entity")
118
+ return {
119
+ slug,
120
+ type: ColumnTypeNames.EntityName,
121
+ name,
122
+ }
123
+
124
+ if (slug === "day")
125
+ return {
126
+ slug,
127
+ type: ColumnTypeNames.Day,
128
+ name: "Day",
129
+ }
130
+
131
+ if (slug === "year" || slug === "Year")
132
+ return {
133
+ slug,
134
+ type: ColumnTypeNames.Year,
135
+ name: "Year",
136
+ }
137
+
138
+ if (slug === OwidTableSlugs.entityName) return OwidEntityNameColumnDef
139
+ if (slug === OwidTableSlugs.entityCode) return OwidEntityCodeColumnDef
140
+ if (slug === OwidTableSlugs.entityId) return OwidEntityIdColumnDef
141
+
142
+ if (slug === "date")
143
+ return {
144
+ slug,
145
+ type: ColumnTypeNames.Date,
146
+ name: "Date",
147
+ }
148
+
149
+ if (valueType === "number")
150
+ return {
151
+ slug,
152
+ type: ColumnTypeNames.Numeric,
153
+ name,
154
+ }
155
+
156
+ if (valueType === "string") {
157
+ if (String(sampleValue).match(/^\d+$/))
158
+ return {
159
+ slug,
160
+ type: ColumnTypeNames.Numeric,
161
+ name,
162
+ }
163
+ }
164
+
165
+ return { slug, type: ColumnTypeNames.String, name }
166
+ }
167
+
168
+ export const makeRowFromColumnStore = (
169
+ rowIndex: number,
170
+ columnStore: CoreColumnStore
171
+ ): CoreRow => {
172
+ const row: CoreRow = {}
173
+ Object.entries(columnStore).forEach(([slug, col]) => {
174
+ if (col.length <= rowIndex) row[slug] = undefined
175
+ else row[slug] = col[rowIndex]
176
+ })
177
+ return row
178
+ }
179
+
180
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type
181
+ export interface InterpolationContext {}
182
+
183
+ export interface LinearInterpolationContext extends InterpolationContext {
184
+ // whether to extrapolate a variable at the start or end, where we cannot do linear interpolation
185
+ // but need to just copy over the first/last value present over to empty fields.
186
+ // e.g. [Error, Error, 2, 3, 4] would become [2, 2, 2, 3, 4] with extrapolateAtStart=true.
187
+ extrapolateAtStart?: boolean
188
+ extrapolateAtEnd?: boolean
189
+ }
190
+
191
+ export interface ToleranceInterpolationContext extends InterpolationContext {
192
+ timeToleranceForwards: number
193
+ timeToleranceBackwards: number
194
+ }
195
+
196
+ export type InterpolationProvider<C extends InterpolationContext> = (
197
+ valuesSortedByTimeAsc: CoreValueType[],
198
+ timesAsc: Time[],
199
+ validIndices: number[],
200
+ context: C,
201
+ start: number,
202
+ end: number
203
+ ) => void
204
+
205
+ export function linearInterpolation(
206
+ valuesSortedByTimeAsc: CoreValueType[],
207
+ timesAsc: Time[],
208
+ validIndices: number[],
209
+ context: LinearInterpolationContext,
210
+ start: number = 0,
211
+ end: number = valuesSortedByTimeAsc.length
212
+ ): void {
213
+ if (!valuesSortedByTimeAsc.length) return
214
+
215
+ const startIndexInValidIndices = R.sortedIndex(validIndices, start)
216
+ const endIndexInValidIndices = R.sortedIndex(validIndices, end)
217
+
218
+ const distBetweenStartAndEnd =
219
+ endIndexInValidIndices - startIndexInValidIndices
220
+
221
+ if (distBetweenStartAndEnd === 0) {
222
+ // No valid values in this range, we can short-circuit
223
+ for (let index = start; index < end; index++) {
224
+ valuesSortedByTimeAsc[index] =
225
+ ErrorValueTypes.NoValueForInterpolation
226
+ }
227
+ return
228
+ }
229
+ // All values in this range are already valid, we don't need to do anything
230
+ else if (distBetweenStartAndEnd === end - start) {
231
+ return
232
+ }
233
+
234
+ let currentValidIndexPointer = startIndexInValidIndices
235
+ let prevNonBlankIndex: number | undefined = undefined
236
+ let nextNonBlankIndex: number | undefined = undefined
237
+
238
+ for (let index = start; index < end; index++) {
239
+ if (
240
+ nextNonBlankIndex !== -1 &&
241
+ (nextNonBlankIndex === undefined || nextNonBlankIndex <= index)
242
+ ) {
243
+ nextNonBlankIndex = validIndices[currentValidIndexPointer] ?? -1
244
+ if (nextNonBlankIndex >= end) {
245
+ nextNonBlankIndex = -1
246
+ }
247
+ }
248
+
249
+ // Check whether the current index has a valid value
250
+ if (index === nextNonBlankIndex) {
251
+ prevNonBlankIndex = index
252
+ currentValidIndexPointer++
253
+ continue
254
+ }
255
+
256
+ const timeOfCurrent = timesAsc[index]
257
+
258
+ const timeOfPrevIndex =
259
+ prevNonBlankIndex !== undefined
260
+ ? (timesAsc[prevNonBlankIndex] ?? -Infinity)
261
+ : -Infinity
262
+ const timeOfNextIndex = timesAsc[nextNonBlankIndex] ?? Infinity
263
+
264
+ const prevValue: number | undefined =
265
+ prevNonBlankIndex !== undefined
266
+ ? (valuesSortedByTimeAsc[prevNonBlankIndex] as
267
+ | number
268
+ | undefined)
269
+ : undefined
270
+ const nextValue: number | undefined = valuesSortedByTimeAsc[
271
+ nextNonBlankIndex
272
+ ] as number | undefined
273
+
274
+ let value
275
+ if (typeof prevValue === "number" && typeof nextValue === "number") {
276
+ const distLeft = timeOfCurrent - timeOfPrevIndex
277
+ const distRight = timeOfNextIndex - timeOfCurrent
278
+ value =
279
+ (prevValue * distRight + nextValue * distLeft) /
280
+ (distLeft + distRight)
281
+ } else if (typeof prevValue === "number" && context.extrapolateAtEnd)
282
+ value = prevValue
283
+ else if (typeof nextValue === "number" && context.extrapolateAtStart)
284
+ value = nextValue
285
+ else value = ErrorValueTypes.NoValueForInterpolation
286
+
287
+ valuesSortedByTimeAsc[index] = value
288
+ }
289
+ }
290
+
291
+ export function toleranceInterpolation(
292
+ valuesSortedByTimeAsc: CoreValueType[],
293
+ timesAsc: Time[],
294
+ validIndices: number[],
295
+ context: ToleranceInterpolationContext,
296
+ start: number = 0,
297
+ end: number = valuesSortedByTimeAsc.length
298
+ ): void {
299
+ if (!valuesSortedByTimeAsc.length) return
300
+
301
+ // `validIndices` contains the indexes of all non-error values in `valuesSortedByTimeAsc`.
302
+ // Here, we find the *indexes* of where `start` and `end` appear in `validIndices` (note that
303
+ // `start` and `end` themselves are indexes into `valuesSortedByTimeAsc`).
304
+ const startIndexInValidIndices = R.sortedIndex(validIndices, start)
305
+ const endIndexInValidIndices = R.sortedIndex(validIndices, end)
306
+
307
+ const distBetweenStartAndEnd =
308
+ endIndexInValidIndices - startIndexInValidIndices
309
+
310
+ // If the two indices are the same, then there are no valid values in this range.
311
+ if (distBetweenStartAndEnd === 0) {
312
+ // No valid values in this range, we can short-circuit
313
+ for (let index = start; index < end; index++) {
314
+ valuesSortedByTimeAsc[index] =
315
+ ErrorValueTypes.NoValueWithinTolerance
316
+ }
317
+ return
318
+ }
319
+ // All values in this range are valid, we can short-circuit
320
+ else if (distBetweenStartAndEnd === end - start) {
321
+ return
322
+ }
323
+ // If the two indices differ by 1, then there is only one valid value in this range.
324
+ else if (distBetweenStartAndEnd === 1) {
325
+ // Only one valid value in this range, we can short-circuit
326
+ const onlyValidIndex = validIndices[startIndexInValidIndices]
327
+ const timeOfOnlyValid = timesAsc[onlyValidIndex]
328
+
329
+ for (let index = start; index < end; index++) {
330
+ if (index === onlyValidIndex) continue
331
+
332
+ const timeOfCurrent = timesAsc[index]
333
+ const timeDiff = timeOfOnlyValid - timeOfCurrent
334
+ if (
335
+ (timeDiff < 0 &&
336
+ Math.abs(timeDiff) <= context.timeToleranceBackwards) ||
337
+ (timeDiff > 0 &&
338
+ Math.abs(timeDiff) <= context.timeToleranceForwards)
339
+ ) {
340
+ valuesSortedByTimeAsc[index] =
341
+ valuesSortedByTimeAsc[onlyValidIndex]
342
+ timesAsc[index] = timeOfOnlyValid
343
+ } else {
344
+ valuesSortedByTimeAsc[index] =
345
+ ErrorValueTypes.NoValueWithinTolerance
346
+ }
347
+ }
348
+ return
349
+ }
350
+
351
+ let currentValidIndexPointer = startIndexInValidIndices
352
+ let prevNonBlankIndex: number | undefined = undefined
353
+ let nextNonBlankIndex: number | undefined = undefined
354
+
355
+ for (let index = start; index < end; index++) {
356
+ if (
357
+ nextNonBlankIndex !== -1 &&
358
+ (nextNonBlankIndex === undefined || nextNonBlankIndex <= index)
359
+ ) {
360
+ nextNonBlankIndex = validIndices[currentValidIndexPointer] ?? -1
361
+ if (nextNonBlankIndex >= end) {
362
+ nextNonBlankIndex = -1
363
+ }
364
+ }
365
+
366
+ // Check whether the current index has a valid value
367
+ if (index === nextNonBlankIndex) {
368
+ prevNonBlankIndex = index
369
+ currentValidIndexPointer++
370
+ continue
371
+ }
372
+
373
+ const timeOfCurrent = timesAsc[index]
374
+
375
+ const timeOfPrevIndex =
376
+ prevNonBlankIndex !== undefined
377
+ ? (timesAsc[prevNonBlankIndex] ?? -Infinity)
378
+ : -Infinity
379
+ const timeOfNextIndex = timesAsc[nextNonBlankIndex] ?? Infinity
380
+
381
+ const prevTimeDiff = timeOfCurrent - timeOfPrevIndex
382
+ const nextTimeDiff = timeOfNextIndex - timeOfCurrent
383
+
384
+ if (
385
+ nextNonBlankIndex !== undefined &&
386
+ nextNonBlankIndex !== -1 &&
387
+ nextTimeDiff <= prevTimeDiff &&
388
+ nextTimeDiff <= context.timeToleranceForwards
389
+ ) {
390
+ valuesSortedByTimeAsc[index] =
391
+ valuesSortedByTimeAsc[nextNonBlankIndex]
392
+ timesAsc[index] = timesAsc[nextNonBlankIndex]
393
+ } else if (
394
+ prevNonBlankIndex !== undefined &&
395
+ prevTimeDiff <= context.timeToleranceBackwards
396
+ ) {
397
+ valuesSortedByTimeAsc[index] =
398
+ valuesSortedByTimeAsc[prevNonBlankIndex]
399
+ timesAsc[index] = timesAsc[prevNonBlankIndex]
400
+ } else
401
+ valuesSortedByTimeAsc[index] =
402
+ ErrorValueTypes.NoValueWithinTolerance
403
+ }
404
+ }
405
+
406
+ // A dumb function for making a function that makes a key for a row given certain columns.
407
+ export const makeKeyFn = (
408
+ columnStore: CoreColumnStore,
409
+ columnSlugs: ColumnSlug[]
410
+ ): ((rowIndex: number) => string) => {
411
+ const cols = columnSlugs.map((slug) => columnStore[slug])
412
+
413
+ const toStr = (val: CoreValueType): string =>
414
+ val === null || val === undefined
415
+ ? ""
416
+ : typeof val === "string"
417
+ ? val
418
+ : (val as any) + ""
419
+
420
+ // perf: this function is performance-critical, and so for the common cases of 1, 2, or 3 columns, we can provide a
421
+ // faster implementation.
422
+ if (cols.length === 0) return () => ""
423
+ if (cols.length === 1) {
424
+ const col = cols[0]
425
+ return (rowIndex: number): string => toStr(col[rowIndex])
426
+ }
427
+ if (cols.length === 2) {
428
+ const col0 = cols[0],
429
+ col1 = cols[1]
430
+ return (rowIndex: number): string =>
431
+ `${toStr(col0[rowIndex])} ${toStr(col1[rowIndex])}`
432
+ }
433
+ if (cols.length === 3) {
434
+ const col0 = cols[0],
435
+ col1 = cols[1],
436
+ col2 = cols[2]
437
+ return (rowIndex: number): string =>
438
+ `${toStr(col0[rowIndex])} ${toStr(col1[rowIndex])} ${toStr(
439
+ col2[rowIndex]
440
+ )}`
441
+ }
442
+
443
+ return (rowIndex: number): string =>
444
+ // toString() handles `undefined` and `null` values, which can be in the table.
445
+ cols.map((col) => toStr(col[rowIndex])).join(" ")
446
+ }
447
+
448
+ const getColumnStoreLength = (store: CoreColumnStore): number => {
449
+ return _.max(Object.values(store).map((v) => v.length)) ?? 0
450
+ }
451
+
452
+ export const concatColumnStores = (
453
+ stores: CoreColumnStore[],
454
+ slugsToKeep?: ColumnSlug[]
455
+ ): CoreColumnStore => {
456
+ if (!stores.length) return {}
457
+
458
+ const lengths = stores.map(getColumnStoreLength)
459
+ const slugs = slugsToKeep ?? Object.keys(R.first(stores)!)
460
+
461
+ const newColumnStore: CoreColumnStore = {}
462
+
463
+ // The below code is performance-critical.
464
+ // That's why it's written using for loops and mutable arrays rather than using map or flatMap:
465
+ // To this day, that's still faster in JS.
466
+ slugs.forEach((slug) => {
467
+ let newColumnValues: CoreValueType[] = []
468
+ for (const [i, store] of stores.entries()) {
469
+ const values = store[slug] ?? []
470
+ const toFill = Math.max(0, lengths[i] - values.length)
471
+
472
+ newColumnValues = newColumnValues.concat(values)
473
+ if (toFill > 0) {
474
+ newColumnValues = newColumnValues.concat(
475
+ new Array(toFill).fill(
476
+ ErrorValueTypes.MissingValuePlaceholder
477
+ )
478
+ )
479
+ }
480
+ }
481
+ newColumnStore[slug] = newColumnValues
482
+ })
483
+ return newColumnStore
484
+ }
485
+
486
+ export const rowsToColumnStore = (rows: CoreRow[]): CoreColumnStore => {
487
+ const columnsObject: CoreColumnStore = {}
488
+ if (!rows.length) return columnsObject
489
+
490
+ Object.keys(rows[0]).forEach((slug) => {
491
+ columnsObject[slug] = rows.map((row) => row[slug])
492
+ })
493
+ return columnsObject
494
+ }
495
+
496
+ const guessColumnDefsFromRows = (
497
+ rows: CoreRow[],
498
+ definedSlugs: Map<ColumnSlug, any>
499
+ ): CoreColumnDef[] => {
500
+ if (!rows[0]) return []
501
+ return Object.keys(rows[0])
502
+ .filter((slug) => !definedSlugs.has(slug))
503
+ .map((slug) => {
504
+ const firstRowWithValue = rows.find(
505
+ (row) =>
506
+ row[slug] !== undefined &&
507
+ row[slug] !== null &&
508
+ row[slug] !== ""
509
+ )
510
+ const firstValue = firstRowWithValue
511
+ ? firstRowWithValue[slug]
512
+ : undefined
513
+
514
+ return guessColumnDefFromSlugAndRow(slug, firstValue)
515
+ })
516
+ }
517
+
518
+ export const autodetectColumnDefs = (
519
+ rowsOrColumnStore: CoreColumnStore | CoreRow[],
520
+ definedSlugs: Map<ColumnSlug, any>
521
+ ): CoreColumnDef[] => {
522
+ if (!Array.isArray(rowsOrColumnStore)) {
523
+ const columnStore = rowsOrColumnStore as CoreColumnStore
524
+ return Object.keys(columnStore)
525
+ .filter((slug) => !definedSlugs.has(slug))
526
+ .map((slug) => {
527
+ return guessColumnDefFromSlugAndRow(
528
+ slug,
529
+ columnStore[slug].find(
530
+ (val) => val !== undefined && val !== null
531
+ )
532
+ )
533
+ })
534
+ }
535
+ return guessColumnDefsFromRows(rowsOrColumnStore, definedSlugs)
536
+ }
537
+
538
+ // Convenience method when you are replacing columns
539
+ export const replaceDef = <ColumnDef extends CoreColumnDef>(
540
+ defs: ColumnDef[],
541
+ newDefs: ColumnDef[]
542
+ ): ColumnDef[] =>
543
+ defs.map((def) => {
544
+ const newDef = newDefs.find((newDef) => newDef.slug === def.slug)
545
+ return newDef ?? def
546
+ })
547
+
548
+ export const renameColumnStore = (
549
+ columnStore: CoreColumnStore,
550
+ columnRenameMap: { [columnSlug: string]: ColumnSlug }
551
+ ): CoreColumnStore => {
552
+ const newStore: CoreColumnStore = {}
553
+ Object.keys(columnStore).forEach((slug) => {
554
+ if (columnRenameMap[slug])
555
+ newStore[columnRenameMap[slug]] = columnStore[slug]
556
+ else newStore[slug] = columnStore[slug]
557
+ })
558
+ return newStore
559
+ }
560
+
561
+ // Returns a Set of random indexes to drop in an array, preserving the order of the array
562
+ export const getDropIndexes = (
563
+ arrayLength: number,
564
+ howMany: number,
565
+ seed = Date.now()
566
+ ): Set<number> => new Set(sampleFrom(_.range(0, arrayLength), howMany, seed))
567
+
568
+ export const replaceRandomCellsInColumnStore = (
569
+ columnStore: CoreColumnStore,
570
+ howMany = 1,
571
+ columnSlugs: ColumnSlug[] = [],
572
+ seed = Date.now(),
573
+ replacementGenerator: () => any = (): DroppedForTesting =>
574
+ ErrorValueTypes.DroppedForTesting
575
+ ): CoreColumnStore => {
576
+ const newStore: CoreColumnStore = Object.assign({}, columnStore)
577
+ columnSlugs.forEach((slug) => {
578
+ const values = newStore[slug]
579
+ const indexesToDrop = getDropIndexes(values.length, howMany, seed)
580
+ newStore[slug] = values.map((value, index) =>
581
+ indexesToDrop.has(index) ? replacementGenerator() : value
582
+ )
583
+ })
584
+ return newStore
585
+ }
586
+
587
+ export class Timer {
588
+ constructor() {
589
+ this._tickTime = Date.now()
590
+ this._firstTickTime = this._tickTime
591
+ }
592
+
593
+ private _tickTime: number
594
+ private _firstTickTime: number
595
+
596
+ tick(msg?: string): number {
597
+ const elapsed = Date.now() - this._tickTime
598
+ // eslint-disable-next-line no-console
599
+ if (msg) console.log(`${elapsed}ms ${msg}`)
600
+ this._tickTime = Date.now()
601
+ return elapsed
602
+ }
603
+
604
+ getTotalElapsedTime(): number {
605
+ return Date.now() - this._firstTickTime
606
+ }
607
+ }
608
+
609
+ export const rowsFromMatrix = (matrix: CoreMatrix): any[] => {
610
+ const table = trimMatrix(matrix)
611
+ const header = table[0]
612
+ return table.slice(1).map((row) => {
613
+ const newRow: any = {}
614
+ header.forEach((col, index) => {
615
+ newRow[col] = row[index]
616
+ })
617
+ return newRow
618
+ })
619
+ }
620
+
621
+ const trimEmptyColumns = (matrix: CoreMatrix): CoreMatrix =>
622
+ matrix.map(trimArray)
623
+ export const trimMatrix = (matrix: CoreMatrix): CoreMatrix =>
624
+ trimEmptyColumns(trimEmptyRows(matrix))
625
+
626
+ export const matrixToDelimited = (
627
+ table: CoreMatrix,
628
+ delimiter = "\t"
629
+ ): string => {
630
+ return table
631
+ .map((row: any) =>
632
+ row
633
+ .map((cell: any) =>
634
+ cell === null || cell === undefined ? "" : cell
635
+ )
636
+ .join(delimiter)
637
+ )
638
+ .join("\n")
639
+ }
640
+
641
+ /**
642
+ * An array object representing all parsed rows. The array is enhanced with a property listing
643
+ * the names of the parsed columns.
644
+ */
645
+ export interface DSVParsedArray<T> extends Array<T> {
646
+ /**
647
+ * List of column names.
648
+ */
649
+ columns: Array<keyof T>
650
+ }
651
+
652
+ export const parseDelimited = (
653
+ str: string,
654
+ delimiter?: string,
655
+ parseFn?: (rawRow: RawRow) => ParsedRow
656
+ ): DSVParsedArray<Record<string, any>> => {
657
+ // Convert PapaParse result to D3 format for backwards compatibility
658
+ const papaParseToD3 = ({
659
+ data,
660
+ meta,
661
+ }: Papa.ParseResult<any>): DSVParsedArray<Record<string, any>> => {
662
+ const dsvParsed = data as DSVParsedArray<Record<string, any>>
663
+ dsvParsed.columns = meta.fields || []
664
+
665
+ // Some downstream methods expect all rows to have fields for all columns,
666
+ // even if they are missing in that row. This loop ensures that.
667
+ for (const row of dsvParsed) {
668
+ for (const col of dsvParsed.columns) {
669
+ if (!(col in row)) row[col] = ""
670
+ }
671
+ }
672
+
673
+ return dsvParsed
674
+ }
675
+
676
+ const result = Papa.parse(str, {
677
+ delimiter: delimiter ?? detectDelimiter(str),
678
+ header: true,
679
+ skipEmptyLines: true,
680
+ transformHeader: (header: string) => header.trim(),
681
+ transform: (value: string) => value.trim(),
682
+ })
683
+
684
+ if (parseFn) {
685
+ result.data = result.data.map((rawRow) => parseFn(rawRow as RawRow))
686
+ }
687
+
688
+ return papaParseToD3(result)
689
+ }
690
+
691
+ export const detectDelimiter = (str: string): "\t" | "," | " " =>
692
+ str.includes("\t") ? "\t" : str.includes(",") ? "," : " "
693
+
694
+ export const rowsToMatrix = (rows: any[]): CoreMatrix | undefined =>
695
+ rows.length
696
+ ? [Object.keys(rows[0]), ...rows.map((row) => Object.values(row))]
697
+ : undefined
698
+
699
+ const isRowEmpty = (row: any[]): boolean => row.every(isCellEmpty)
700
+
701
+ export const isCellEmpty = (cell: unknown): boolean =>
702
+ cell === null || cell === undefined || cell === ""
703
+
704
+ export const trimEmptyRows = (matrix: CoreMatrix): CoreMatrix => {
705
+ let trimAt = undefined
706
+ for (let rowIndex = matrix.length - 1; rowIndex >= 0; rowIndex--) {
707
+ if (!isRowEmpty(matrix[rowIndex])) break
708
+ trimAt = rowIndex
709
+ }
710
+ return trimAt === undefined ? matrix : matrix.slice(0, trimAt)
711
+ }
712
+
713
+ export const trimArray = (arr: any[]): any[] => {
714
+ let rightIndex: number
715
+ for (rightIndex = arr.length - 1; rightIndex >= 0; rightIndex--) {
716
+ if (!isCellEmpty(arr[rightIndex])) break
717
+ }
718
+ return arr.slice(0, rightIndex + 1)
719
+ }
720
+
721
+ const applyNewSortOrder = (arr: any[], newOrder: number[]): any[] => {
722
+ const newArr = new Array(arr.length)
723
+ for (let i = 0; i < newOrder.length; i++) {
724
+ const index = newOrder[i]
725
+ newArr[i] = arr[index]
726
+ }
727
+ return newArr
728
+ }
729
+
730
+ export const sortColumnStore = (
731
+ columnStore: CoreColumnStore,
732
+ slugs: ColumnSlug[]
733
+ ): CoreColumnStore => {
734
+ const firstCol = Object.values(columnStore)[0]
735
+ if (!firstCol) return {}
736
+ const len = firstCol.length
737
+ const sortFn = makeSortByFn(columnStore, slugs)
738
+
739
+ // Check if column store is already sorted.
740
+ // If it's not sorted, we will detect that within the first few iterations usually.
741
+ let isSorted = true
742
+ for (let i = 0; i < len - 1; i++) {
743
+ if (sortFn(i, i + 1) > 0) {
744
+ isSorted = false
745
+ break
746
+ }
747
+ }
748
+ // Column store is already sorted; return existing store unchanged
749
+ if (isSorted) return columnStore
750
+
751
+ const newStore: CoreColumnStore = {}
752
+ // Compute an array of the new sort order, i.e. [0, 1, 2, ...] -> [2, 0, 1]
753
+ const newOrder = _.range(0, len).sort(sortFn)
754
+ Object.entries(columnStore).forEach(([slug, colValues]) => {
755
+ newStore[slug] = applyNewSortOrder(colValues, newOrder)
756
+ })
757
+
758
+ return newStore
759
+ }
760
+
761
+ const makeSortByFn = (
762
+ columnStore: CoreColumnStore,
763
+ columnSlugs: ColumnSlug[]
764
+ ): ((indexA: number, indexB: number) => 1 | 0 | -1) => {
765
+ const cols = columnSlugs.map((slug) => columnStore[slug])
766
+
767
+ return (indexA: number, indexB: number): 1 | 0 | -1 => {
768
+ const nodeAFirst = -1
769
+ const nodeBFirst = 1
770
+
771
+ // eslint-disable-next-line @typescript-eslint/prefer-for-of
772
+ for (let colIndex = 0; colIndex < cols.length; colIndex++) {
773
+ const col = cols[colIndex]
774
+ const av = col[indexA]
775
+ const bv = col[indexB]
776
+ if (av < bv) return nodeAFirst
777
+ if (av > bv) return nodeBFirst
778
+ // todo: handle ErrorValues
779
+ }
780
+ return 0
781
+ }
782
+ }
783
+
784
+ export const emptyColumnsInFirstRowInDelimited = (str: string): string[] => {
785
+ // todo: don't split a big string here, just do a faster first line scan
786
+ const shortCsv = parseDelimited(str.split("\n").slice(0, 2).join("\n"))
787
+ const firstRow: any = shortCsv[0] ?? {}
788
+ const emptySlugs: string[] = []
789
+ Object.keys(firstRow).forEach((slug) => {
790
+ if (firstRow[slug] === "") emptySlugs.push(slug)
791
+ })
792
+ return emptySlugs
793
+ }