@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,923 @@
1
+ import * as _ from "lodash-es"
2
+ import * as React from "react"
3
+ import * as R from "remeda"
4
+ import { computed, makeObservable } from "mobx"
5
+ import { observer } from "mobx-react"
6
+ import {
7
+ dyFromAlign,
8
+ removeAllWhitespace,
9
+ Bounds,
10
+ HorizontalAlign,
11
+ VerticalAlign,
12
+ makeIdForHumanConsumption,
13
+ } from "../../utils/index.js"
14
+ import { TextWrap } from "../../components/index.js"
15
+ import {
16
+ ColorScaleBin,
17
+ NumericBin,
18
+ CategoricalBin,
19
+ } from "../color/ColorScaleBin"
20
+ import {
21
+ BASE_FONT_SIZE,
22
+ GRAPHER_FONT_SCALE_12,
23
+ GRAPHER_FONT_SCALE_12_8,
24
+ GRAPHER_FONT_SCALE_14,
25
+ } from "../core/GrapherConstants"
26
+ import { darkenColorForLine } from "../color/ColorUtils"
27
+ import {
28
+ LegendInteractionState,
29
+ LegendStyleConfig,
30
+ LegendTextStyle,
31
+ LegendMarkerStyle,
32
+ } from "../legend/LegendInteractionState"
33
+ import { GRAPHER_DARK_TEXT } from "../color/ColorConstants"
34
+
35
+ export interface PositionedBin {
36
+ x: number
37
+ width: number
38
+ bin: ColorScaleBin
39
+ }
40
+
41
+ interface NumericLabel {
42
+ text: string
43
+ fontSize: number
44
+ bounds: Bounds
45
+ priority?: boolean
46
+ hidden: boolean
47
+ raised: boolean
48
+ bin: ColorScaleBin
49
+ }
50
+
51
+ interface CategoricalMark {
52
+ x: number
53
+ y: number
54
+ rectSize: number
55
+ width: number
56
+ label: {
57
+ text: string
58
+ bounds: Bounds
59
+ fontSize: number
60
+ }
61
+ bin: CategoricalBin
62
+ }
63
+
64
+ interface MarkLine {
65
+ totalWidth: number
66
+ marks: CategoricalMark[]
67
+ }
68
+
69
+ // TODO unify properties across categorical & numeric legend.
70
+ // This would make multiple legends per chart less convenient (only used in Map), but we shouldn't
71
+ // be using multiple anyway – instead the numeric should also handle categorical bins too.
72
+ export interface HorizontalColorLegendManager {
73
+ fontSize?: number
74
+ legendX?: number
75
+ legendAlign?: HorizontalAlign
76
+ legendTitle?: string
77
+ categoryLegendY?: number
78
+ numericLegendY?: number
79
+ legendWidth?: number
80
+ legendMaxWidth?: number
81
+ legendHeight?: number
82
+ legendTickSize?: number
83
+ legendCursor?: "pointer" | "default"
84
+ categoricalLegendData?: CategoricalBin[]
85
+ numericLegendData?: ColorScaleBin[]
86
+ numericBinSize?: number
87
+ onLegendMouseEnter?: (d: ColorScaleBin) => void
88
+ onLegendMouseLeave?: () => void
89
+ onLegendMouseOver?: (d: ColorScaleBin) => void
90
+ onLegendClick?: (d: ColorScaleBin) => void
91
+ isStatic?: boolean
92
+ getLegendBinState?: (bin: ColorScaleBin) => LegendInteractionState
93
+ legendStyleConfig?: LegendStyleConfig
94
+ categoricalLegendStyleConfig?: LegendStyleConfig
95
+ numericLegendStyleConfig?: LegendStyleConfig
96
+ }
97
+
98
+ const DEFAULT_NUMERIC_BIN_SIZE = 10
99
+ const DEFAULT_NUMERIC_BIN_STROKE = "#333"
100
+ const DEFAULT_NUMERIC_BIN_STROKE_WIDTH = 0.3
101
+ const DEFAULT_TEXT_COLOR = "#111"
102
+ const DEFAULT_TICK_SIZE = 3
103
+
104
+ const CATEGORICAL_BIN_MIN_WIDTH = 20
105
+ const SPACE_BETWEEN_CATEGORICAL_BINS = 7
106
+ const MINIMUM_LABEL_DISTANCE = 5
107
+
108
+ export abstract class HorizontalColorLegend extends React.Component<{
109
+ manager: HorizontalColorLegendManager
110
+ }> {
111
+ constructor(props: { manager: HorizontalColorLegendManager }) {
112
+ super(props)
113
+ makeObservable(this)
114
+ }
115
+
116
+ @computed protected get manager(): HorizontalColorLegendManager {
117
+ return this.props.manager
118
+ }
119
+
120
+ @computed protected get legendX(): number {
121
+ return this.manager.legendX ?? 0
122
+ }
123
+
124
+ @computed protected get categoryLegendY(): number {
125
+ return this.manager.categoryLegendY ?? 0
126
+ }
127
+
128
+ @computed protected get numericLegendY(): number {
129
+ return this.manager.numericLegendY ?? 0
130
+ }
131
+
132
+ @computed protected get legendMaxWidth(): number | undefined {
133
+ return this.manager.legendMaxWidth
134
+ }
135
+
136
+ @computed protected get legendHeight(): number {
137
+ return this.manager.legendHeight ?? 200
138
+ }
139
+
140
+ @computed protected get legendAlign(): HorizontalAlign {
141
+ // Assume center alignment if none specified, for backwards-compatibility
142
+ return this.manager.legendAlign ?? HorizontalAlign.center
143
+ }
144
+
145
+ @computed protected get fontSize(): number {
146
+ return this.manager.fontSize ?? BASE_FONT_SIZE
147
+ }
148
+
149
+ @computed protected get legendTickSize(): number {
150
+ return this.manager.legendTickSize ?? DEFAULT_TICK_SIZE
151
+ }
152
+
153
+ protected getBinState(bin: ColorScaleBin): LegendInteractionState {
154
+ return (
155
+ this.manager.getLegendBinState?.(bin) ??
156
+ LegendInteractionState.Default
157
+ )
158
+ }
159
+
160
+ abstract get height(): number
161
+ abstract get width(): number
162
+ }
163
+
164
+ @observer
165
+ export class HorizontalNumericColorLegend extends HorizontalColorLegend {
166
+ base = React.createRef<SVGGElement>()
167
+
168
+ constructor(props: { manager: HorizontalColorLegendManager }) {
169
+ super(props)
170
+
171
+ makeObservable(this)
172
+ }
173
+
174
+ @computed private get numericLegendData(): ColorScaleBin[] {
175
+ return this.manager.numericLegendData ?? []
176
+ }
177
+
178
+ @computed private get visibleBins(): ColorScaleBin[] {
179
+ return this.numericLegendData.filter((bin) => !bin.isHidden)
180
+ }
181
+
182
+ @computed private get numericBins(): NumericBin[] {
183
+ return this.visibleBins.filter(
184
+ (bin): bin is NumericBin => bin instanceof NumericBin
185
+ )
186
+ }
187
+
188
+ @computed private get numericBinSize(): number {
189
+ return this.props.manager.numericBinSize ?? DEFAULT_NUMERIC_BIN_SIZE
190
+ }
191
+
192
+ @computed private get tickFontSize(): number {
193
+ return GRAPHER_FONT_SCALE_12 * this.fontSize
194
+ }
195
+
196
+ @computed private get itemMargin(): number {
197
+ return Math.round(this.fontSize * 1.125)
198
+ }
199
+
200
+ @computed private get maxWidth(): number {
201
+ return this.manager.legendMaxWidth ?? this.manager.legendWidth ?? 200
202
+ }
203
+
204
+ private getTickLabelWidth(label: string): number {
205
+ return Bounds.forText(label, {
206
+ fontSize: this.tickFontSize,
207
+ }).width
208
+ }
209
+
210
+ private getCategoricalBinWidth(bin: ColorScaleBin): number {
211
+ return Math.max(
212
+ this.getTickLabelWidth(bin.text),
213
+ CATEGORICAL_BIN_MIN_WIDTH
214
+ )
215
+ }
216
+
217
+ @computed private get totalCategoricalWidth(): number {
218
+ const { visibleBins, itemMargin } = this
219
+ const widths = visibleBins.map((bin) =>
220
+ bin instanceof CategoricalBin && !bin.isHidden
221
+ ? this.getCategoricalBinWidth(bin) + itemMargin
222
+ : 0
223
+ )
224
+ return _.sum(widths)
225
+ }
226
+
227
+ @computed private get isAutoWidth(): boolean {
228
+ return (
229
+ this.manager.legendWidth === undefined &&
230
+ this.manager.legendMaxWidth !== undefined
231
+ )
232
+ }
233
+
234
+ private getNumericLabelMinWidth(bin: NumericBin): number {
235
+ if (bin.text) {
236
+ const tickLabelWidth = this.getTickLabelWidth(bin.text)
237
+ return tickLabelWidth + MINIMUM_LABEL_DISTANCE
238
+ } else {
239
+ const combinedLabelWidths = _.sum(
240
+ [bin.minText, bin.maxText].map(
241
+ (text) =>
242
+ // because labels are center-aligned, only half the label space is required
243
+ this.getTickLabelWidth(text) / 2
244
+ )
245
+ )
246
+ return combinedLabelWidths + MINIMUM_LABEL_DISTANCE * 2
247
+ }
248
+ }
249
+
250
+ // Overstretched legends don't look good.
251
+ // If the manager provides `legendMaxWidth`, then we calculate an _ideal_ width for the legend.
252
+ @computed private get idealNumericWidth(): number {
253
+ const binCount = this.numericBins.length
254
+ const spaceRequirements = this.numericBins.map((bin) => ({
255
+ labelSpace: this.getNumericLabelMinWidth(bin),
256
+ }))
257
+ // Make sure the legend is big enough to avoid overlapping labels (including `raisedMode`)
258
+
259
+ // Try to keep the minimum close to the size of the "No data" bin,
260
+ // so they look visually balanced somewhat.
261
+ const minBinWidth = this.fontSize * 3.25
262
+ const maxBinWidth =
263
+ _.max(
264
+ spaceRequirements.map(({ labelSpace }) =>
265
+ Math.max(labelSpace, minBinWidth)
266
+ )
267
+ ) ?? 0
268
+ return Math.round(maxBinWidth * binCount)
269
+ }
270
+
271
+ @computed get width(): number {
272
+ if (this.isAutoWidth) {
273
+ return Math.min(
274
+ this.maxWidth,
275
+ this.legendTitleWidth +
276
+ this.totalCategoricalWidth +
277
+ this.idealNumericWidth
278
+ )
279
+ } else {
280
+ return this.maxWidth
281
+ }
282
+ }
283
+
284
+ @computed private get availableNumericWidth(): number {
285
+ return this.width - this.totalCategoricalWidth - this.legendTitleWidth
286
+ }
287
+
288
+ // Since we calculate the width automatically in some cases (when `isAutoWidth` is true),
289
+ // we need to shift X to align the legend horizontally (`legendAlign`).
290
+ @computed private get x(): number {
291
+ const { width, maxWidth, legendAlign, legendX } = this
292
+ const widthDiff = maxWidth - width
293
+ if (legendAlign === HorizontalAlign.center) {
294
+ return legendX + widthDiff / 2
295
+ } else if (legendAlign === HorizontalAlign.right) {
296
+ return legendX + widthDiff
297
+ } else {
298
+ return legendX // left align
299
+ }
300
+ }
301
+
302
+ @computed private get positionedBins(): PositionedBin[] {
303
+ const {
304
+ availableNumericWidth,
305
+ visibleBins,
306
+ numericBins,
307
+ legendTitleWidth,
308
+ x,
309
+ } = this
310
+
311
+ let xOffset = x + legendTitleWidth
312
+ let prevBin: ColorScaleBin | undefined
313
+
314
+ return visibleBins.map((bin, index) => {
315
+ const isFirst = index === 0
316
+ let width: number = this.getCategoricalBinWidth(bin)
317
+ let marginLeft: number = isFirst ? 0 : this.itemMargin
318
+
319
+ if (bin instanceof NumericBin) {
320
+ width = availableNumericWidth / numericBins.length
321
+
322
+ // Don't add any margin between numeric bins
323
+ if (prevBin instanceof NumericBin) {
324
+ marginLeft = 0
325
+ }
326
+ }
327
+
328
+ const x = xOffset + marginLeft
329
+ xOffset = x + width
330
+ prevBin = bin
331
+
332
+ return {
333
+ x,
334
+ width,
335
+ bin,
336
+ }
337
+ })
338
+ }
339
+
340
+ @computed private get legendTitleFontSize(): number {
341
+ return this.fontSize * GRAPHER_FONT_SCALE_14
342
+ }
343
+
344
+ @computed private get legendTitle(): TextWrap | undefined {
345
+ const { legendTitle } = this.manager
346
+ return legendTitle
347
+ ? new TextWrap({
348
+ text: legendTitle,
349
+ fontSize: this.legendTitleFontSize,
350
+ fontWeight: 700,
351
+ maxWidth: this.maxWidth / 3,
352
+ lineHeight: 1,
353
+ })
354
+ : undefined
355
+ }
356
+
357
+ @computed private get legendTitleWidth(): number {
358
+ return this.legendTitle ? this.legendTitle.width + this.itemMargin : 0
359
+ }
360
+
361
+ @computed private get numericLabels(): NumericLabel[] {
362
+ const { numericBinSize, positionedBins, tickFontSize } = this
363
+
364
+ const makeBoundaryLabel = (
365
+ bin: PositionedBin,
366
+ minOrMax: "min" | "max",
367
+ text: string
368
+ ): NumericLabel => {
369
+ const labelBounds = Bounds.forText(text, { fontSize: tickFontSize })
370
+ const x =
371
+ bin.x +
372
+ (minOrMax === "min" ? 0 : bin.width) -
373
+ labelBounds.width / 2
374
+ const y = -numericBinSize - labelBounds.height - this.legendTickSize
375
+
376
+ return {
377
+ text: text,
378
+ fontSize: tickFontSize,
379
+ bounds: labelBounds.set({ x: x, y: y }),
380
+ hidden: false,
381
+ raised: false,
382
+ bin: bin.bin,
383
+ }
384
+ }
385
+
386
+ const makeRangeLabel = (bin: PositionedBin): NumericLabel => {
387
+ const labelBounds = Bounds.forText(bin.bin.text, {
388
+ fontSize: tickFontSize,
389
+ })
390
+ const x = bin.x + bin.width / 2 - labelBounds.width / 2
391
+ const y = -numericBinSize - labelBounds.height - this.legendTickSize
392
+
393
+ return {
394
+ text: bin.bin.text,
395
+ fontSize: tickFontSize,
396
+ bounds: labelBounds.set({ x: x, y: y }),
397
+ priority: true,
398
+ hidden: false,
399
+ raised: false,
400
+ bin: bin.bin,
401
+ }
402
+ }
403
+
404
+ let labels: NumericLabel[] = []
405
+ for (const bin of positionedBins) {
406
+ if (bin.bin.text) labels.push(makeRangeLabel(bin))
407
+ else if (bin.bin instanceof NumericBin) {
408
+ if (bin.bin.minText)
409
+ labels.push(makeBoundaryLabel(bin, "min", bin.bin.minText))
410
+ if (bin === R.last(positionedBins) && bin.bin.maxText)
411
+ labels.push(makeBoundaryLabel(bin, "max", bin.bin.maxText))
412
+ }
413
+ }
414
+
415
+ for (let index = 0; index < labels.length; index++) {
416
+ const l1 = labels[index]
417
+ if (l1.hidden) continue
418
+
419
+ for (let j = index + 1; j < labels.length; j++) {
420
+ const l2 = labels[j]
421
+ if (
422
+ l1.bounds.right + MINIMUM_LABEL_DISTANCE >
423
+ l2.bounds.centerX ||
424
+ (l2.bounds.left - MINIMUM_LABEL_DISTANCE <
425
+ l1.bounds.centerX &&
426
+ !l2.priority)
427
+ )
428
+ l2.hidden = true
429
+ }
430
+ }
431
+
432
+ labels = labels.filter((label) => !label.hidden)
433
+
434
+ // If labels overlap, first we try alternating raised labels
435
+ let raisedMode = false
436
+ for (let index = 1; index < labels.length; index++) {
437
+ const l1 = labels[index - 1],
438
+ l2 = labels[index]
439
+ if (l1.bounds.right + MINIMUM_LABEL_DISTANCE > l2.bounds.left) {
440
+ raisedMode = true
441
+ break
442
+ }
443
+ }
444
+
445
+ if (raisedMode) {
446
+ for (let index = 1; index < labels.length; index++) {
447
+ const label = labels[index]
448
+ if (index % 2 !== 0) {
449
+ label.bounds = label.bounds.set({
450
+ y: label.bounds.y - label.bounds.height - 1,
451
+ })
452
+ label.raised = true
453
+ }
454
+ }
455
+ }
456
+
457
+ return labels
458
+ }
459
+
460
+ @computed get height(): number {
461
+ return Math.abs(
462
+ _.min(this.numericLabels.map((label) => label.bounds.y)) ?? 0
463
+ )
464
+ }
465
+
466
+ @computed private get legendStyleConfig(): LegendStyleConfig | undefined {
467
+ return (
468
+ this.manager.numericLegendStyleConfig ??
469
+ this.manager.legendStyleConfig
470
+ )
471
+ }
472
+
473
+ private getTextStyleConfig(bin: ColorScaleBin): LegendTextStyle {
474
+ const state = this.getBinState(bin)
475
+ const styleConfig = this.legendStyleConfig?.text
476
+ const defaultStyle = styleConfig?.default
477
+ const currentStyle = styleConfig?.[state]
478
+ return { color: GRAPHER_DARK_TEXT, ...defaultStyle, ...currentStyle }
479
+ }
480
+
481
+ private getMarkerStyleConfig(bin: ColorScaleBin): LegendMarkerStyle {
482
+ const state = this.getBinState(bin)
483
+ const styleConfig = this.legendStyleConfig?.marker
484
+ const defaultStyle = styleConfig?.default
485
+ const current = styleConfig?.[state]
486
+ return {
487
+ fill: bin.color,
488
+ stroke: DEFAULT_NUMERIC_BIN_STROKE,
489
+ strokeWidth: DEFAULT_NUMERIC_BIN_STROKE_WIDTH,
490
+ ...defaultStyle,
491
+ ...current,
492
+ }
493
+ }
494
+
495
+ override render(): React.ReactElement {
496
+ const { numericLabels, numericBinSize, positionedBins, height } = this
497
+
498
+ const bottomY = this.numericLegendY + height
499
+
500
+ const textColor =
501
+ this.legendStyleConfig?.text?.default?.color ?? DEFAULT_TEXT_COLOR
502
+
503
+ return (
504
+ <g
505
+ ref={this.base}
506
+ id={makeIdForHumanConsumption("numeric-color-legend")}
507
+ className="numericColorLegend"
508
+ >
509
+ <g id={makeIdForHumanConsumption("lines")}>
510
+ {numericLabels.map((label, index) => {
511
+ const style = this.getMarkerStyleConfig(label.bin)
512
+ return (
513
+ <line
514
+ key={index}
515
+ id={makeIdForHumanConsumption(label.text)}
516
+ x1={label.bounds.x + label.bounds.width / 2}
517
+ y1={bottomY - numericBinSize}
518
+ x2={label.bounds.x + label.bounds.width / 2}
519
+ y2={
520
+ bottomY +
521
+ label.bounds.y +
522
+ label.bounds.height
523
+ }
524
+ // if we use a light color for stroke (e.g. white), we want it to stay
525
+ // "invisible", except for raised labels, where we want *some* contrast.
526
+ stroke={
527
+ label.raised && style.stroke
528
+ ? darkenColorForLine(style.stroke)
529
+ : style.stroke
530
+ }
531
+ strokeWidth={style.strokeWidth}
532
+ />
533
+ )
534
+ })}
535
+ </g>
536
+ <g id={makeIdForHumanConsumption("swatches")}>
537
+ {_.sortBy(
538
+ positionedBins.map((positionedBin, index) => {
539
+ const bin = positionedBin.bin
540
+ const style = this.getMarkerStyleConfig(bin)
541
+
542
+ const fill = bin.patternRef
543
+ ? `url(#${bin.patternRef})`
544
+ : style.fill
545
+
546
+ return (
547
+ <NumericBinRect
548
+ key={index}
549
+ x={positionedBin.x}
550
+ y={bottomY - numericBinSize}
551
+ width={positionedBin.width}
552
+ height={numericBinSize}
553
+ fill={fill}
554
+ stroke={style.stroke}
555
+ strokeWidth={style.strokeWidth}
556
+ opacity={style.opacity}
557
+ isOpenLeft={
558
+ bin instanceof NumericBin
559
+ ? bin.props.isOpenLeft
560
+ : false
561
+ }
562
+ isOpenRight={
563
+ bin instanceof NumericBin
564
+ ? bin.props.isOpenRight
565
+ : false
566
+ }
567
+ onMouseEnter={() => {
568
+ this.manager.onLegendMouseEnter?.(bin)
569
+ this.manager.onLegendMouseOver?.(bin)
570
+ }}
571
+ onMouseLeave={() =>
572
+ this.manager.onLegendMouseLeave?.()
573
+ }
574
+ />
575
+ )
576
+ }),
577
+ (rect) => rect.props["strokeWidth"]
578
+ )}
579
+ </g>
580
+ <g id={makeIdForHumanConsumption("labels")}>
581
+ {numericLabels.map((label, index) => {
582
+ const style = this.getTextStyleConfig(label.bin)
583
+ return (
584
+ <text
585
+ key={index}
586
+ x={label.bounds.x}
587
+ y={bottomY + label.bounds.y}
588
+ // we can't use dominant-baseline to do proper alignment since our svg-to-png library Sharp
589
+ // doesn't support that (https://github.com/lovell/sharp/issues/1996), so we'll have to make
590
+ // do with some rough positioning.
591
+ dy={dyFromAlign(VerticalAlign.bottom)}
592
+ fontSize={label.fontSize}
593
+ style={{ fill: style.color, ...style }}
594
+ >
595
+ {label.text}
596
+ </text>
597
+ )
598
+ })}
599
+ </g>
600
+ {this.legendTitle?.renderSVG(
601
+ this.x,
602
+ // Align legend title baseline with bottom of color bins
603
+ this.numericLegendY +
604
+ height -
605
+ this.legendTitle.height +
606
+ this.legendTitleFontSize * 0.2,
607
+ { textProps: { fill: textColor } }
608
+ )}
609
+ </g>
610
+ )
611
+ }
612
+ }
613
+
614
+ interface NumericBinRectProps extends React.SVGAttributes<SVGElement> {
615
+ x: number
616
+ y: number
617
+ width: number
618
+ height: number
619
+ isOpenLeft?: boolean
620
+ isOpenRight?: boolean
621
+ }
622
+
623
+ /** The width of the arrowhead for open-ended bins (left or right) */
624
+ const ARROW_SIZE = 5
625
+
626
+ const NumericBinRect = (props: NumericBinRectProps) => {
627
+ const { isOpenLeft, isOpenRight, x, y, width, height, ...restProps } = props
628
+ if (isOpenRight) {
629
+ const a = ARROW_SIZE
630
+ const w = width - a
631
+ const d = removeAllWhitespace(`
632
+ M ${x}, ${y}
633
+ l ${w}, 0
634
+ l ${a}, ${height / 2}
635
+ l ${-a}, ${height / 2}
636
+ l ${-w}, 0
637
+ z
638
+ `)
639
+ return <path d={d} {...restProps} />
640
+ } else if (isOpenLeft) {
641
+ const a = ARROW_SIZE
642
+ const w = width - a
643
+ const d = removeAllWhitespace(`
644
+ M ${x + a}, ${y}
645
+ l ${w}, 0
646
+ l 0, ${height}
647
+ l ${-w}, 0
648
+ l ${-a}, ${-height / 2}
649
+ z
650
+ `)
651
+ return <path d={d} {...restProps} />
652
+ } else {
653
+ return <rect x={x} y={y} width={width} height={height} {...restProps} />
654
+ }
655
+ }
656
+
657
+ @observer
658
+ export class HorizontalCategoricalColorLegend extends HorizontalColorLegend {
659
+ private rectPadding = 5
660
+ private markPadding = 5
661
+
662
+ constructor(props: { manager: HorizontalColorLegendManager }) {
663
+ super(props)
664
+
665
+ makeObservable(this)
666
+ }
667
+
668
+ @computed get width(): number {
669
+ return this.manager.legendWidth ?? this.manager.legendMaxWidth ?? 200
670
+ }
671
+
672
+ @computed private get categoricalLegendData(): CategoricalBin[] {
673
+ return this.manager.categoricalLegendData ?? []
674
+ }
675
+
676
+ @computed private get visibleCategoricalBins(): CategoricalBin[] {
677
+ return this.categoricalLegendData.filter((bin) => !bin.isHidden)
678
+ }
679
+
680
+ @computed private get markLines(): MarkLine[] {
681
+ const fontSize = this.fontSize * GRAPHER_FONT_SCALE_12_8
682
+ const rectSize = this.fontSize * 0.75
683
+
684
+ const lines: MarkLine[] = []
685
+ let marks: CategoricalMark[] = []
686
+ let xOffset = 0
687
+ let yOffset = 0
688
+ this.visibleCategoricalBins.forEach((bin) => {
689
+ const labelBounds = Bounds.forText(bin.text, { fontSize })
690
+ const markWidth =
691
+ rectSize +
692
+ this.rectPadding +
693
+ labelBounds.width +
694
+ this.markPadding
695
+
696
+ if (xOffset + markWidth > this.width && marks.length > 0) {
697
+ lines.push({
698
+ totalWidth: xOffset - this.markPadding,
699
+ marks: marks,
700
+ })
701
+ marks = []
702
+ xOffset = 0
703
+ yOffset += rectSize + this.rectPadding
704
+ }
705
+
706
+ const markX = xOffset
707
+ const markY = yOffset
708
+
709
+ const label = {
710
+ text: bin.text,
711
+ bounds: labelBounds.set({
712
+ x: markX + rectSize + this.rectPadding,
713
+ y: markY + rectSize / 2,
714
+ }),
715
+ fontSize,
716
+ }
717
+
718
+ marks.push({
719
+ x: markX,
720
+ y: markY,
721
+ width: markWidth,
722
+ rectSize,
723
+ label,
724
+ bin,
725
+ })
726
+
727
+ xOffset += markWidth + SPACE_BETWEEN_CATEGORICAL_BINS
728
+ })
729
+
730
+ if (marks.length > 0)
731
+ lines.push({ totalWidth: xOffset - this.markPadding, marks: marks })
732
+
733
+ return lines
734
+ }
735
+
736
+ @computed private get contentWidth(): number {
737
+ return _.max(this.markLines.map((l) => l.totalWidth)) as number
738
+ }
739
+
740
+ @computed private get containerWidth(): number {
741
+ return this.width ?? this.contentWidth
742
+ }
743
+
744
+ @computed private get marks(): CategoricalMark[] {
745
+ const lines = this.markLines
746
+ const align = this.legendAlign
747
+ const width = this.containerWidth
748
+
749
+ // Center each line
750
+ lines.forEach((line) => {
751
+ // TODO abstract this
752
+ const xShift =
753
+ align === HorizontalAlign.center
754
+ ? (width - line.totalWidth) / 2
755
+ : align === HorizontalAlign.right
756
+ ? width - line.totalWidth
757
+ : 0
758
+ line.marks.forEach((mark) => {
759
+ mark.x += xShift
760
+ mark.label.bounds = mark.label.bounds.set({
761
+ x: mark.label.bounds.x + xShift,
762
+ })
763
+ })
764
+ })
765
+
766
+ return lines.flatMap((l) => l.marks)
767
+ }
768
+
769
+ @computed get height(): number {
770
+ return _.max(this.marks.map((mark) => mark.y + mark.rectSize)) ?? 0
771
+ }
772
+
773
+ @computed private get legendStyleConfig(): LegendStyleConfig | undefined {
774
+ return (
775
+ this.manager.categoricalLegendStyleConfig ??
776
+ this.manager.legendStyleConfig
777
+ )
778
+ }
779
+
780
+ private getTextStyleConfig(bin: ColorScaleBin): LegendTextStyle {
781
+ const state = this.getBinState(bin)
782
+ const styleConfig = this.legendStyleConfig?.text
783
+ const defaultStyle = styleConfig?.default
784
+ const currentStyle = styleConfig?.[state]
785
+ return { color: GRAPHER_DARK_TEXT, ...defaultStyle, ...currentStyle }
786
+ }
787
+
788
+ protected getMarkerStyleConfig(bin: ColorScaleBin): LegendMarkerStyle {
789
+ const state = this.getBinState(bin)
790
+ const styleConfig = this.legendStyleConfig?.marker
791
+ const defaultStyle = styleConfig?.default
792
+ const currentStyle = styleConfig?.[state]
793
+ return {
794
+ fill: bin.color,
795
+ strokeWidth: 0.4,
796
+ ...defaultStyle,
797
+ ...currentStyle,
798
+ }
799
+ }
800
+
801
+ renderLabels(): React.ReactElement {
802
+ const { marks } = this
803
+
804
+ return (
805
+ <g id={makeIdForHumanConsumption("labels")}>
806
+ {marks.map((mark, index) => {
807
+ const style = this.getTextStyleConfig(mark.bin)
808
+
809
+ return (
810
+ <text
811
+ key={`${mark.label}-${index}`}
812
+ x={this.legendX + mark.label.bounds.x}
813
+ y={this.categoryLegendY + mark.label.bounds.y}
814
+ // we can't use dominant-baseline to do proper alignment since our svg-to-png library Sharp
815
+ // doesn't support that (https://github.com/lovell/sharp/issues/1996), so we'll have to make
816
+ // do with some rough positioning.
817
+ dy={dyFromAlign(VerticalAlign.middle)}
818
+ fontSize={mark.label.fontSize}
819
+ fontWeight={style.fontWeight}
820
+ style={{ fill: style.color, ...style }}
821
+ >
822
+ {mark.label.text}
823
+ </text>
824
+ )
825
+ })}
826
+ </g>
827
+ )
828
+ }
829
+
830
+ renderSwatches(): React.ReactElement {
831
+ const { marks } = this
832
+
833
+ return (
834
+ <g id={makeIdForHumanConsumption("swatches")}>
835
+ {marks.map((mark, index) => {
836
+ const style = this.getMarkerStyleConfig(mark.bin)
837
+
838
+ const fill = mark.bin.patternRef
839
+ ? `url(#${mark.bin.patternRef})`
840
+ : style.fill
841
+
842
+ return (
843
+ <rect
844
+ id={makeIdForHumanConsumption(mark.label.text)}
845
+ key={`${mark.label}-${index}`}
846
+ x={this.legendX + mark.x}
847
+ y={this.categoryLegendY + mark.y}
848
+ width={mark.rectSize}
849
+ height={mark.rectSize}
850
+ style={{ ...style, fill }}
851
+ />
852
+ )
853
+ })}
854
+ </g>
855
+ )
856
+ }
857
+
858
+ renderInteractiveElements(): React.ReactElement {
859
+ const { manager, marks } = this
860
+
861
+ return (
862
+ <g>
863
+ {marks.map((mark, index) => {
864
+ const mouseEnter = (): void =>
865
+ manager.onLegendMouseEnter
866
+ ? manager.onLegendMouseEnter(mark.bin)
867
+ : undefined
868
+ const mouseOver = (): void =>
869
+ manager.onLegendMouseOver
870
+ ? manager.onLegendMouseOver(mark.bin)
871
+ : undefined
872
+ const mouseLeave = (): void =>
873
+ manager.onLegendMouseLeave
874
+ ? manager.onLegendMouseLeave()
875
+ : undefined
876
+ const click = manager.onLegendClick
877
+ ? (): void => manager.onLegendClick?.(mark.bin)
878
+ : undefined
879
+
880
+ return (
881
+ <g
882
+ key={`${mark.label}-${index}`}
883
+ onMouseEnter={mouseEnter}
884
+ onMouseOver={mouseOver}
885
+ onMouseLeave={mouseLeave}
886
+ onClick={click}
887
+ style={{ cursor: manager.legendCursor }}
888
+ >
889
+ {/* for hover interaction */}
890
+ <rect
891
+ x={this.legendX + mark.x}
892
+ y={
893
+ this.categoryLegendY +
894
+ mark.y -
895
+ this.rectPadding / 2
896
+ }
897
+ height={mark.rectSize + this.rectPadding}
898
+ width={
899
+ mark.width + SPACE_BETWEEN_CATEGORICAL_BINS
900
+ }
901
+ fill="#fff"
902
+ opacity={0}
903
+ />
904
+ </g>
905
+ )
906
+ })}
907
+ </g>
908
+ )
909
+ }
910
+
911
+ override render(): React.ReactElement {
912
+ return (
913
+ <g
914
+ id={makeIdForHumanConsumption("categorical-color-legend")}
915
+ className="categoricalColorLegend"
916
+ >
917
+ {this.renderSwatches()}
918
+ {this.renderLabels()}
919
+ {!this.manager.isStatic && this.renderInteractiveElements()}
920
+ </g>
921
+ )
922
+ }
923
+ }