@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,1144 @@
1
+ import * as _ from "lodash-es"
2
+ import React from "react"
3
+ import * as R from "remeda"
4
+ import {
5
+ Bounds,
6
+ excludeUndefined,
7
+ HorizontalAlign,
8
+ Position,
9
+ SortConfig,
10
+ SortOrder,
11
+ getRelativeMouse,
12
+ EntitySelectionMode,
13
+ makeIdForHumanConsumption,
14
+ dyFromAlign,
15
+ exposeInstanceOnWindow,
16
+ } from "../../utils/index.js"
17
+ import { action, computed, makeObservable, observable } from "mobx"
18
+ import { observer } from "mobx-react"
19
+ import {
20
+ BASE_FONT_SIZE,
21
+ DEFAULT_GRAPHER_BOUNDS,
22
+ GRAPHER_FONT_SCALE_12,
23
+ } from "../core/GrapherConstants"
24
+ import { DualAxisComponent } from "../axis/AxisViews"
25
+ import { NoDataModal } from "../noDataModal/NoDataModal"
26
+ import { AxisConfig, AxisManager } from "../axis/AxisConfig"
27
+ import { ChartInterface } from "../chart/ChartInterface"
28
+ import {
29
+ EntityName,
30
+ VerticalAlign,
31
+ ColorScaleConfigInterface,
32
+ } from "../../types/index.js"
33
+ import { OwidTable, CoreColumn } from "../../core-table/index.js"
34
+ import { getShortNameForEntity } from "../chart/ChartUtils"
35
+ import {
36
+ LEGEND_STYLE_FOR_STACKED_CHARTS,
37
+ StackedSeries,
38
+ } from "./StackedConstants"
39
+ import { TooltipFooterIcon } from "../tooltip/TooltipProps.js"
40
+ import {
41
+ Tooltip,
42
+ TooltipValue,
43
+ TooltipState,
44
+ makeTooltipRoundingNotice,
45
+ makeTooltipToleranceNotice,
46
+ } from "../tooltip/Tooltip"
47
+ import {
48
+ HorizontalCategoricalColorLegend,
49
+ HorizontalColorLegendManager,
50
+ } from "../legend/HorizontalColorLegends"
51
+ import { CategoricalBin, ColorScaleBin } from "../color/ColorScaleBin"
52
+ import {
53
+ LegendInteractionState,
54
+ LegendStyleConfig,
55
+ } from "../legend/LegendInteractionState"
56
+ import { DualAxis, HorizontalAxis, VerticalAxis } from "../axis/Axis"
57
+ import { ColorScale } from "../color/ColorScale"
58
+ import { SelectionArray } from "../selection/SelectionArray"
59
+ import {
60
+ MarimekkoChartManager,
61
+ Item,
62
+ PlacedItem,
63
+ EntityWithSize,
64
+ LabelCandidate,
65
+ LabelWithPlacement,
66
+ LabelCandidateWithElement,
67
+ Bar,
68
+ } from "./MarimekkoChartConstants"
69
+ import { MarimekkoChartState } from "./MarimekkoChartState"
70
+ import { ChartComponentProps } from "../chart/ChartTypeMap.js"
71
+ import { MarimekkoBars } from "./MarimekkoBars"
72
+ import { toPlacedMarimekkoItems } from "./MarimekkoChartHelpers"
73
+
74
+ const MARKER_MARGIN: number = 4
75
+ const MARKER_AREA_HEIGHT: number = 25
76
+ const MAX_LABEL_COUNT: number = 20
77
+
78
+ export type MarimekkoChartProps = ChartComponentProps<MarimekkoChartState>
79
+
80
+ @observer
81
+ export class MarimekkoChart
82
+ extends React.Component<MarimekkoChartProps>
83
+ implements ChartInterface, HorizontalColorLegendManager, AxisManager
84
+ {
85
+ base = React.createRef<SVGGElement>()
86
+
87
+ constructor(props: MarimekkoChartProps) {
88
+ super(props)
89
+
90
+ makeObservable(this, {
91
+ focusColorBin: observable,
92
+ tooltipState: observable,
93
+ })
94
+ }
95
+
96
+ labelAngleInDegrees = -45 // 0 is horizontal, -90 is vertical from bottom to top, ...
97
+
98
+ // currently hovered legend color
99
+ focusColorBin: ColorScaleBin | undefined = undefined
100
+
101
+ // current tooltip target & position
102
+ tooltipState = new TooltipState<{
103
+ entityName: string
104
+ }>()
105
+
106
+ @computed get chartState(): MarimekkoChartState {
107
+ return this.props.chartState
108
+ }
109
+
110
+ @computed private get manager(): MarimekkoChartManager {
111
+ return this.chartState.manager
112
+ }
113
+
114
+ @computed private get inputTable(): OwidTable {
115
+ return this.chartState.inputTable
116
+ }
117
+
118
+ @computed private get series(): readonly StackedSeries<EntityName>[] {
119
+ return this.chartState.series
120
+ }
121
+
122
+ @computed private get yColumnSlugs(): string[] {
123
+ return this.chartState.yColumnSlugs
124
+ }
125
+
126
+ @computed private get xColumnSlug(): string | undefined {
127
+ return this.chartState.xColumnSlug
128
+ }
129
+
130
+ @computed private get xColumn(): CoreColumn | undefined {
131
+ return this.chartState.xColumn
132
+ }
133
+
134
+ @computed private get colorColumn(): CoreColumn | undefined {
135
+ return this.chartState.colorColumn
136
+ }
137
+
138
+ @computed private get latestTime(): number | undefined {
139
+ const times =
140
+ this.manager.tableAfterAuthorTimelineAndActiveChartTransform?.getTimesUniqSortedAscForColumns(
141
+ this.yColumnSlugs
142
+ )
143
+
144
+ return times ? R.last(times) : undefined
145
+ }
146
+ @computed private get tableAtLatestTimelineTimepoint():
147
+ | OwidTable
148
+ | undefined {
149
+ if (this.latestTime)
150
+ return this.manager.tableAfterAuthorTimelineAndActiveChartTransform?.filterByTargetTimes(
151
+ [this.latestTime],
152
+ 0
153
+ )
154
+ else return undefined
155
+ }
156
+ @computed private get xColumnAtLastTimePoint(): CoreColumn | undefined {
157
+ if (this.xColumnSlug === undefined) return undefined
158
+ const columnSlug = [this.xColumnSlug]
159
+ if (this.tableAtLatestTimelineTimepoint)
160
+ return this.tableAtLatestTimelineTimepoint.getColumns(columnSlug)[0]
161
+ else return undefined
162
+ }
163
+
164
+ @computed private get yColumnsAtLastTimePoint(): CoreColumn[] {
165
+ const columnSlugs = this.yColumnSlugs
166
+ return (
167
+ this.tableAtLatestTimelineTimepoint?.getColumns(columnSlugs) ?? []
168
+ )
169
+ }
170
+
171
+ @computed private get yColumns(): CoreColumn[] {
172
+ return this.chartState.yColumns
173
+ }
174
+
175
+ @computed private get colorColumnSlug(): string | undefined {
176
+ return this.chartState.colorColumnSlug
177
+ }
178
+
179
+ @computed private get colorScale(): ColorScale {
180
+ return this.chartState.colorScale
181
+ }
182
+
183
+ @computed private get colorScaleConfig():
184
+ | ColorScaleConfigInterface
185
+ | undefined {
186
+ return this.chartState.colorScaleConfig
187
+ }
188
+
189
+ @computed private get sortConfig(): SortConfig {
190
+ return this.manager.sortConfig ?? {}
191
+ }
192
+
193
+ @computed private get bounds(): Bounds {
194
+ return (this.props.bounds ?? DEFAULT_GRAPHER_BOUNDS).padRight(10)
195
+ }
196
+
197
+ @computed private get innerBounds(): Bounds {
198
+ // This is a workaround to get the actual width of the vertical axis - dualAxis does this
199
+ // internally but we can't access this.dualAxis here due to a dependency cycle
200
+ const axis = this.verticalAxisPart.clone()
201
+ axis.range = [0, this.bounds.height]
202
+ const verticalAxisTrueWidth = axis.width
203
+
204
+ const whiteSpaceOnLeft = this.bounds.left + verticalAxisTrueWidth
205
+ const labelLinesHeight = MARKER_AREA_HEIGHT
206
+ // only pad left by the amount the longest label would exceed whatever space the
207
+ // vertical axis needs anyhow for label and tickmarks
208
+ const marginToEnsureWidestEntityLabelFitsEvenIfAtX0 =
209
+ Math.max(whiteSpaceOnLeft, this.longestLabelWidth) -
210
+ whiteSpaceOnLeft
211
+ return this.bounds
212
+ .padBottom(this.longestLabelHeight + 2)
213
+ .padBottom(labelLinesHeight)
214
+ .padTop(
215
+ this.showLegend ? this.legend.height + this.legendPaddingTop : 0
216
+ )
217
+ .padLeft(marginToEnsureWidestEntityLabelFitsEvenIfAtX0)
218
+ }
219
+
220
+ @computed get isStatic(): boolean {
221
+ return this.manager.isStatic ?? false
222
+ }
223
+
224
+ @computed get fontSize(): number {
225
+ return this.manager.fontSize ?? BASE_FONT_SIZE
226
+ }
227
+
228
+ @computed private get xRange(): [number, number] {
229
+ return [this.bounds.left, this.bounds.right]
230
+ }
231
+
232
+ @computed private get yAxisConfig(): AxisConfig {
233
+ return new AxisConfig(this.manager.yAxisConfig, this)
234
+ }
235
+
236
+ @computed private get xAxisConfig(): AxisConfig {
237
+ const { xColumnSlug } = this
238
+ return new AxisConfig(
239
+ {
240
+ ...this.manager.xAxisConfig,
241
+ orient: Position.top,
242
+ hideAxis: xColumnSlug === undefined,
243
+ hideGridlines: xColumnSlug === undefined,
244
+ },
245
+ this
246
+ )
247
+ }
248
+
249
+ @computed private get verticalAxisPart(): VerticalAxis {
250
+ return this.chartState.toVerticalAxis(this.yAxisConfig)
251
+ }
252
+
253
+ @computed private get horizontalAxisPart(): HorizontalAxis {
254
+ return this.chartState.toHorizontalAxis(this.xAxisConfig)
255
+ }
256
+
257
+ @computed private get dualAxis(): DualAxis {
258
+ return new DualAxis({
259
+ bounds: this.innerBounds,
260
+ verticalAxis: this.verticalAxisPart,
261
+ horizontalAxis: this.horizontalAxisPart,
262
+ comparisonLines: this.manager.comparisonLines,
263
+ })
264
+ }
265
+
266
+ @computed private get selectionArray(): SelectionArray {
267
+ return this.chartState.selectionArray
268
+ }
269
+
270
+ @computed private get items(): Item[] {
271
+ return this.chartState.items
272
+ }
273
+
274
+ @computed get placedItems(): PlacedItem[] {
275
+ return toPlacedMarimekkoItems(this.chartState, {
276
+ dualAxis: this.dualAxis,
277
+ })
278
+ }
279
+
280
+ @computed private get placedItemsMap(): Map<string, PlacedItem> {
281
+ return new Map(this.placedItems.map((item) => [item.entityName, item]))
282
+ }
283
+
284
+ // legend props
285
+
286
+ @computed private get legendPaddingTop(): number {
287
+ return this.legend.height > 0 ? this.fontSize : 0
288
+ }
289
+
290
+ @computed get legendX(): number {
291
+ return this.bounds.x
292
+ }
293
+
294
+ @computed get categoryLegendY(): number {
295
+ return this.bounds.top
296
+ }
297
+
298
+ @computed get legendWidth(): number {
299
+ return this.bounds.width
300
+ }
301
+
302
+ @computed get legendAlign(): HorizontalAlign {
303
+ return HorizontalAlign.left
304
+ }
305
+
306
+ @computed get detailsOrderedByReference(): string[] {
307
+ return this.manager.detailsOrderedByReference ?? []
308
+ }
309
+
310
+ @computed get categoricalLegendData(): CategoricalBin[] {
311
+ const { colorColumnSlug, colorScale, series } = this
312
+ if (colorColumnSlug) {
313
+ return colorScale.categoricalLegendBins
314
+ } else if (series.length > 0) {
315
+ const customHiddenCategories =
316
+ this.colorScaleConfig?.customHiddenCategories
317
+ return series.map((series, index) => {
318
+ return new CategoricalBin({
319
+ index,
320
+ value: series.seriesName,
321
+ label: series.seriesName,
322
+ color: series.color,
323
+ isHidden: !!customHiddenCategories?.[series.seriesName],
324
+ })
325
+ })
326
+ }
327
+ return []
328
+ }
329
+
330
+ getLegendBinState(bin: ColorScaleBin): LegendInteractionState {
331
+ const { focusColorBin } = this
332
+
333
+ // If nothing is focused, all items are active
334
+ if (!focusColorBin && this.hoverColors.length === 0)
335
+ return LegendInteractionState.Default
336
+
337
+ const isHovered = this.hoverColors?.includes(bin.color)
338
+ if (isHovered) return LegendInteractionState.Focused
339
+
340
+ // Check if this bin matches the focused color bin
341
+ const isFocused = focusColorBin && bin.equals(focusColorBin)
342
+ return isFocused
343
+ ? LegendInteractionState.Focused
344
+ : LegendInteractionState.Muted
345
+ }
346
+
347
+ legendStyleConfig: LegendStyleConfig = LEGEND_STYLE_FOR_STACKED_CHARTS
348
+
349
+ @computed get hoverColors(): string[] {
350
+ if (this.focusColorBin) return [this.focusColorBin.color]
351
+ if (this.tooltipItem?.entityColor)
352
+ return [this.tooltipItem.entityColor.color]
353
+ if (this.selectionArray.hasSelection) {
354
+ const selectedItems = this.items.filter((item) =>
355
+ this.selectionArray.selectedSet.has(item.entityName)
356
+ )
357
+ const uniqueSelectedColors = new Set(
358
+ selectedItems.map((item) => item.entityColor?.color)
359
+ )
360
+ return this.categoricalLegendData
361
+ .filter((bin) => uniqueSelectedColors.has(bin.color as any))
362
+ .map((bin) => bin.color)
363
+ }
364
+ return []
365
+ }
366
+
367
+ @computed private get showLegend(): boolean {
368
+ return (
369
+ (!!this.colorColumnSlug || this.categoricalLegendData.length > 1) &&
370
+ !this.manager.isDisplayedAlongsideComplementaryTable
371
+ )
372
+ }
373
+
374
+ @action.bound onLegendMouseOver(bin: ColorScaleBin): void {
375
+ this.focusColorBin = bin
376
+ }
377
+
378
+ @action.bound onLegendMouseLeave(): void {
379
+ this.focusColorBin = undefined
380
+ }
381
+
382
+ @computed private get legend(): HorizontalCategoricalColorLegend {
383
+ return new HorizontalCategoricalColorLegend({ manager: this })
384
+ }
385
+
386
+ @action.bound private onEntityMouseOver(entityName: string): void {
387
+ this.tooltipState.target = { entityName }
388
+ }
389
+
390
+ @action.bound private onMouseMove(ev: React.MouseEvent): void {
391
+ const ref = this.manager.base?.current
392
+ if (ref) {
393
+ this.tooltipState.position = getRelativeMouse(ref, ev)
394
+ }
395
+ }
396
+
397
+ @action.bound private dismissTooltip(): void {
398
+ this.tooltipState.target = null
399
+ }
400
+
401
+ @action.bound private onEntityClick(entityName: string): void {
402
+ this.onSelectEntity(entityName)
403
+ }
404
+
405
+ @action.bound private onSelectEntity(entityName: string): void {
406
+ if (this.canAddCountry) this.selectionArray.toggleSelection(entityName)
407
+ }
408
+ @computed private get canAddCountry(): boolean {
409
+ const { addCountryMode } = this.manager
410
+ return (addCountryMode &&
411
+ addCountryMode !== EntitySelectionMode.Disabled) as boolean
412
+ }
413
+
414
+ @computed private get tooltipItem(): Item | undefined {
415
+ const { target } = this.tooltipState
416
+ return (
417
+ target &&
418
+ this.items.find(
419
+ ({ entityName }) => entityName === target.entityName
420
+ )
421
+ )
422
+ }
423
+
424
+ override componentDidMount(): void {
425
+ exposeInstanceOnWindow(this)
426
+ }
427
+
428
+ override render(): React.ReactElement {
429
+ if (this.chartState.errorInfo.reason)
430
+ return (
431
+ <NoDataModal
432
+ manager={this.manager}
433
+ bounds={this.bounds}
434
+ message={this.chartState.errorInfo.reason}
435
+ />
436
+ )
437
+
438
+ const {
439
+ manager,
440
+ bounds,
441
+ dualAxis,
442
+ tooltipItem,
443
+ xColumn,
444
+ yColumns,
445
+ colorColumn,
446
+ colorScale,
447
+ manager: { endTime, xOverrideTime },
448
+ inputTable: { timeColumn },
449
+ tooltipState: { target, position, fading },
450
+ } = this
451
+
452
+ const { entityName, xPoint, bars } = tooltipItem ?? {}
453
+
454
+ const yValues =
455
+ bars?.map((bar: Bar) => {
456
+ const column = this.chartState.transformedTable.get(
457
+ bar.columnSlug
458
+ )
459
+
460
+ const shouldShowYTimeNotice =
461
+ bar.yPoint.value !== undefined &&
462
+ bar.yPoint.time !== endTime
463
+
464
+ return {
465
+ name: bar.seriesName,
466
+ value: bar.yPoint.value,
467
+ column,
468
+ originalTime: shouldShowYTimeNotice
469
+ ? column.formatTime(bar.yPoint.time)
470
+ : undefined,
471
+ }
472
+ }) ?? []
473
+
474
+ // TODO: when we have proper time support to work across date/year variables then
475
+ // this should be set properly and the x axis time be passed in on it's own.
476
+ // For now we disable x axis notices when the xOverrideTime is set which is
477
+ // usually the case when matching day and year variables
478
+ const shouldShowXTimeNotice =
479
+ xPoint && xPoint.time !== endTime && xOverrideTime === undefined
480
+ const xOriginalTime = shouldShowXTimeNotice ? xPoint?.time : undefined
481
+ const xOriginalTimeFormatted = xOriginalTime
482
+ ? xColumn?.formatTime(xOriginalTime)
483
+ : undefined
484
+ const targetNotice =
485
+ xOriginalTime || yValues.some(({ originalTime }) => !!originalTime)
486
+ ? timeColumn.formatValue(endTime)
487
+ : undefined
488
+ const toleranceNotice = targetNotice
489
+ ? {
490
+ icon: TooltipFooterIcon.Notice,
491
+ text: makeTooltipToleranceNotice(targetNotice),
492
+ }
493
+ : undefined
494
+
495
+ const columns = excludeUndefined([xColumn, ...yColumns])
496
+ const allRoundedToSigFigs = columns.every(
497
+ (column) => column.roundsToSignificantFigures
498
+ )
499
+ const anyRoundedToSigFigs = columns.some(
500
+ (column) => column.roundsToSignificantFigures
501
+ )
502
+ const sigFigs = excludeUndefined(
503
+ columns.map((column) =>
504
+ column.roundsToSignificantFigures
505
+ ? column.numSignificantFigures
506
+ : undefined
507
+ )
508
+ )
509
+ const roundingNotice = anyRoundedToSigFigs
510
+ ? {
511
+ icon: allRoundedToSigFigs
512
+ ? TooltipFooterIcon.None
513
+ : TooltipFooterIcon.Significance,
514
+ text: makeTooltipRoundingNotice(sigFigs, {
515
+ plural: sigFigs.length > 1,
516
+ }),
517
+ }
518
+ : undefined
519
+ const superscript =
520
+ !!roundingNotice && roundingNotice.icon !== TooltipFooterIcon.None
521
+
522
+ const footer = excludeUndefined([toleranceNotice, roundingNotice])
523
+
524
+ return (
525
+ <g
526
+ ref={this.base}
527
+ id={makeIdForHumanConsumption("marimekko-chart")}
528
+ className="MarimekkoChart"
529
+ onMouseMove={(ev): void => this.onMouseMove(ev)}
530
+ onMouseLeave={(): void => this.dismissTooltip()}
531
+ >
532
+ <rect
533
+ x={bounds.left}
534
+ y={bounds.top}
535
+ width={bounds.width}
536
+ height={bounds.height}
537
+ opacity={0}
538
+ fill="rgba(255,255,255,0)"
539
+ />
540
+ <DualAxisComponent
541
+ dualAxis={dualAxis}
542
+ showTickMarks={true}
543
+ detailsMarker={manager.detailsMarkerInSvg}
544
+ backgroundColor={manager.backgroundColor}
545
+ />
546
+ {this.showLegend && (
547
+ <HorizontalCategoricalColorLegend manager={this} />
548
+ )}
549
+ {this.renderBars()}
550
+ {this.labelLines}
551
+ {this.placedLabels}
552
+ {target && (
553
+ <Tooltip
554
+ id="marimekkoTooltip"
555
+ tooltipManager={this.manager}
556
+ x={position.x}
557
+ y={position.y}
558
+ style={{ maxWidth: "250px" }}
559
+ offsetX={20}
560
+ offsetY={-16}
561
+ title={entityName}
562
+ subtitle={timeColumn.formatValue(endTime)}
563
+ footer={footer}
564
+ dissolve={fading}
565
+ dismiss={() => (this.tooltipState.target = null)}
566
+ >
567
+ {yValues.map(
568
+ ({ name, value, column, originalTime }) => (
569
+ <TooltipValue
570
+ key={name}
571
+ label={column.displayName}
572
+ unit={column.displayUnit}
573
+ value={column.formatValueShort(value)}
574
+ originalTime={originalTime}
575
+ isRoundedToSignificantFigures={
576
+ column.roundsToSignificantFigures
577
+ }
578
+ showSignificanceSuperscript={superscript}
579
+ />
580
+ )
581
+ )}
582
+ {xColumn && !xColumn.isMissing && (
583
+ <TooltipValue
584
+ label={xColumn.displayName}
585
+ unit={xColumn.displayUnit}
586
+ value={xColumn.formatValueShort(xPoint?.value)}
587
+ originalTime={xOriginalTimeFormatted}
588
+ isRoundedToSignificantFigures={
589
+ xColumn.roundsToSignificantFigures
590
+ }
591
+ showSignificanceSuperscript={superscript}
592
+ />
593
+ )}
594
+ {colorColumn &&
595
+ !colorColumn.isMissing &&
596
+ tooltipItem?.entityColor && (
597
+ <TooltipValue
598
+ label={
599
+ colorScale.legendDescription ??
600
+ colorColumn.displayName
601
+ }
602
+ value={
603
+ colorScale.getBinForValue(
604
+ tooltipItem.entityColor
605
+ .colorDomainValue
606
+ )?.label ??
607
+ tooltipItem.entityColor.colorDomainValue
608
+ }
609
+ />
610
+ )}
611
+ </Tooltip>
612
+ )}
613
+ </g>
614
+ )
615
+ }
616
+
617
+ private renderBars(): React.ReactElement {
618
+ return (
619
+ <MarimekkoBars
620
+ dualAxis={this.dualAxis}
621
+ focusColorBin={this.focusColorBin}
622
+ placedItems={this.placedItems}
623
+ tooltipState={this.tooltipState}
624
+ fontSize={this.fontSize}
625
+ x0={this.chartState.x0}
626
+ y0={this.chartState.y0}
627
+ selectionArray={this.selectionArray}
628
+ selectedItems={this.chartState.selectedItems}
629
+ onEntityClick={this.onEntityClick}
630
+ onEntityMouseLeave={this.dismissTooltip}
631
+ onEntityMouseOver={this.onEntityMouseOver}
632
+ />
633
+ )
634
+ }
635
+
636
+ private paddingInPixels = 5
637
+
638
+ private static labelCandidateFromItem(
639
+ item: EntityWithSize,
640
+ fontSize: number,
641
+ isSelected: boolean
642
+ ): LabelCandidate {
643
+ const label = item.shortEntityName ?? item.entityName
644
+ const labelBounds = Bounds.forText(label, { fontSize })
645
+
646
+ return {
647
+ item: item,
648
+ label,
649
+ bounds: labelBounds,
650
+ isPicked: isSelected,
651
+ isSelected,
652
+ }
653
+ }
654
+
655
+ /** This function splits label candidates into N groups so that each group has approximately
656
+ the same sum of x value metric. This is useful for picking labels because we want to have e.g.
657
+ 20 labels relatively evenly spaced (in x domain space) and this function gives us 20 groups that
658
+ are roughly of equal size and then we can pick the largest of each group */
659
+ private static splitIntoEqualDomainSizeChunks(
660
+ items: Item[],
661
+ candidates: LabelCandidate[],
662
+ numChunks: number
663
+ ): LabelCandidate[][] {
664
+ // candidates contains all entities available in the chart for some time
665
+ // items is just the entities for the currently selected time, so can be a way smaller subset
666
+ const validItemNames = items.map(({ entityName }) => entityName)
667
+
668
+ // filter the list to remove any candidates that are not currently visible
669
+ // all further calculations are then done only with validCandidates
670
+ const validCandidates = candidates.filter((candidate) =>
671
+ validItemNames.includes(candidate.item.entityName)
672
+ )
673
+
674
+ const chunks: LabelCandidate[][] = []
675
+ let currentChunk: LabelCandidate[] = []
676
+ let domainSizeOfChunk = 0
677
+ const domainSizeThreshold = Math.ceil(
678
+ _.sumBy(validCandidates, (candidate) => candidate.item.xValue) /
679
+ numChunks
680
+ )
681
+ for (const candidate of validCandidates) {
682
+ while (domainSizeOfChunk > domainSizeThreshold) {
683
+ chunks.push(currentChunk)
684
+ currentChunk = []
685
+ domainSizeOfChunk -= domainSizeThreshold
686
+ }
687
+ domainSizeOfChunk += candidate.item.xValue
688
+ currentChunk.push(candidate)
689
+ }
690
+ chunks.push(currentChunk)
691
+
692
+ return chunks.filter((chunk) => chunk.length > 0)
693
+ }
694
+
695
+ @computed private get pickedLabelCandidates(): LabelCandidate[] {
696
+ const {
697
+ xColumnAtLastTimePoint,
698
+ yColumnsAtLastTimePoint,
699
+ xRange,
700
+ sortConfig,
701
+ paddingInPixels,
702
+ items,
703
+ } = this
704
+ const { selectedItems } = this.chartState
705
+
706
+ if (yColumnsAtLastTimePoint.length === 0) return []
707
+
708
+ // Measure the labels (before any rotation, just normal horizontal labels)
709
+ const selectedItemsSet = new Set(
710
+ selectedItems.map((item) => item.entityName)
711
+ )
712
+
713
+ // This is similar to what we would get with .sortedItems but
714
+ // we want this for the last year to pick all labels there - sortedItems
715
+ // changes with the time point the user selects
716
+ const ySizeMap: Map<string, number> = new Map(
717
+ yColumnsAtLastTimePoint[0].owidRows.map((row) => [
718
+ row.entityName,
719
+ row.value,
720
+ ])
721
+ )
722
+
723
+ // We want labels to be chosen according to the latest time point available in the chart.
724
+ // The reason for this is that it makes it so the labels are pretty consistent across time,
725
+ // and not very jumpy when the user drags across the timeline.
726
+ const labelCandidateSource = xColumnAtLastTimePoint
727
+ ? xColumnAtLastTimePoint
728
+ : yColumnsAtLastTimePoint[0]
729
+
730
+ let labelCandidates: LabelCandidate[] =
731
+ labelCandidateSource.owidRows.map((row) =>
732
+ MarimekkoChart.labelCandidateFromItem(
733
+ {
734
+ entityName: row.entityName,
735
+ shortEntityName: getShortNameForEntity(row.entityName),
736
+ xValue:
737
+ xColumnAtLastTimePoint !== undefined
738
+ ? row.value
739
+ : 1,
740
+ ySortValue: ySizeMap.get(row.entityName),
741
+ },
742
+ this.entityLabelFontSize,
743
+ selectedItemsSet.has(row.entityName)
744
+ )
745
+ )
746
+
747
+ // If focus mode is active, only label focused series
748
+ if (this.chartState.focusArray.hasFocusedSeries) {
749
+ labelCandidates = labelCandidates.filter((candidate) =>
750
+ this.chartState.focusArray.has(candidate.item.entityName)
751
+ )
752
+ }
753
+
754
+ if (labelCandidates.length === 0) return []
755
+
756
+ labelCandidates.sort((a, b) => {
757
+ const yRowsForA = a.item.ySortValue
758
+ const yRowsForB = b.item.ySortValue
759
+
760
+ if (yRowsForA !== undefined && yRowsForB !== undefined) {
761
+ const diff = yRowsForB - yRowsForA
762
+ if (diff !== 0) return diff
763
+ else return b.item.entityName.localeCompare(a.item.entityName)
764
+ } else if (yRowsForA === undefined && yRowsForB !== undefined)
765
+ return -1
766
+ else if (yRowsForA !== undefined && yRowsForB === undefined)
767
+ return 1
768
+ // (yRowsForA === undefined && yRowsForB === undefined)
769
+ else return 0
770
+ })
771
+
772
+ if (sortConfig.sortOrder === SortOrder.desc) {
773
+ labelCandidates.reverse()
774
+ }
775
+
776
+ const [sortedLabelsWithValues, sortedLabelsWithoutValues] = _.partition(
777
+ labelCandidates,
778
+ (item) =>
779
+ item.item.ySortValue !== 0 && item.item.ySortValue !== undefined
780
+ )
781
+
782
+ if (sortedLabelsWithValues.length) {
783
+ R.first(sortedLabelsWithValues)!.isPicked = true
784
+ R.last(sortedLabelsWithValues)!.isPicked = true
785
+ }
786
+ if (sortedLabelsWithoutValues.length) {
787
+ if (sortConfig.sortOrder === SortOrder.desc)
788
+ R.first(sortedLabelsWithoutValues)!.isPicked = true
789
+ else R.last(sortedLabelsWithoutValues)!.isPicked = true
790
+ }
791
+ const availablePixels = xRange[1] - xRange[0]
792
+
793
+ const labelHeight = labelCandidates[0].bounds.height
794
+
795
+ const numLabelsToAdd = Math.floor(
796
+ Math.min(
797
+ availablePixels / (labelHeight + paddingInPixels) / 3, // factor 3 is arbitrary to taste
798
+ MAX_LABEL_COUNT
799
+ )
800
+ )
801
+ const chunks = MarimekkoChart.splitIntoEqualDomainSizeChunks(
802
+ items,
803
+ labelCandidates,
804
+ numLabelsToAdd
805
+ )
806
+ const picks = chunks.flatMap((chunk) => {
807
+ const picked = chunk.filter((candidate) => candidate.isPicked)
808
+ if (picked.length > 0) return picked
809
+ else {
810
+ return _.maxBy(chunk, (candidate) => candidate.item.xValue)
811
+ }
812
+ })
813
+ for (const max of picks) {
814
+ if (max) max.isPicked = true
815
+ }
816
+ const picked = labelCandidates.filter((candidate) => candidate.isPicked)
817
+
818
+ return picked
819
+ }
820
+
821
+ @computed private get labelsWithPlacementInfo(): LabelWithPlacement[] {
822
+ const {
823
+ dualAxis,
824
+ placedItemsMap,
825
+ labels,
826
+ unrotatedLongestLabelWidth,
827
+ unrotatedHighestLabelHeight,
828
+ labelAngleInDegrees,
829
+ } = this
830
+ const { x0 } = this.chartState
831
+ const labelsYPosition = dualAxis.verticalAxis.place(0)
832
+
833
+ const labelsWithPlacements: LabelWithPlacement[] = labels
834
+ .map(({ candidate, labelElement }) => {
835
+ const item = placedItemsMap.get(candidate.item.entityName)
836
+ if (!item)
837
+ console.error("Could not find item in placedItemsMap")
838
+ const xPoint = item?.xPoint?.value ?? 1
839
+ const barWidth =
840
+ dualAxis.horizontalAxis.place(xPoint) -
841
+ dualAxis.horizontalAxis.place(x0)
842
+
843
+ const labelId = candidate.item.entityName
844
+ if (!item) {
845
+ console.error(
846
+ "Could not find item",
847
+ candidate.item.entityName
848
+ )
849
+ return null
850
+ } else {
851
+ const currentX =
852
+ dualAxis.horizontalAxis.place(x0) + item.xPosition
853
+ const labelWithPlacement = {
854
+ label: (
855
+ <g
856
+ transform={`translate(${0}, ${labelsYPosition})`}
857
+ >
858
+ {labelElement}
859
+ </g>
860
+ ),
861
+ preferredPlacement: currentX + barWidth / 2,
862
+ correctedPlacement: currentX + barWidth / 2,
863
+ labelKey: labelId,
864
+ }
865
+ return labelWithPlacement
866
+ }
867
+ })
868
+ .filter(
869
+ (item: LabelWithPlacement | null): item is LabelWithPlacement =>
870
+ item !== null
871
+ )
872
+
873
+ // This collision detection code is optimized for the particular
874
+ // case of distributing items in 1D, knowing that we picked a low
875
+ // enough number of labels that we will be able to fit all labels.
876
+ // The algorithm iterates the list twice, i.e. works in linear time
877
+ // with the number of labels to show
878
+ // The logic in pseudo code:
879
+ // for current, next in iterate-left-to-right-pairs:
880
+ // if next.x < current.x + label-width:
881
+ // next.x = current.x + label-width
882
+ // last.x = Math.min(last.x, max-x)
883
+ // for current, prev in iterate-right-to-left-pairs:
884
+ // if prev.x > current.x - label-width:
885
+ // prev.x = current.x - label-width
886
+
887
+ // The label width is uniform for now and starts with
888
+ // the height of a label when printed in normal horizontal layout
889
+ // Since labels are rotated we need to make a bit more space so that they
890
+ // stack correctly. Consider:
891
+ // ╱---╱ ╱---╱
892
+ // ╱ ╱ ╱ ╱
893
+ // ╱ ╱ ╱ ╱
894
+ // ╱---╱ ╱---╱
895
+ // If we would just use exactly the label width then the flatter the angle
896
+ // the more they would actually overlap so we need a correction factor. It turns
897
+ // out than tan(angle) is the correction factor we want, although for horizontal
898
+ // labels we don't want to use +infinity :) so we Math.min it with the longest label width
899
+ if (labelsWithPlacements.length === 0) return []
900
+
901
+ labelsWithPlacements.sort((a, b) => {
902
+ const diff = a.preferredPlacement - b.preferredPlacement
903
+ if (diff !== 0) return diff
904
+ else return a.labelKey.localeCompare(b.labelKey)
905
+ })
906
+
907
+ const labelWidth = unrotatedHighestLabelHeight
908
+ const correctionFactor =
909
+ 1 +
910
+ Math.min(
911
+ unrotatedLongestLabelWidth / labelWidth,
912
+ Math.abs(Math.tan(labelAngleInDegrees))
913
+ )
914
+ const correctedLabelWidth = labelWidth * correctionFactor
915
+
916
+ for (let i = 0; i < labelsWithPlacements.length - 1; i++) {
917
+ const current = labelsWithPlacements[i]
918
+ const next = labelsWithPlacements[i + 1]
919
+ const minNextX = current.correctedPlacement + correctedLabelWidth
920
+ if (next.correctedPlacement < minNextX)
921
+ next.correctedPlacement = minNextX
922
+ }
923
+ labelsWithPlacements[
924
+ labelsWithPlacements.length - 1
925
+ ].correctedPlacement = Math.min(
926
+ labelsWithPlacements[labelsWithPlacements.length - 1]
927
+ .correctedPlacement,
928
+ dualAxis.horizontalAxis.rangeSize +
929
+ dualAxis.horizontalAxis.place(x0)
930
+ )
931
+ for (let i = labelsWithPlacements.length - 1; i > 0; i--) {
932
+ const current = labelsWithPlacements[i]
933
+ const previous = labelsWithPlacements[i - 1]
934
+ const maxPreviousX =
935
+ current.correctedPlacement - correctedLabelWidth
936
+ if (previous.correctedPlacement > maxPreviousX)
937
+ previous.correctedPlacement = maxPreviousX
938
+ }
939
+
940
+ return labelsWithPlacements
941
+ }
942
+
943
+ @computed private get labelLines(): React.ReactElement[] {
944
+ const { labelsWithPlacementInfo, dualAxis } = this
945
+ const { selectedItems } = this.chartState
946
+ const shiftedGroups: LabelWithPlacement[][] = []
947
+ const unshiftedElements: LabelWithPlacement[] = []
948
+ const selectedItemsKeys = new Set(
949
+ selectedItems.map((item) => item.entityName)
950
+ )
951
+ let startNewGroup = true
952
+
953
+ const barEndpointY = dualAxis.verticalAxis.place(0)
954
+
955
+ for (const labelWithPlacement of labelsWithPlacementInfo) {
956
+ if (
957
+ labelWithPlacement.preferredPlacement ===
958
+ labelWithPlacement.correctedPlacement
959
+ ) {
960
+ unshiftedElements.push(labelWithPlacement)
961
+ startNewGroup = true
962
+ } else {
963
+ if (startNewGroup) {
964
+ shiftedGroups.push([labelWithPlacement])
965
+ startNewGroup = false
966
+ } else {
967
+ shiftedGroups[shiftedGroups.length - 1].push(
968
+ labelWithPlacement
969
+ )
970
+ }
971
+ }
972
+ }
973
+ // If we wanted to hide the label lines if all lines are straight
974
+ // then we could do this but this makes it jumpy over time
975
+ // if (shiftedGroups.length === 0) return []
976
+ // else {
977
+ const labelLines: React.ReactElement[] = []
978
+ for (const group of shiftedGroups) {
979
+ let indexInGroup = 0
980
+ for (const item of group) {
981
+ const lineColor = selectedItemsKeys.has(item.labelKey)
982
+ ? "#999"
983
+ : "#bbb"
984
+ const markerBarEndpointX = item.preferredPlacement
985
+ const markerTextEndpointX = item.correctedPlacement
986
+ const markerBarEndpointY = barEndpointY + MARKER_MARGIN
987
+ const markerTextEndpointY =
988
+ barEndpointY + MARKER_AREA_HEIGHT - MARKER_MARGIN
989
+ const markerNetHeight = MARKER_AREA_HEIGHT - 2 * MARKER_MARGIN
990
+ const markerStepSize = markerNetHeight / (group.length + 1)
991
+ const directionUnawareMakerYMid =
992
+ (indexInGroup + 1) * markerStepSize
993
+ const markerYMid =
994
+ markerBarEndpointX > markerTextEndpointX
995
+ ? directionUnawareMakerYMid
996
+ : markerNetHeight - directionUnawareMakerYMid
997
+ labelLines.push(
998
+ <g
999
+ id={makeIdForHumanConsumption(
1000
+ "label-line",
1001
+ item.labelKey
1002
+ )}
1003
+ className="indicator"
1004
+ key={`labelline-${item.labelKey}`}
1005
+ >
1006
+ <path
1007
+ d={`M${markerBarEndpointX},${markerBarEndpointY} v${markerYMid} H${markerTextEndpointX} V${markerTextEndpointY}`}
1008
+ stroke={lineColor}
1009
+ strokeWidth={1}
1010
+ fill="none"
1011
+ />
1012
+ </g>
1013
+ )
1014
+ indexInGroup++
1015
+ }
1016
+ }
1017
+ for (const item of unshiftedElements) {
1018
+ const lineColor = selectedItemsKeys.has(item.labelKey)
1019
+ ? "#555"
1020
+ : "#bbb"
1021
+ const markerBarEndpointX = item.preferredPlacement
1022
+ const markerBarEndpointY = barEndpointY + MARKER_MARGIN
1023
+ const markerTextEndpointY =
1024
+ barEndpointY + MARKER_AREA_HEIGHT - MARKER_MARGIN
1025
+
1026
+ labelLines.push(
1027
+ <g
1028
+ id={makeIdForHumanConsumption("label-line", item.labelKey)}
1029
+ className="indicator"
1030
+ key={`labelline-${item.labelKey}`}
1031
+ >
1032
+ <path
1033
+ d={`M${markerBarEndpointX},${markerBarEndpointY} V${markerTextEndpointY}`}
1034
+ stroke={lineColor}
1035
+ strokeWidth={1}
1036
+ fill="none"
1037
+ />
1038
+ </g>
1039
+ )
1040
+ }
1041
+ return labelLines
1042
+ //}
1043
+ }
1044
+
1045
+ @computed private get placedLabels(): React.ReactElement[] {
1046
+ const labelOffset = MARKER_AREA_HEIGHT
1047
+ // old logic tried to hide labellines but that is too jumpy
1048
+ // labelLines.length
1049
+ // ? MARKER_AREA_HEIGHT
1050
+ // : this.baseFontSize / 2
1051
+ const placedLabels = this.labelsWithPlacementInfo.map((item) => (
1052
+ <g
1053
+ key={`label-${item.labelKey}`}
1054
+ id={makeIdForHumanConsumption("label", item.labelKey)}
1055
+ className="bar-label"
1056
+ transform={`translate(${item.correctedPlacement}, ${labelOffset})`}
1057
+ >
1058
+ {item.label}
1059
+ </g>
1060
+ ))
1061
+
1062
+ return placedLabels
1063
+ }
1064
+
1065
+ @computed private get unrotatedLongestLabelWidth(): number {
1066
+ const widths = this.pickedLabelCandidates.map(
1067
+ (candidate) => candidate.bounds.width
1068
+ )
1069
+ const maxWidth = Math.max(...widths)
1070
+ return maxWidth
1071
+ }
1072
+
1073
+ @computed private get unrotatedHighestLabelHeight(): number {
1074
+ const heights = this.pickedLabelCandidates.map(
1075
+ (candidate) => candidate.bounds.height
1076
+ )
1077
+ return Math.max(...heights)
1078
+ }
1079
+
1080
+ @computed private get longestLabelHeight(): number {
1081
+ // This takes the angle of rotation of the entity labels into account
1082
+ // This is somewhat simplified as we treat this as a one-dimensional
1083
+ // entity whereas in reality the textbox if of course 2D. To account
1084
+ // for that we do max(fontSize, rotatedLabelHeight) in the end
1085
+ // as a rough proxy
1086
+ const rotatedLabelHeight =
1087
+ this.unrotatedLongestLabelWidth *
1088
+ Math.abs(Math.sin((this.labelAngleInDegrees * Math.PI) / 180))
1089
+ return Math.max(this.fontSize, rotatedLabelHeight)
1090
+ }
1091
+
1092
+ @computed private get longestLabelWidth(): number {
1093
+ // This takes the angle of rotation of the entity labels into account
1094
+ // This is somewhat simplified as we treat this as a one-dimensional
1095
+ // entity whereas in reality the textbox if of course 2D. To account
1096
+ // for that we do max(fontSize, rotatedLabelHeight) in the end
1097
+ // as a rough proxy
1098
+ const rotatedLabelWidth =
1099
+ this.unrotatedLongestLabelWidth *
1100
+ Math.abs(Math.cos((this.labelAngleInDegrees * Math.PI) / 180))
1101
+ return Math.max(this.fontSize, rotatedLabelWidth)
1102
+ }
1103
+
1104
+ @computed private get entityLabelFontSize(): number {
1105
+ return GRAPHER_FONT_SCALE_12 * this.fontSize
1106
+ }
1107
+
1108
+ @computed private get labels(): LabelCandidateWithElement[] {
1109
+ const { labelAngleInDegrees, series } = this
1110
+ const { domainColorForEntityMap } = this.chartState
1111
+ return this.pickedLabelCandidates.map((candidate) => {
1112
+ const domainColor = domainColorForEntityMap.get(
1113
+ candidate.item.entityName
1114
+ )
1115
+ const seriesColor = series[0].color
1116
+ const color = domainColor?.color ?? seriesColor ?? "#000"
1117
+ return {
1118
+ candidate,
1119
+ labelElement: (
1120
+ <text
1121
+ key={`${candidate.item.entityName}-label`}
1122
+ y={0}
1123
+ fontWeight={candidate.isSelected ? 700 : 400}
1124
+ fill={color}
1125
+ transform={`rotate(${labelAngleInDegrees}, 0, 0)`}
1126
+ opacity={1}
1127
+ fontSize={this.entityLabelFontSize}
1128
+ textAnchor="end"
1129
+ dy={dyFromAlign(VerticalAlign.middle)}
1130
+ onMouseOver={(): void =>
1131
+ this.onEntityMouseOver(candidate.item.entityName)
1132
+ }
1133
+ onMouseLeave={(): void => this.dismissTooltip()}
1134
+ onClick={(): void =>
1135
+ this.onEntityClick(candidate.item.entityName)
1136
+ }
1137
+ >
1138
+ {candidate.label}
1139
+ </text>
1140
+ ),
1141
+ }
1142
+ })
1143
+ }
1144
+ }