@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,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ARCHIVE_DATE_TIME_FORMAT,
|
|
3
|
+
ArchivalDateString,
|
|
4
|
+
} from "../../types/index.js"
|
|
5
|
+
import dayjs from "../dayjs.js"
|
|
6
|
+
|
|
7
|
+
export interface ArchivalTimestamp {
|
|
8
|
+
date: Date
|
|
9
|
+
formattedDate: ArchivalDateString
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type DateInput = Date | ArchivalDateString | string | dayjs.Dayjs
|
|
13
|
+
|
|
14
|
+
export const parseArchivalDate = (dateInput: DateInput): dayjs.Dayjs => {
|
|
15
|
+
if (typeof dateInput === "string") {
|
|
16
|
+
if (dateInput.length === ARCHIVE_DATE_TIME_FORMAT.length)
|
|
17
|
+
return dayjs.utc(dateInput, ARCHIVE_DATE_TIME_FORMAT)
|
|
18
|
+
else return dayjs.utc(dateInput)
|
|
19
|
+
}
|
|
20
|
+
return dayjs.utc(dateInput)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const formatAsArchivalDate = (date: dayjs.Dayjs): ArchivalDateString =>
|
|
24
|
+
date.format(ARCHIVE_DATE_TIME_FORMAT) as ArchivalDateString
|
|
25
|
+
|
|
26
|
+
export const convertToArchivalDateStringIfNecessary = (
|
|
27
|
+
dateInput: DateInput
|
|
28
|
+
): ArchivalDateString => {
|
|
29
|
+
if (
|
|
30
|
+
typeof dateInput === "string" &&
|
|
31
|
+
dateInput.length === ARCHIVE_DATE_TIME_FORMAT.length
|
|
32
|
+
)
|
|
33
|
+
return dateInput as ArchivalDateString
|
|
34
|
+
|
|
35
|
+
return formatAsArchivalDate(parseArchivalDate(dateInput))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const getDateForArchival = (): ArchivalTimestamp => {
|
|
39
|
+
const date = dayjs
|
|
40
|
+
.utc()
|
|
41
|
+
// it's important here that we explicitly set the milliseconds to 0 -
|
|
42
|
+
// otherwise we run the risk of MySQL rounding up to the next second,
|
|
43
|
+
// which then breaks the archival URL
|
|
44
|
+
.millisecond(0)
|
|
45
|
+
const formattedDate = formatAsArchivalDate(date)
|
|
46
|
+
|
|
47
|
+
return { date: date.toDate(), formattedDate }
|
|
48
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Imports dayjs and loads the plugins we need. Then exports it with the correct types.
|
|
2
|
+
|
|
3
|
+
import dayjs, { Dayjs } from "dayjs"
|
|
4
|
+
import customParseFormat from "dayjs/plugin/customParseFormat.js"
|
|
5
|
+
import isToday from "dayjs/plugin/isToday.js"
|
|
6
|
+
import isYesterday from "dayjs/plugin/isYesterday.js"
|
|
7
|
+
import relativeTime from "dayjs/plugin/relativeTime.js"
|
|
8
|
+
import utc from "dayjs/plugin/utc.js"
|
|
9
|
+
|
|
10
|
+
dayjs.extend(customParseFormat)
|
|
11
|
+
dayjs.extend(isToday)
|
|
12
|
+
dayjs.extend(isYesterday)
|
|
13
|
+
dayjs.extend(relativeTime)
|
|
14
|
+
dayjs.extend(utc)
|
|
15
|
+
|
|
16
|
+
export default dayjs
|
|
17
|
+
|
|
18
|
+
// We need these explicit plugin type imports _and exports_ to get the right Dayjs type down the line
|
|
19
|
+
import type customParseFormatType from "dayjs/plugin/customParseFormat.js"
|
|
20
|
+
import type isTodayType from "dayjs/plugin/isToday.js"
|
|
21
|
+
import type isYesterdayType from "dayjs/plugin/isYesterday.js"
|
|
22
|
+
import type relativeTimeType from "dayjs/plugin/relativeTime.js"
|
|
23
|
+
import type utcType from "dayjs/plugin/utc.js"
|
|
24
|
+
|
|
25
|
+
export type {
|
|
26
|
+
Dayjs,
|
|
27
|
+
customParseFormatType,
|
|
28
|
+
isTodayType,
|
|
29
|
+
isYesterdayType,
|
|
30
|
+
relativeTimeType,
|
|
31
|
+
utcType,
|
|
32
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { FormatSpecifier } from "d3-format"
|
|
2
|
+
import { createFormatter } from "./Util.js"
|
|
3
|
+
import {
|
|
4
|
+
OwidVariableRoundingMode,
|
|
5
|
+
TickFormattingOptions,
|
|
6
|
+
} from "../types/index.js"
|
|
7
|
+
|
|
8
|
+
// Used outside this module to figure out if the unit will be joined with the number.
|
|
9
|
+
export function checkIsVeryShortUnit(unit: string): unit is "$" | "£" | "%" {
|
|
10
|
+
return ["%", "$", "£"].includes(unit)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function checkIsUnitCurrency(unit: string): unit is "$" | "£" {
|
|
14
|
+
return ["$", "£"].includes(unit)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function checkIsUnitPercent(unit: string): unit is "%" {
|
|
18
|
+
return unit[0] === "%"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getTrim({
|
|
22
|
+
roundingMode,
|
|
23
|
+
trailingZeroes,
|
|
24
|
+
}: {
|
|
25
|
+
roundingMode: OwidVariableRoundingMode
|
|
26
|
+
trailingZeroes: boolean
|
|
27
|
+
}): "~" | "" {
|
|
28
|
+
// always show trailing zeroes when rounding to significant figures
|
|
29
|
+
return roundingMode === OwidVariableRoundingMode.significantFigures
|
|
30
|
+
? ""
|
|
31
|
+
: trailingZeroes
|
|
32
|
+
? ""
|
|
33
|
+
: "~"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getSign({ showPlus }: { showPlus: boolean }): "+" | "" {
|
|
37
|
+
return showPlus ? "+" : ""
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getSymbol({ unit }: { unit: string }): "$" | "" {
|
|
41
|
+
return checkIsUnitCurrency(unit) ? "$" : ""
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getType({
|
|
45
|
+
roundingMode,
|
|
46
|
+
numberAbbreviation,
|
|
47
|
+
value,
|
|
48
|
+
unit,
|
|
49
|
+
}: {
|
|
50
|
+
roundingMode: OwidVariableRoundingMode
|
|
51
|
+
numberAbbreviation: "long" | "short" | false
|
|
52
|
+
value: number
|
|
53
|
+
unit: string
|
|
54
|
+
}): "f" | "s" | "r" {
|
|
55
|
+
// f: fixed-point notation (i.e. fixed number of decimal points)
|
|
56
|
+
// r: decimal notation, rounded to significant digits
|
|
57
|
+
// s: decimal notation with an SI prefix, rounded to significant digits
|
|
58
|
+
|
|
59
|
+
const typeMap: Record<OwidVariableRoundingMode, "f" | "r"> = {
|
|
60
|
+
[OwidVariableRoundingMode.decimalPlaces]: "f",
|
|
61
|
+
[OwidVariableRoundingMode.significantFigures]: "r",
|
|
62
|
+
}
|
|
63
|
+
const type = typeMap[roundingMode]
|
|
64
|
+
|
|
65
|
+
if (checkIsUnitPercent(unit)) {
|
|
66
|
+
return type
|
|
67
|
+
}
|
|
68
|
+
if (numberAbbreviation === "long") {
|
|
69
|
+
// do not abbreviate until 1 million
|
|
70
|
+
return Math.abs(value) < 1e6 ? type : "s"
|
|
71
|
+
}
|
|
72
|
+
if (numberAbbreviation === "short") {
|
|
73
|
+
// do not abbreviate until 1 thousand
|
|
74
|
+
return Math.abs(value) < 1e3 ? type : "s"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return type
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getPrecision({
|
|
81
|
+
value,
|
|
82
|
+
roundingMode,
|
|
83
|
+
numDecimalPlaces,
|
|
84
|
+
numSignificantFigures,
|
|
85
|
+
type,
|
|
86
|
+
}: {
|
|
87
|
+
value: number
|
|
88
|
+
roundingMode: OwidVariableRoundingMode
|
|
89
|
+
numDecimalPlaces: number
|
|
90
|
+
numSignificantFigures: number
|
|
91
|
+
type: "f" | "s" | "r"
|
|
92
|
+
}): string {
|
|
93
|
+
if (roundingMode === OwidVariableRoundingMode.significantFigures) {
|
|
94
|
+
return `${numSignificantFigures}`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (type === "f") {
|
|
98
|
+
return `${numDecimalPlaces}`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// when dealing with abbreviated numbers, adjust precision so we get 12.84 million instead of 13 million
|
|
102
|
+
// the modulo one-liner counts the "place columns" of the number, resetting every 3
|
|
103
|
+
// 1 -> 1, 48 -> 2, 981 -> 3, 7222 -> 1
|
|
104
|
+
const numberOfDigits = String(Math.floor(Math.abs(value))).length
|
|
105
|
+
const precisionPadding = ((numberOfDigits - 1) % 3) + 1
|
|
106
|
+
|
|
107
|
+
// hard-coded 2 decimal places for abbreviated numbers
|
|
108
|
+
return `${precisionPadding + 2}`
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function replaceSIPrefixes({
|
|
112
|
+
string,
|
|
113
|
+
numberAbbreviation,
|
|
114
|
+
}: {
|
|
115
|
+
string: string
|
|
116
|
+
numberAbbreviation: "short" | "long"
|
|
117
|
+
}): string {
|
|
118
|
+
const prefix = string[string.length - 1]
|
|
119
|
+
|
|
120
|
+
const prefixMap: Record<string, Record<string, string>> = {
|
|
121
|
+
short: {
|
|
122
|
+
k: "k",
|
|
123
|
+
M: "M",
|
|
124
|
+
G: "B",
|
|
125
|
+
T: "T",
|
|
126
|
+
P: "quad",
|
|
127
|
+
E: "quint",
|
|
128
|
+
Z: "sext",
|
|
129
|
+
Y: "sept",
|
|
130
|
+
},
|
|
131
|
+
long: {
|
|
132
|
+
k: "k",
|
|
133
|
+
M: " million",
|
|
134
|
+
G: " billion",
|
|
135
|
+
T: " trillion",
|
|
136
|
+
P: " quadrillion",
|
|
137
|
+
E: " quintillion",
|
|
138
|
+
Z: " sextillion",
|
|
139
|
+
Y: " septillion",
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (prefixMap[numberAbbreviation][prefix]) {
|
|
144
|
+
return string.replace(prefix, prefixMap[numberAbbreviation][prefix])
|
|
145
|
+
}
|
|
146
|
+
return string
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function postprocessString({
|
|
150
|
+
string,
|
|
151
|
+
roundingMode,
|
|
152
|
+
numberAbbreviation,
|
|
153
|
+
spaceBeforeUnit,
|
|
154
|
+
useNoBreakSpace,
|
|
155
|
+
unit,
|
|
156
|
+
value,
|
|
157
|
+
numDecimalPlaces,
|
|
158
|
+
}: {
|
|
159
|
+
string: string
|
|
160
|
+
roundingMode: OwidVariableRoundingMode
|
|
161
|
+
numberAbbreviation: "long" | "short" | false
|
|
162
|
+
spaceBeforeUnit: boolean
|
|
163
|
+
useNoBreakSpace: boolean
|
|
164
|
+
unit: string
|
|
165
|
+
value: number
|
|
166
|
+
numDecimalPlaces: number
|
|
167
|
+
}): string {
|
|
168
|
+
let output = string
|
|
169
|
+
|
|
170
|
+
// handling infinitesimal values
|
|
171
|
+
if (roundingMode !== OwidVariableRoundingMode.significantFigures) {
|
|
172
|
+
const tooSmallThreshold = Math.pow(10, -numDecimalPlaces).toPrecision(1)
|
|
173
|
+
if (numberAbbreviation && 0 < value && value < +tooSmallThreshold) {
|
|
174
|
+
output = "<" + output.replace(/0\.?(\d+)?/, tooSmallThreshold)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (numberAbbreviation) {
|
|
179
|
+
output = replaceSIPrefixes({
|
|
180
|
+
string: output,
|
|
181
|
+
numberAbbreviation,
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (unit && !checkIsUnitCurrency(unit)) {
|
|
186
|
+
const spaceCharacter = useNoBreakSpace ? "\u00a0" : " "
|
|
187
|
+
const appendage = spaceBeforeUnit ? spaceCharacter + unit : unit
|
|
188
|
+
output += appendage
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return output
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function formatValue(
|
|
195
|
+
value: number,
|
|
196
|
+
{
|
|
197
|
+
roundingMode = OwidVariableRoundingMode.decimalPlaces,
|
|
198
|
+
trailingZeroes = false, // only applies to fixed-point notation
|
|
199
|
+
unit = "",
|
|
200
|
+
spaceBeforeUnit = !checkIsUnitPercent(unit),
|
|
201
|
+
useNoBreakSpace = false,
|
|
202
|
+
showPlus = false,
|
|
203
|
+
numDecimalPlaces = 2, // only applies to fixed-point notation
|
|
204
|
+
numSignificantFigures = 3, // only applies to sig fig rounding
|
|
205
|
+
numberAbbreviation = "long",
|
|
206
|
+
}: TickFormattingOptions
|
|
207
|
+
): string {
|
|
208
|
+
const formatter = createFormatter(unit)
|
|
209
|
+
|
|
210
|
+
// Explore how specifiers work here
|
|
211
|
+
// https://observablehq.com/@ikesau/d3-format-interactive-demo
|
|
212
|
+
const specifier = new FormatSpecifier({
|
|
213
|
+
zero: "0",
|
|
214
|
+
trim: getTrim({ roundingMode, trailingZeroes }),
|
|
215
|
+
sign: getSign({ showPlus }),
|
|
216
|
+
symbol: getSymbol({ unit }),
|
|
217
|
+
comma: ",",
|
|
218
|
+
precision: getPrecision({
|
|
219
|
+
roundingMode,
|
|
220
|
+
value,
|
|
221
|
+
numDecimalPlaces,
|
|
222
|
+
numSignificantFigures,
|
|
223
|
+
type: getType({ roundingMode, numberAbbreviation, value, unit }),
|
|
224
|
+
}),
|
|
225
|
+
type: getType({ roundingMode, numberAbbreviation, value, unit }),
|
|
226
|
+
}).toString()
|
|
227
|
+
|
|
228
|
+
const formattedString = formatter(specifier)(value)
|
|
229
|
+
|
|
230
|
+
const postprocessedString = postprocessString({
|
|
231
|
+
string: formattedString,
|
|
232
|
+
roundingMode,
|
|
233
|
+
numberAbbreviation,
|
|
234
|
+
spaceBeforeUnit,
|
|
235
|
+
useNoBreakSpace,
|
|
236
|
+
unit,
|
|
237
|
+
value,
|
|
238
|
+
numDecimalPlaces,
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
return postprocessedString
|
|
242
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { GrapherInterface } from "../types/index.js"
|
|
2
|
+
import * as _ from "lodash-es"
|
|
3
|
+
import {
|
|
4
|
+
excludeUndefined,
|
|
5
|
+
omitUndefinedValuesRecursive,
|
|
6
|
+
omitEmptyObjectsRecursive,
|
|
7
|
+
traverseObjects,
|
|
8
|
+
merge,
|
|
9
|
+
} from "./Util"
|
|
10
|
+
|
|
11
|
+
const REQUIRED_KEYS = ["$schema", "dimensions"]
|
|
12
|
+
|
|
13
|
+
const KEYS_EXCLUDED_FROM_INHERITANCE = [
|
|
14
|
+
"$schema",
|
|
15
|
+
"id",
|
|
16
|
+
"slug",
|
|
17
|
+
"version",
|
|
18
|
+
"isPublished",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
export function mergeGrapherConfigs(
|
|
22
|
+
...grapherConfigs: GrapherInterface[]
|
|
23
|
+
): GrapherInterface {
|
|
24
|
+
const configsToMerge = grapherConfigs.filter((c) => !_.isEmpty(c))
|
|
25
|
+
|
|
26
|
+
// return early if there are no configs to merge
|
|
27
|
+
if (configsToMerge.length === 0) return {}
|
|
28
|
+
if (configsToMerge.length === 1) return configsToMerge[0]
|
|
29
|
+
|
|
30
|
+
// warn if one of the configs is missing a schema version
|
|
31
|
+
const configsWithoutSchema = configsToMerge.filter(
|
|
32
|
+
(c) => c["$schema"] === undefined
|
|
33
|
+
)
|
|
34
|
+
if (configsWithoutSchema.length > 0) {
|
|
35
|
+
const configsJson = JSON.stringify(configsWithoutSchema, null, 2)
|
|
36
|
+
console.warn(
|
|
37
|
+
`About to merge Grapher configs with missing schema information: ${configsJson}`
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// abort if the grapher configs have different schema versions
|
|
42
|
+
const uniqueSchemas = _.uniq(
|
|
43
|
+
excludeUndefined(configsToMerge.map((c) => c["$schema"]))
|
|
44
|
+
)
|
|
45
|
+
if (uniqueSchemas.length > 1) {
|
|
46
|
+
const message = `Merging Grapher configs with different schema versions. This may lead to unexpected behavior. Found: ${uniqueSchemas.join(
|
|
47
|
+
", "
|
|
48
|
+
)}`
|
|
49
|
+
console.warn(message)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// keys that should not be inherited are removed from all but the last config
|
|
53
|
+
const cleanedConfigs = configsToMerge.map((config, index) => {
|
|
54
|
+
if (index === configsToMerge.length - 1) return config
|
|
55
|
+
return _.omit(config, KEYS_EXCLUDED_FROM_INHERITANCE)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
return merge(
|
|
59
|
+
...(cleanedConfigs as [GrapherInterface, ...GrapherInterface[]])
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function diffGrapherConfigs(
|
|
64
|
+
config: GrapherInterface,
|
|
65
|
+
reference: GrapherInterface
|
|
66
|
+
): GrapherInterface {
|
|
67
|
+
const keepKeys = [...REQUIRED_KEYS, ...KEYS_EXCLUDED_FROM_INHERITANCE]
|
|
68
|
+
const keep = _.pick(config, keepKeys)
|
|
69
|
+
|
|
70
|
+
const diffed = omitEmptyObjectsRecursive(
|
|
71
|
+
omitUndefinedValuesRecursive(
|
|
72
|
+
traverseObjects(config, reference, (value, refValue) => {
|
|
73
|
+
if (refValue === undefined) return value
|
|
74
|
+
if (!_.isEqual(value, refValue)) return value
|
|
75
|
+
return undefined
|
|
76
|
+
})
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return { ...diffed, ...keep }
|
|
81
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/*
|
|
3
|
+
Common utlities for deriving properties from image metadata.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { traverseEnrichedBlock } from "./Util.js"
|
|
7
|
+
import {
|
|
8
|
+
AssetMap,
|
|
9
|
+
OwidGdoc,
|
|
10
|
+
OwidGdocType,
|
|
11
|
+
ImageMetadata,
|
|
12
|
+
} from "../types/index.js"
|
|
13
|
+
import { match, P } from "ts-pattern"
|
|
14
|
+
|
|
15
|
+
export const AUTHOR_BYLINE_WIDTH = 48
|
|
16
|
+
export const THUMBNAIL_WIDTH = 100
|
|
17
|
+
export const LARGE_THUMBNAIL_WIDTH = 350
|
|
18
|
+
export const LARGEST_IMAGE_WIDTH = 1350
|
|
19
|
+
|
|
20
|
+
export function getArchivalSizes(
|
|
21
|
+
originalWidth: ImageMetadata["originalWidth"]
|
|
22
|
+
): number[] {
|
|
23
|
+
if (!originalWidth) return []
|
|
24
|
+
const widths: number[] = []
|
|
25
|
+
if (originalWidth > LARGE_THUMBNAIL_WIDTH) {
|
|
26
|
+
widths.push(LARGE_THUMBNAIL_WIDTH)
|
|
27
|
+
}
|
|
28
|
+
if (originalWidth > LARGEST_IMAGE_WIDTH) {
|
|
29
|
+
widths.push(LARGEST_IMAGE_WIDTH)
|
|
30
|
+
}
|
|
31
|
+
widths.push(originalWidth)
|
|
32
|
+
return widths
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getSizes(
|
|
36
|
+
originalWidth: ImageMetadata["originalWidth"]
|
|
37
|
+
): number[] {
|
|
38
|
+
if (!originalWidth) return []
|
|
39
|
+
// ensure a thumbnail and author byline is generated
|
|
40
|
+
const widths = [AUTHOR_BYLINE_WIDTH, THUMBNAIL_WIDTH]
|
|
41
|
+
// start at large thumbnail and go up by 500 to a max of 1350 before we just show the original image
|
|
42
|
+
let width = LARGE_THUMBNAIL_WIDTH
|
|
43
|
+
while (width < originalWidth && width <= LARGEST_IMAGE_WIDTH) {
|
|
44
|
+
widths.push(width)
|
|
45
|
+
width += 500
|
|
46
|
+
}
|
|
47
|
+
widths.push(originalWidth)
|
|
48
|
+
return widths
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function generateSrcSet(
|
|
52
|
+
sizes: number[],
|
|
53
|
+
id: ImageMetadata["cloudflareId"],
|
|
54
|
+
absoluteUrl: string = ""
|
|
55
|
+
): string {
|
|
56
|
+
return sizes
|
|
57
|
+
.map((size) => {
|
|
58
|
+
const path = `${absoluteUrl}/${id}/w=${size}`
|
|
59
|
+
return `${path} ${size}w`
|
|
60
|
+
})
|
|
61
|
+
.join(", ")
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getFilenameWithoutExtension(
|
|
65
|
+
filename: ImageMetadata["filename"]
|
|
66
|
+
): string {
|
|
67
|
+
return filename.slice(0, filename.indexOf("."))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getFilenameExtension(
|
|
71
|
+
filename: ImageMetadata["filename"]
|
|
72
|
+
): string {
|
|
73
|
+
return filename.slice(filename.lastIndexOf(".") + 1)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getFilenameAsPng(filename: ImageMetadata["filename"]): string {
|
|
77
|
+
return `${getFilenameWithoutExtension(filename)}.png`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function getFilenameMIMEType(filename: string): string | undefined {
|
|
81
|
+
const fileExtension = getFilenameExtension(filename)
|
|
82
|
+
const MIMEType = {
|
|
83
|
+
png: "image/png",
|
|
84
|
+
svg: "image/svg+xml",
|
|
85
|
+
jpg: "image/jpg",
|
|
86
|
+
jpeg: "image/jpeg",
|
|
87
|
+
webp: "image/webp",
|
|
88
|
+
}[fileExtension]
|
|
89
|
+
|
|
90
|
+
return MIMEType
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type SourceProps = {
|
|
94
|
+
media: string | undefined
|
|
95
|
+
srcSet: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function appendImageSizeSuffix(filename: string, size: number): string {
|
|
99
|
+
const extIndex = filename.lastIndexOf(".")
|
|
100
|
+
if (extIndex === -1) return `${filename}-${size}w`
|
|
101
|
+
const name = filename.slice(0, extIndex)
|
|
102
|
+
const ext = filename.slice(extIndex)
|
|
103
|
+
return `${name}-${size}w${ext}`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function generateArchivalSrcSet(
|
|
107
|
+
image: ImageMetadata,
|
|
108
|
+
sizes: number[],
|
|
109
|
+
assetMap: AssetMap
|
|
110
|
+
): string {
|
|
111
|
+
return sizes
|
|
112
|
+
.map((size) => {
|
|
113
|
+
const filename =
|
|
114
|
+
size === image.originalWidth
|
|
115
|
+
? image.filename
|
|
116
|
+
: appendImageSizeSuffix(image.filename, size)
|
|
117
|
+
const path = assetMap[filename]
|
|
118
|
+
if (!path) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Image ${image.filename} not found in asset map`
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
return `${path} ${size}w`
|
|
124
|
+
})
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.join(", ")
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* When we have a small and large image, we want to generate two <source> elements.
|
|
131
|
+
* The first will only be active at small screen sizes, due to its `media` property.
|
|
132
|
+
* The second will be active at all screen sizes.
|
|
133
|
+
* These props work in conjuction with a `sizes` attribute on the <source> element.
|
|
134
|
+
*/
|
|
135
|
+
export function generateSourceProps(
|
|
136
|
+
smallImage: ImageMetadata | undefined,
|
|
137
|
+
regularImage: ImageMetadata,
|
|
138
|
+
absoluteUrl: string = "",
|
|
139
|
+
assetMap?: AssetMap
|
|
140
|
+
): SourceProps[] {
|
|
141
|
+
const props: SourceProps[] = []
|
|
142
|
+
|
|
143
|
+
function buildProps(image: ImageMetadata, media: string | undefined): void {
|
|
144
|
+
const sizes = assetMap
|
|
145
|
+
? getArchivalSizes(image.originalWidth)
|
|
146
|
+
: getSizes(image.originalWidth)
|
|
147
|
+
if (sizes.length === 0) return
|
|
148
|
+
|
|
149
|
+
let srcSet: string | undefined
|
|
150
|
+
if (assetMap) {
|
|
151
|
+
srcSet = generateArchivalSrcSet(image, sizes, assetMap)
|
|
152
|
+
} else if (image.cloudflareId) {
|
|
153
|
+
const encodedId = encodeURIComponent(image.cloudflareId)
|
|
154
|
+
srcSet = generateSrcSet(sizes, encodedId, absoluteUrl)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!srcSet) return
|
|
158
|
+
|
|
159
|
+
props.push({
|
|
160
|
+
media,
|
|
161
|
+
srcSet,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (smallImage) {
|
|
166
|
+
buildProps(smallImage, "(max-width: 768px)")
|
|
167
|
+
}
|
|
168
|
+
if (regularImage) {
|
|
169
|
+
buildProps(regularImage, undefined)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return props
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function getFeaturedImageFilename(gdoc: OwidGdoc): string | undefined {
|
|
176
|
+
return match(gdoc)
|
|
177
|
+
.with(
|
|
178
|
+
{
|
|
179
|
+
content: {
|
|
180
|
+
type: P.union(
|
|
181
|
+
OwidGdocType.Article,
|
|
182
|
+
OwidGdocType.TopicPage,
|
|
183
|
+
OwidGdocType.LinearTopicPage,
|
|
184
|
+
OwidGdocType.AboutPage,
|
|
185
|
+
OwidGdocType.Author,
|
|
186
|
+
OwidGdocType.Announcement
|
|
187
|
+
),
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
(match) => {
|
|
191
|
+
const featuredImageFilename = match.content["featured-image"]
|
|
192
|
+
return featuredImageFilename
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
.with({ content: { type: OwidGdocType.DataInsight } }, (gdoc) => {
|
|
196
|
+
// Use the first image in the document as the featured image
|
|
197
|
+
let filename: string | undefined = undefined
|
|
198
|
+
for (const block of gdoc.content.body) {
|
|
199
|
+
traverseEnrichedBlock(block, (block) => {
|
|
200
|
+
if (!filename && block.type === "image") {
|
|
201
|
+
filename = block.smallFilename || block.filename
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
return filename
|
|
206
|
+
})
|
|
207
|
+
.with(
|
|
208
|
+
{
|
|
209
|
+
content: { type: OwidGdocType.Profile },
|
|
210
|
+
},
|
|
211
|
+
(match) => match.content["featured-image"]
|
|
212
|
+
)
|
|
213
|
+
.with(
|
|
214
|
+
{
|
|
215
|
+
content: {
|
|
216
|
+
type: P.optional(
|
|
217
|
+
P.union(OwidGdocType.Fragment, OwidGdocType.Homepage)
|
|
218
|
+
),
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
// This will fallback to DEFAULT_THUMBNAIL_FILENAME in Head.tsx
|
|
222
|
+
() => undefined
|
|
223
|
+
)
|
|
224
|
+
.exhaustive()
|
|
225
|
+
}
|