@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,841 @@
|
|
|
1
|
+
// todo: Remove this file when we've migrated OWID data and OWID charts to next version
|
|
2
|
+
|
|
3
|
+
import * as _ from "lodash-es"
|
|
4
|
+
import {
|
|
5
|
+
ColumnTypeNames,
|
|
6
|
+
CoreColumnDef,
|
|
7
|
+
StandardOwidColumnDefs,
|
|
8
|
+
OwidTableSlugs,
|
|
9
|
+
OwidColumnDef,
|
|
10
|
+
OwidVariableDimensions,
|
|
11
|
+
OwidVariableDataMetadataDimensions,
|
|
12
|
+
ErrorValue,
|
|
13
|
+
OwidChartDimensionInterfaceWithMandatorySlug,
|
|
14
|
+
OwidChartDimensionInterface,
|
|
15
|
+
EntityName,
|
|
16
|
+
} from "../../types/index.js"
|
|
17
|
+
import {
|
|
18
|
+
OwidTable,
|
|
19
|
+
ErrorValueTypes,
|
|
20
|
+
makeKeyFn,
|
|
21
|
+
} from "../../core-table/index.js"
|
|
22
|
+
import {
|
|
23
|
+
diffDateISOStringInDays,
|
|
24
|
+
getYearFromISOStringAndDayOffset,
|
|
25
|
+
intersection,
|
|
26
|
+
makeAnnotationsSlug,
|
|
27
|
+
trimObject,
|
|
28
|
+
OwidEntityKey,
|
|
29
|
+
MultipleOwidVariableDataDimensionsMap,
|
|
30
|
+
OwidVariableWithSource,
|
|
31
|
+
OwidVariableMixedData,
|
|
32
|
+
OwidVariableWithSourceAndDimension,
|
|
33
|
+
ColumnSlug,
|
|
34
|
+
EPOCH_DATE,
|
|
35
|
+
OwidVariableType,
|
|
36
|
+
} from "../../utils/index.js"
|
|
37
|
+
import { isContinentsVariableId } from "./GrapherConstants"
|
|
38
|
+
import * as R from "remeda"
|
|
39
|
+
import { getDimensionColumnSlug } from "../chart/ChartDimension.js"
|
|
40
|
+
|
|
41
|
+
export const legacyToOwidTableAndDimensionsWithMandatorySlug = (
|
|
42
|
+
json: MultipleOwidVariableDataDimensionsMap,
|
|
43
|
+
dimensions: OwidChartDimensionInterface[],
|
|
44
|
+
selectedEntityColors:
|
|
45
|
+
| { [entityName: string]: string | undefined }
|
|
46
|
+
| undefined
|
|
47
|
+
): OwidTable => {
|
|
48
|
+
const dimensionsWithSlug = dimensions?.map((dimension) => ({
|
|
49
|
+
...dimension,
|
|
50
|
+
slug:
|
|
51
|
+
dimension.slug ??
|
|
52
|
+
getDimensionColumnSlug(dimension.variableId, dimension.targetYear),
|
|
53
|
+
}))
|
|
54
|
+
return legacyToOwidTableAndDimensions(
|
|
55
|
+
json,
|
|
56
|
+
dimensionsWithSlug,
|
|
57
|
+
selectedEntityColors
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const legacyToOwidTableAndDimensions = (
|
|
62
|
+
json: MultipleOwidVariableDataDimensionsMap,
|
|
63
|
+
dimensions: OwidChartDimensionInterfaceWithMandatorySlug[],
|
|
64
|
+
selectedEntityColors:
|
|
65
|
+
| { [entityName: string]: string | undefined }
|
|
66
|
+
| undefined
|
|
67
|
+
): OwidTable => {
|
|
68
|
+
// Entity meta map
|
|
69
|
+
|
|
70
|
+
const entityMeta = [...json.values()].flatMap(
|
|
71
|
+
(value) => value.metadata.dimensions.entities.values
|
|
72
|
+
)
|
|
73
|
+
const entityMetaById: OwidEntityKey = Object.fromEntries(
|
|
74
|
+
entityMeta.map((entity) => [entity.id.toString(), entity])
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
// Base column defs, shared by all variable tables
|
|
78
|
+
|
|
79
|
+
const baseColumnDefs: Map<ColumnSlug, CoreColumnDef> = new Map()
|
|
80
|
+
StandardOwidColumnDefs.forEach((def) => {
|
|
81
|
+
baseColumnDefs.set(def.slug, def)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// We need to create a column for each unique [variable, targetTime] pair. So there can be
|
|
85
|
+
// multiple columns for a single variable.
|
|
86
|
+
const dimensionColumns = _.uniqBy(dimensions, (dim) => dim.slug)
|
|
87
|
+
|
|
88
|
+
const variableTablesToJoinByYear: OwidTable[] = []
|
|
89
|
+
const variableTablesToJoinByDay: OwidTable[] = []
|
|
90
|
+
const variableTablesWithYearToJoinByEntityOnly: OwidTable[] = []
|
|
91
|
+
for (const dimension of dimensionColumns) {
|
|
92
|
+
const variable = json.get(dimension.variableId)
|
|
93
|
+
|
|
94
|
+
// TODO: this shouldn't happen but it does sometimes
|
|
95
|
+
// when adding dimensions in the chart editor
|
|
96
|
+
if (!variable) continue
|
|
97
|
+
|
|
98
|
+
// Copy the base columnDef
|
|
99
|
+
const columnDefs = new Map(baseColumnDefs)
|
|
100
|
+
|
|
101
|
+
// Time column
|
|
102
|
+
const timeColumnDef = timeColumnDefFromOwidVariable(variable.metadata)
|
|
103
|
+
columnDefs.set(timeColumnDef.slug, timeColumnDef)
|
|
104
|
+
|
|
105
|
+
// Value column
|
|
106
|
+
const valueColumnDef = columnDefFromOwidVariable(variable.metadata)
|
|
107
|
+
const valueColumnColor = dimension.display?.color
|
|
108
|
+
// Ensure the column slug is unique by copying it from the dimensions
|
|
109
|
+
// (there can be two columns of the same variable with different targetTimes)
|
|
110
|
+
if (dimension.slug) valueColumnDef.slug = dimension.slug
|
|
111
|
+
else throw new Error("Dimension slug was undefined")
|
|
112
|
+
// Because database columns can contain mixed types, we want to avoid
|
|
113
|
+
// parsing for Grapher data until we fix that.
|
|
114
|
+
valueColumnDef.skipParsing = true
|
|
115
|
+
if (valueColumnColor) {
|
|
116
|
+
valueColumnDef.color = valueColumnColor
|
|
117
|
+
}
|
|
118
|
+
if (dimension) {
|
|
119
|
+
valueColumnDef.display = {
|
|
120
|
+
...trimObject(valueColumnDef.display),
|
|
121
|
+
...trimObject(dimension.display),
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (dimension.targetYear !== undefined)
|
|
125
|
+
valueColumnDef.targetTime = dimension.targetYear
|
|
126
|
+
columnDefs.set(valueColumnDef.slug, valueColumnDef)
|
|
127
|
+
|
|
128
|
+
// Annotations column
|
|
129
|
+
const [annotationMap, annotationColumnDef] =
|
|
130
|
+
annotationMapAndDefFromOwidVariable(variable.metadata)
|
|
131
|
+
|
|
132
|
+
// Column values
|
|
133
|
+
|
|
134
|
+
const times = timeColumnValuesFromOwidVariable(
|
|
135
|
+
variable.metadata,
|
|
136
|
+
variable.data
|
|
137
|
+
)
|
|
138
|
+
const entityIds = variable.data.entities ?? []
|
|
139
|
+
const entityNames = entityIds.map(
|
|
140
|
+
// if entityMetaById[id] does not exist, then we don't have entity
|
|
141
|
+
// from variable metadata in MySQL. This can happen because we take
|
|
142
|
+
// data from S3 and metadata from MySQL. After we unify it, it should
|
|
143
|
+
// no longer be a problem
|
|
144
|
+
(id) => entityMetaById[id]?.name ?? id.toString()
|
|
145
|
+
)
|
|
146
|
+
// see comment above about entityMetaById[id]
|
|
147
|
+
const entityCodes = entityIds.map((id) => entityMetaById[id]?.code)
|
|
148
|
+
|
|
149
|
+
// If there is a conversionFactor, apply it.
|
|
150
|
+
let values = variable.data.values || []
|
|
151
|
+
const conversionFactor = valueColumnDef.display?.conversionFactor
|
|
152
|
+
if (conversionFactor !== undefined) {
|
|
153
|
+
values = values.map((value) =>
|
|
154
|
+
_.isNumber(value) ? value * conversionFactor : value
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
// If a non-int conversion factor is applied to an integer column,
|
|
158
|
+
// we end up with a numeric column.
|
|
159
|
+
if (
|
|
160
|
+
valueColumnDef.type === ColumnTypeNames.Integer &&
|
|
161
|
+
!_.isInteger(conversionFactor)
|
|
162
|
+
)
|
|
163
|
+
valueColumnDef.type = ColumnTypeNames.Numeric
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const columnStore: { [key: string]: any[] } = {
|
|
167
|
+
[OwidTableSlugs.entityId]: entityIds,
|
|
168
|
+
[OwidTableSlugs.entityCode]: entityCodes,
|
|
169
|
+
[OwidTableSlugs.entityName]: entityNames,
|
|
170
|
+
[timeColumnDef.slug]: times,
|
|
171
|
+
[valueColumnDef.slug]: values,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (annotationColumnDef) {
|
|
175
|
+
columnStore[annotationColumnDef.slug] = entityNames.map(
|
|
176
|
+
(entityName) => annotationMap!.get(entityName)
|
|
177
|
+
)
|
|
178
|
+
columnDefs.set(annotationColumnDef.slug, annotationColumnDef)
|
|
179
|
+
}
|
|
180
|
+
// Build the tables
|
|
181
|
+
|
|
182
|
+
let variableTable = new OwidTable(
|
|
183
|
+
columnStore,
|
|
184
|
+
Array.from(columnDefs.values())
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
// If there is a targetTime set on the dimension, we need to perform the join on the
|
|
188
|
+
// entities columns only, excluding any time columns.
|
|
189
|
+
// We do this by dropping the column. We interpolate before which adds an originalTime
|
|
190
|
+
// column which can be used to recover the time.
|
|
191
|
+
const targetTime = dimension?.targetYear
|
|
192
|
+
if (_.isNumber(targetTime)) {
|
|
193
|
+
variableTable = variableTable
|
|
194
|
+
// interpolateColumnWithTolerance() won't handle injecting times beyond the current
|
|
195
|
+
// allTimes. So if targetYear is 2018, and we have data up to 2017, the
|
|
196
|
+
// interpolation won't add the 2018 rows (unless we apply the interpolation after
|
|
197
|
+
// the big join).
|
|
198
|
+
// This is why we use filterByTargetTimes() which handles that case.
|
|
199
|
+
.filterByTargetTimes(
|
|
200
|
+
[targetTime],
|
|
201
|
+
valueColumnDef.display?.tolerance
|
|
202
|
+
)
|
|
203
|
+
// Interpolate with 0 to add originalTimes column
|
|
204
|
+
.interpolateColumnWithTolerance(valueColumnDef.slug, {
|
|
205
|
+
toleranceOverride: 0,
|
|
206
|
+
})
|
|
207
|
+
.dropColumns([timeColumnDef.slug])
|
|
208
|
+
// We keep variables that have a targetTime set in a special bucket and will join them
|
|
209
|
+
// on entity only (disregarding the year since we already filtered all other years out for
|
|
210
|
+
// those variables)
|
|
211
|
+
variableTablesWithYearToJoinByEntityOnly.push(variableTable)
|
|
212
|
+
} else if (variable.metadata.display?.yearIsDay)
|
|
213
|
+
variableTablesToJoinByDay.push(variableTable)
|
|
214
|
+
else variableTablesToJoinByYear.push(variableTable)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// If we only had years then all we would need to do is a single fullJoinTables call and
|
|
218
|
+
// we'd be done with it. But since we also have days this is a bit trickier. The
|
|
219
|
+
// basic approach is to say that if we have day variables then we should join those internally
|
|
220
|
+
// on day+entity first and day+entity becomes the primary index for our final table.
|
|
221
|
+
// We then join this merged days table with all the year based variables.
|
|
222
|
+
// The multi table join iterates over all the unique index values (day+entity).
|
|
223
|
+
// To join this with years we derive the year from the day. The fullJoinTables then uses a number
|
|
224
|
+
// of fallbacks when trying to find matching rows in the various tables: we first try to join by day+entity,
|
|
225
|
+
// then by year+entity and finally entity only.
|
|
226
|
+
// This last join by entity only is important so that variables that don't have values for years
|
|
227
|
+
// that come up in the day+entity index we still retain the values. E.g. our continents table
|
|
228
|
+
// has values for all countries but only for the year 2015. If we join that with covid era days
|
|
229
|
+
// we still want to retain the continents so we have the fallback to entity only (this was also
|
|
230
|
+
// the only behaviour prior to July 2022). Remember that tolerance will only be applied much later -
|
|
231
|
+
// here we are only concerned with merging multiple variables into an inputTable that retains information.
|
|
232
|
+
// Another approach would be to convert years into days when we have days - then we could simplify the fallback
|
|
233
|
+
// join key logic described above.
|
|
234
|
+
// Another caveat is that by switching to day+entity as the primary index that we use to join we can drop some entities.
|
|
235
|
+
// This happens e.g. with Antarctica if the continents table is used. Continents contains an entry for the entity Antarctica for 2015
|
|
236
|
+
// that maps it (and 3 other territories) to the Antarctica continent. If the days variables don't have values for any of the
|
|
237
|
+
// Antarctica entities then they will not be enumerated for the final join table and thus they will be dropped from the final table.
|
|
238
|
+
// This is maybe counter to what you would expect from a full join but is simply an artifact of making days+entities the primary
|
|
239
|
+
// index and not backporting years to days. We might want to revisit this in the future and/or also apply tolerance already
|
|
240
|
+
// at this level here.
|
|
241
|
+
|
|
242
|
+
// Merge all day based variables together (returns an empty table if there are none)
|
|
243
|
+
const variablesJoinedByDay = fullJoinTables(variableTablesToJoinByDay, [
|
|
244
|
+
OwidTableSlugs.day,
|
|
245
|
+
OwidTableSlugs.entityId,
|
|
246
|
+
])
|
|
247
|
+
|
|
248
|
+
let joinedVariablesTable: OwidTable
|
|
249
|
+
// If we have both day and year based variables we need to do some special logic as described above
|
|
250
|
+
if (
|
|
251
|
+
variableTablesToJoinByYear.length > 0 &&
|
|
252
|
+
variableTablesToJoinByDay.length > 0
|
|
253
|
+
) {
|
|
254
|
+
// Derive the year from the day column and add it to the joined days table
|
|
255
|
+
const daysColumn = variablesJoinedByDay.getColumns([
|
|
256
|
+
OwidTableSlugs.day,
|
|
257
|
+
])[0]
|
|
258
|
+
const getYearFromISOStringMemoized = _.memoize((dayValue: number) =>
|
|
259
|
+
getYearFromISOStringAndDayOffset(EPOCH_DATE, dayValue)
|
|
260
|
+
)
|
|
261
|
+
const yearsForDaysValues = daysColumn.values.map((dayValue) =>
|
|
262
|
+
getYearFromISOStringMemoized(dayValue as number)
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
const newYearColumn = {
|
|
266
|
+
...daysColumn,
|
|
267
|
+
slug: OwidTableSlugs.year,
|
|
268
|
+
name: OwidTableSlugs.year,
|
|
269
|
+
values: yearsForDaysValues,
|
|
270
|
+
} as OwidColumnDef
|
|
271
|
+
const variablesJoinedByDayWithYearFilled =
|
|
272
|
+
variablesJoinedByDay.appendColumns([newYearColumn])
|
|
273
|
+
|
|
274
|
+
// Now join the already merged days table with all the years. It is important
|
|
275
|
+
// to not join the years together into one table already before so that each
|
|
276
|
+
// table lookup for fallback values is looked at individually.
|
|
277
|
+
// See the longer comment above for the idea behind the fallback cascade here of
|
|
278
|
+
// trying to merge first by day+entity, then year+entity and finally entity only
|
|
279
|
+
joinedVariablesTable = fullJoinTables(
|
|
280
|
+
[variablesJoinedByDayWithYearFilled, ...variableTablesToJoinByYear],
|
|
281
|
+
[OwidTableSlugs.day, OwidTableSlugs.entityId],
|
|
282
|
+
[
|
|
283
|
+
[OwidTableSlugs.year, OwidTableSlugs.entityId],
|
|
284
|
+
[OwidTableSlugs.entityId],
|
|
285
|
+
]
|
|
286
|
+
)
|
|
287
|
+
// If we have scatter/marimekko variables that had a targetTime set
|
|
288
|
+
// then these are now joined in by matching entity only
|
|
289
|
+
if (variableTablesWithYearToJoinByEntityOnly.length > 0)
|
|
290
|
+
joinedVariablesTable = fullJoinTables(
|
|
291
|
+
[
|
|
292
|
+
joinedVariablesTable,
|
|
293
|
+
...variableTablesWithYearToJoinByEntityOnly,
|
|
294
|
+
],
|
|
295
|
+
[OwidTableSlugs.day, OwidTableSlugs.entityId],
|
|
296
|
+
[[OwidTableSlugs.entityId]]
|
|
297
|
+
)
|
|
298
|
+
} else if (variableTablesToJoinByYear.length > 0) {
|
|
299
|
+
// If we only have year based variables then life is easy and we just join
|
|
300
|
+
// those together without any special cases
|
|
301
|
+
joinedVariablesTable = fullJoinTables(variableTablesToJoinByYear, [
|
|
302
|
+
OwidTableSlugs.year,
|
|
303
|
+
OwidTableSlugs.entityId,
|
|
304
|
+
])
|
|
305
|
+
|
|
306
|
+
// If we have scatter/marimekko variables that had a targetTime set
|
|
307
|
+
// then these are now joined in by matching entity only
|
|
308
|
+
if (variableTablesWithYearToJoinByEntityOnly.length > 0)
|
|
309
|
+
joinedVariablesTable = fullJoinTables(
|
|
310
|
+
[
|
|
311
|
+
joinedVariablesTable,
|
|
312
|
+
...variableTablesWithYearToJoinByEntityOnly,
|
|
313
|
+
],
|
|
314
|
+
[OwidTableSlugs.year, OwidTableSlugs.entityId],
|
|
315
|
+
[[OwidTableSlugs.entityId]]
|
|
316
|
+
)
|
|
317
|
+
} else {
|
|
318
|
+
// If we only have day variables life is also easy but this case is rare
|
|
319
|
+
joinedVariablesTable = variablesJoinedByDay
|
|
320
|
+
|
|
321
|
+
// If we have scatter/marimekko variables that had a targetTime set
|
|
322
|
+
// then these are now joined in by matching entity only
|
|
323
|
+
if (variableTablesWithYearToJoinByEntityOnly.length > 0)
|
|
324
|
+
joinedVariablesTable = fullJoinTables(
|
|
325
|
+
[
|
|
326
|
+
joinedVariablesTable,
|
|
327
|
+
...variableTablesWithYearToJoinByEntityOnly,
|
|
328
|
+
],
|
|
329
|
+
[OwidTableSlugs.day, OwidTableSlugs.entityId],
|
|
330
|
+
[[OwidTableSlugs.entityId]]
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Inject a common "time" column that is used as the main time column for the table
|
|
335
|
+
// e.g. for the timeline.
|
|
336
|
+
for (const dayOrYearSlug of [OwidTableSlugs.day, OwidTableSlugs.year]) {
|
|
337
|
+
if (joinedVariablesTable.columnSlugs.includes(dayOrYearSlug)) {
|
|
338
|
+
joinedVariablesTable = joinedVariablesTable.duplicateColumn(
|
|
339
|
+
dayOrYearSlug,
|
|
340
|
+
{
|
|
341
|
+
slug: OwidTableSlugs.time,
|
|
342
|
+
name: OwidTableSlugs.time,
|
|
343
|
+
}
|
|
344
|
+
)
|
|
345
|
+
// Do not inject multiple columns, terminate after one is successful
|
|
346
|
+
break
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Append the entity color column if we have selected entity colors
|
|
351
|
+
if (!_.isEmpty(selectedEntityColors)) {
|
|
352
|
+
const entityColorColumnSlug = OwidTableSlugs.entityColor
|
|
353
|
+
|
|
354
|
+
const valueFn = (
|
|
355
|
+
entityName: EntityName | undefined
|
|
356
|
+
): string | ErrorValue => {
|
|
357
|
+
if (!entityName) return ErrorValueTypes.UndefinedButShouldBeString
|
|
358
|
+
return entityName && selectedEntityColors
|
|
359
|
+
? (selectedEntityColors[entityName] ??
|
|
360
|
+
ErrorValueTypes.UndefinedButShouldBeString)
|
|
361
|
+
: ErrorValueTypes.UndefinedButShouldBeString
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const values =
|
|
365
|
+
joinedVariablesTable.entityNameColumn.valuesIncludingErrorValues.map(
|
|
366
|
+
(entityName) => valueFn(entityName as EntityName)
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
joinedVariablesTable = joinedVariablesTable.appendColumns([
|
|
370
|
+
{
|
|
371
|
+
slug: entityColorColumnSlug,
|
|
372
|
+
name: entityColorColumnSlug,
|
|
373
|
+
type: ColumnTypeNames.Color,
|
|
374
|
+
values: values,
|
|
375
|
+
},
|
|
376
|
+
])
|
|
377
|
+
}
|
|
378
|
+
return joinedVariablesTable
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const fullJoinTables = (
|
|
382
|
+
tables: OwidTable[],
|
|
383
|
+
indexColumnNames: OwidTableSlugs[],
|
|
384
|
+
mergeFallbackLookupColumns?: OwidTableSlugs[][]
|
|
385
|
+
): OwidTable => {
|
|
386
|
+
// This function merges a number of OwidTables together using a given list of columns
|
|
387
|
+
// to be used as the merge key. The merge key columns are used to construct a full set
|
|
388
|
+
// of index values from the various tables - all tables are enumerated, we create
|
|
389
|
+
// a merged string value from the index column values for each row and then we create
|
|
390
|
+
// a set from all these string values.
|
|
391
|
+
// Note that not every table has to have values for all columns - not even all the index
|
|
392
|
+
// columns have to exist on all tables (the index columns have to exist on the first table though)!
|
|
393
|
+
// The reason for this and how this can possibly work
|
|
394
|
+
// is that we also have a list of fallback merge columns. This is required so we can handle
|
|
395
|
+
// not just the easy case where we have year+entity for every table to be merged (which in our
|
|
396
|
+
// data model is by far the most common default), but also handle cases where we merge year and
|
|
397
|
+
// day based variables together. For this latter case we need to still construct the set of index
|
|
398
|
+
// values for the final table, but then when we try to look up values in the various tables to
|
|
399
|
+
// merge together we will not find values by day+entity for the year based tables. So for this
|
|
400
|
+
// case we get a series of fallback column tuples that we try in turn if the main index lookup
|
|
401
|
+
// fails. These fallback tuples are year+entity first and then entity only. The reasoning here is
|
|
402
|
+
// that e.g. when merging population to a day variable we want to merge the values from the year
|
|
403
|
+
// matching the day from the population variable (year+entity lookup) but for variables that don't
|
|
404
|
+
// have overlapping years (e.g. continents that only has 2015 as the single year) we want to fall back
|
|
405
|
+
// to merging by entity alone as a last resort
|
|
406
|
+
if (tables.length === 0) return new OwidTable()
|
|
407
|
+
else if (tables.length === 1) return tables[0]
|
|
408
|
+
|
|
409
|
+
// Get all the index values per table and then figure out the full set of all stringified index values
|
|
410
|
+
const indexValuesPerTable: Map<string, number[]>[] = tables.map((table) =>
|
|
411
|
+
// When we get a mergeFallbackLookupColumn then it can happen that a table does not have all the
|
|
412
|
+
// columns of the main index. In this case, just return an empty map because that will lead all
|
|
413
|
+
// lookups by main index to fail and we'll try the fallback index
|
|
414
|
+
mergeFallbackLookupColumns &&
|
|
415
|
+
_.difference(indexColumnNames, table.columnSlugs).length > 0
|
|
416
|
+
? new Map()
|
|
417
|
+
: table.rowIndex(indexColumnNames)
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
// Construct all the fallback index lookup values for all tables. mergeFallbackLookupColumns is an
|
|
421
|
+
// array of array that is supposed to be treated as a sequence of tuples of column names
|
|
422
|
+
const mergeFallbackLookupValuesPerTable = mergeFallbackLookupColumns
|
|
423
|
+
? mergeFallbackLookupColumns.map((fallbackColumns) =>
|
|
424
|
+
tables.map((table) => table.rowIndex(fallbackColumns))
|
|
425
|
+
)
|
|
426
|
+
: undefined
|
|
427
|
+
|
|
428
|
+
// Figure out which column names are shared. Shared columns (the index columns but also in our
|
|
429
|
+
// data model stuff like entityCode and entityName that we usually have in addition to entityId)
|
|
430
|
+
// will end up only once in the final table. This is a bit of a footgun for arbitrary data models
|
|
431
|
+
// as we don't make sure that the values are equal for the same index values (we only assume that
|
|
432
|
+
// this is true) - but this is how we have handled it in the past and it works with the setup we
|
|
433
|
+
// have.
|
|
434
|
+
const sharedColumnNames = intersection(
|
|
435
|
+
...tables.map((table) => table.columnSlugs)
|
|
436
|
+
)
|
|
437
|
+
const allIndexValuesArray = indexValuesPerTable.flatMap((index) => [
|
|
438
|
+
...index.keys(),
|
|
439
|
+
])
|
|
440
|
+
const allIndexValues = new Set(allIndexValuesArray)
|
|
441
|
+
|
|
442
|
+
// Now identify for each table which columns should be copied (i.e. all non-index columns).
|
|
443
|
+
const columnsToAddPerTable = tables.map((table) =>
|
|
444
|
+
_.difference(table.columnSlugs, sharedColumnNames)
|
|
445
|
+
)
|
|
446
|
+
// Prepare a special entry for the Table + column names tuple that we will zip and
|
|
447
|
+
// map in the next step. This special entry is the first table and contains only the
|
|
448
|
+
// unique columns (index + other tables that are shared among all tables).
|
|
449
|
+
// We will preferentially get the unique values from the first table but because
|
|
450
|
+
// the first table is not guaranteed to contain all index values we'll later on
|
|
451
|
+
// try other tables for these shared columns if the given row index does
|
|
452
|
+
// not exist in the first table
|
|
453
|
+
const firstTableDuplicateForIndices: [
|
|
454
|
+
OwidTable | undefined,
|
|
455
|
+
string[] | undefined,
|
|
456
|
+
] = [tables[0], sharedColumnNames]
|
|
457
|
+
const defsToAddPerTable = [firstTableDuplicateForIndices]
|
|
458
|
+
.concat(R.zip(tables, columnsToAddPerTable))
|
|
459
|
+
.map(
|
|
460
|
+
(tableAndColumns) =>
|
|
461
|
+
tableAndColumns[0]!
|
|
462
|
+
.getColumns(tableAndColumns[1]!)
|
|
463
|
+
.map((col) => {
|
|
464
|
+
const def = { ...col.def }
|
|
465
|
+
def.values = []
|
|
466
|
+
return def
|
|
467
|
+
}) as OwidColumnDef[]
|
|
468
|
+
)
|
|
469
|
+
// Now loop over all unique index values and for each assemble as full a row as we can manage by looking
|
|
470
|
+
// up the values in the different source tables
|
|
471
|
+
for (const index of allIndexValues.values()) {
|
|
472
|
+
// First we handle the unique/index columns (defsToAddPerTable[0])
|
|
473
|
+
for (const def of defsToAddPerTable[0]) {
|
|
474
|
+
// The index columns are special - the first table might not have a value for each of the index columns
|
|
475
|
+
// at the current index (e.g. a year that does not exist in the first table). We therefore have to keep
|
|
476
|
+
// looking in the other tables until we find a table that has the values. Because the index values were
|
|
477
|
+
// generated from the combined set of all index values we are guaranteed to find a value eventually before
|
|
478
|
+
// we exceed the length of the tables array
|
|
479
|
+
let tableIndex = 0
|
|
480
|
+
let indexHits = indexValuesPerTable[tableIndex].get(index)
|
|
481
|
+
while (indexHits === undefined) {
|
|
482
|
+
tableIndex++
|
|
483
|
+
indexHits = indexValuesPerTable[tableIndex].get(index)
|
|
484
|
+
}
|
|
485
|
+
def.values?.push(
|
|
486
|
+
tables[tableIndex].columnStore[def.slug][indexHits[0]]
|
|
487
|
+
)
|
|
488
|
+
}
|
|
489
|
+
// Now figure out the fallback merge lookup index value from the first table.
|
|
490
|
+
// This is the fallback index we use when looking up values and we don't find a value in the normal index.
|
|
491
|
+
// This is the case when we join year and day variables and then use day+entityid as the key but the year
|
|
492
|
+
// variables don't have those - so for the year variables we then check if there is a match using year+entityId
|
|
493
|
+
// where the year to use comes from the first table that by convention HAS TO contain a year column with the
|
|
494
|
+
// value to merge years on.
|
|
495
|
+
const indexHits = indexValuesPerTable[0].get(index)
|
|
496
|
+
const fallbackMergeIndices =
|
|
497
|
+
mergeFallbackLookupColumns && indexHits
|
|
498
|
+
? mergeFallbackLookupColumns.map((columnSet) =>
|
|
499
|
+
makeKeyFn(tables[0].columnStore, columnSet)(indexHits![0])
|
|
500
|
+
)
|
|
501
|
+
: undefined
|
|
502
|
+
// now add all the nonindex value columns. We now loop over all tables and for each non-shared column
|
|
503
|
+
// we look up the value for the current row. We find the value for the current row by first trying to
|
|
504
|
+
// look up a match based on the shared index values, indexValuesPerTable[i]. If we don't have a hit
|
|
505
|
+
// for this row in this table then we try the fallbackMergeIndices in turn to see if we can merge
|
|
506
|
+
// based on these fallback indexes. If no option leads to a match we store ErrorValueTypes.NoMatchingValueAfterJoin
|
|
507
|
+
// in the cell.
|
|
508
|
+
|
|
509
|
+
// note that defsToAddPerTable has one more element than tables (the one duplicate of the
|
|
510
|
+
// first table that we added in the beginning that we use as the primary source for the shared columns)
|
|
511
|
+
for (let i = 0; i < tables.length; i++) {
|
|
512
|
+
let indexHits = indexValuesPerTable[i].get(index)
|
|
513
|
+
|
|
514
|
+
if (indexHits !== undefined && indexHits.length > 1)
|
|
515
|
+
// This case should be rare but it can come up. The old algorithm ran into this often because it
|
|
516
|
+
// joined a lot more stuff on entity only and then when you look up by entity into a table that has
|
|
517
|
+
// several years you end up with multiple matches. The old algorithm used to just pick one value.
|
|
518
|
+
// We still do this ultimately but because we try to match even day and year variables by year+entity
|
|
519
|
+
// first we should usually be able to find a unique match. The error output is here so that when
|
|
520
|
+
// something is weird in an edge case then this shows up as a debugging hint in the console.
|
|
521
|
+
console.error(
|
|
522
|
+
`Found more than one matching row in table ${tables[i].tableSlug}`
|
|
523
|
+
)
|
|
524
|
+
for (const def of defsToAddPerTable[i + 1]) {
|
|
525
|
+
// If the main index led to a hit then we just copy the value into the new row from the source table
|
|
526
|
+
if (indexHits !== undefined)
|
|
527
|
+
def.values?.push(
|
|
528
|
+
tables[i].columnStore[def.slug][indexHits[0]]
|
|
529
|
+
)
|
|
530
|
+
else {
|
|
531
|
+
// If the main index did not lead to a hit then we try the fallback indices in turn
|
|
532
|
+
indexHits = undefined
|
|
533
|
+
for (
|
|
534
|
+
let fallbackIndex = 0;
|
|
535
|
+
fallbackMergeIndices &&
|
|
536
|
+
mergeFallbackLookupValuesPerTable &&
|
|
537
|
+
indexHits === undefined &&
|
|
538
|
+
fallbackIndex < fallbackMergeIndices!.length;
|
|
539
|
+
fallbackIndex++
|
|
540
|
+
) {
|
|
541
|
+
indexHits = mergeFallbackLookupValuesPerTable[
|
|
542
|
+
fallbackIndex
|
|
543
|
+
][i].get(fallbackMergeIndices[fallbackIndex])
|
|
544
|
+
}
|
|
545
|
+
if (indexHits !== undefined)
|
|
546
|
+
// If any of the fallbacks led to a hit then we use this hit
|
|
547
|
+
// Note that we choose the last of the indexHits entries to look up the row in the columnstore here.
|
|
548
|
+
// In the usual case the fallback index should still have enough information to match just one row (e.g.
|
|
549
|
+
// year+entity). But for cases when we join day and year variables we have as the ultimate fallback a match
|
|
550
|
+
// by entity only and this can then of course result in many indexHits when looking up an entity like Germany
|
|
551
|
+
// in e.g. the population variable. If this join here were properly time and tolerance aware then we could
|
|
552
|
+
// avoid matching on entity alone as the ultimate fallback but for now this function is not that clever.
|
|
553
|
+
// For the cases when we do get multiple hits for an index we could thus choose any data point. Pre July 2022
|
|
554
|
+
// the old code always chose the first datapoint, which is often the first year that the variable has data for
|
|
555
|
+
// for the given entity. This is not great, e.g. when using a covid date based variable and joining it to
|
|
556
|
+
// population or similar. What we do here now is to use indexHits.length - 1 as the index, i.e. the last
|
|
557
|
+
// row that this variable has data for for this entity. This could in theory be pretty wrong as well but in
|
|
558
|
+
// practice it often means a very close match.
|
|
559
|
+
// The proper solution as mentioned above would be to never fall back to entity matches only and move
|
|
560
|
+
// the tolerance matching into this function as well instead.
|
|
561
|
+
def.values?.push(
|
|
562
|
+
tables[i].columnStore[def.slug][
|
|
563
|
+
indexHits[indexHits.length - 1]
|
|
564
|
+
]
|
|
565
|
+
)
|
|
566
|
+
// If none of the fallback values worked either we write ErrorValueTypes.NoMatchingValueAfterJoin into the cell
|
|
567
|
+
else
|
|
568
|
+
def.values?.push(
|
|
569
|
+
ErrorValueTypes.NoMatchingValueAfterJoin as any
|
|
570
|
+
)
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return new OwidTable(
|
|
576
|
+
[],
|
|
577
|
+
defsToAddPerTable.flatMap((defs) => defs)
|
|
578
|
+
)
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const variableTypeToColumnType = (type: OwidVariableType): ColumnTypeNames => {
|
|
582
|
+
switch (type) {
|
|
583
|
+
case "ordinal":
|
|
584
|
+
return ColumnTypeNames.Ordinal
|
|
585
|
+
case "string":
|
|
586
|
+
return ColumnTypeNames.String
|
|
587
|
+
case "int":
|
|
588
|
+
return ColumnTypeNames.Integer
|
|
589
|
+
case "float":
|
|
590
|
+
return ColumnTypeNames.Numeric
|
|
591
|
+
case "mixed":
|
|
592
|
+
default:
|
|
593
|
+
return ColumnTypeNames.NumberOrString
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const getSortFromDimensions = (
|
|
598
|
+
dimensions: OwidVariableDimensions
|
|
599
|
+
): string[] | undefined => {
|
|
600
|
+
const values = dimensions.values?.values
|
|
601
|
+
if (!values) return
|
|
602
|
+
|
|
603
|
+
const sort = values
|
|
604
|
+
.map((value) => value.name)
|
|
605
|
+
.filter((name) => name !== undefined)
|
|
606
|
+
|
|
607
|
+
if (sort.length === 0) return
|
|
608
|
+
|
|
609
|
+
return sort
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const columnDefFromOwidVariable = (
|
|
613
|
+
variable: OwidVariableWithSourceAndDimension
|
|
614
|
+
): OwidColumnDef => {
|
|
615
|
+
const slug = variable.id.toString() // For now, the variableId will be the column slug
|
|
616
|
+
const {
|
|
617
|
+
unit,
|
|
618
|
+
shortUnit,
|
|
619
|
+
description,
|
|
620
|
+
coverage,
|
|
621
|
+
datasetId,
|
|
622
|
+
datasetName,
|
|
623
|
+
descriptionShort,
|
|
624
|
+
descriptionProcessing,
|
|
625
|
+
descriptionKey,
|
|
626
|
+
descriptionFromProducer,
|
|
627
|
+
source,
|
|
628
|
+
origins,
|
|
629
|
+
display,
|
|
630
|
+
timespan,
|
|
631
|
+
nonRedistributable,
|
|
632
|
+
presentation,
|
|
633
|
+
catalogPath,
|
|
634
|
+
updatePeriodDays,
|
|
635
|
+
shortName,
|
|
636
|
+
} = variable
|
|
637
|
+
|
|
638
|
+
const isContinent = isContinentsVariableId(variable.id)
|
|
639
|
+
const name = variable.name
|
|
640
|
+
|
|
641
|
+
// The column's type
|
|
642
|
+
const parsedType = variable.type
|
|
643
|
+
? variableTypeToColumnType(variable.type)
|
|
644
|
+
: ColumnTypeNames.NumberOrString
|
|
645
|
+
|
|
646
|
+
// Override the column type for the special Continents variable
|
|
647
|
+
const type = isContinent ? ColumnTypeNames.Continent : parsedType
|
|
648
|
+
|
|
649
|
+
// Extract the sort order for ordinal variables from their dimension metadata.
|
|
650
|
+
// This preserves the author-specified ordering of categorical values
|
|
651
|
+
// (e.g., "Low", "Medium", "High").
|
|
652
|
+
const sort =
|
|
653
|
+
parsedType === ColumnTypeNames.Ordinal
|
|
654
|
+
? getSortFromDimensions(variable.dimensions)
|
|
655
|
+
: undefined
|
|
656
|
+
|
|
657
|
+
return {
|
|
658
|
+
name,
|
|
659
|
+
slug,
|
|
660
|
+
isDailyMeasurement: display?.yearIsDay,
|
|
661
|
+
unit,
|
|
662
|
+
shortUnit,
|
|
663
|
+
description,
|
|
664
|
+
descriptionShort,
|
|
665
|
+
descriptionProcessing,
|
|
666
|
+
descriptionKey,
|
|
667
|
+
descriptionFromProducer,
|
|
668
|
+
coverage,
|
|
669
|
+
datasetId,
|
|
670
|
+
datasetName,
|
|
671
|
+
display,
|
|
672
|
+
color: display?.color,
|
|
673
|
+
nonRedistributable,
|
|
674
|
+
sourceLink: source?.link,
|
|
675
|
+
sourceName: source?.name,
|
|
676
|
+
dataPublishedBy: source?.dataPublishedBy,
|
|
677
|
+
dataPublisherSource: source?.dataPublisherSource,
|
|
678
|
+
retrievedDate: source?.retrievedDate,
|
|
679
|
+
additionalInfo: source?.additionalInfo,
|
|
680
|
+
timespan,
|
|
681
|
+
origins,
|
|
682
|
+
presentation,
|
|
683
|
+
catalogPath,
|
|
684
|
+
updatePeriodDays,
|
|
685
|
+
owidVariableId: variable.id,
|
|
686
|
+
owidProcessingLevel: variable.processingLevel,
|
|
687
|
+
owidSchemaVersion: variable.schemaVersion,
|
|
688
|
+
type,
|
|
689
|
+
sort,
|
|
690
|
+
shortName,
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const timeColumnDefFromOwidVariable = (
|
|
695
|
+
variableMetadata: OwidVariableWithSource
|
|
696
|
+
): OwidColumnDef => {
|
|
697
|
+
return variableMetadata.display?.yearIsDay
|
|
698
|
+
? {
|
|
699
|
+
slug: OwidTableSlugs.day,
|
|
700
|
+
type: ColumnTypeNames.Day,
|
|
701
|
+
name: "Day",
|
|
702
|
+
}
|
|
703
|
+
: {
|
|
704
|
+
slug: OwidTableSlugs.year,
|
|
705
|
+
type: ColumnTypeNames.Year,
|
|
706
|
+
name: "Year",
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const timeColumnValuesFromOwidVariable = (
|
|
711
|
+
variableMetadata: OwidVariableWithSource,
|
|
712
|
+
variableData: OwidVariableMixedData
|
|
713
|
+
): number[] => {
|
|
714
|
+
const { display } = variableMetadata
|
|
715
|
+
const { years } = variableData
|
|
716
|
+
const yearsNeedTransform =
|
|
717
|
+
display &&
|
|
718
|
+
display.yearIsDay &&
|
|
719
|
+
display.zeroDay !== undefined &&
|
|
720
|
+
display.zeroDay !== EPOCH_DATE
|
|
721
|
+
const yearsRaw = years || []
|
|
722
|
+
return yearsNeedTransform
|
|
723
|
+
? convertLegacyYears(yearsRaw, display!.zeroDay!)
|
|
724
|
+
: yearsRaw
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const convertLegacyYears = (years: number[], zeroDay: string): number[] => {
|
|
728
|
+
// Only shift years if the variable zeroDay is different from EPOCH_DATE
|
|
729
|
+
// When the dataset uses days (`yearIsDay == true`), the days are expressed as integer
|
|
730
|
+
// days since the specified `zeroDay`, which can be different for different variables.
|
|
731
|
+
// In order to correctly join variables with different `zeroDay`s in a single chart, we
|
|
732
|
+
// normalize all days to be in reference to a single epoch date.
|
|
733
|
+
const diff = diffDateISOStringInDays(zeroDay, EPOCH_DATE)
|
|
734
|
+
return years.map((y) => y + diff)
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const annotationMapAndDefFromOwidVariable = (
|
|
738
|
+
variable: OwidVariableWithSourceAndDimension
|
|
739
|
+
): [Map<string, string>, OwidColumnDef] | [] => {
|
|
740
|
+
if (variable.display?.entityAnnotationsMap) {
|
|
741
|
+
const slug = makeAnnotationsSlug(variable.id.toString())
|
|
742
|
+
const annotationMap = annotationsToMap(
|
|
743
|
+
variable.display.entityAnnotationsMap
|
|
744
|
+
)
|
|
745
|
+
const columnDef = {
|
|
746
|
+
slug,
|
|
747
|
+
type: ColumnTypeNames.SeriesAnnotation,
|
|
748
|
+
name: slug,
|
|
749
|
+
display: {
|
|
750
|
+
includeInTable: false,
|
|
751
|
+
},
|
|
752
|
+
}
|
|
753
|
+
return [annotationMap, columnDef]
|
|
754
|
+
}
|
|
755
|
+
return []
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const annotationsToMap = (annotations: string): Map<string, string> => {
|
|
759
|
+
// Todo: let's delete this and switch to traditional columns
|
|
760
|
+
const entityAnnotationsMap = new Map<string, string>()
|
|
761
|
+
const delimiter = ":"
|
|
762
|
+
annotations.split("\n").forEach((line) => {
|
|
763
|
+
const [key, ...words] = line.split(delimiter)
|
|
764
|
+
entityAnnotationsMap.set(key.trim(), words.join(delimiter).trim())
|
|
765
|
+
})
|
|
766
|
+
return entityAnnotationsMap
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Loads a single variable into an OwidTable.
|
|
771
|
+
*/
|
|
772
|
+
export function buildVariableTable(
|
|
773
|
+
variable: OwidVariableDataMetadataDimensions
|
|
774
|
+
): OwidTable {
|
|
775
|
+
const entityMeta = variable.metadata.dimensions.entities.values
|
|
776
|
+
const entityMetaById: OwidEntityKey = Object.fromEntries(
|
|
777
|
+
entityMeta.map((entity) => [entity.id.toString(), entity])
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
// Base column defs, present in all OwidTables
|
|
781
|
+
const baseColumnDefs: Map<ColumnSlug, CoreColumnDef> = new Map(
|
|
782
|
+
StandardOwidColumnDefs.map((def) => [def.slug, def])
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
const columnDefs = new Map(baseColumnDefs)
|
|
786
|
+
|
|
787
|
+
// Time column
|
|
788
|
+
const timeColumnDef = timeColumnDefFromOwidVariable(variable.metadata)
|
|
789
|
+
columnDefs.set(timeColumnDef.slug, timeColumnDef)
|
|
790
|
+
|
|
791
|
+
// Value column
|
|
792
|
+
const valueColumnDef = columnDefFromOwidVariable(variable.metadata)
|
|
793
|
+
// Because database columns can contain mixed types, we want to avoid
|
|
794
|
+
// parsing for Grapher data until we fix that.
|
|
795
|
+
valueColumnDef.skipParsing = true
|
|
796
|
+
columnDefs.set(valueColumnDef.slug, valueColumnDef)
|
|
797
|
+
|
|
798
|
+
// Column values
|
|
799
|
+
|
|
800
|
+
const times = timeColumnValuesFromOwidVariable(
|
|
801
|
+
variable.metadata,
|
|
802
|
+
variable.data
|
|
803
|
+
)
|
|
804
|
+
const entityIds = variable.data.entities ?? []
|
|
805
|
+
const entityNames = entityIds.map(
|
|
806
|
+
// if entityMetaById[id] does not exist, then we don't have entity
|
|
807
|
+
// from variable metadata in MySQL. This can happen because we take
|
|
808
|
+
// data from S3 and metadata from MySQL. After we unify it, it should
|
|
809
|
+
// no longer be a problem
|
|
810
|
+
(id) => entityMetaById[id]?.name ?? id.toString()
|
|
811
|
+
)
|
|
812
|
+
// see comment above about entityMetaById[id]
|
|
813
|
+
const entityCodes = entityIds.map((id) => entityMetaById[id]?.code)
|
|
814
|
+
|
|
815
|
+
// If there is a conversionFactor, apply it.
|
|
816
|
+
let values = variable.data.values || []
|
|
817
|
+
const conversionFactor = valueColumnDef.display?.conversionFactor
|
|
818
|
+
if (conversionFactor !== undefined) {
|
|
819
|
+
values = values.map((value) =>
|
|
820
|
+
_.isNumber(value) ? value * conversionFactor : value
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
// If a non-int conversion factor is applied to an integer column,
|
|
824
|
+
// we end up with a numeric column.
|
|
825
|
+
if (
|
|
826
|
+
valueColumnDef.type === ColumnTypeNames.Integer &&
|
|
827
|
+
!_.isInteger(conversionFactor)
|
|
828
|
+
)
|
|
829
|
+
valueColumnDef.type = ColumnTypeNames.Numeric
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const columnStore: { [key: string]: any[] } = {
|
|
833
|
+
[OwidTableSlugs.entityId]: entityIds,
|
|
834
|
+
[OwidTableSlugs.entityCode]: entityCodes,
|
|
835
|
+
[OwidTableSlugs.entityName]: entityNames,
|
|
836
|
+
[timeColumnDef.slug]: times,
|
|
837
|
+
[valueColumnDef.slug]: values,
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
return new OwidTable(columnStore, Array.from(columnDefs.values()))
|
|
841
|
+
}
|