@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,973 @@
1
+ import * as _ from "lodash-es"
2
+ import { scaleLog, scaleLinear, ScaleLinear, ScaleLogarithmic } from "d3-scale"
3
+ import { observable, computed, makeObservable } from "mobx"
4
+ import {
5
+ rollingMap,
6
+ numberMagnitude,
7
+ Bounds,
8
+ AxisAlign,
9
+ HorizontalAlign,
10
+ Position,
11
+ ScaleType,
12
+ VerticalAlign,
13
+ TickFormattingOptions,
14
+ Tickmark,
15
+ ValueRange,
16
+ OwidVariableRoundingMode,
17
+ } from "../../utils/index.js"
18
+ import { ComparisonLineConfig } from "../../types/index.js"
19
+ import { AxisConfig, AxisManager } from "./AxisConfig"
20
+ import { MarkdownTextWrap } from "../../components/index.js"
21
+ import { CoreColumn } from "../../core-table/index.js"
22
+ import {
23
+ DEFAULT_GRAPHER_BOUNDS,
24
+ GRAPHER_FONT_SCALE_10_5,
25
+ GRAPHER_FONT_SCALE_11,
26
+ GRAPHER_FONT_SCALE_12,
27
+ } from "../core/GrapherConstants.js"
28
+ import { makeAxisLabel } from "../chart/ChartUtils"
29
+ import * as R from "remeda"
30
+ import { isValidVerticalComparisonLineConfig } from "../comparisonLine/ComparisonLineHelpers"
31
+
32
+ interface TickLabelPlacement {
33
+ value: number
34
+ formattedValue: string
35
+ x: number
36
+ y: number
37
+ width: number
38
+ height: number
39
+ xAlign?: HorizontalAlign
40
+ yAlign?: VerticalAlign
41
+ isHidden: boolean
42
+ }
43
+
44
+ type Scale = ScaleLinear<number, number> | ScaleLogarithmic<number, number>
45
+
46
+ const OUTER_PADDING = 4
47
+
48
+ const doIntersect = (bounds: Bounds, bounds2: Bounds): boolean => {
49
+ return bounds.intersects(bounds2)
50
+ }
51
+
52
+ const boundsFromLabelPlacement = (label: TickLabelPlacement): Bounds => {
53
+ const { x, y, width, height, xAlign, yAlign } = label
54
+ const xShift =
55
+ xAlign === HorizontalAlign.center
56
+ ? -width / 2
57
+ : xAlign === HorizontalAlign.right
58
+ ? -width
59
+ : 0
60
+ const yShift =
61
+ yAlign === VerticalAlign.middle
62
+ ? -height / 2
63
+ : yAlign === VerticalAlign.bottom
64
+ ? -height
65
+ : 0
66
+ return new Bounds(x + xShift, y + yShift, width, height)
67
+ }
68
+
69
+ abstract class AbstractAxis {
70
+ config: AxisConfig
71
+ axisManager?: AxisManager
72
+
73
+ domain: ValueRange
74
+ formatColumn: CoreColumn | undefined = undefined // Pass the column purely for formatting reasons. Might be a better way to do this.
75
+ hideFractionalTicks = false
76
+ range: ValueRange = [0, 0]
77
+ private _scaleType: ScaleType | undefined = undefined
78
+ private _label: string | undefined = undefined
79
+
80
+ constructor(config: AxisConfig, axisManager?: AxisManager) {
81
+ makeObservable<AbstractAxis, "_scaleType" | "_label">(this, {
82
+ domain: observable.ref,
83
+ formatColumn: observable,
84
+ hideFractionalTicks: observable,
85
+ range: observable.struct,
86
+ _scaleType: observable,
87
+ _label: observable,
88
+ })
89
+ this.config = config
90
+ this.domain = [config.domain[0], config.domain[1]]
91
+ this.axisManager = axisManager
92
+ }
93
+
94
+ /**
95
+ * The orthogonal size of the axis.
96
+ * For horizontal axes, this is the height.
97
+ * For vertical axes, this is the width.
98
+ */
99
+ abstract get size(): number
100
+ abstract get orient(): Position
101
+ abstract get labelMaxWidth(): number
102
+
103
+ abstract placeTickLabel(value: number): TickLabelPlacement
104
+ abstract get tickLabels(): TickLabelPlacement[]
105
+
106
+ @computed get hideAxis(): boolean {
107
+ return this.config.hideAxis ?? false
108
+ }
109
+
110
+ @computed get hideGridlines(): boolean {
111
+ return this.config.hideGridlines ?? false
112
+ }
113
+
114
+ @computed get tickPadding(): number {
115
+ return this.config.tickPadding ?? 5
116
+ }
117
+
118
+ @computed get labelPadding(): number {
119
+ return this.config.labelPadding ?? 10
120
+ }
121
+
122
+ @computed get nice(): boolean {
123
+ return this.config.nice ?? false
124
+ }
125
+
126
+ @computed get fontSize(): number {
127
+ return this.config.fontSize
128
+ }
129
+
130
+ @computed protected get minTicks(): number {
131
+ return 2
132
+ }
133
+
134
+ @computed private get maxTicks(): number {
135
+ return this.config.maxTicks ?? 6
136
+ }
137
+
138
+ @computed get canChangeScaleType(): boolean | undefined {
139
+ return this.config.canChangeScaleType
140
+ }
141
+
142
+ @computed get scaleType(): ScaleType {
143
+ return this._scaleType ?? this.config.scaleType ?? ScaleType.linear
144
+ }
145
+
146
+ @computed get isLogScale(): boolean {
147
+ return this.scaleType === ScaleType.log
148
+ }
149
+
150
+ set scaleType(value: ScaleType) {
151
+ this._scaleType = value
152
+ }
153
+
154
+ @computed get label(): string {
155
+ return this._label ?? this.config.label ?? ""
156
+ }
157
+
158
+ set label(value: string) {
159
+ this._label = value
160
+ }
161
+
162
+ // This will expand the domain but never shrink.
163
+ // This will change the min unless the user's min setting is less
164
+ // This will change the max unless the user's max setting is greater
165
+ // Undefined values are ignored
166
+ updateDomainPreservingUserSettings(
167
+ domain: [number | undefined, number | undefined]
168
+ ): this {
169
+ const left =
170
+ domain[0] !== undefined
171
+ ? _.min([this.domain[0], domain[0]])
172
+ : this.domain[0]
173
+ const right =
174
+ domain[1] !== undefined
175
+ ? _.max([this.domain[1], domain[1]])
176
+ : this.domain[1]
177
+ this.domain = [left ?? 0, right ?? 0]
178
+ return this
179
+ }
180
+
181
+ // todo: refactor. switch to a parent pattern?
182
+ _update(parentAxis: AbstractAxis): this {
183
+ this.formatColumn = parentAxis.formatColumn
184
+ this.domain = parentAxis.domain.slice() as ValueRange
185
+ this.hideFractionalTicks = parentAxis.hideFractionalTicks
186
+ this.range = parentAxis.range.slice() as ValueRange
187
+ this._scaleType = parentAxis._scaleType
188
+ this._label = parentAxis._label
189
+ return this
190
+ }
191
+
192
+ private static calculateBandWidth({
193
+ values,
194
+ scale,
195
+ }: {
196
+ values: number[]
197
+ scale: Scale
198
+ }): number {
199
+ const range = scale.range()
200
+ const rangeSize = Math.abs(range[1] - range[0])
201
+ const maxBandWidth = 0.4 * rangeSize
202
+
203
+ if (values.length < 2) return maxBandWidth
204
+
205
+ // the band width is the smallest distance between
206
+ // two adjacent values placed on the axis
207
+ const sortedValues = _.sortBy(values)
208
+ const positions = sortedValues.map((value) => scale(value))
209
+ const diffs = positions
210
+ .slice(1)
211
+ .map((pos, index) => pos - positions[index])
212
+ const bandWidth = _.min(diffs) ?? 0
213
+
214
+ return _.min([bandWidth, maxBandWidth]) ?? 0
215
+ }
216
+
217
+ /**
218
+ * Maximum width a single value can take up on the axis.
219
+ * Not meaningful if no domain values are given.
220
+ */
221
+ @computed get bandWidth(): number | undefined {
222
+ const { domainValues } = this.config
223
+ if (!domainValues) return undefined
224
+ return AbstractAxis.calculateBandWidth({
225
+ values: domainValues,
226
+ scale: this.d3_scale,
227
+ })
228
+ }
229
+
230
+ private static makeScaleNice(
231
+ scale: ScaleLinear<number, number>,
232
+ totalTicksTarget: number
233
+ ): { scale: ScaleLinear<number, number>; ticks?: number[] } {
234
+ let ticks = scale.ticks(totalTicksTarget)
235
+
236
+ // use d3's nice function when there is only one tick
237
+ if (ticks.length < 2) return { scale: scale.nice(totalTicksTarget) }
238
+
239
+ const tickStep = ticks[1] - ticks[0]
240
+ const firstTick = ticks[0]
241
+ const lastTick = R.last(ticks)!
242
+
243
+ // if the the max or min value exceeds the last grid line by more than 25%,
244
+ // expand the domain to include an additional grid line
245
+ const [minValue, maxValue] = scale.domain()
246
+ if (maxValue > lastTick + 0.25 * tickStep) {
247
+ scale.domain([scale.domain()[0], lastTick + tickStep])
248
+ ticks = [...ticks, lastTick + tickStep]
249
+ }
250
+ if (minValue < firstTick - 0.25 * tickStep) {
251
+ scale.domain([firstTick - tickStep, scale.domain()[1]])
252
+ ticks = [firstTick - tickStep, ...ticks]
253
+ }
254
+
255
+ return { scale, ticks }
256
+ }
257
+
258
+ private niceTicks?: number[]
259
+ @computed private get d3_scale(): Scale {
260
+ const d3Scale = this.isLogScale ? scaleLog : scaleLinear
261
+ let scale = d3Scale().domain(this.domain).range(this.range)
262
+
263
+ if (this.nice && !this.isLogScale) {
264
+ const { scale: niceScale, ticks: niceTicks } =
265
+ AbstractAxis.makeScaleNice(scale, this.totalTicksTarget)
266
+ scale = niceScale
267
+ this.niceTicks = niceTicks
268
+ } else {
269
+ this.niceTicks = undefined
270
+ }
271
+
272
+ if (this.config.domainValues) {
273
+ // compute bandwidth and adjust the scale
274
+ const bandWidth = AbstractAxis.calculateBandWidth({
275
+ values: this.config.domainValues,
276
+ scale,
277
+ })
278
+ const offset = bandWidth / 2 + OUTER_PADDING
279
+ const r = scale.range()
280
+ return scale.range([r[0] + offset, r[1] - offset])
281
+ } else {
282
+ return scale
283
+ }
284
+ }
285
+
286
+ @computed get rangeSize(): number {
287
+ return Math.abs(this.range[1] - this.range[0])
288
+ }
289
+
290
+ @computed get rangeMax(): number {
291
+ return Math.max(this.range[1], this.range[0])
292
+ }
293
+
294
+ @computed get rangeMin(): number {
295
+ return Math.min(this.range[1], this.range[0])
296
+ }
297
+
298
+ @computed get rangeCenter(): number {
299
+ return this.rangeMin + this.rangeSize / 2
300
+ }
301
+
302
+ /** The number of ticks we should _aim_ to show, not necessarily a strict target. */
303
+ @computed private get totalTicksTarget(): number {
304
+ // Chose 1.8 here by trying a bunch of different faceted charts and figuring out what
305
+ // a reasonable lower bound is.
306
+ // NOTE: This setting is used between both log & linear axes, check both when tweaking.
307
+ // -@danielgavrilov, 2021-06-15
308
+ return Math.round(
309
+ R.clamp(this.rangeSize / (this.fontSize * 1.8), {
310
+ min: this.minTicks,
311
+ max: this.maxTicks,
312
+ })
313
+ )
314
+ }
315
+
316
+ getTickValues(): Tickmark[] {
317
+ const { d3_scale } = this
318
+
319
+ let ticks: Tickmark[]
320
+
321
+ if (this.config.ticks) {
322
+ // If custom ticks are supplied, use them without any transformations or additions.
323
+ const [minValue, maxValue] = d3_scale.domain()
324
+ return (
325
+ this.config.ticks
326
+ // replace ±Infinity with minimum/maximum
327
+ .map((tick) => {
328
+ if (tick.value === -Infinity)
329
+ return { ...tick, value: minValue }
330
+ if (tick.value === Infinity)
331
+ return { ...tick, value: maxValue }
332
+ return tick
333
+ })
334
+ // filter out custom ticks outside the plottable area
335
+ .filter(
336
+ (tick) =>
337
+ tick.value >= minValue && tick.value <= maxValue
338
+ )
339
+ )
340
+ } else if (this.isLogScale) {
341
+ // Show a bit more ticks for log axes
342
+ const maxLabelledTicks = Math.round(this.totalTicksTarget * 1.25)
343
+ const maxTicks = Math.round(this.totalTicksTarget * 3)
344
+
345
+ // This is a wild heuristic that decides how many tick lines and grid lines we want to
346
+ // show for log charts.
347
+ //
348
+ // It tries to achive multiple goals:
349
+ // * make it obvious for the user which values they're looking at
350
+ // * ideally, make it very clear that this is a log axis by looking like log paper
351
+ // * (but) don't overwhelm the user
352
+ // * avoid cases where only one tick is shown for the whole axis (we had those!)
353
+ //
354
+ // This code roughly works as follows:
355
+ // First, we let d3 generate ticks for the axis. d3 gives values of the form `y * 10^x`,
356
+ // with 0 < y < 10.
357
+ // We then assign priorities to these values:
358
+ // * priority 1 (highest) to values of the form `1 * 10^x` (e.g. 100)
359
+ // * priority 2 to values of the form `2 * 10^x` or `5 * 10^x` (e.g. 5, 2000)
360
+ // * priority 3 (lowest) to all other ("in-between") values (e.g. 70, 300)
361
+ //
362
+ // We then decide depending on the number of tick candidates what to do:
363
+ // * if we have less than `maxLabelledTicks`, just show all
364
+ // * if we have between `maxLabelledTicks` and `maxTicks`, show all "in-between" lines
365
+ // as faint grid lines without labels to give the chart that log paper look.
366
+ // We also show all priority 1 and 2 lines with labels, because there aren't too many
367
+ // of them.
368
+ // * otherwise, remove priority 3 and, if necessary, priority 2 labels until we're below
369
+ // `maxLabelledTicks` labels overall
370
+ //
371
+ // -@MarcelGerber, 2020-08-07
372
+ const tickCandidates = d3_scale.ticks(maxLabelledTicks)
373
+ ticks = tickCandidates.map((value) => {
374
+ // 10^x
375
+ if (Math.fround(Math.log10(value)) % 1 === 0)
376
+ return { value, priority: 1 }
377
+ // 5 * 10^x
378
+ else if (Math.fround(Math.log10(value * 2)) % 1 === 0)
379
+ return { value, priority: 2 }
380
+ // 2 * 10^x
381
+ else if (Math.fround(Math.log10(value / 2)) % 1 === 0)
382
+ return { value, priority: 2 }
383
+ return { value, priority: 3 }
384
+ })
385
+
386
+ if (ticks.length > maxLabelledTicks) {
387
+ if (ticks.length <= maxTicks) {
388
+ // Convert all "in-between" lines to faint grid lines without labels
389
+ ticks = ticks.map((tick) => {
390
+ if (tick.priority === 3)
391
+ tick = {
392
+ ...tick,
393
+ faint: true,
394
+ gridLineOnly: true,
395
+ }
396
+ return tick
397
+ })
398
+ } else {
399
+ // Remove some tickmarks again because the chart would get too overwhelming
400
+ // otherwise
401
+ for (let priority = 3; priority > 1; priority--) {
402
+ if (ticks.length > maxLabelledTicks)
403
+ ticks = ticks.filter(
404
+ (tick) => tick.priority < priority
405
+ )
406
+ }
407
+ }
408
+ }
409
+ } else {
410
+ const d3_ticks =
411
+ this.niceTicks ?? d3_scale.ticks(this.totalTicksTarget)
412
+
413
+ // Only use priority 2 here because we want the start / end ticks
414
+ // to be priority 1
415
+ ticks = d3_ticks.map((tickValue) => ({
416
+ value: tickValue,
417
+ priority: 2,
418
+ }))
419
+ }
420
+
421
+ if (this.hideFractionalTicks)
422
+ ticks = ticks.filter((t) => t.value % 1 === 0)
423
+
424
+ // mark value=0 ticks as solid for non-time columns
425
+ if (!this.formatColumn?.isTimeColumn) {
426
+ ticks = ticks.map((tick) =>
427
+ tick.value === 0 ? { ...tick, solid: true } : tick
428
+ )
429
+ }
430
+
431
+ return _.uniq(ticks)
432
+ }
433
+
434
+ private getTickFormattingOptions(): TickFormattingOptions {
435
+ const options: TickFormattingOptions = {
436
+ ...this.config.tickFormattingOptions,
437
+ roundingMode: OwidVariableRoundingMode.decimalPlaces,
438
+ }
439
+
440
+ // The chart's tick formatting function is used by default to format axis ticks. This means
441
+ // that the chart's `numDecimalPlaces` is also used by default to format the axis ticks.
442
+ //
443
+ // However, the author-specified decimal places are not always appropriate for rendering
444
+ // ticks, because:
445
+ // 1. Subsets of the data may require higher fidelity, e.g. users can use the timeline to
446
+ // end up in a subset of the dataset where values happen to be much lower than usual.
447
+ // 2. Ticks may be rendered at granularities that may not exist in the data, e.g. the data
448
+ // may only contain 0 and 1, but we may show ticks in between those values.
449
+ //
450
+ // Therefore, when formatting ticks, we determine the `numDecimalPlaces` automatically, by
451
+ // finding the smallest difference between any pair of ticks and making sure that we have
452
+ // sufficient decimal places to express the difference to the first significant figure (the
453
+ // first non-zero digit).
454
+ //
455
+ // One significant figure is sufficient because we use D3's ticks() and that creates
456
+ // "uniformly-spaced, nicely-rounded values [...] where each value is a power of ten
457
+ // multiplied by 1, 2 or 5"
458
+ // See: https://github.com/d3/d3-array/blob/master/README.md#ticks
459
+ //
460
+ // -@danielgavrilov, 2020-05-27
461
+ const minDist = _.min(
462
+ rollingMap(this.baseTicks, (a, b) => Math.abs(a.value - b.value))
463
+ )
464
+ if (minDist !== undefined) {
465
+ // Find the decimal places required to reach the first non-zero digit
466
+ const dp = -numberMagnitude(minDist) + 1
467
+ if (dp >= 0) {
468
+ options.numDecimalPlaces = dp
469
+ }
470
+ }
471
+ return options
472
+ }
473
+
474
+ place(value: number): number {
475
+ if (!this.range) {
476
+ console.error(
477
+ "Can't place value on scale without a defined output range"
478
+ )
479
+ return value
480
+ } else if (this.isLogScale && value <= 0) {
481
+ console.error(`Can't have ${value} which is <= 0 on a log scale`)
482
+ return value
483
+ } else if (this.domain[0] === this.domain[1]) {
484
+ // When the domain is a single value, the D3 scale will by default place
485
+ // the value at the middle of the range.
486
+ // We instead want to customize what happens - sometimes we want to place the point
487
+ // at the start of the range instead.
488
+ // see https://github.com/owid/owid-grapher/pull/1367#issuecomment-1090845181.
489
+ //
490
+ // -@marcelgerber, 2022-04-12
491
+ switch (this.config.singleValueAxisPointAlign) {
492
+ case AxisAlign.start:
493
+ return this.range[0]
494
+ case AxisAlign.end:
495
+ return this.range[1]
496
+ case AxisAlign.middle:
497
+ default:
498
+ return (this.range[0] + this.range[1]) / 2
499
+ }
500
+ }
501
+ const placedValue = this.d3_scale(value)
502
+ if (placedValue === undefined) {
503
+ console.error(`Placed value is undefined for ${value}`)
504
+ return value
505
+ }
506
+ return parseFloat(placedValue.toFixed(1))
507
+ }
508
+
509
+ /** This function returns the inverse of place - i.e. given a screen space
510
+ * coordinate, it returns the corresponding domain value. This is useful
511
+ * for cases where you want to make sure that something is at least one pixel high.
512
+ */
513
+ invert(value: number): number {
514
+ return this.d3_scale.invert(value)
515
+ }
516
+
517
+ @computed get tickFontSize(): number {
518
+ return Math.floor(GRAPHER_FONT_SCALE_12 * this.fontSize)
519
+ }
520
+
521
+ @computed protected get baseTicks(): Tickmark[] {
522
+ return this.getTickValues().filter((tick) => !tick.gridLineOnly)
523
+ }
524
+
525
+ formatTick(
526
+ tick: number,
527
+ formattingOptionsOverride?: TickFormattingOptions
528
+ ): string {
529
+ const tickFormattingOptions: TickFormattingOptions = {
530
+ ...this.getTickFormattingOptions(),
531
+ ...formattingOptionsOverride,
532
+ }
533
+ return (
534
+ this.formatColumn?.formatForTick(tick, tickFormattingOptions) ??
535
+ tick.toString()
536
+ )
537
+ }
538
+
539
+ @computed get labelFontSize(): number {
540
+ return Math.floor(GRAPHER_FONT_SCALE_12 * this.fontSize)
541
+ }
542
+
543
+ @computed get labelTextWrap(): MarkdownTextWrap | undefined {
544
+ if (!this.label) return
545
+
546
+ const textWrapProps = {
547
+ maxWidth: this.labelMaxWidth,
548
+ fontSize: this.labelFontSize,
549
+ lineHeight: 1,
550
+ detailsOrderedByReference:
551
+ this.axisManager?.detailsOrderedByReference,
552
+ }
553
+
554
+ const axisLabel = makeAxisLabel({
555
+ label: this.label,
556
+ displayUnit: this.formatColumn?.displayUnit,
557
+ })
558
+
559
+ const logScaleNotice = "plotted on a logarithmic axis"
560
+
561
+ if (axisLabel.unit) {
562
+ const secondaryText = this.isLogScale
563
+ ? `(${axisLabel.unit}; ${logScaleNotice})`
564
+ : `(${axisLabel.unit})`
565
+ return MarkdownTextWrap.fromFragments({
566
+ main: { text: axisLabel.mainLabel, bold: true },
567
+ secondary: { text: secondaryText },
568
+ newLine: "avoid-wrap",
569
+ textWrapProps,
570
+ })
571
+ }
572
+
573
+ if (this.isLogScale) {
574
+ return MarkdownTextWrap.fromFragments({
575
+ main: { text: axisLabel.mainLabel, bold: true },
576
+ secondary: { text: `(${logScaleNotice})` },
577
+ newLine: "avoid-wrap",
578
+ textWrapProps,
579
+ })
580
+ }
581
+
582
+ return new MarkdownTextWrap({
583
+ text: axisLabel.mainLabel,
584
+ fontWeight: 700,
585
+ ...textWrapProps,
586
+ })
587
+ }
588
+
589
+ @computed get labelHeight(): number {
590
+ return this.labelTextWrap
591
+ ? this.labelTextWrap.height + this.labelPadding
592
+ : 0
593
+ }
594
+ }
595
+
596
+ export class HorizontalAxis extends AbstractAxis {
597
+ constructor(config: AxisConfig, axisManager?: AxisManager) {
598
+ super(config, axisManager)
599
+
600
+ makeObservable(this)
601
+ }
602
+
603
+ clone(): HorizontalAxis {
604
+ return new HorizontalAxis(this.config, this.axisManager)._update(this)
605
+ }
606
+
607
+ @computed get orient(): Position {
608
+ // Default to `bottom` unless overriden to `top`.
609
+ return this.config.orient === Position.top
610
+ ? Position.top
611
+ : Position.bottom
612
+ }
613
+
614
+ @computed get labelOffset(): number {
615
+ return this.labelHeight
616
+ }
617
+
618
+ @computed get labelMaxWidth(): number {
619
+ return this.rangeSize
620
+ }
621
+
622
+ // note that we intentionally don't take `hideAxisLabels` into account here.
623
+ // tick labels might be hidden in faceted charts. when faceted, it's important
624
+ // the axis size doesn't change as a result of hiding the axis labels, or else
625
+ // we might end up with misaligned axes.
626
+ @computed get height(): number {
627
+ if (this.hideAxis) return 0
628
+ const { labelOffset, tickPadding } = this
629
+ const maxTickHeight = _.max(this.tickLabels.map((tick) => tick.height))
630
+ const tickHeight = maxTickHeight ? maxTickHeight + tickPadding : 0
631
+ return Math.max(tickHeight + labelOffset, this.config.minSize ?? 0)
632
+ }
633
+
634
+ @computed get size(): number {
635
+ return this.height
636
+ }
637
+
638
+ protected override get baseTicks(): Tickmark[] {
639
+ let ticks = this.getTickValues().filter(
640
+ (tick): boolean => !tick.gridLineOnly
641
+ )
642
+ const { domain } = this
643
+
644
+ // Make sure the start and end values are present, if they're whole numbers
645
+ const startEndPrio = this.isLogScale ? 2 : 1
646
+ if (domain[0] % 1 === 0)
647
+ ticks = [
648
+ {
649
+ value: domain[0],
650
+ priority: startEndPrio,
651
+ },
652
+ ...ticks,
653
+ ]
654
+ if (domain[1] % 1 === 0 && this.hideFractionalTicks)
655
+ ticks = [
656
+ ...ticks,
657
+ {
658
+ value: domain[1],
659
+ priority: startEndPrio,
660
+ },
661
+ ]
662
+
663
+ // sort by value, then priority.
664
+ // this way, we don't end up with two ticks of the same value but different priorities.
665
+ // instead, we deduplicate by choosing the highest priority (i.e. lowest priority value).
666
+ const sortedTicks = _.sortBy(ticks, [
667
+ (t): number => t.value,
668
+ (t): number => t.priority,
669
+ ])
670
+ return _.sortedUniqBy(sortedTicks, (t) => t.value)
671
+ }
672
+
673
+ @computed get tickLabels(): TickLabelPlacement[] {
674
+ // Get ticks with coordinates, sorted by priority
675
+ const tickLabels = _.sortBy(
676
+ this.baseTicks,
677
+ (tick) => tick.priority
678
+ ).map((tick) => this.placeTickLabel(tick.value))
679
+ const visibleTickLabels = hideOverlappingTickLabels(tickLabels, {
680
+ padding: 3,
681
+ })
682
+ return visibleTickLabels
683
+ }
684
+
685
+ placeTickLabel(value: number): TickLabelPlacement {
686
+ const formattedValue = this.formatTick(value)
687
+ const { width, height } = Bounds.forText(formattedValue, {
688
+ fontSize: this.tickFontSize,
689
+ })
690
+ let x = this.place(value)
691
+ let xAlign = HorizontalAlign.center
692
+ const left = x - width / 2
693
+ const right = x + width / 2
694
+ const offset = this.bandWidth ? this.bandWidth / 2 + OUTER_PADDING : 0
695
+ if (left < this.rangeMin - offset) {
696
+ x = this.rangeMin
697
+ xAlign = HorizontalAlign.left
698
+ }
699
+ if (right > this.rangeMax + offset) {
700
+ x = this.rangeMax
701
+ xAlign = HorizontalAlign.right
702
+ }
703
+ return {
704
+ value,
705
+ formattedValue,
706
+ x,
707
+ y: 0,
708
+ width,
709
+ height,
710
+ xAlign,
711
+ isHidden: false,
712
+ }
713
+ }
714
+
715
+ // Add some padding before checking for intersection
716
+ protected doIntersect(bounds: Bounds, bounds2: Bounds): boolean {
717
+ return bounds.intersects(bounds2.padWidth(-5))
718
+ }
719
+ }
720
+
721
+ export class VerticalAxis extends AbstractAxis {
722
+ constructor(config: AxisConfig, axisManager?: AxisManager) {
723
+ super(config, axisManager)
724
+
725
+ makeObservable(this)
726
+ }
727
+
728
+ clone(): VerticalAxis {
729
+ return new VerticalAxis(this.config, this.axisManager)._update(this)
730
+ }
731
+
732
+ @computed get orient(): Position {
733
+ return Position.left
734
+ }
735
+
736
+ @computed get labelMaxWidth(): number {
737
+ return this.axisManager?.axisBounds?.width ?? Infinity
738
+ }
739
+
740
+ @computed get labelOffsetTop(): number {
741
+ return this.labelHeight
742
+ }
743
+
744
+ // note that we intentionally don't take `hideAxisLabels` into account here.
745
+ // tick labels might be hidden in faceted charts. when faceted, it's important
746
+ // the axis size doesn't change as a result of hiding the axis labels, or else
747
+ // we might end up with misaligned axes.
748
+ @computed get width(): number {
749
+ if (this.hideAxis) return 0
750
+ const { tickPadding } = this
751
+ const maxTickWidth = _.max(this.tickLabels.map((tick) => tick.width))
752
+ const tickWidth =
753
+ maxTickWidth !== undefined ? maxTickWidth + tickPadding : 0
754
+ return Math.max(tickWidth, this.config.minSize ?? 0)
755
+ }
756
+
757
+ @computed get height(): number {
758
+ return this.rangeSize
759
+ }
760
+
761
+ @computed get size(): number {
762
+ return this.width
763
+ }
764
+
765
+ @computed get tickLabels(): TickLabelPlacement[] {
766
+ const { domain } = this
767
+
768
+ const tickLabels = _.sortBy(
769
+ this.baseTicks,
770
+ (tick) => tick.priority
771
+ ).map((tick) => this.placeTickLabel(tick.value))
772
+
773
+ // hide overlapping ticks, and allow for some padding
774
+ let visibleTicks = hideOverlappingTickLabels(tickLabels, { padding: 3 })
775
+
776
+ // if we end up with too few ticks, try again with less padding
777
+ if (visibleTicks.length < this.minTicks) {
778
+ visibleTicks = hideOverlappingTickLabels(tickLabels, { padding: 1 })
779
+ }
780
+
781
+ // if we still have too few ticks, de-prioritize the zero tick
782
+ // if it's a start or end value and drawn as a solid line
783
+ if (visibleTicks.length < this.minTicks) {
784
+ const updatedBaseTicks = _.cloneDeep(this.baseTicks)
785
+ if (domain[0] === 0 || domain[1] === 0) {
786
+ const zeroIndex = updatedBaseTicks
787
+ .map((tick) => tick.value)
788
+ .indexOf(0)
789
+ if (zeroIndex >= 0 && updatedBaseTicks[zeroIndex].solid) {
790
+ updatedBaseTicks[zeroIndex] = {
791
+ value: 0,
792
+ priority: 3,
793
+ }
794
+ }
795
+ }
796
+
797
+ const tickLabels = _.sortBy(
798
+ updatedBaseTicks,
799
+ (tick) => tick.priority
800
+ ).map((tick) => this.placeTickLabel(tick.value))
801
+ visibleTicks = hideOverlappingTickLabels(tickLabels, { padding: 1 })
802
+ }
803
+
804
+ return visibleTicks
805
+ }
806
+
807
+ placeTickLabel(value: number): TickLabelPlacement {
808
+ const formattedValue = this.formatTick(value)
809
+ const { width, height } = Bounds.forText(formattedValue, {
810
+ fontSize: this.tickFontSize,
811
+ })
812
+ const y = this.place(value)
813
+ return {
814
+ value,
815
+ formattedValue,
816
+ x: 0,
817
+ y,
818
+ width,
819
+ height,
820
+ xAlign: HorizontalAlign.right,
821
+ yAlign: VerticalAlign.middle,
822
+ isHidden: false,
823
+ }
824
+ }
825
+
826
+ @computed get shouldShowLogNotice(): boolean {
827
+ return this.isLogScale && !this.labelTextWrap
828
+ }
829
+
830
+ @computed get logNoticeTextWrap(): MarkdownTextWrap | undefined {
831
+ if (!this.shouldShowLogNotice) return undefined
832
+
833
+ const fontSize = Math.floor(GRAPHER_FONT_SCALE_11 * this.fontSize)
834
+
835
+ return new MarkdownTextWrap({
836
+ text: "log axis",
837
+ maxWidth: Infinity, // No line breaks
838
+ fontSize,
839
+ })
840
+ }
841
+
842
+ @computed get logNoticeWidth(): number {
843
+ return this.logNoticeTextWrap ? this.logNoticeTextWrap.width : 0
844
+ }
845
+
846
+ @computed get logNoticeHeight(): number {
847
+ const padding = this.tickFontSize
848
+ return this.logNoticeTextWrap
849
+ ? this.logNoticeTextWrap.height + padding
850
+ : 0
851
+ }
852
+ }
853
+
854
+ interface DualAxisProps {
855
+ bounds?: Bounds
856
+ horizontalAxis: HorizontalAxis
857
+ verticalAxis: VerticalAxis
858
+ comparisonLines?: ComparisonLineConfig[]
859
+ }
860
+
861
+ // DualAxis has the important task of coordinating two axes so that they work together!
862
+ // There is a *two-way dependency* between the bounding size of each axis.
863
+ // e.g. if the y axis becomes wider because a label is present, the x axis then has less
864
+ // space to work with, and vice versa
865
+ export class DualAxis {
866
+ private props: DualAxisProps
867
+ constructor(props: DualAxisProps) {
868
+ makeObservable(this)
869
+ this.props = props
870
+ }
871
+
872
+ @computed get horizontalAxis(): HorizontalAxis {
873
+ const axis = this.props.horizontalAxis.clone()
874
+ axis.range = this.innerBounds.xRange()
875
+ return axis
876
+ }
877
+
878
+ @computed get verticalAxis(): VerticalAxis {
879
+ const axis = this.props.verticalAxis.clone()
880
+ axis.range = this.innerBounds.yRange()
881
+ return axis
882
+ }
883
+
884
+ // We calculate an initial height from the range of the input bounds
885
+ @computed private get horizontalAxisSize(): number {
886
+ const axis = this.props.horizontalAxis.clone()
887
+ axis.range = [0, this.bounds.width]
888
+ return axis.size
889
+ }
890
+
891
+ // We calculate an initial width from the range of the input bounds
892
+ @computed private get verticalAxisSize(): number {
893
+ const axis = this.props.verticalAxis.clone()
894
+ axis.range = [0, this.bounds.height]
895
+ return axis.size
896
+ }
897
+
898
+ // Now we can determine the "true" inner bounds of the dual axis
899
+ @computed get innerBounds(): Bounds {
900
+ return (
901
+ this.bounds
902
+ // Add padding to account for the width of the vertical axis
903
+ // and the height of the horizontal axis
904
+ .pad({
905
+ [this.props.horizontalAxis.orient]: this.horizontalAxisSize,
906
+ [this.props.verticalAxis.orient]: this.verticalAxisSize,
907
+ })
908
+ // Make space for the y-axis label if plotted above the axis
909
+ .padTop(this.props.verticalAxis.labelOffsetTop)
910
+ // Make space for vertical comparison line labels if any
911
+ .padTop(this.comparisonLineLabelOffset)
912
+ .padTop(
913
+ this.shouldShowLogNotice
914
+ ? this.props.verticalAxis.logNoticeHeight
915
+ : 0
916
+ )
917
+ )
918
+ }
919
+
920
+ @computed get bounds(): Bounds {
921
+ return this.props.bounds ?? DEFAULT_GRAPHER_BOUNDS
922
+ }
923
+
924
+ @computed get comparisonLines(): ComparisonLineConfig[] {
925
+ return this.props.comparisonLines ?? []
926
+ }
927
+
928
+ @computed get comparisonLineLabelFontSize(): number {
929
+ return Math.floor(
930
+ GRAPHER_FONT_SCALE_10_5 * this.props.verticalAxis.fontSize
931
+ )
932
+ }
933
+
934
+ @computed private get comparisonLineLabelOffset(): number {
935
+ const hasVerticalComparisonLines = this.comparisonLines.some((line) =>
936
+ isValidVerticalComparisonLineConfig(line)
937
+ )
938
+
939
+ if (!hasVerticalComparisonLines) return 0
940
+
941
+ return this.comparisonLineLabelFontSize
942
+ }
943
+
944
+ @computed private get shouldShowLogNotice(): boolean {
945
+ return (
946
+ this.props.verticalAxis.shouldShowLogNotice &&
947
+ // Only show the notice if it fits in the margin
948
+ this.props.verticalAxis.logNoticeWidth <= this.verticalAxisSize
949
+ )
950
+ }
951
+ }
952
+
953
+ function hideOverlappingTickLabels(
954
+ tickLabels: TickLabelPlacement[],
955
+ { padding = 0 }: { padding?: number } = {}
956
+ ): TickLabelPlacement[] {
957
+ for (let i = 0; i < tickLabels.length; i++) {
958
+ for (let j = i + 1; j < tickLabels.length; j++) {
959
+ const t1 = tickLabels[i],
960
+ t2 = tickLabels[j]
961
+ if (t1 === t2 || t1.isHidden || t2.isHidden) continue
962
+ if (
963
+ doIntersect(
964
+ // Expand bounds so that labels aren't too close together.
965
+ boundsFromLabelPlacement(t1).expand(padding),
966
+ boundsFromLabelPlacement(t2).expand(padding)
967
+ )
968
+ )
969
+ t2.isHidden = true
970
+ }
971
+ }
972
+ return tickLabels.filter((tick) => !tick.isHidden)
973
+ }