@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.
- package/LICENSE.md +8 -0
- package/README.md +113 -0
- package/package.json +137 -0
- package/src/components/BodyPortal/BodyPortal.tsx +40 -0
- package/src/components/Button/Button.scss +110 -0
- package/src/components/Button/Button.tsx +101 -0
- package/src/components/Checkbox.scss +93 -0
- package/src/components/Checkbox.tsx +47 -0
- package/src/components/ExpandableToggle/ExpandableToggle.scss +123 -0
- package/src/components/ExpandableToggle/ExpandableToggle.tsx +60 -0
- package/src/components/GrapherTabIcon.tsx +156 -0
- package/src/components/GrapherTrendArrow.scss +16 -0
- package/src/components/GrapherTrendArrow.tsx +30 -0
- package/src/components/Halo/Halo.tsx +44 -0
- package/src/components/LabeledSwitch/LabeledSwitch.scss +109 -0
- package/src/components/LabeledSwitch/LabeledSwitch.tsx +62 -0
- package/src/components/MarkdownTextWrap/MarkdownTextWrap.tsx +1173 -0
- package/src/components/OverlayHeader.scss +18 -0
- package/src/components/OverlayHeader.tsx +29 -0
- package/src/components/RadioButton.scss +69 -0
- package/src/components/RadioButton.tsx +42 -0
- package/src/components/SimpleMarkdownText.tsx +89 -0
- package/src/components/TextInput.scss +17 -0
- package/src/components/TextInput.tsx +19 -0
- package/src/components/TextWrap/TextWrap.tsx +361 -0
- package/src/components/TextWrap/TextWrapUtils.ts +32 -0
- package/src/components/closeButton/CloseButton.scss +40 -0
- package/src/components/closeButton/CloseButton.tsx +27 -0
- package/src/components/index.ts +70 -0
- package/src/components/loadingIndicator/LoadingIndicator.scss +40 -0
- package/src/components/loadingIndicator/LoadingIndicator.tsx +28 -0
- package/src/components/markdown/remarkPlainLinks.ts +36 -0
- package/src/components/reactUtil.ts +20 -0
- package/src/components/stubs/CodeSnippet.tsx +19 -0
- package/src/components/stubs/DataCitation.tsx +16 -0
- package/src/components/stubs/IndicatorKeyData.tsx +45 -0
- package/src/components/stubs/IndicatorProcessing.tsx +15 -0
- package/src/components/stubs/IndicatorSources.tsx +15 -0
- package/src/components/styles/colors.scss +113 -0
- package/src/components/styles/mixins.scss +630 -0
- package/src/components/styles/typography.scss +579 -0
- package/src/components/styles/util.scss +89 -0
- package/src/components/styles/variables.scss +208 -0
- package/src/config/ChartsConfig.ts +163 -0
- package/src/config/ChartsProvider.tsx +157 -0
- package/src/config/index.ts +20 -0
- package/src/core-table/CoreTable.ts +1355 -0
- package/src/core-table/CoreTableColumns.ts +973 -0
- package/src/core-table/CoreTableUtils.ts +793 -0
- package/src/core-table/ErrorValues.ts +73 -0
- package/src/core-table/OwidTable.ts +1175 -0
- package/src/core-table/OwidTableSynthesizers.ts +272 -0
- package/src/core-table/OwidTableUtil.ts +76 -0
- package/src/core-table/Transforms.ts +484 -0
- package/src/core-table/index.ts +82 -0
- package/src/explorer/ColumnGrammar.ts +217 -0
- package/src/explorer/Explorer.sample.ts +212 -0
- package/src/explorer/Explorer.scss +148 -0
- package/src/explorer/Explorer.tsx +1283 -0
- package/src/explorer/ExplorerConstants.ts +85 -0
- package/src/explorer/ExplorerControls.scss +156 -0
- package/src/explorer/ExplorerControls.tsx +210 -0
- package/src/explorer/ExplorerDecisionMatrix.ts +471 -0
- package/src/explorer/ExplorerGrammar.ts +161 -0
- package/src/explorer/ExplorerProgram.ts +568 -0
- package/src/explorer/ExplorerUtils.ts +59 -0
- package/src/explorer/GrapherGrammar.ts +387 -0
- package/src/explorer/gridLang/GrammarUtils.ts +121 -0
- package/src/explorer/gridLang/GridCell.ts +298 -0
- package/src/explorer/gridLang/GridLangConstants.ts +255 -0
- package/src/explorer/gridLang/GridProgram.ts +311 -0
- package/src/explorer/gridLang/readme.md +17 -0
- package/src/explorer/index.ts +69 -0
- package/src/explorer/readme.md +19 -0
- package/src/explorer/urlMigrations/CO2UrlMigration.ts +46 -0
- package/src/explorer/urlMigrations/CovidUrlMigration.ts +37 -0
- package/src/explorer/urlMigrations/EnergyUrlMigration.ts +41 -0
- package/src/explorer/urlMigrations/ExplorerPageUrlMigrationSpec.ts +12 -0
- package/src/explorer/urlMigrations/ExplorerUrlMigrationUtils.ts +45 -0
- package/src/explorer/urlMigrations/ExplorerUrlMigrations.ts +33 -0
- package/src/explorer/urlMigrations/LegacyCovidUrlMigration.ts +144 -0
- package/src/explorer/urlMigrations/readme.md +39 -0
- package/src/grapher/axis/Axis.ts +973 -0
- package/src/grapher/axis/AxisConfig.ts +179 -0
- package/src/grapher/axis/AxisViews.tsx +597 -0
- package/src/grapher/barCharts/DiscreteBarChart.tsx +728 -0
- package/src/grapher/barCharts/DiscreteBarChartConstants.ts +60 -0
- package/src/grapher/barCharts/DiscreteBarChartHelpers.ts +338 -0
- package/src/grapher/barCharts/DiscreteBarChartState.ts +354 -0
- package/src/grapher/barCharts/DiscreteBarChartThumbnail.tsx +34 -0
- package/src/grapher/captionedChart/CaptionedChart.scss +61 -0
- package/src/grapher/captionedChart/CaptionedChart.tsx +523 -0
- package/src/grapher/captionedChart/Logos.tsx +141 -0
- package/src/grapher/captionedChart/LogosSVG.tsx +16 -0
- package/src/grapher/captionedChart/StaticChartRasterizer.tsx +178 -0
- package/src/grapher/captionedChart/assets/buildcanada-logo-square.svg +15 -0
- package/src/grapher/captionedChart/assets/buildcanada-logo.svg +15 -0
- package/src/grapher/captionedChart/assets/canadaspends.svg +7 -0
- package/src/grapher/captionedChart/readme.md +14 -0
- package/src/grapher/chart/Chart.tsx +62 -0
- package/src/grapher/chart/ChartAreaContent.tsx +172 -0
- package/src/grapher/chart/ChartDimension.ts +121 -0
- package/src/grapher/chart/ChartInterface.ts +83 -0
- package/src/grapher/chart/ChartManager.ts +113 -0
- package/src/grapher/chart/ChartTabs.ts +178 -0
- package/src/grapher/chart/ChartTypeMap.tsx +158 -0
- package/src/grapher/chart/ChartTypeSwitcher.tsx +26 -0
- package/src/grapher/chart/ChartUtils.tsx +364 -0
- package/src/grapher/chart/DimensionSlot.ts +45 -0
- package/src/grapher/chart/StaticChartWrapper.tsx +94 -0
- package/src/grapher/chart/guidedChartUtils.ts +82 -0
- package/src/grapher/color/BinningStrategies.ts +484 -0
- package/src/grapher/color/BinningStrategyEqualSizeBins.ts +132 -0
- package/src/grapher/color/BinningStrategyLogarithmic.ts +121 -0
- package/src/grapher/color/CategoricalColorAssigner.ts +97 -0
- package/src/grapher/color/ColorBrewerSchemes.ts +80 -0
- package/src/grapher/color/ColorConstants.ts +20 -0
- package/src/grapher/color/ColorScale.ts +339 -0
- package/src/grapher/color/ColorScaleBin.ts +147 -0
- package/src/grapher/color/ColorScaleConfig.ts +204 -0
- package/src/grapher/color/ColorScheme.ts +137 -0
- package/src/grapher/color/ColorSchemes.ts +149 -0
- package/src/grapher/color/ColorUtils.ts +86 -0
- package/src/grapher/color/CustomSchemes.ts +1772 -0
- package/src/grapher/color/readme.md +84 -0
- package/src/grapher/comparisonLine/ComparisonLine.tsx +31 -0
- package/src/grapher/comparisonLine/ComparisonLineConstants.ts +11 -0
- package/src/grapher/comparisonLine/ComparisonLineGenerator.ts +60 -0
- package/src/grapher/comparisonLine/ComparisonLineHelpers.ts +10 -0
- package/src/grapher/comparisonLine/CustomComparisonLine.tsx +159 -0
- package/src/grapher/comparisonLine/VerticalComparisonLine.tsx +208 -0
- package/src/grapher/controls/ActionButtons.scss +97 -0
- package/src/grapher/controls/ActionButtons.tsx +453 -0
- package/src/grapher/controls/CommandPalette.scss +50 -0
- package/src/grapher/controls/CommandPalette.tsx +74 -0
- package/src/grapher/controls/ContentSwitchers.scss +93 -0
- package/src/grapher/controls/ContentSwitchers.tsx +238 -0
- package/src/grapher/controls/Controls.scss +158 -0
- package/src/grapher/controls/DataTableFilterDropdown.scss +7 -0
- package/src/grapher/controls/DataTableFilterDropdown.tsx +168 -0
- package/src/grapher/controls/DataTableSearchField.scss +3 -0
- package/src/grapher/controls/DataTableSearchField.tsx +76 -0
- package/src/grapher/controls/Dropdown.scss +252 -0
- package/src/grapher/controls/Dropdown.tsx +235 -0
- package/src/grapher/controls/EntitySelectionToggle.tsx +135 -0
- package/src/grapher/controls/MapRegionDropdown.scss +3 -0
- package/src/grapher/controls/MapRegionDropdown.tsx +104 -0
- package/src/grapher/controls/MapResetButton.tsx +115 -0
- package/src/grapher/controls/MapZoomDropdown.scss +9 -0
- package/src/grapher/controls/MapZoomDropdown.tsx +270 -0
- package/src/grapher/controls/MapZoomToSelectionButton.tsx +87 -0
- package/src/grapher/controls/SearchField.scss +78 -0
- package/src/grapher/controls/SearchField.tsx +63 -0
- package/src/grapher/controls/SettingsMenu.scss +191 -0
- package/src/grapher/controls/SettingsMenu.tsx +399 -0
- package/src/grapher/controls/ShareMenu.scss +58 -0
- package/src/grapher/controls/ShareMenu.tsx +304 -0
- package/src/grapher/controls/SortIcon.tsx +39 -0
- package/src/grapher/controls/VerticalScrollContainer.tsx +263 -0
- package/src/grapher/controls/controlsRow/ControlsRow.tsx +168 -0
- package/src/grapher/controls/dropdown-icons.scss +4 -0
- package/src/grapher/controls/entityPicker/EntityPicker.scss +255 -0
- package/src/grapher/controls/entityPicker/EntityPicker.tsx +816 -0
- package/src/grapher/controls/entityPicker/EntityPickerConstants.ts +23 -0
- package/src/grapher/controls/globalEntitySelector/GlobalEntitySelector.scss +129 -0
- package/src/grapher/controls/globalEntitySelector/GlobalEntitySelector.tsx +463 -0
- package/src/grapher/controls/globalEntitySelector/GlobalEntitySelectorConstants.ts +3 -0
- package/src/grapher/controls/globalEntitySelector/readme.md +17 -0
- package/src/grapher/controls/settings/AbsRelToggle.tsx +64 -0
- package/src/grapher/controls/settings/AxisScaleToggle.tsx +53 -0
- package/src/grapher/controls/settings/FacetStrategySelector.tsx +110 -0
- package/src/grapher/controls/settings/FacetYDomainToggle.tsx +51 -0
- package/src/grapher/controls/settings/NoDataAreaToggle.tsx +38 -0
- package/src/grapher/controls/settings/ZoomToggle.tsx +36 -0
- package/src/grapher/core/EntitiesByRegionType.ts +174 -0
- package/src/grapher/core/EntityCodes.ts +19 -0
- package/src/grapher/core/EntityUrlBuilder.ts +200 -0
- package/src/grapher/core/FetchingGrapher.tsx +156 -0
- package/src/grapher/core/Grapher.tsx +760 -0
- package/src/grapher/core/GrapherAnalytics.ts +229 -0
- package/src/grapher/core/GrapherConstants.ts +173 -0
- package/src/grapher/core/GrapherState.tsx +3659 -0
- package/src/grapher/core/GrapherUrl.ts +184 -0
- package/src/grapher/core/GrapherUrlMigrations.ts +29 -0
- package/src/grapher/core/GrapherUseHelpers.tsx +147 -0
- package/src/grapher/core/LegacyToOwidTable.ts +841 -0
- package/src/grapher/core/grapher.entry.ts +5 -0
- package/src/grapher/core/grapher.scss +257 -0
- package/src/grapher/core/loadGrapherTableHelpers.ts +116 -0
- package/src/grapher/core/loadVariable.ts +104 -0
- package/src/grapher/core/relatedQuestion.ts +12 -0
- package/src/grapher/core/typography.scss +206 -0
- package/src/grapher/dataTable/DataTable.sample.ts +206 -0
- package/src/grapher/dataTable/DataTable.scss +249 -0
- package/src/grapher/dataTable/DataTable.tsx +1332 -0
- package/src/grapher/dataTable/DataTableConstants.ts +186 -0
- package/src/grapher/entitySelector/EntitySelector.scss +255 -0
- package/src/grapher/entitySelector/EntitySelector.tsx +1838 -0
- package/src/grapher/facet/FacetChart.tsx +943 -0
- package/src/grapher/facet/FacetChartConstants.ts +24 -0
- package/src/grapher/facet/FacetChartUtils.ts +51 -0
- package/src/grapher/facet/FacetMap.tsx +604 -0
- package/src/grapher/facet/FacetMapConstants.ts +23 -0
- package/src/grapher/facet/readme.md +13 -0
- package/src/grapher/focus/FocusArray.ts +79 -0
- package/src/grapher/footer/Footer.scss +63 -0
- package/src/grapher/footer/Footer.tsx +809 -0
- package/src/grapher/footer/FooterManager.ts +44 -0
- package/src/grapher/fullScreen/FullScreen.scss +11 -0
- package/src/grapher/fullScreen/FullScreen.tsx +61 -0
- package/src/grapher/header/Header.scss +35 -0
- package/src/grapher/header/Header.tsx +372 -0
- package/src/grapher/header/HeaderManager.ts +28 -0
- package/src/grapher/index.ts +157 -0
- package/src/grapher/interaction/InteractionState.ts +60 -0
- package/src/grapher/legend/HorizontalColorLegends.tsx +923 -0
- package/src/grapher/legend/LegendInteractionState.ts +40 -0
- package/src/grapher/legend/VerticalColorLegend.tsx +295 -0
- package/src/grapher/lineCharts/LineChart.tsx +968 -0
- package/src/grapher/lineCharts/LineChartConstants.ts +89 -0
- package/src/grapher/lineCharts/LineChartHelpers.ts +184 -0
- package/src/grapher/lineCharts/LineChartState.ts +394 -0
- package/src/grapher/lineCharts/LineChartThumbnail.tsx +437 -0
- package/src/grapher/lineCharts/Lines.tsx +258 -0
- package/src/grapher/lineLegend/LineLegend.tsx +723 -0
- package/src/grapher/lineLegend/LineLegendConstants.ts +9 -0
- package/src/grapher/lineLegend/LineLegendFilterAlgorithms.ts +143 -0
- package/src/grapher/lineLegend/LineLegendHelpers.ts +253 -0
- package/src/grapher/lineLegend/LineLegendTypes.ts +32 -0
- package/src/grapher/mapCharts/CanadaTopology.ts +17922 -0
- package/src/grapher/mapCharts/ChoroplethGlobe.tsx +949 -0
- package/src/grapher/mapCharts/ChoroplethMap.tsx +662 -0
- package/src/grapher/mapCharts/GeoFeatures.ts +184 -0
- package/src/grapher/mapCharts/GlobeController.ts +496 -0
- package/src/grapher/mapCharts/MapAnnotationPlacements.json +1040 -0
- package/src/grapher/mapCharts/MapAnnotationPlacements.ts +31 -0
- package/src/grapher/mapCharts/MapAnnotations.ts +723 -0
- package/src/grapher/mapCharts/MapChart.sample.ts +59 -0
- package/src/grapher/mapCharts/MapChart.scss +5 -0
- package/src/grapher/mapCharts/MapChart.tsx +720 -0
- package/src/grapher/mapCharts/MapChartConstants.ts +260 -0
- package/src/grapher/mapCharts/MapChartState.ts +416 -0
- package/src/grapher/mapCharts/MapChartThumbnail.tsx +25 -0
- package/src/grapher/mapCharts/MapComponents.tsx +338 -0
- package/src/grapher/mapCharts/MapConfig.ts +156 -0
- package/src/grapher/mapCharts/MapHelpers.ts +181 -0
- package/src/grapher/mapCharts/MapProjections.ts +49 -0
- package/src/grapher/mapCharts/MapSparkline.tsx +257 -0
- package/src/grapher/mapCharts/MapTooltip.scss +49 -0
- package/src/grapher/mapCharts/MapTooltip.tsx +409 -0
- package/src/grapher/mapCharts/MapTopology.ts +1766 -0
- package/src/grapher/mapCharts/d3-bboxCollide.js +204 -0
- package/src/grapher/mapCharts/d3-geo-projection.ts +198 -0
- package/src/grapher/modal/DownloadIcons.tsx +39 -0
- package/src/grapher/modal/DownloadModal.scss +300 -0
- package/src/grapher/modal/DownloadModal.tsx +1226 -0
- package/src/grapher/modal/EmbedModal.scss +40 -0
- package/src/grapher/modal/EmbedModal.tsx +160 -0
- package/src/grapher/modal/EntitySelectorModal.tsx +59 -0
- package/src/grapher/modal/Modal.scss +31 -0
- package/src/grapher/modal/Modal.tsx +90 -0
- package/src/grapher/modal/ModalHeader.scss +12 -0
- package/src/grapher/modal/ModalHeader.tsx +16 -0
- package/src/grapher/modal/SourcesDescriptions.scss +87 -0
- package/src/grapher/modal/SourcesDescriptions.tsx +89 -0
- package/src/grapher/modal/SourcesKeyDataTable.scss +49 -0
- package/src/grapher/modal/SourcesKeyDataTable.tsx +87 -0
- package/src/grapher/modal/SourcesModal.scss +301 -0
- package/src/grapher/modal/SourcesModal.tsx +568 -0
- package/src/grapher/noDataModal/NoDataModal.tsx +125 -0
- package/src/grapher/scatterCharts/ConnectedScatterLegend.tsx +143 -0
- package/src/grapher/scatterCharts/MultiColorPolyline.tsx +129 -0
- package/src/grapher/scatterCharts/NoDataSection.scss +14 -0
- package/src/grapher/scatterCharts/NoDataSection.tsx +56 -0
- package/src/grapher/scatterCharts/ScatterPlotChart.tsx +792 -0
- package/src/grapher/scatterCharts/ScatterPlotChartConstants.ts +157 -0
- package/src/grapher/scatterCharts/ScatterPlotChartState.ts +678 -0
- package/src/grapher/scatterCharts/ScatterPlotChartThumbnail.tsx +155 -0
- package/src/grapher/scatterCharts/ScatterPlotTooltip.tsx +560 -0
- package/src/grapher/scatterCharts/ScatterPoints.tsx +153 -0
- package/src/grapher/scatterCharts/ScatterPointsWithLabels.tsx +708 -0
- package/src/grapher/scatterCharts/ScatterSizeLegend.tsx +327 -0
- package/src/grapher/scatterCharts/ScatterUtils.ts +265 -0
- package/src/grapher/scatterCharts/Triangle.tsx +41 -0
- package/src/grapher/schema/README.md +33 -0
- package/src/grapher/schema/defaultGrapherConfig.ts +100 -0
- package/src/grapher/schema/grapher-schema.009.yaml +781 -0
- package/src/grapher/schema/migrations/helpers.ts +58 -0
- package/src/grapher/schema/migrations/migrate.ts +75 -0
- package/src/grapher/schema/migrations/migrations.ts +158 -0
- package/src/grapher/selection/MapSelectionArray.ts +99 -0
- package/src/grapher/selection/SelectionArray.ts +71 -0
- package/src/grapher/selection/readme.md +16 -0
- package/src/grapher/sidePanel/SidePanel.scss +10 -0
- package/src/grapher/sidePanel/SidePanel.tsx +23 -0
- package/src/grapher/slideInDrawer/SlideInDrawer.scss +57 -0
- package/src/grapher/slideInDrawer/SlideInDrawer.tsx +125 -0
- package/src/grapher/slideshowController/SlideShowController.tsx +43 -0
- package/src/grapher/slideshowController/readme.md +7 -0
- package/src/grapher/slopeCharts/MarkX.tsx +45 -0
- package/src/grapher/slopeCharts/Slope.tsx +102 -0
- package/src/grapher/slopeCharts/SlopeChart.tsx +1152 -0
- package/src/grapher/slopeCharts/SlopeChartConstants.ts +33 -0
- package/src/grapher/slopeCharts/SlopeChartHelpers.ts +73 -0
- package/src/grapher/slopeCharts/SlopeChartState.ts +392 -0
- package/src/grapher/slopeCharts/SlopeChartThumbnail.tsx +368 -0
- package/src/grapher/stackedCharts/AbstractStackedChartState.ts +370 -0
- package/src/grapher/stackedCharts/MarimekkoBars.tsx +190 -0
- package/src/grapher/stackedCharts/MarimekkoBarsForOneEntity.tsx +168 -0
- package/src/grapher/stackedCharts/MarimekkoChart.tsx +1144 -0
- package/src/grapher/stackedCharts/MarimekkoChartConstants.ts +112 -0
- package/src/grapher/stackedCharts/MarimekkoChartHelpers.ts +21 -0
- package/src/grapher/stackedCharts/MarimekkoChartState.ts +465 -0
- package/src/grapher/stackedCharts/MarimekkoChartThumbnail.tsx +168 -0
- package/src/grapher/stackedCharts/MarimekkoInternalLabels.tsx +124 -0
- package/src/grapher/stackedCharts/StackedAreaChart.tsx +678 -0
- package/src/grapher/stackedCharts/StackedAreaChartState.ts +34 -0
- package/src/grapher/stackedCharts/StackedAreaChartThumbnail.tsx +215 -0
- package/src/grapher/stackedCharts/StackedAreas.tsx +223 -0
- package/src/grapher/stackedCharts/StackedBarChart.tsx +619 -0
- package/src/grapher/stackedCharts/StackedBarChartState.ts +80 -0
- package/src/grapher/stackedCharts/StackedBarChartThumbnail.tsx +220 -0
- package/src/grapher/stackedCharts/StackedBarSegment.tsx +87 -0
- package/src/grapher/stackedCharts/StackedBars.tsx +102 -0
- package/src/grapher/stackedCharts/StackedConstants.ts +109 -0
- package/src/grapher/stackedCharts/StackedDiscreteBarChart.tsx +270 -0
- package/src/grapher/stackedCharts/StackedDiscreteBarChartState.ts +296 -0
- package/src/grapher/stackedCharts/StackedDiscreteBarChartThumbnail.tsx +27 -0
- package/src/grapher/stackedCharts/StackedDiscreteBars.tsx +648 -0
- package/src/grapher/stackedCharts/StackedUtils.ts +142 -0
- package/src/grapher/tabs/Tabs.scss +169 -0
- package/src/grapher/tabs/Tabs.tsx +54 -0
- package/src/grapher/tabs/TabsWithDropdown.scss +62 -0
- package/src/grapher/tabs/TabsWithDropdown.tsx +114 -0
- package/src/grapher/testData/OwidTestData.sample.ts +273 -0
- package/src/grapher/testData/OwidTestData.ts +64 -0
- package/src/grapher/timeline/TimelineComponent.scss +139 -0
- package/src/grapher/timeline/TimelineComponent.tsx +658 -0
- package/src/grapher/timeline/TimelineController.ts +368 -0
- package/src/grapher/timeline/readme.md +7 -0
- package/src/grapher/tooltip/Tooltip.scss +510 -0
- package/src/grapher/tooltip/Tooltip.tsx +294 -0
- package/src/grapher/tooltip/TooltipContents.tsx +383 -0
- package/src/grapher/tooltip/TooltipProps.ts +123 -0
- package/src/grapher/tooltip/TooltipState.ts +81 -0
- package/src/grapher/verticalLabels/VerticalLabels.tsx +31 -0
- package/src/grapher/verticalLabels/VerticalLabelsState.ts +154 -0
- package/src/index.ts +226 -0
- package/src/styles/charts.scss +15 -0
- package/src/types/NominalType.ts +30 -0
- package/src/types/OwidOrigin.ts +18 -0
- package/src/types/OwidSource.ts +9 -0
- package/src/types/OwidVariable.ts +133 -0
- package/src/types/OwidVariableDisplayConfigInterface.ts +49 -0
- package/src/types/analyticsTypes.ts +54 -0
- package/src/types/dbTypes/Tags.ts +11 -0
- package/src/types/domainTypes/Archive.ts +139 -0
- package/src/types/domainTypes/Author.ts +28 -0
- package/src/types/domainTypes/ContentGraph.ts +76 -0
- package/src/types/domainTypes/CoreTableTypes.ts +305 -0
- package/src/types/domainTypes/DeployStatus.ts +23 -0
- package/src/types/domainTypes/Layout.ts +34 -0
- package/src/types/domainTypes/Posts.ts +34 -0
- package/src/types/domainTypes/Search.ts +299 -0
- package/src/types/domainTypes/Site.ts +8 -0
- package/src/types/domainTypes/StaticViz.ts +64 -0
- package/src/types/domainTypes/Toc.ts +11 -0
- package/src/types/domainTypes/Tombstone.ts +19 -0
- package/src/types/domainTypes/Various.ts +79 -0
- package/src/types/gdocTypes/Gdoc.ts +280 -0
- package/src/types/grapherTypes/BinningStrategyTypes.ts +46 -0
- package/src/types/grapherTypes/GrapherConstants.ts +53 -0
- package/src/types/grapherTypes/GrapherTypes.ts +743 -0
- package/src/types/index.ts +316 -0
- package/src/types/wordpressTypes/WordpressTypes.ts +9 -0
- package/src/utils/Bounds.ts +439 -0
- package/src/utils/BrowserUtils.ts +12 -0
- package/src/utils/FuzzySearch.ts +74 -0
- package/src/utils/MultiDimDataPageConfig.ts +31 -0
- package/src/utils/OwidVariable.ts +82 -0
- package/src/utils/PointVector.ts +97 -0
- package/src/utils/PromiseCache.ts +36 -0
- package/src/utils/PromiseSwitcher.ts +52 -0
- package/src/utils/TimeBounds.ts +130 -0
- package/src/utils/Tippy.tsx +57 -0
- package/src/utils/Util.ts +2369 -0
- package/src/utils/archival/archivalDate.ts +48 -0
- package/src/utils/dayjs.ts +32 -0
- package/src/utils/formatValue.ts +242 -0
- package/src/utils/grapherConfigUtils.ts +81 -0
- package/src/utils/image.ts +225 -0
- package/src/utils/index.ts +318 -0
- package/src/utils/isPresent.ts +5 -0
- package/src/utils/metadataHelpers.ts +329 -0
- package/src/utils/persistable/Persistable.ts +82 -0
- package/src/utils/persistable/readme.md +50 -0
- package/src/utils/regions.json +5635 -0
- package/src/utils/regions.ts +463 -0
- package/src/utils/serializers.ts +16 -0
- package/src/utils/string.ts +42 -0
- package/src/utils/urls/Url.ts +195 -0
- package/src/utils/urls/UrlMigration.ts +10 -0
- package/src/utils/urls/UrlUtils.ts +54 -0
- package/src/utils/urls/readme.md +90 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as topojson from "topojson-client"
|
|
2
|
+
import {
|
|
3
|
+
GeoFeature,
|
|
4
|
+
GlobeRenderFeature,
|
|
5
|
+
MapRenderFeature,
|
|
6
|
+
RenderFeatureType,
|
|
7
|
+
} from "./MapChartConstants"
|
|
8
|
+
import { Bounds, lazy, MapRegionName, PointVector } from "../../utils/index.js"
|
|
9
|
+
import { CanadaTopology } from "./CanadaTopology"
|
|
10
|
+
import { MapTopology } from "./MapTopology"
|
|
11
|
+
import { geoBounds, geoCentroid, geoPath } from "d3-geo"
|
|
12
|
+
import { MAP_PROJECTIONS } from "./MapProjections"
|
|
13
|
+
|
|
14
|
+
// Get the underlying geographical topology elements we're going to display
|
|
15
|
+
export const GeoFeatures: GeoFeature[] = (
|
|
16
|
+
topojson.feature(
|
|
17
|
+
MapTopology as any,
|
|
18
|
+
MapTopology.objects.world as any
|
|
19
|
+
) as any
|
|
20
|
+
).features
|
|
21
|
+
|
|
22
|
+
export const GeoFeaturesById = new Map(
|
|
23
|
+
GeoFeatures.map((feature) => [feature.id, feature])
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
// Canadian province features
|
|
27
|
+
export const CanadaGeoFeatures: GeoFeature[] = (
|
|
28
|
+
topojson.feature(
|
|
29
|
+
CanadaTopology as any,
|
|
30
|
+
CanadaTopology.objects.canada as any
|
|
31
|
+
) as any
|
|
32
|
+
).features
|
|
33
|
+
|
|
34
|
+
export const CanadaGeoFeaturesById = new Map(
|
|
35
|
+
CanadaGeoFeatures.map((feature) => [feature.id, feature])
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
// Get the svg path specification string for every feature
|
|
39
|
+
const geoPathCache = new Map<MapRegionName, string[]>()
|
|
40
|
+
const geoPathsFor = (regionName: MapRegionName): string[] => {
|
|
41
|
+
if (geoPathCache.has(regionName)) return geoPathCache.get(regionName)!
|
|
42
|
+
|
|
43
|
+
const projectionGeo = geoPath()
|
|
44
|
+
.digits(1)
|
|
45
|
+
.projection(MAP_PROJECTIONS[regionName])
|
|
46
|
+
const strs = GeoFeatures.map((feature) => projectionGeo(feature) ?? "")
|
|
47
|
+
|
|
48
|
+
geoPathCache.set(regionName, strs)
|
|
49
|
+
return geoPathCache.get(regionName)!
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Get the bounding box for every geographical feature
|
|
53
|
+
const geoBoundsCache = new Map<MapRegionName, Bounds[]>()
|
|
54
|
+
const geoBoundsFor = (regionName: MapRegionName): Bounds[] => {
|
|
55
|
+
if (geoBoundsCache.has(regionName)) return geoBoundsCache.get(regionName)!
|
|
56
|
+
|
|
57
|
+
const projectionGeo = geoPath().projection(MAP_PROJECTIONS[regionName])
|
|
58
|
+
const bounds = GeoFeatures.map((feature) => {
|
|
59
|
+
const corners = projectionGeo.bounds(feature)
|
|
60
|
+
|
|
61
|
+
const bounds = Bounds.fromCorners(
|
|
62
|
+
new PointVector(...corners[0]),
|
|
63
|
+
new PointVector(...corners[1])
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// HACK (Mispy): The path generator calculates weird bounds for Fiji (probably it wraps around the map)
|
|
67
|
+
if (feature.id === "Fiji")
|
|
68
|
+
return bounds.set({
|
|
69
|
+
x: bounds.right - bounds.height,
|
|
70
|
+
width: bounds.height,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
return bounds
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
geoBoundsCache.set(regionName, bounds)
|
|
77
|
+
return geoBoundsCache.get(regionName)!
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const geoCentroidsForFeatures = GeoFeatures.map((feature) =>
|
|
81
|
+
geoCentroid(feature.geometry)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const geoBoundsForFeatures = GeoFeatures.map((feature) => {
|
|
85
|
+
const corners = geoBounds(feature)
|
|
86
|
+
return Bounds.fromCorners(
|
|
87
|
+
new PointVector(...corners[0]),
|
|
88
|
+
new PointVector(...corners[1])
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// Canada-specific computed values
|
|
93
|
+
const canadaGeoCentroidsForFeatures = CanadaGeoFeatures.map((feature) =>
|
|
94
|
+
geoCentroid(feature.geometry)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const canadaGeoBoundsForFeatures = CanadaGeoFeatures.map((feature) => {
|
|
98
|
+
const corners = geoBounds(feature)
|
|
99
|
+
return Bounds.fromCorners(
|
|
100
|
+
new PointVector(...corners[0]),
|
|
101
|
+
new PointVector(...corners[1])
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Canada-specific path and bounds functions
|
|
106
|
+
const canadaGeoPathsFor = (): string[] => {
|
|
107
|
+
const projectionGeo = geoPath()
|
|
108
|
+
.digits(1)
|
|
109
|
+
.projection(MAP_PROJECTIONS[MapRegionName.Canada])
|
|
110
|
+
return CanadaGeoFeatures.map((feature) => projectionGeo(feature) ?? "")
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const canadaGeoBoundsFor = (): Bounds[] => {
|
|
114
|
+
const projectionGeo = geoPath().projection(MAP_PROJECTIONS[MapRegionName.Canada])
|
|
115
|
+
return CanadaGeoFeatures.map((feature) => {
|
|
116
|
+
const corners = projectionGeo.bounds(feature)
|
|
117
|
+
return Bounds.fromCorners(
|
|
118
|
+
new PointVector(...corners[0]),
|
|
119
|
+
new PointVector(...corners[1])
|
|
120
|
+
)
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const geoFeaturesForMapCache = new Map<MapRegionName, MapRenderFeature[]>()
|
|
125
|
+
export const getGeoFeaturesForMap = (
|
|
126
|
+
regionName: MapRegionName
|
|
127
|
+
): MapRenderFeature[] => {
|
|
128
|
+
if (geoFeaturesForMapCache.has(regionName))
|
|
129
|
+
return geoFeaturesForMapCache.get(regionName)!
|
|
130
|
+
|
|
131
|
+
// Handle Canada region separately using Canadian province features
|
|
132
|
+
if (regionName === MapRegionName.Canada) {
|
|
133
|
+
const projBounds = canadaGeoBoundsFor()
|
|
134
|
+
const projPaths = canadaGeoPathsFor()
|
|
135
|
+
|
|
136
|
+
const features = CanadaGeoFeatures.map((geo, index) => ({
|
|
137
|
+
type: RenderFeatureType.Map,
|
|
138
|
+
id: geo.id as string,
|
|
139
|
+
geo: geo,
|
|
140
|
+
projBounds: projBounds[index],
|
|
141
|
+
geoBounds: canadaGeoBoundsForFeatures[index],
|
|
142
|
+
geoCentroid: canadaGeoCentroidsForFeatures[index],
|
|
143
|
+
path: projPaths[index],
|
|
144
|
+
})) satisfies MapRenderFeature[]
|
|
145
|
+
|
|
146
|
+
geoFeaturesForMapCache.set(regionName, features)
|
|
147
|
+
return geoFeaturesForMapCache.get(regionName)!
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const projBounds = geoBoundsFor(regionName)
|
|
151
|
+
const projPaths = geoPathsFor(regionName)
|
|
152
|
+
|
|
153
|
+
const features = (
|
|
154
|
+
GeoFeatures.map((geo, index) => ({
|
|
155
|
+
type: RenderFeatureType.Map,
|
|
156
|
+
id: geo.id as string,
|
|
157
|
+
geo: geo,
|
|
158
|
+
projBounds: projBounds[index], // projected
|
|
159
|
+
geoBounds: geoBoundsForFeatures[index], // unprojected
|
|
160
|
+
geoCentroid: geoCentroidsForFeatures[index], // unprojected
|
|
161
|
+
path: projPaths[index],
|
|
162
|
+
})) satisfies MapRenderFeature[]
|
|
163
|
+
).filter((feature) => feature.id !== "Antarctica") // exclude Antarctica since it's distorted and uses up too much space
|
|
164
|
+
|
|
165
|
+
geoFeaturesForMapCache.set(regionName, features)
|
|
166
|
+
return geoFeaturesForMapCache.get(regionName)!
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const getGeoFeaturesForGlobe = lazy((): GlobeRenderFeature[] => {
|
|
170
|
+
return GeoFeatures.map((geo, index) => {
|
|
171
|
+
const corners = geoBounds(geo)
|
|
172
|
+
const bounds = Bounds.fromCorners(
|
|
173
|
+
new PointVector(...corners[0]),
|
|
174
|
+
new PointVector(...corners[1])
|
|
175
|
+
)
|
|
176
|
+
return {
|
|
177
|
+
type: RenderFeatureType.Globe,
|
|
178
|
+
id: geo.id as string,
|
|
179
|
+
geo: geo,
|
|
180
|
+
geoCentroid: geoCentroidsForFeatures[index],
|
|
181
|
+
geoBounds: bounds,
|
|
182
|
+
}
|
|
183
|
+
}) satisfies GlobeRenderFeature[]
|
|
184
|
+
})
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import { FeatureCollection } from "geojson"
|
|
2
|
+
import { geoInterpolate, geoOrthographic, geoPath } from "d3-geo"
|
|
3
|
+
import { interpolateNumber } from "d3-interpolate"
|
|
4
|
+
import { easeCubicOut } from "d3-ease"
|
|
5
|
+
import * as R from "remeda"
|
|
6
|
+
import { EntityName, GlobeConfig, GlobeRegionName } from "../../types/index.js"
|
|
7
|
+
import {
|
|
8
|
+
Bounds,
|
|
9
|
+
excludeUndefined,
|
|
10
|
+
PartialBy,
|
|
11
|
+
PointVector,
|
|
12
|
+
checkIsOwidContinent,
|
|
13
|
+
getCountryNamesForRegion,
|
|
14
|
+
getRegionByName,
|
|
15
|
+
checkHasMembers,
|
|
16
|
+
} from "../../utils/index.js"
|
|
17
|
+
import { MapConfig } from "./MapConfig"
|
|
18
|
+
import { getGeoFeaturesForGlobe } from "./GeoFeatures"
|
|
19
|
+
import {
|
|
20
|
+
DEFAULT_GLOBE_ROTATION,
|
|
21
|
+
DEFAULT_GLOBE_ROTATIONS_FOR_TIME,
|
|
22
|
+
DEFAULT_GLOBE_SIZE,
|
|
23
|
+
GeoFeature,
|
|
24
|
+
GLOBE_COUNTRY_ZOOM,
|
|
25
|
+
GLOBE_LATITUDE_MAX,
|
|
26
|
+
GLOBE_LATITUDE_MIN,
|
|
27
|
+
GLOBE_MAX_ZOOM,
|
|
28
|
+
GLOBE_MIN_ZOOM,
|
|
29
|
+
GLOBE_VIEWPORTS,
|
|
30
|
+
GlobeRenderFeature,
|
|
31
|
+
MAP_REGION_NAMES,
|
|
32
|
+
} from "./MapChartConstants"
|
|
33
|
+
import { isPointPlacedOnVisibleHemisphere } from "./MapHelpers"
|
|
34
|
+
import { ckmeans } from "simple-statistics"
|
|
35
|
+
import { MapSelectionArray } from "../selection/MapSelectionArray"
|
|
36
|
+
import { center } from "@turf/center"
|
|
37
|
+
import { action } from "mobx"
|
|
38
|
+
|
|
39
|
+
const geoFeaturesById = new Map<string, GlobeRenderFeature>(
|
|
40
|
+
getGeoFeaturesForGlobe().map((f: GlobeRenderFeature) => [f.id, f])
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const LONGITUDE_OFFSET = 40
|
|
44
|
+
const ANIMATION_DURATION = 600
|
|
45
|
+
|
|
46
|
+
interface Target {
|
|
47
|
+
coords: [number, number]
|
|
48
|
+
zoom: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface GlobeControllerManager {
|
|
52
|
+
mapConfig: MapConfig
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class GlobeController {
|
|
56
|
+
private manager: GlobeControllerManager
|
|
57
|
+
|
|
58
|
+
constructor(manager: GlobeControllerManager) {
|
|
59
|
+
this.manager = manager
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private get globeConfig(): GlobeConfig {
|
|
63
|
+
return this.manager.mapConfig.globe
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@action.bound showGlobe(): void {
|
|
67
|
+
this.globeConfig.isActive = true
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@action.bound hideGlobe(): void {
|
|
71
|
+
this.globeConfig.isActive = false
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@action.bound toggleGlobe(): void {
|
|
75
|
+
this.globeConfig.isActive = !this.globeConfig.isActive
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@action.bound resetGlobe(): void {
|
|
79
|
+
this.globeConfig.rotation = DEFAULT_GLOBE_ROTATION
|
|
80
|
+
this.globeConfig.zoom = 1
|
|
81
|
+
this.globeConfig.focusCountry = undefined
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@action.bound setFocusCountry(country: EntityName): void {
|
|
85
|
+
this.globeConfig.focusCountry = country
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@action.bound dismissCountryFocus(): void {
|
|
89
|
+
this.globeConfig.focusCountry = undefined
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@action.bound private jumpTo(target: Partial<Target>): void {
|
|
93
|
+
if (target.coords) this.globeConfig.rotation = target.coords
|
|
94
|
+
if (target.zoom) this.globeConfig.zoom = target.zoom
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private showGlobeAndRotateTo(target: Target): void {
|
|
98
|
+
// if the globe isn't currently shown, jump to the offset position
|
|
99
|
+
// before switching to it so that rotating is predictable
|
|
100
|
+
if (!this.globeConfig.isActive) {
|
|
101
|
+
this.jumpTo({ coords: addLongitudeOffset(target.coords) })
|
|
102
|
+
this.showGlobe()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
void this.rotateTo(target)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
jumpToOwidContinent(continent: GlobeRegionName): void {
|
|
109
|
+
const target = calculateTargetForOwidContinent(continent)
|
|
110
|
+
this.jumpTo(target)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
rotateToCountry(country: EntityName, zoom?: number): void {
|
|
114
|
+
const target = calculateTargetForCountry(country, zoom)
|
|
115
|
+
if (target) this.showGlobeAndRotateTo(target)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
rotateToOwidContinent(continent: GlobeRegionName): void {
|
|
119
|
+
const target = calculateTargetForOwidContinent(continent)
|
|
120
|
+
this.showGlobeAndRotateTo(target)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
rotateToDefaultBasedOnTime(): void {
|
|
124
|
+
const target = calculateTargetBasedOnTime()
|
|
125
|
+
this.showGlobeAndRotateTo(target)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
rotateToSelection(): void {
|
|
129
|
+
const target = calculateTargetForSelection(
|
|
130
|
+
this.manager.mapConfig.selection
|
|
131
|
+
)
|
|
132
|
+
if (target) this.showGlobeAndRotateTo(target)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
rotateToRegion(regionName: string): void {
|
|
136
|
+
const target = calculateTargetForRegion(regionName)
|
|
137
|
+
if (target) this.showGlobeAndRotateTo(target)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private currentAnimation?: AbortController
|
|
141
|
+
private async rotateTo(target: PartialBy<Target, "zoom">): Promise<void> {
|
|
142
|
+
// cancel any ongoing rotation
|
|
143
|
+
if (this.currentAnimation) {
|
|
144
|
+
this.currentAnimation.abort()
|
|
145
|
+
this.currentAnimation = undefined
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// set up a new abort controller
|
|
149
|
+
const controller = new AbortController()
|
|
150
|
+
this.currentAnimation = controller
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
await this._rotateTo(controller.signal, target.coords, target.zoom)
|
|
154
|
+
} catch {
|
|
155
|
+
// aborted
|
|
156
|
+
} finally {
|
|
157
|
+
if (this.currentAnimation === controller) {
|
|
158
|
+
this.currentAnimation = undefined
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async _rotateTo(
|
|
164
|
+
signal: AbortSignal,
|
|
165
|
+
targetCoords: [number, number],
|
|
166
|
+
targetZoom?: number
|
|
167
|
+
): Promise<void> {
|
|
168
|
+
const currentCoords = this.globeConfig.rotation
|
|
169
|
+
const animatedCoords = geoInterpolate(currentCoords, targetCoords)
|
|
170
|
+
|
|
171
|
+
const currentZoom = this.globeConfig.zoom
|
|
172
|
+
const animatedZoom =
|
|
173
|
+
targetZoom !== undefined
|
|
174
|
+
? interpolateNumber(currentZoom, targetZoom)
|
|
175
|
+
: undefined
|
|
176
|
+
|
|
177
|
+
const animPromise = new Promise<void>((resolve, reject) => {
|
|
178
|
+
const now = Date.now()
|
|
179
|
+
const step = action((): void => {
|
|
180
|
+
const elapsed = Date.now() - now
|
|
181
|
+
const t = Math.min(1, elapsed / ANIMATION_DURATION)
|
|
182
|
+
|
|
183
|
+
// Check if the animation was canceled
|
|
184
|
+
if (signal.aborted) {
|
|
185
|
+
reject()
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// animate globe rotation
|
|
190
|
+
this.globeConfig.rotation = animatedCoords(easeCubicOut(t))
|
|
191
|
+
|
|
192
|
+
// animate zoom
|
|
193
|
+
if (animatedZoom)
|
|
194
|
+
this.globeConfig.zoom = animatedZoom(easeCubicOut(t))
|
|
195
|
+
|
|
196
|
+
if (t < 1) {
|
|
197
|
+
requestAnimationFrame(step)
|
|
198
|
+
} else {
|
|
199
|
+
resolve()
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
requestAnimationFrame(step)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
await animPromise
|
|
206
|
+
.catch(() => {
|
|
207
|
+
// ignore
|
|
208
|
+
})
|
|
209
|
+
.then(
|
|
210
|
+
action(() => {
|
|
211
|
+
// ensure we end exactly at the target values
|
|
212
|
+
this.globeConfig.rotation = targetCoords
|
|
213
|
+
if (targetZoom !== undefined)
|
|
214
|
+
this.globeConfig.zoom = targetZoom
|
|
215
|
+
})
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function calculateTargetForCountry(
|
|
221
|
+
country: EntityName,
|
|
222
|
+
zoom?: number
|
|
223
|
+
): Target | undefined {
|
|
224
|
+
const geoFeature = geoFeaturesById.get(country)
|
|
225
|
+
if (!geoFeature) return
|
|
226
|
+
|
|
227
|
+
const coords: [number, number] = [
|
|
228
|
+
geoFeature.geoCentroid[0],
|
|
229
|
+
R.clamp(geoFeature.geoCentroid[1], {
|
|
230
|
+
min: GLOBE_LATITUDE_MIN,
|
|
231
|
+
max: GLOBE_LATITUDE_MAX,
|
|
232
|
+
}),
|
|
233
|
+
]
|
|
234
|
+
|
|
235
|
+
// make sure the whole country is visible after zooming
|
|
236
|
+
const zoomToFit = calculateZoomToFitForGeoFeature(geoFeature.geo)
|
|
237
|
+
const targetZoom = Math.min(zoom ?? GLOBE_COUNTRY_ZOOM, zoomToFit)
|
|
238
|
+
|
|
239
|
+
return { coords, zoom: targetZoom }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function calculateZoomToFitForGeoFeature(geoFeature: GeoFeature): number {
|
|
243
|
+
const centerPoint = getCenterPoint(geoFeature)
|
|
244
|
+
const projection = geoOrthographic().rotate(negateCoords(centerPoint))
|
|
245
|
+
|
|
246
|
+
const corners = geoPath().projection(projection).bounds(geoFeature)
|
|
247
|
+
const bounds = Bounds.fromCorners(
|
|
248
|
+
new PointVector(...corners[0]),
|
|
249
|
+
new PointVector(...corners[1])
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return calculateZoomToFitForBounds(bounds)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function calculateZoomToFitForBounds(bounds: Bounds): number {
|
|
256
|
+
// calculate the zoom needed for the bounds to be visible
|
|
257
|
+
let zoom = Math.min(
|
|
258
|
+
DEFAULT_GLOBE_SIZE / bounds.width,
|
|
259
|
+
DEFAULT_GLOBE_SIZE / bounds.height
|
|
260
|
+
)
|
|
261
|
+
if (Number.isNaN(zoom)) zoom = 1
|
|
262
|
+
|
|
263
|
+
// it's nicer to have a bit of padding around the zoomed-to area
|
|
264
|
+
zoom = zoom - 0.05
|
|
265
|
+
|
|
266
|
+
// clamp the zoom to the allowed range
|
|
267
|
+
zoom = R.clamp(zoom, { min: GLOBE_MIN_ZOOM, max: GLOBE_MAX_ZOOM })
|
|
268
|
+
|
|
269
|
+
return zoom
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function calculateTargetForOwidContinent(continent: GlobeRegionName): Target {
|
|
273
|
+
const viewport = GLOBE_VIEWPORTS[continent]
|
|
274
|
+
return { coords: viewport.rotation, zoom: viewport.zoom }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function calculateTargetBasedOnTime(): Target {
|
|
278
|
+
const coords = getCoordsBasedOnTime()
|
|
279
|
+
return { coords, zoom: 1 }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function calculateTargetForRegion(regionName: string): Target | undefined {
|
|
283
|
+
const region = getRegionByName(regionName)
|
|
284
|
+
if (!region || !checkHasMembers(region)) return
|
|
285
|
+
const countryNames = getCountryNamesForRegion(region)
|
|
286
|
+
return calculateTargetForCountryCollection(countryNames)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function calculateTargetForSelection(
|
|
290
|
+
selection: MapSelectionArray
|
|
291
|
+
): Target | undefined {
|
|
292
|
+
// if at least one country is selected, then rotate to the countries (and ignore the regions)
|
|
293
|
+
if (selection.selectedCountryNamesInForeground.length > 0) {
|
|
294
|
+
return calculateTargetForCountryCollection(
|
|
295
|
+
selection.selectedCountryNamesInForeground
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// if a single owid continent is selected, then rotate to it
|
|
300
|
+
// (the hard-coded coords/zoom values are nicer than dynamically computing it)
|
|
301
|
+
if (
|
|
302
|
+
selection.selectedRegions.length === 1 &&
|
|
303
|
+
checkIsOwidContinent(selection.selectedRegions[0])
|
|
304
|
+
) {
|
|
305
|
+
return calculateTargetForOwidContinent(
|
|
306
|
+
MAP_REGION_NAMES[
|
|
307
|
+
selection.selectedRegions[0].name
|
|
308
|
+
] as GlobeRegionName
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// rotate and zoom to all countries in the selected regions
|
|
313
|
+
const countryNames = [
|
|
314
|
+
...selection.selectedCountryNamesInForeground,
|
|
315
|
+
...selection.countryNamesForSelectedRegions,
|
|
316
|
+
]
|
|
317
|
+
return calculateTargetForCountryCollection(countryNames)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function calculateTargetForCountryCollection(
|
|
321
|
+
countryNames: string[]
|
|
322
|
+
): Target | undefined {
|
|
323
|
+
// early return if the selection is empty or a single country is selected
|
|
324
|
+
if (countryNames.length === 0) return
|
|
325
|
+
if (countryNames.length === 1) {
|
|
326
|
+
return calculateTargetForCountry(countryNames[0])
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// find a subset of countries that can be shown on the globe,
|
|
330
|
+
// e.g. if 'Mexico', 'Guatemala' and 'Australia' are selected, then
|
|
331
|
+
// 'Australia' is dropped as it's on the opposite site from South America
|
|
332
|
+
const visibleCountries = findVisibleCountrySubset(countryNames)
|
|
333
|
+
|
|
334
|
+
// early return if no country or a single country is visible
|
|
335
|
+
if (visibleCountries.length === 0) return
|
|
336
|
+
if (visibleCountries.length === 1) {
|
|
337
|
+
return calculateTargetForCountry(visibleCountries[0])
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// calculate target coords and zoom for two or more countries
|
|
341
|
+
return getCoordsAndZoomForCountryCollection(visibleCountries)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function getCoordsBasedOnTime(): [number, number] {
|
|
345
|
+
const date = new Date()
|
|
346
|
+
const hours = date.getUTCHours()
|
|
347
|
+
|
|
348
|
+
if (hours <= 7) {
|
|
349
|
+
return DEFAULT_GLOBE_ROTATIONS_FOR_TIME.UTC_MORNING
|
|
350
|
+
} else if (hours <= 15) {
|
|
351
|
+
return DEFAULT_GLOBE_ROTATIONS_FOR_TIME.UTC_MIDDAY
|
|
352
|
+
} else {
|
|
353
|
+
return DEFAULT_GLOBE_ROTATIONS_FOR_TIME.UTC_EVENING
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function getCenterForCountryCollection(
|
|
358
|
+
countryNames: string[]
|
|
359
|
+
): [number, number] {
|
|
360
|
+
const featureCollection = makeFeatureCollectionForCountries(countryNames)
|
|
361
|
+
return getCenterPoint(featureCollection)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function getCenterPoint(
|
|
365
|
+
geojson: GeoFeature | FeatureCollection
|
|
366
|
+
): [number, number] {
|
|
367
|
+
const centerPoint = center(geojson)
|
|
368
|
+
|
|
369
|
+
return [
|
|
370
|
+
centerPoint.geometry.coordinates[0],
|
|
371
|
+
R.clamp(centerPoint.geometry.coordinates[1], {
|
|
372
|
+
min: GLOBE_LATITUDE_MIN,
|
|
373
|
+
max: GLOBE_LATITUDE_MAX,
|
|
374
|
+
}),
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function getCoordsAndZoomForCountryCollection(countryNames: string[]): {
|
|
379
|
+
coords: [number, number]
|
|
380
|
+
zoom: number
|
|
381
|
+
} {
|
|
382
|
+
const centerPoint = getCenterForCountryCollection(countryNames)
|
|
383
|
+
const projection = geoOrthographic().rotate(negateCoords(centerPoint))
|
|
384
|
+
|
|
385
|
+
const bounds = excludeUndefined(
|
|
386
|
+
countryNames.map((countryName) => {
|
|
387
|
+
const feature = geoFeaturesById.get(countryName)
|
|
388
|
+
if (!feature) return
|
|
389
|
+
const corners = geoPath().projection(projection).bounds(feature.geo)
|
|
390
|
+
if (corners[0][0] === Number.POSITIVE_INFINITY) return
|
|
391
|
+
return Bounds.fromCorners(
|
|
392
|
+
new PointVector(...corners[0]),
|
|
393
|
+
new PointVector(...corners[1])
|
|
394
|
+
)
|
|
395
|
+
})
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
// merge bounds and calculate the zoom needed for the countries to be visible
|
|
399
|
+
const mergedBounds = Bounds.merge(bounds)
|
|
400
|
+
const zoom = calculateZoomToFitForBounds(mergedBounds)
|
|
401
|
+
|
|
402
|
+
return { coords: centerPoint, zoom }
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function findVisibleCountrySubset(countryNames: string[]): string[] {
|
|
406
|
+
// rotate the globe to the center point of all given countries,
|
|
407
|
+
// and find all countries that are then visible on the globe
|
|
408
|
+
const centerPoint = getCenterForCountryCollection(countryNames)
|
|
409
|
+
const projection = geoOrthographic().rotate(negateCoords(centerPoint))
|
|
410
|
+
const visibleCountries = countryNames.filter((countryName) => {
|
|
411
|
+
const feature = geoFeaturesById.get(countryName)
|
|
412
|
+
if (!feature) return false
|
|
413
|
+
|
|
414
|
+
// check if the centroid is visible
|
|
415
|
+
const isCentroidVisible = isPointPlacedOnVisibleHemisphere(
|
|
416
|
+
feature.geoCentroid,
|
|
417
|
+
centerPoint
|
|
418
|
+
)
|
|
419
|
+
if (!isCentroidVisible) return false
|
|
420
|
+
|
|
421
|
+
// if the centroid is visible, then also check if the bounds are
|
|
422
|
+
// visible (if they're infinite, then they're not)
|
|
423
|
+
const corners = geoPath().projection(projection).bounds(feature.geo)
|
|
424
|
+
if (corners[0][0] === Number.POSITIVE_INFINITY) return false
|
|
425
|
+
|
|
426
|
+
return true
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
// it's possible for no country to be visible if the countries are on opposite
|
|
430
|
+
// sides from the globe. in that case, we need to drop a subset of countries
|
|
431
|
+
if (visibleCountries.length === 0) {
|
|
432
|
+
// cluster countries into two groups based on their centroid longitude
|
|
433
|
+
const clusters = clusterCountriesByCentroidLongitude(countryNames)
|
|
434
|
+
|
|
435
|
+
// keep the bigger cluster
|
|
436
|
+
// (if both clusters have the same number of countries, keep any)
|
|
437
|
+
return clusters[0].length > clusters[1].length
|
|
438
|
+
? clusters[0]
|
|
439
|
+
: clusters[1]
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return visibleCountries
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function clusterCountriesByCentroidLongitude(
|
|
446
|
+
countryNames: string[]
|
|
447
|
+
): string[][] {
|
|
448
|
+
const nameToLon: Record<string, number> = {}
|
|
449
|
+
const lonToName: Record<number, string> = {}
|
|
450
|
+
|
|
451
|
+
// map country names to their centroid's longitude and vice versa
|
|
452
|
+
// (assumes that no two countries have the same longitude)
|
|
453
|
+
countryNames.forEach((countryName) => {
|
|
454
|
+
const feature = geoFeaturesById.get(countryName)
|
|
455
|
+
if (!feature) return
|
|
456
|
+
|
|
457
|
+
const lon = R.round(feature.geoCentroid[0], 5)
|
|
458
|
+
nameToLon[countryName] = lon
|
|
459
|
+
lonToName[lon] = countryName
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
// cluster longitudes into two groups
|
|
463
|
+
const clusters = ckmeans(
|
|
464
|
+
excludeUndefined(countryNames.map((name) => nameToLon[name])),
|
|
465
|
+
2
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
// map longitudes back to country names
|
|
469
|
+
return clusters.map((cluster) =>
|
|
470
|
+
cluster.map((centroidLon) => lonToName[centroidLon])
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function makeFeatureCollectionForCountries(
|
|
475
|
+
countryNames: string[]
|
|
476
|
+
): FeatureCollection {
|
|
477
|
+
const features = excludeUndefined(
|
|
478
|
+
countryNames.map((name) => geoFeaturesById.get(name))
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
type: "FeatureCollection",
|
|
483
|
+
features: features.map((feature) => feature.geo),
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function addLongitudeOffset(
|
|
488
|
+
coords: [number, number],
|
|
489
|
+
offset = LONGITUDE_OFFSET
|
|
490
|
+
): [number, number] {
|
|
491
|
+
return [coords[0] + offset, coords[1]]
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function negateCoords(coords: [number, number]): [number, number] {
|
|
495
|
+
return [-coords[0], -coords[1]]
|
|
496
|
+
}
|