@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,82 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { GrapherProgrammaticInterface } from "../core/Grapher.js"
|
|
3
|
+
import { GrapherState } from "../core/GrapherState.js"
|
|
4
|
+
import { MultiDimDataPageConfig, Url } from "../../utils/index.js"
|
|
5
|
+
import {
|
|
6
|
+
GrapherQueryParams,
|
|
7
|
+
MultiDimDimensionChoices,
|
|
8
|
+
ChartConfigType,
|
|
9
|
+
} from "../../types/index.js"
|
|
10
|
+
|
|
11
|
+
export interface ArchiveGuidedChartRegistration {
|
|
12
|
+
iframeRef: React.RefObject<HTMLIFrameElement | null>
|
|
13
|
+
baseUrl: string
|
|
14
|
+
defaultQueryParams: Record<string, string | undefined>
|
|
15
|
+
chartConfigType: ChartConfigType
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface GuidedChartContextValue {
|
|
19
|
+
grapherStateRef: React.RefObject<GrapherState>
|
|
20
|
+
chartRef?: React.RefObject<HTMLDivElement>
|
|
21
|
+
onGuidedChartLinkClick?: (href: string) => void
|
|
22
|
+
registerArchiveChart?: (
|
|
23
|
+
registration: ArchiveGuidedChartRegistration
|
|
24
|
+
) => () => void
|
|
25
|
+
registerMultiDim?: (registrationData: {
|
|
26
|
+
config: MultiDimDataPageConfig
|
|
27
|
+
onSettingsChange: (
|
|
28
|
+
newSettings: MultiDimDimensionChoices,
|
|
29
|
+
queryParams: GrapherQueryParams
|
|
30
|
+
) => void
|
|
31
|
+
grapherContainerRef: React.RefObject<HTMLDivElement | null>
|
|
32
|
+
}) => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const GuidedChartContext =
|
|
36
|
+
React.createContext<GuidedChartContextValue | null>(null)
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* If called within a `GuidedChartContext`, sets the context's `grapherStateRef`
|
|
40
|
+
* to a new `GrapherState` instance initialized with the provided config.
|
|
41
|
+
* If no context is available, returns a local ref initialized with the config.
|
|
42
|
+
* This is so the `GrapherState` can be controlled from a GuidedChart,
|
|
43
|
+
* but also allows for local usage when not
|
|
44
|
+
*/
|
|
45
|
+
export function useMaybeGlobalGrapherStateRef(
|
|
46
|
+
config: GrapherProgrammaticInterface
|
|
47
|
+
): React.RefObject<GrapherState> {
|
|
48
|
+
const context = React.useContext(GuidedChartContext)
|
|
49
|
+
const localRef = React.useRef<GrapherState | null>(null)
|
|
50
|
+
|
|
51
|
+
// If a context is provided, use it; otherwise, use the local ref
|
|
52
|
+
const refToUse = context?.grapherStateRef || localRef
|
|
53
|
+
|
|
54
|
+
// Only initialize if the ref is empty
|
|
55
|
+
if (!refToUse.current) {
|
|
56
|
+
refToUse.current = new GrapherState(config)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return refToUse as React.RefObject<GrapherState>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function useGuidedChartLinkHandler():
|
|
63
|
+
| ((href: string) => void)
|
|
64
|
+
| undefined {
|
|
65
|
+
const context = React.useContext(GuidedChartContext)
|
|
66
|
+
return context?.onGuidedChartLinkClick
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const buildArchiveGuidedChartSrc = (
|
|
70
|
+
registration: ArchiveGuidedChartRegistration,
|
|
71
|
+
guidedUrl: Url
|
|
72
|
+
): string => {
|
|
73
|
+
const baseUrl = Url.fromURL(registration.baseUrl)
|
|
74
|
+
const mergedQuery = {
|
|
75
|
+
...registration.defaultQueryParams,
|
|
76
|
+
...guidedUrl.queryParams,
|
|
77
|
+
}
|
|
78
|
+
const updatedUrl = baseUrl.setQueryParams(mergedQuery)
|
|
79
|
+
|
|
80
|
+
const hash = guidedUrl.hash || baseUrl.hash
|
|
81
|
+
return hash ? updatedUrl.update({ hash }).fullUrl : updatedUrl.fullUrl
|
|
82
|
+
}
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import {
|
|
2
|
+
automaticBinningStrategies,
|
|
3
|
+
AutomaticBinningStrategy,
|
|
4
|
+
MidpointMode,
|
|
5
|
+
ResolvedBinningStrategy,
|
|
6
|
+
} from "../../types/index.js"
|
|
7
|
+
import {
|
|
8
|
+
firstOfNonEmptyArray,
|
|
9
|
+
lastOfNonEmptyArray,
|
|
10
|
+
} from "../../utils/index.js"
|
|
11
|
+
import { quantile } from "d3"
|
|
12
|
+
import { sortedUniq } from "lodash-es"
|
|
13
|
+
import * as R from "remeda"
|
|
14
|
+
import { match, P } from "ts-pattern"
|
|
15
|
+
import {
|
|
16
|
+
isLogBinningStrategy,
|
|
17
|
+
runLogBinningStrategy,
|
|
18
|
+
} from "./BinningStrategyLogarithmic.js"
|
|
19
|
+
import {
|
|
20
|
+
isEqualSizeBinningStrategy,
|
|
21
|
+
runEqualSizeBinningStrategy,
|
|
22
|
+
} from "./BinningStrategyEqualSizeBins.js"
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Strategies:
|
|
26
|
+
* - The log scales result in log-like steps, e.g. 1, 2, 5, 10, ...
|
|
27
|
+
* They are fully defined given a minValue, maxValue and steps, and
|
|
28
|
+
* then generate as many bins as needed to cover the range.
|
|
29
|
+
* `log-auto` chooses the step size automatically in order to get a decent number of bins.
|
|
30
|
+
* - The equal size bins result in evenly spaced steps, e.g. 0, 1, 2, 3, ...
|
|
31
|
+
* They are defined by a minValue, maxValue, and a rough target number of bins.
|
|
32
|
+
* `equalSizeBins-few-bins` chooses a small number of bins, while
|
|
33
|
+
* `equalSizeBins-many-bins` chooses a large number of bins.
|
|
34
|
+
* They then generate nice round bin thresholds given the input data.
|
|
35
|
+
* - `equalSizeBins-percent` is a special case, where for data that looks like percent
|
|
36
|
+
* from 0% to 100% we want to mostly use 0%, 10%, 20%, etc. bins.
|
|
37
|
+
*
|
|
38
|
+
* Midpoints:
|
|
39
|
+
* Sometimes, we do have a midpoint in our data. In many cases, a natural midpoint is zero
|
|
40
|
+
* (e.g. for year-over-year change, net migration, temperature anomaly, etc.), but other
|
|
41
|
+
* midpoints also make sense (e.g. for sex ratio, or survey responses ranging 0-10).
|
|
42
|
+
* If we have a midpoint, then we want to account for it when binning, and generate bins
|
|
43
|
+
* that are centered or symmetric around the midpoint.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
export interface BinningStrategyConfig {
|
|
47
|
+
strategy: AutomaticBinningStrategy
|
|
48
|
+
minValue?: number
|
|
49
|
+
maxValue?: number
|
|
50
|
+
sortedValues: number[]
|
|
51
|
+
isPercent?: boolean
|
|
52
|
+
numDecimalPlaces?: number
|
|
53
|
+
midpointMode?: MidpointMode
|
|
54
|
+
midpoint?: number
|
|
55
|
+
createBinForMidpoint?: boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ResolvedBinningStrategyConfig {
|
|
59
|
+
strategy: ResolvedBinningStrategy
|
|
60
|
+
minValue?: number
|
|
61
|
+
maxValue?: number
|
|
62
|
+
numDecimalPlaces?: number
|
|
63
|
+
sortedValues: number[]
|
|
64
|
+
midpointMode: MidpointMode
|
|
65
|
+
midpoint: number
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface BinningStrategyOutput {
|
|
69
|
+
bins: number[]
|
|
70
|
+
midpoint?: number
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// If the log10 difference between the min and max values is less than or equal to 1.2,
|
|
74
|
+
// the data is considered to be close enough in magnitude to use equal-size bins.
|
|
75
|
+
// This threshold was chosen empirically.
|
|
76
|
+
const AUTO_EQUAL_BINS_MAX_MAGNITUDE_DIFF = 1.2
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Calculates the log10 difference between two numbers, i.e. compute x such that lowerValue * 10^x = upperValue.
|
|
80
|
+
*/
|
|
81
|
+
export const calcMagnitudeDiff = (
|
|
82
|
+
lowerValue: number,
|
|
83
|
+
upperValue: number
|
|
84
|
+
): number => {
|
|
85
|
+
if (lowerValue > upperValue)
|
|
86
|
+
throw new Error("lowerValue must be less than upperValue")
|
|
87
|
+
|
|
88
|
+
return Math.log10(upperValue) - Math.log10(lowerValue)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const pruneUnusedBins = (
|
|
92
|
+
bins: number[],
|
|
93
|
+
{ minValue, maxValue }: { minValue?: number; maxValue?: number }
|
|
94
|
+
): number[] => {
|
|
95
|
+
if (minValue !== undefined) {
|
|
96
|
+
bins = R.dropWhile(
|
|
97
|
+
bins,
|
|
98
|
+
(v, i) => bins[i + 1] !== undefined && bins[i + 1] <= minValue
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (maxValue !== undefined) {
|
|
103
|
+
bins = R.dropLastWhile(
|
|
104
|
+
bins,
|
|
105
|
+
(v, i) => bins[i - 1] !== undefined && bins[i - 1] >= maxValue
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return bins
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const runBinningStrategy = (
|
|
113
|
+
conf: BinningStrategyConfig
|
|
114
|
+
): BinningStrategyOutput => {
|
|
115
|
+
if (conf.sortedValues.length === 0) {
|
|
116
|
+
return { bins: [0] }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
conf.midpoint ??= 0
|
|
120
|
+
|
|
121
|
+
const hasValuesBelowMidpoint = conf.sortedValues[0] < conf.midpoint
|
|
122
|
+
const hasValuesAboveMidpoint =
|
|
123
|
+
lastOfNonEmptyArray(conf.sortedValues) > conf.midpoint
|
|
124
|
+
const hasValuesBelowAndAboveMidpoint =
|
|
125
|
+
hasValuesBelowMidpoint && hasValuesAboveMidpoint
|
|
126
|
+
|
|
127
|
+
if (hasValuesBelowAndAboveMidpoint && conf.midpointMode === undefined) {
|
|
128
|
+
// Default to symmetric midpoint if there are negative and positive values
|
|
129
|
+
conf.midpointMode ??= "symmetric"
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
conf.midpointMode ??= "none"
|
|
133
|
+
|
|
134
|
+
let resolvedStrategy: ResolvedBinningStrategy
|
|
135
|
+
if (conf.strategy === "auto") {
|
|
136
|
+
resolvedStrategy = autoChooseBinningStrategy(conf)
|
|
137
|
+
} else {
|
|
138
|
+
resolvedStrategy = conf.strategy as ResolvedBinningStrategy
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const validationResult = hasValidConfigForBinningStrategy(
|
|
142
|
+
conf.strategy,
|
|
143
|
+
conf
|
|
144
|
+
)
|
|
145
|
+
if (!validationResult.valid) return { bins: [0] } // Placeholder binning for invalid configurations
|
|
146
|
+
|
|
147
|
+
let bins = runBinningStrategyAroundMidpoint({
|
|
148
|
+
...conf,
|
|
149
|
+
midpointMode: conf.midpointMode,
|
|
150
|
+
midpoint: conf.midpoint,
|
|
151
|
+
strategy: resolvedStrategy,
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
if (conf.createBinForMidpoint) {
|
|
155
|
+
const midpointIdx = R.sortedIndex(bins, conf.midpoint)
|
|
156
|
+
bins = bins.toSpliced(midpointIdx, 0, conf.midpoint)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
bins,
|
|
161
|
+
midpoint: conf.midpointMode !== "none" ? conf.midpoint : undefined,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
type MinMaxValueResult =
|
|
166
|
+
| { valid: true }
|
|
167
|
+
| { valid: false; reason: string; field: string }
|
|
168
|
+
|
|
169
|
+
export const hasValidConfigForBinningStrategy = (
|
|
170
|
+
strategy: AutomaticBinningStrategy,
|
|
171
|
+
config: { minValue?: number; maxValue?: number; midpoint?: number }
|
|
172
|
+
): MinMaxValueResult => {
|
|
173
|
+
const { minValue, maxValue, midpoint } = config
|
|
174
|
+
|
|
175
|
+
if (!automaticBinningStrategies.includes(strategy))
|
|
176
|
+
return {
|
|
177
|
+
valid: false,
|
|
178
|
+
reason: `Binning: Invalid strategy '${strategy}'`,
|
|
179
|
+
field: "binningStrategy",
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (
|
|
183
|
+
minValue !== undefined &&
|
|
184
|
+
maxValue !== undefined &&
|
|
185
|
+
maxValue < minValue
|
|
186
|
+
) {
|
|
187
|
+
return {
|
|
188
|
+
valid: false,
|
|
189
|
+
reason: "Binning: minValue is greater than maxValue",
|
|
190
|
+
field: "minValue",
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return match(strategy)
|
|
195
|
+
.with("auto", () => {
|
|
196
|
+
return { valid: true } as const
|
|
197
|
+
})
|
|
198
|
+
.when(isEqualSizeBinningStrategy, () => {
|
|
199
|
+
return { valid: true } as const
|
|
200
|
+
})
|
|
201
|
+
.when(isLogBinningStrategy, () => {
|
|
202
|
+
if (minValue !== undefined && minValue <= 0) {
|
|
203
|
+
return {
|
|
204
|
+
valid: false,
|
|
205
|
+
reason: "Binning: Logarithmic binning requires non-zero positive minValue",
|
|
206
|
+
field: "minValue",
|
|
207
|
+
} as const
|
|
208
|
+
}
|
|
209
|
+
if (maxValue !== undefined && maxValue <= 0) {
|
|
210
|
+
return {
|
|
211
|
+
valid: false,
|
|
212
|
+
reason: "Binning: Logarithmic binning requires non-zero positive maxValue",
|
|
213
|
+
field: "maxValue",
|
|
214
|
+
} as const
|
|
215
|
+
}
|
|
216
|
+
if (midpoint !== undefined && midpoint !== 0) {
|
|
217
|
+
return {
|
|
218
|
+
valid: false,
|
|
219
|
+
reason: "Binning: Logarithmic binning does not support midpoints other than 0",
|
|
220
|
+
field: "midpoint",
|
|
221
|
+
} as const
|
|
222
|
+
}
|
|
223
|
+
return { valid: true } as const
|
|
224
|
+
})
|
|
225
|
+
.exhaustive()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* minValue and maxValue may either be explicitly given, or automatically computed from the data.
|
|
230
|
+
* If auto-computed, they are based on some very much heuristic rules, based on quantiles etc.
|
|
231
|
+
*
|
|
232
|
+
* This also means that these lines contain a bunch of magic numbers, which have been chosen like so
|
|
233
|
+
* because they work well in practice for most cases.
|
|
234
|
+
*/
|
|
235
|
+
export const computeMinMaxForStrategy = (
|
|
236
|
+
strategy: ResolvedBinningStrategy,
|
|
237
|
+
sortedValues: number[],
|
|
238
|
+
conf?: Partial<ResolvedBinningStrategyConfig>
|
|
239
|
+
): { minValue: number; maxValue: number } => {
|
|
240
|
+
let { minValue, maxValue } = conf || {}
|
|
241
|
+
if (
|
|
242
|
+
conf?.midpointMode !== "none" &&
|
|
243
|
+
minValue !== undefined &&
|
|
244
|
+
conf?.midpoint !== undefined
|
|
245
|
+
) {
|
|
246
|
+
// Ensure that the midpoint is within the min/max range, otherwise we'll get very weird results when mirroring around the midpoint
|
|
247
|
+
minValue = Math.max(minValue, conf.midpoint)
|
|
248
|
+
}
|
|
249
|
+
if (minValue !== undefined && maxValue !== undefined) {
|
|
250
|
+
maxValue = Math.max(minValue, maxValue)
|
|
251
|
+
return { minValue, maxValue }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
match(strategy)
|
|
255
|
+
.with("equalSizeBins-percent", () => {
|
|
256
|
+
// Percent strategy always uses 0 to 100
|
|
257
|
+
minValue ??= 0
|
|
258
|
+
maxValue ??= 100
|
|
259
|
+
})
|
|
260
|
+
.when(isEqualSizeBinningStrategy, () => {
|
|
261
|
+
const uniqValues = sortedUniq(sortedValues)
|
|
262
|
+
minValue ??= quantile(uniqValues, 0.05)
|
|
263
|
+
maxValue ??= quantile(uniqValues, 0.95)
|
|
264
|
+
})
|
|
265
|
+
.when(isLogBinningStrategy, () => {
|
|
266
|
+
const posValues = R.dropWhile(sortedValues, (v) => v <= 0)
|
|
267
|
+
if (posValues.length === 0) {
|
|
268
|
+
throw new Error("Log binning strategy requires positive values")
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const uniqValues = sortedUniq(posValues)
|
|
272
|
+
minValue ??= quantile(uniqValues, 0.15)
|
|
273
|
+
maxValue ??= quantile(uniqValues, 0.995)
|
|
274
|
+
|
|
275
|
+
if (minValue === undefined || maxValue === undefined)
|
|
276
|
+
throw new Error("Couldn't obtain minValue or maxValue")
|
|
277
|
+
|
|
278
|
+
// Ensure that the min/max values are at least as high as the number of decimals precision.
|
|
279
|
+
// E.g. if we have 1 decimal place, the minimum value should be at least 0.1
|
|
280
|
+
if (conf?.numDecimalPlaces !== undefined) {
|
|
281
|
+
if (minValue > 0)
|
|
282
|
+
minValue = Math.max(
|
|
283
|
+
minValue,
|
|
284
|
+
Math.pow(10, -conf.numDecimalPlaces)
|
|
285
|
+
)
|
|
286
|
+
if (maxValue < 0)
|
|
287
|
+
maxValue = Math.min(
|
|
288
|
+
maxValue,
|
|
289
|
+
-Math.pow(10, -conf.numDecimalPlaces)
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
.exhaustive()
|
|
294
|
+
|
|
295
|
+
if (minValue === undefined || maxValue === undefined)
|
|
296
|
+
throw new Error("Couldn't obtain minValue or maxValue")
|
|
297
|
+
|
|
298
|
+
// Ensure that we have minValue <= maxValue
|
|
299
|
+
maxValue = Math.max(minValue, maxValue)
|
|
300
|
+
return { minValue, maxValue }
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const runBinningStrategyAroundMidpoint = (
|
|
304
|
+
conf: ResolvedBinningStrategyConfig
|
|
305
|
+
): number[] => {
|
|
306
|
+
if (
|
|
307
|
+
isLogBinningStrategy(conf.strategy) &&
|
|
308
|
+
conf.midpointMode !== "none" &&
|
|
309
|
+
conf.midpoint !== 0
|
|
310
|
+
) {
|
|
311
|
+
throw new Error(
|
|
312
|
+
"Log binning strategy does not support midpoints other than 0"
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const minValue = firstOfNonEmptyArray(conf.sortedValues)
|
|
317
|
+
const maxValue = lastOfNonEmptyArray(conf.sortedValues)
|
|
318
|
+
|
|
319
|
+
const bins = match(conf.midpointMode)
|
|
320
|
+
.with("none", () => {
|
|
321
|
+
return runResolvedBinningStrategy(conf, { hasMidpoint: false })
|
|
322
|
+
})
|
|
323
|
+
.with(P.union("symmetric", "asymmetric"), () => {
|
|
324
|
+
const leftRange = Math.max(conf.midpoint - minValue, 0)
|
|
325
|
+
const rightRange = Math.max(maxValue - conf.midpoint, 0)
|
|
326
|
+
const biggerRange = Math.max(leftRange, rightRange)
|
|
327
|
+
|
|
328
|
+
let posValues: number[]
|
|
329
|
+
if (biggerRange === leftRange) {
|
|
330
|
+
posValues = R.takeWhile(
|
|
331
|
+
conf.sortedValues,
|
|
332
|
+
(v) => v < conf.midpoint
|
|
333
|
+
)
|
|
334
|
+
.map((v) => conf.midpoint - v)
|
|
335
|
+
.reverse()
|
|
336
|
+
} else {
|
|
337
|
+
posValues = R.dropWhile(
|
|
338
|
+
conf.sortedValues,
|
|
339
|
+
(v) => v <= conf.midpoint
|
|
340
|
+
).map((v) => v - conf.midpoint)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const binsRight = runResolvedBinningStrategy(
|
|
344
|
+
{ ...conf, sortedValues: posValues },
|
|
345
|
+
{ hasMidpoint: true }
|
|
346
|
+
)
|
|
347
|
+
const bins = mirrorBinsAroundMidpoint(binsRight, conf.midpoint)
|
|
348
|
+
|
|
349
|
+
if (conf.midpointMode === "symmetric") {
|
|
350
|
+
return bins
|
|
351
|
+
} else {
|
|
352
|
+
// asymmetric: create bins the same way, but then prune any unused ones
|
|
353
|
+
|
|
354
|
+
return pruneUnusedBins(bins, {
|
|
355
|
+
minValue,
|
|
356
|
+
maxValue,
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
})
|
|
360
|
+
.with("same-num-bins", () => {
|
|
361
|
+
const leftValues = R.takeWhile(
|
|
362
|
+
conf.sortedValues,
|
|
363
|
+
(v) => v < conf.midpoint
|
|
364
|
+
)
|
|
365
|
+
.map((v) => conf.midpoint - v)
|
|
366
|
+
.reverse()
|
|
367
|
+
|
|
368
|
+
const rightValues = R.dropWhile(
|
|
369
|
+
conf.sortedValues,
|
|
370
|
+
(v) => v <= conf.midpoint
|
|
371
|
+
).map((v) => v - conf.midpoint)
|
|
372
|
+
|
|
373
|
+
const binsRight = runResolvedBinningStrategy(
|
|
374
|
+
{ ...conf, sortedValues: rightValues },
|
|
375
|
+
{ hasMidpoint: true }
|
|
376
|
+
).filter((v) => v !== 0)
|
|
377
|
+
const binsLeft = runResolvedBinningStrategy(
|
|
378
|
+
{ ...conf, sortedValues: leftValues },
|
|
379
|
+
{ hasMidpoint: true }
|
|
380
|
+
).filter((v) => v !== 0)
|
|
381
|
+
|
|
382
|
+
return [
|
|
383
|
+
...binsLeft.map((v) => conf.midpoint - v).reverse(),
|
|
384
|
+
conf.midpoint,
|
|
385
|
+
...binsRight.map((v) => v + conf.midpoint),
|
|
386
|
+
]
|
|
387
|
+
})
|
|
388
|
+
.with(undefined, () => {
|
|
389
|
+
throw new Error("Invalid unresolved midpoint mode")
|
|
390
|
+
})
|
|
391
|
+
.exhaustive()
|
|
392
|
+
|
|
393
|
+
return bins
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const runResolvedBinningStrategy = (
|
|
397
|
+
conf: ResolvedBinningStrategyConfig,
|
|
398
|
+
{ hasMidpoint }: { hasMidpoint: boolean }
|
|
399
|
+
): number[] => {
|
|
400
|
+
const { minValue, maxValue } = computeMinMaxForStrategy(
|
|
401
|
+
conf.strategy,
|
|
402
|
+
conf.sortedValues,
|
|
403
|
+
conf
|
|
404
|
+
)
|
|
405
|
+
return match(conf.strategy)
|
|
406
|
+
.when(isEqualSizeBinningStrategy, () =>
|
|
407
|
+
runEqualSizeBinningStrategy(
|
|
408
|
+
{ ...conf, minValue, maxValue },
|
|
409
|
+
{ hasMidpoint }
|
|
410
|
+
)
|
|
411
|
+
)
|
|
412
|
+
.when(isLogBinningStrategy, () =>
|
|
413
|
+
runLogBinningStrategy(
|
|
414
|
+
{ ...conf, minValue, maxValue },
|
|
415
|
+
{ hasMidpoint }
|
|
416
|
+
)
|
|
417
|
+
)
|
|
418
|
+
.exhaustive()
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const autoChooseBinningStrategy = (
|
|
422
|
+
conf: BinningStrategyConfig
|
|
423
|
+
): ResolvedBinningStrategy => {
|
|
424
|
+
if (conf.midpointMode !== "none") {
|
|
425
|
+
return "equalSizeBins-normal"
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const posValuesOnly = R.dropWhile(conf.sortedValues, (v) => v <= 0)
|
|
429
|
+
if (posValuesOnly.length === 0) {
|
|
430
|
+
// All values are negative or zero, use equal size bins as a simple fallback (this is rare anyways)
|
|
431
|
+
return "equalSizeBins-normal"
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// If either minValue or maxValue is non-positive, we cannot use log bins
|
|
435
|
+
const hasNegativeMinOrMaxValue =
|
|
436
|
+
(conf.minValue !== undefined && conf.minValue <= 0) ||
|
|
437
|
+
(conf.maxValue !== undefined && conf.maxValue <= 0)
|
|
438
|
+
|
|
439
|
+
let minValueForLog, maxValueForLog
|
|
440
|
+
if (hasNegativeMinOrMaxValue) {
|
|
441
|
+
minValueForLog = maxValueForLog = 0
|
|
442
|
+
} else {
|
|
443
|
+
const { minValue, maxValue } = computeMinMaxForStrategy(
|
|
444
|
+
"log-auto",
|
|
445
|
+
posValuesOnly,
|
|
446
|
+
{ minValue: conf.minValue, maxValue: conf.maxValue }
|
|
447
|
+
)
|
|
448
|
+
minValueForLog = minValue
|
|
449
|
+
maxValueForLog = maxValue
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const magnitudeDiff = calcMagnitudeDiff(minValueForLog, maxValueForLog)
|
|
453
|
+
|
|
454
|
+
if (
|
|
455
|
+
magnitudeDiff < AUTO_EQUAL_BINS_MAX_MAGNITUDE_DIFF ||
|
|
456
|
+
hasNegativeMinOrMaxValue
|
|
457
|
+
) {
|
|
458
|
+
if (conf.isPercent) {
|
|
459
|
+
const lastValue = lastOfNonEmptyArray(posValuesOnly)
|
|
460
|
+
const percentile99 = quantile(posValuesOnly, 0.99)
|
|
461
|
+
if (
|
|
462
|
+
lastValue <= 100 &&
|
|
463
|
+
percentile99 !== undefined &&
|
|
464
|
+
percentile99 >= 60
|
|
465
|
+
) {
|
|
466
|
+
return "equalSizeBins-percent"
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return "equalSizeBins-normal"
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return "log-auto"
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export const mirrorBinsAroundMidpoint = (
|
|
477
|
+
binOffsets: number[],
|
|
478
|
+
midpoint: number
|
|
479
|
+
): number[] => {
|
|
480
|
+
const filteredRightBins = binOffsets.filter((v) => v !== 0)
|
|
481
|
+
const leftBins = filteredRightBins.map((v) => midpoint - v).reverse()
|
|
482
|
+
const rightBins = filteredRightBins.map((v) => v + midpoint)
|
|
483
|
+
return [...leftBins, midpoint, ...rightBins]
|
|
484
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BinningStrategyIncludingManual,
|
|
3
|
+
EqualSizeBinningStrategy,
|
|
4
|
+
ResolvedBinningStrategy,
|
|
5
|
+
} from "../../types/index.js"
|
|
6
|
+
import {
|
|
7
|
+
normaliseToSingleDigitNumber,
|
|
8
|
+
RequiredBy,
|
|
9
|
+
roundSigFig,
|
|
10
|
+
} from "../../utils/index.js"
|
|
11
|
+
import * as R from "remeda"
|
|
12
|
+
import { match, P } from "ts-pattern"
|
|
13
|
+
import { ResolvedBinningStrategyConfig } from "./BinningStrategies.js"
|
|
14
|
+
|
|
15
|
+
export const isEqualSizeBinningStrategy = (
|
|
16
|
+
strategy: BinningStrategyIncludingManual | ResolvedBinningStrategy
|
|
17
|
+
): strategy is EqualSizeBinningStrategy => strategy.startsWith("equalSizeBins-")
|
|
18
|
+
|
|
19
|
+
export const runEqualSizeBinningStrategy = (
|
|
20
|
+
conf: RequiredBy<ResolvedBinningStrategyConfig, "minValue" | "maxValue">,
|
|
21
|
+
{ hasMidpoint }: { hasMidpoint?: boolean } = {}
|
|
22
|
+
): number[] => {
|
|
23
|
+
if (!isEqualSizeBinningStrategy(conf.strategy)) {
|
|
24
|
+
throw new Error("Invalid strategy")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { minValue, maxValue } = conf
|
|
28
|
+
const targetBinCount = getTargetBinCountForEqualSizeBinsStrategy(
|
|
29
|
+
conf.strategy,
|
|
30
|
+
{ hasMidpoint }
|
|
31
|
+
)
|
|
32
|
+
return createEqualSizeBins({ minValue, maxValue, targetBinCount })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The equal-size binning strategies all operate with a range of target bins depending on the strategy.
|
|
37
|
+
* For example, [5, 9] means that there should be at least 5 bins and at most 9.
|
|
38
|
+
* If there is a midpoint, the number of bins needs to be adjusted downward, because we are potentially
|
|
39
|
+
* creating up to twice as many bins overall.
|
|
40
|
+
*/
|
|
41
|
+
const getTargetBinCountForEqualSizeBinsStrategy = (
|
|
42
|
+
strategy: EqualSizeBinningStrategy,
|
|
43
|
+
{ hasMidpoint }: { hasMidpoint?: boolean } = {}
|
|
44
|
+
): readonly [number, number] => {
|
|
45
|
+
return match(strategy)
|
|
46
|
+
.with("equalSizeBins-few-bins", () =>
|
|
47
|
+
hasMidpoint ? ([1, 3] as const) : ([2, 5] as const)
|
|
48
|
+
)
|
|
49
|
+
.with("equalSizeBins-normal", () =>
|
|
50
|
+
hasMidpoint ? ([3, 6] as const) : ([5, 9] as const)
|
|
51
|
+
)
|
|
52
|
+
.with(
|
|
53
|
+
P.union("equalSizeBins-many-bins", "equalSizeBins-percent"),
|
|
54
|
+
() => (hasMidpoint ? ([4, 8] as const) : ([8, 12] as const))
|
|
55
|
+
)
|
|
56
|
+
.exhaustive()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const createEqualSizeBins = ({
|
|
60
|
+
minValue,
|
|
61
|
+
maxValue,
|
|
62
|
+
targetBinCount,
|
|
63
|
+
}: {
|
|
64
|
+
minValue: number
|
|
65
|
+
maxValue: number
|
|
66
|
+
targetBinCount: readonly number[]
|
|
67
|
+
}): number[] => {
|
|
68
|
+
if (minValue > maxValue) {
|
|
69
|
+
throw new Error("minValue must be less than maxValue")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const range = maxValue - minValue
|
|
73
|
+
|
|
74
|
+
// Normalise the range to be within 1 and 10
|
|
75
|
+
const { normalised: normalisedRange, factor } =
|
|
76
|
+
normaliseToSingleDigitNumber(range)
|
|
77
|
+
|
|
78
|
+
// These are all common and "good" step sizes
|
|
79
|
+
const stepSizeCandidates = [1, 0.1, 0.5, 0.2, 2, 3, 0.3, 0.75, 0.25]
|
|
80
|
+
|
|
81
|
+
// Compute the number of bins we would get for each candidate step size
|
|
82
|
+
const stepSizeInfo = stepSizeCandidates.map((candidateStepSize) => {
|
|
83
|
+
// if (normalisedRange === 0) return true
|
|
84
|
+
|
|
85
|
+
const numSteps = Math.ceil(normalisedRange / candidateStepSize)
|
|
86
|
+
return { stepSize: candidateStepSize, numSteps }
|
|
87
|
+
// return numSteps >= targetBinCount[0] && numSteps <= targetBinCount[1]
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// Find the first step size that gives us the target bin count
|
|
91
|
+
let stepSize = stepSizeInfo.find(
|
|
92
|
+
(info) =>
|
|
93
|
+
info.numSteps >= targetBinCount[0] &&
|
|
94
|
+
info.numSteps <= targetBinCount[1]
|
|
95
|
+
)?.stepSize
|
|
96
|
+
|
|
97
|
+
if (stepSize === undefined) {
|
|
98
|
+
// It could be interesting to see which charts cause this
|
|
99
|
+
console.warn(
|
|
100
|
+
"Equal size binning: Couldn't find a step size fitting targetBinCount; instead using next-best candidate",
|
|
101
|
+
{ targetBinCount, minValue, maxValue }
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
// prefer too many bins over too few (by weighing "too many" less highly than "too few")
|
|
105
|
+
const stepSizeDifference = (candidate: number): number =>
|
|
106
|
+
Math.max(candidate - targetBinCount[1], 0) * 1 + // this is how many more bins there are than targetBinCount specifies
|
|
107
|
+
Math.max(targetBinCount[0] - candidate, 0) * 2 // and this is how many fewer there are
|
|
108
|
+
|
|
109
|
+
// Find the step size that minimizes the distance function, i.e. such that we are not much above/below what targetBinCount requests
|
|
110
|
+
stepSize = R.firstBy(stepSizeInfo, (info) =>
|
|
111
|
+
stepSizeDifference(info.numSteps)
|
|
112
|
+
)?.stepSize
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (stepSize === undefined) {
|
|
116
|
+
// This should never happen
|
|
117
|
+
throw new Error("Could not find a valid step size")
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const actualStepSize = stepSize * factor
|
|
121
|
+
|
|
122
|
+
// Round min value to step size, e.g. if the step size is 20 then this would round 50 down to 40
|
|
123
|
+
const minValueRounded =
|
|
124
|
+
Math.floor(minValue / actualStepSize) * actualStepSize
|
|
125
|
+
|
|
126
|
+
const steps = Math.ceil((maxValue - minValueRounded) / actualStepSize)
|
|
127
|
+
|
|
128
|
+
return R.range(0, steps + 1).map((i) => {
|
|
129
|
+
const value = i * actualStepSize
|
|
130
|
+
return minValueRounded + roundSigFig(value, 3) // to avoid floating point issues
|
|
131
|
+
})
|
|
132
|
+
}
|