@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,793 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import * as _ from "lodash-es"
|
|
3
|
+
import * as Papa from "papaparse"
|
|
4
|
+
import * as R from "remeda"
|
|
5
|
+
import { sampleFrom, slugifySameCase, ColumnSlug } from "../utils/index.js"
|
|
6
|
+
import {
|
|
7
|
+
CoreColumnStore,
|
|
8
|
+
CoreRow,
|
|
9
|
+
CoreMatrix,
|
|
10
|
+
Time,
|
|
11
|
+
CoreValueType,
|
|
12
|
+
ColumnTypeNames,
|
|
13
|
+
CoreColumnDef,
|
|
14
|
+
ErrorValue,
|
|
15
|
+
OwidEntityCodeColumnDef,
|
|
16
|
+
OwidEntityIdColumnDef,
|
|
17
|
+
OwidEntityNameColumnDef,
|
|
18
|
+
OwidTableSlugs,
|
|
19
|
+
} from "../types/index.js"
|
|
20
|
+
import { ErrorValueTypes, DroppedForTesting } from "./ErrorValues.js"
|
|
21
|
+
|
|
22
|
+
export const columnStoreToRows = (
|
|
23
|
+
columnStore: CoreColumnStore
|
|
24
|
+
): Record<string, CoreValueType>[] => {
|
|
25
|
+
const firstCol = Object.values(columnStore)[0]
|
|
26
|
+
if (!firstCol) return []
|
|
27
|
+
const slugs = Object.keys(columnStore)
|
|
28
|
+
return firstCol.map((val, index) => {
|
|
29
|
+
const newRow: Record<string, CoreValueType> = {}
|
|
30
|
+
slugs.forEach((slug) => {
|
|
31
|
+
newRow[slug] = columnStore[slug][index]
|
|
32
|
+
})
|
|
33
|
+
return newRow
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// If string exceeds maxLength, will replace the end char with a ... and drop the rest
|
|
38
|
+
export const truncate = (str: string, maxLength: number): string =>
|
|
39
|
+
str.length > maxLength ? `${str.substr(0, maxLength - 3)}...` : str
|
|
40
|
+
|
|
41
|
+
// Picks a type for each column from the first row then autotypes all rows after that so all values in
|
|
42
|
+
// a column will have the same type. Only chooses between strings and numbers.
|
|
43
|
+
const numberOnly = /^-?\d+\.?\d*$/
|
|
44
|
+
type RawRow = Record<string, unknown> | undefined
|
|
45
|
+
type ParsedRow = Record<string, string | number | ErrorValue> | undefined | null
|
|
46
|
+
export const makeAutoTypeFn = (
|
|
47
|
+
numericSlugs?: ColumnSlug[]
|
|
48
|
+
): ((object?: RawRow) => ParsedRow) => {
|
|
49
|
+
const slugToType: any = {}
|
|
50
|
+
numericSlugs?.forEach((slug) => {
|
|
51
|
+
slugToType[slug] = "number"
|
|
52
|
+
})
|
|
53
|
+
return (object: RawRow): ParsedRow => {
|
|
54
|
+
for (const columnSlug in object) {
|
|
55
|
+
const value = object[columnSlug]
|
|
56
|
+
const type = slugToType[columnSlug]
|
|
57
|
+
if (type === "string") {
|
|
58
|
+
object[columnSlug] = value
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const number = parseFloat(value as string) // The "+" type casting that d3 does for perf converts "" to 0, so use parseFloat.
|
|
63
|
+
if (type === "number") {
|
|
64
|
+
object[columnSlug] = isNaN(number)
|
|
65
|
+
? ErrorValueTypes.NaNButShouldBeNumber
|
|
66
|
+
: number
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (isNaN(number) || !numberOnly.test(value as string)) {
|
|
71
|
+
object[columnSlug] = value
|
|
72
|
+
slugToType[columnSlug] = "string"
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
object[columnSlug] = number
|
|
77
|
+
slugToType[columnSlug] = "number"
|
|
78
|
+
}
|
|
79
|
+
return object as ParsedRow
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Removes whitespace and non-word characters from column slugs if any exist.
|
|
84
|
+
// The original names are moved to the name property on the column def.
|
|
85
|
+
export const standardizeSlugs = (
|
|
86
|
+
rows: CoreRow[]
|
|
87
|
+
): { rows: CoreRow[]; defs: { name: string; slug: string }[] } | undefined => {
|
|
88
|
+
const firstRow = rows[0] ?? {}
|
|
89
|
+
const colsToRename = Object.keys(firstRow)
|
|
90
|
+
.map((name) => {
|
|
91
|
+
return {
|
|
92
|
+
name,
|
|
93
|
+
slug: slugifySameCase(name),
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
.filter((col) => col.name !== col.slug)
|
|
97
|
+
if (!colsToRename.length) return undefined
|
|
98
|
+
|
|
99
|
+
rows.forEach((row: CoreRow) => {
|
|
100
|
+
colsToRename.forEach((col) => {
|
|
101
|
+
row[col.slug] = row[col.name]
|
|
102
|
+
delete row[col.name]
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
return { rows, defs: colsToRename }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const guessColumnDefFromSlugAndRow = (
|
|
110
|
+
slug: string,
|
|
111
|
+
sampleValue: unknown
|
|
112
|
+
): CoreColumnDef => {
|
|
113
|
+
const valueType = typeof sampleValue
|
|
114
|
+
|
|
115
|
+
const name = slug
|
|
116
|
+
|
|
117
|
+
if (slug === "Entity")
|
|
118
|
+
return {
|
|
119
|
+
slug,
|
|
120
|
+
type: ColumnTypeNames.EntityName,
|
|
121
|
+
name,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (slug === "day")
|
|
125
|
+
return {
|
|
126
|
+
slug,
|
|
127
|
+
type: ColumnTypeNames.Day,
|
|
128
|
+
name: "Day",
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (slug === "year" || slug === "Year")
|
|
132
|
+
return {
|
|
133
|
+
slug,
|
|
134
|
+
type: ColumnTypeNames.Year,
|
|
135
|
+
name: "Year",
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (slug === OwidTableSlugs.entityName) return OwidEntityNameColumnDef
|
|
139
|
+
if (slug === OwidTableSlugs.entityCode) return OwidEntityCodeColumnDef
|
|
140
|
+
if (slug === OwidTableSlugs.entityId) return OwidEntityIdColumnDef
|
|
141
|
+
|
|
142
|
+
if (slug === "date")
|
|
143
|
+
return {
|
|
144
|
+
slug,
|
|
145
|
+
type: ColumnTypeNames.Date,
|
|
146
|
+
name: "Date",
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (valueType === "number")
|
|
150
|
+
return {
|
|
151
|
+
slug,
|
|
152
|
+
type: ColumnTypeNames.Numeric,
|
|
153
|
+
name,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (valueType === "string") {
|
|
157
|
+
if (String(sampleValue).match(/^\d+$/))
|
|
158
|
+
return {
|
|
159
|
+
slug,
|
|
160
|
+
type: ColumnTypeNames.Numeric,
|
|
161
|
+
name,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { slug, type: ColumnTypeNames.String, name }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const makeRowFromColumnStore = (
|
|
169
|
+
rowIndex: number,
|
|
170
|
+
columnStore: CoreColumnStore
|
|
171
|
+
): CoreRow => {
|
|
172
|
+
const row: CoreRow = {}
|
|
173
|
+
Object.entries(columnStore).forEach(([slug, col]) => {
|
|
174
|
+
if (col.length <= rowIndex) row[slug] = undefined
|
|
175
|
+
else row[slug] = col[rowIndex]
|
|
176
|
+
})
|
|
177
|
+
return row
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type
|
|
181
|
+
export interface InterpolationContext {}
|
|
182
|
+
|
|
183
|
+
export interface LinearInterpolationContext extends InterpolationContext {
|
|
184
|
+
// whether to extrapolate a variable at the start or end, where we cannot do linear interpolation
|
|
185
|
+
// but need to just copy over the first/last value present over to empty fields.
|
|
186
|
+
// e.g. [Error, Error, 2, 3, 4] would become [2, 2, 2, 3, 4] with extrapolateAtStart=true.
|
|
187
|
+
extrapolateAtStart?: boolean
|
|
188
|
+
extrapolateAtEnd?: boolean
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface ToleranceInterpolationContext extends InterpolationContext {
|
|
192
|
+
timeToleranceForwards: number
|
|
193
|
+
timeToleranceBackwards: number
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export type InterpolationProvider<C extends InterpolationContext> = (
|
|
197
|
+
valuesSortedByTimeAsc: CoreValueType[],
|
|
198
|
+
timesAsc: Time[],
|
|
199
|
+
validIndices: number[],
|
|
200
|
+
context: C,
|
|
201
|
+
start: number,
|
|
202
|
+
end: number
|
|
203
|
+
) => void
|
|
204
|
+
|
|
205
|
+
export function linearInterpolation(
|
|
206
|
+
valuesSortedByTimeAsc: CoreValueType[],
|
|
207
|
+
timesAsc: Time[],
|
|
208
|
+
validIndices: number[],
|
|
209
|
+
context: LinearInterpolationContext,
|
|
210
|
+
start: number = 0,
|
|
211
|
+
end: number = valuesSortedByTimeAsc.length
|
|
212
|
+
): void {
|
|
213
|
+
if (!valuesSortedByTimeAsc.length) return
|
|
214
|
+
|
|
215
|
+
const startIndexInValidIndices = R.sortedIndex(validIndices, start)
|
|
216
|
+
const endIndexInValidIndices = R.sortedIndex(validIndices, end)
|
|
217
|
+
|
|
218
|
+
const distBetweenStartAndEnd =
|
|
219
|
+
endIndexInValidIndices - startIndexInValidIndices
|
|
220
|
+
|
|
221
|
+
if (distBetweenStartAndEnd === 0) {
|
|
222
|
+
// No valid values in this range, we can short-circuit
|
|
223
|
+
for (let index = start; index < end; index++) {
|
|
224
|
+
valuesSortedByTimeAsc[index] =
|
|
225
|
+
ErrorValueTypes.NoValueForInterpolation
|
|
226
|
+
}
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
// All values in this range are already valid, we don't need to do anything
|
|
230
|
+
else if (distBetweenStartAndEnd === end - start) {
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let currentValidIndexPointer = startIndexInValidIndices
|
|
235
|
+
let prevNonBlankIndex: number | undefined = undefined
|
|
236
|
+
let nextNonBlankIndex: number | undefined = undefined
|
|
237
|
+
|
|
238
|
+
for (let index = start; index < end; index++) {
|
|
239
|
+
if (
|
|
240
|
+
nextNonBlankIndex !== -1 &&
|
|
241
|
+
(nextNonBlankIndex === undefined || nextNonBlankIndex <= index)
|
|
242
|
+
) {
|
|
243
|
+
nextNonBlankIndex = validIndices[currentValidIndexPointer] ?? -1
|
|
244
|
+
if (nextNonBlankIndex >= end) {
|
|
245
|
+
nextNonBlankIndex = -1
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check whether the current index has a valid value
|
|
250
|
+
if (index === nextNonBlankIndex) {
|
|
251
|
+
prevNonBlankIndex = index
|
|
252
|
+
currentValidIndexPointer++
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const timeOfCurrent = timesAsc[index]
|
|
257
|
+
|
|
258
|
+
const timeOfPrevIndex =
|
|
259
|
+
prevNonBlankIndex !== undefined
|
|
260
|
+
? (timesAsc[prevNonBlankIndex] ?? -Infinity)
|
|
261
|
+
: -Infinity
|
|
262
|
+
const timeOfNextIndex = timesAsc[nextNonBlankIndex] ?? Infinity
|
|
263
|
+
|
|
264
|
+
const prevValue: number | undefined =
|
|
265
|
+
prevNonBlankIndex !== undefined
|
|
266
|
+
? (valuesSortedByTimeAsc[prevNonBlankIndex] as
|
|
267
|
+
| number
|
|
268
|
+
| undefined)
|
|
269
|
+
: undefined
|
|
270
|
+
const nextValue: number | undefined = valuesSortedByTimeAsc[
|
|
271
|
+
nextNonBlankIndex
|
|
272
|
+
] as number | undefined
|
|
273
|
+
|
|
274
|
+
let value
|
|
275
|
+
if (typeof prevValue === "number" && typeof nextValue === "number") {
|
|
276
|
+
const distLeft = timeOfCurrent - timeOfPrevIndex
|
|
277
|
+
const distRight = timeOfNextIndex - timeOfCurrent
|
|
278
|
+
value =
|
|
279
|
+
(prevValue * distRight + nextValue * distLeft) /
|
|
280
|
+
(distLeft + distRight)
|
|
281
|
+
} else if (typeof prevValue === "number" && context.extrapolateAtEnd)
|
|
282
|
+
value = prevValue
|
|
283
|
+
else if (typeof nextValue === "number" && context.extrapolateAtStart)
|
|
284
|
+
value = nextValue
|
|
285
|
+
else value = ErrorValueTypes.NoValueForInterpolation
|
|
286
|
+
|
|
287
|
+
valuesSortedByTimeAsc[index] = value
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function toleranceInterpolation(
|
|
292
|
+
valuesSortedByTimeAsc: CoreValueType[],
|
|
293
|
+
timesAsc: Time[],
|
|
294
|
+
validIndices: number[],
|
|
295
|
+
context: ToleranceInterpolationContext,
|
|
296
|
+
start: number = 0,
|
|
297
|
+
end: number = valuesSortedByTimeAsc.length
|
|
298
|
+
): void {
|
|
299
|
+
if (!valuesSortedByTimeAsc.length) return
|
|
300
|
+
|
|
301
|
+
// `validIndices` contains the indexes of all non-error values in `valuesSortedByTimeAsc`.
|
|
302
|
+
// Here, we find the *indexes* of where `start` and `end` appear in `validIndices` (note that
|
|
303
|
+
// `start` and `end` themselves are indexes into `valuesSortedByTimeAsc`).
|
|
304
|
+
const startIndexInValidIndices = R.sortedIndex(validIndices, start)
|
|
305
|
+
const endIndexInValidIndices = R.sortedIndex(validIndices, end)
|
|
306
|
+
|
|
307
|
+
const distBetweenStartAndEnd =
|
|
308
|
+
endIndexInValidIndices - startIndexInValidIndices
|
|
309
|
+
|
|
310
|
+
// If the two indices are the same, then there are no valid values in this range.
|
|
311
|
+
if (distBetweenStartAndEnd === 0) {
|
|
312
|
+
// No valid values in this range, we can short-circuit
|
|
313
|
+
for (let index = start; index < end; index++) {
|
|
314
|
+
valuesSortedByTimeAsc[index] =
|
|
315
|
+
ErrorValueTypes.NoValueWithinTolerance
|
|
316
|
+
}
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
// All values in this range are valid, we can short-circuit
|
|
320
|
+
else if (distBetweenStartAndEnd === end - start) {
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
// If the two indices differ by 1, then there is only one valid value in this range.
|
|
324
|
+
else if (distBetweenStartAndEnd === 1) {
|
|
325
|
+
// Only one valid value in this range, we can short-circuit
|
|
326
|
+
const onlyValidIndex = validIndices[startIndexInValidIndices]
|
|
327
|
+
const timeOfOnlyValid = timesAsc[onlyValidIndex]
|
|
328
|
+
|
|
329
|
+
for (let index = start; index < end; index++) {
|
|
330
|
+
if (index === onlyValidIndex) continue
|
|
331
|
+
|
|
332
|
+
const timeOfCurrent = timesAsc[index]
|
|
333
|
+
const timeDiff = timeOfOnlyValid - timeOfCurrent
|
|
334
|
+
if (
|
|
335
|
+
(timeDiff < 0 &&
|
|
336
|
+
Math.abs(timeDiff) <= context.timeToleranceBackwards) ||
|
|
337
|
+
(timeDiff > 0 &&
|
|
338
|
+
Math.abs(timeDiff) <= context.timeToleranceForwards)
|
|
339
|
+
) {
|
|
340
|
+
valuesSortedByTimeAsc[index] =
|
|
341
|
+
valuesSortedByTimeAsc[onlyValidIndex]
|
|
342
|
+
timesAsc[index] = timeOfOnlyValid
|
|
343
|
+
} else {
|
|
344
|
+
valuesSortedByTimeAsc[index] =
|
|
345
|
+
ErrorValueTypes.NoValueWithinTolerance
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let currentValidIndexPointer = startIndexInValidIndices
|
|
352
|
+
let prevNonBlankIndex: number | undefined = undefined
|
|
353
|
+
let nextNonBlankIndex: number | undefined = undefined
|
|
354
|
+
|
|
355
|
+
for (let index = start; index < end; index++) {
|
|
356
|
+
if (
|
|
357
|
+
nextNonBlankIndex !== -1 &&
|
|
358
|
+
(nextNonBlankIndex === undefined || nextNonBlankIndex <= index)
|
|
359
|
+
) {
|
|
360
|
+
nextNonBlankIndex = validIndices[currentValidIndexPointer] ?? -1
|
|
361
|
+
if (nextNonBlankIndex >= end) {
|
|
362
|
+
nextNonBlankIndex = -1
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Check whether the current index has a valid value
|
|
367
|
+
if (index === nextNonBlankIndex) {
|
|
368
|
+
prevNonBlankIndex = index
|
|
369
|
+
currentValidIndexPointer++
|
|
370
|
+
continue
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const timeOfCurrent = timesAsc[index]
|
|
374
|
+
|
|
375
|
+
const timeOfPrevIndex =
|
|
376
|
+
prevNonBlankIndex !== undefined
|
|
377
|
+
? (timesAsc[prevNonBlankIndex] ?? -Infinity)
|
|
378
|
+
: -Infinity
|
|
379
|
+
const timeOfNextIndex = timesAsc[nextNonBlankIndex] ?? Infinity
|
|
380
|
+
|
|
381
|
+
const prevTimeDiff = timeOfCurrent - timeOfPrevIndex
|
|
382
|
+
const nextTimeDiff = timeOfNextIndex - timeOfCurrent
|
|
383
|
+
|
|
384
|
+
if (
|
|
385
|
+
nextNonBlankIndex !== undefined &&
|
|
386
|
+
nextNonBlankIndex !== -1 &&
|
|
387
|
+
nextTimeDiff <= prevTimeDiff &&
|
|
388
|
+
nextTimeDiff <= context.timeToleranceForwards
|
|
389
|
+
) {
|
|
390
|
+
valuesSortedByTimeAsc[index] =
|
|
391
|
+
valuesSortedByTimeAsc[nextNonBlankIndex]
|
|
392
|
+
timesAsc[index] = timesAsc[nextNonBlankIndex]
|
|
393
|
+
} else if (
|
|
394
|
+
prevNonBlankIndex !== undefined &&
|
|
395
|
+
prevTimeDiff <= context.timeToleranceBackwards
|
|
396
|
+
) {
|
|
397
|
+
valuesSortedByTimeAsc[index] =
|
|
398
|
+
valuesSortedByTimeAsc[prevNonBlankIndex]
|
|
399
|
+
timesAsc[index] = timesAsc[prevNonBlankIndex]
|
|
400
|
+
} else
|
|
401
|
+
valuesSortedByTimeAsc[index] =
|
|
402
|
+
ErrorValueTypes.NoValueWithinTolerance
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// A dumb function for making a function that makes a key for a row given certain columns.
|
|
407
|
+
export const makeKeyFn = (
|
|
408
|
+
columnStore: CoreColumnStore,
|
|
409
|
+
columnSlugs: ColumnSlug[]
|
|
410
|
+
): ((rowIndex: number) => string) => {
|
|
411
|
+
const cols = columnSlugs.map((slug) => columnStore[slug])
|
|
412
|
+
|
|
413
|
+
const toStr = (val: CoreValueType): string =>
|
|
414
|
+
val === null || val === undefined
|
|
415
|
+
? ""
|
|
416
|
+
: typeof val === "string"
|
|
417
|
+
? val
|
|
418
|
+
: (val as any) + ""
|
|
419
|
+
|
|
420
|
+
// perf: this function is performance-critical, and so for the common cases of 1, 2, or 3 columns, we can provide a
|
|
421
|
+
// faster implementation.
|
|
422
|
+
if (cols.length === 0) return () => ""
|
|
423
|
+
if (cols.length === 1) {
|
|
424
|
+
const col = cols[0]
|
|
425
|
+
return (rowIndex: number): string => toStr(col[rowIndex])
|
|
426
|
+
}
|
|
427
|
+
if (cols.length === 2) {
|
|
428
|
+
const col0 = cols[0],
|
|
429
|
+
col1 = cols[1]
|
|
430
|
+
return (rowIndex: number): string =>
|
|
431
|
+
`${toStr(col0[rowIndex])} ${toStr(col1[rowIndex])}`
|
|
432
|
+
}
|
|
433
|
+
if (cols.length === 3) {
|
|
434
|
+
const col0 = cols[0],
|
|
435
|
+
col1 = cols[1],
|
|
436
|
+
col2 = cols[2]
|
|
437
|
+
return (rowIndex: number): string =>
|
|
438
|
+
`${toStr(col0[rowIndex])} ${toStr(col1[rowIndex])} ${toStr(
|
|
439
|
+
col2[rowIndex]
|
|
440
|
+
)}`
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return (rowIndex: number): string =>
|
|
444
|
+
// toString() handles `undefined` and `null` values, which can be in the table.
|
|
445
|
+
cols.map((col) => toStr(col[rowIndex])).join(" ")
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const getColumnStoreLength = (store: CoreColumnStore): number => {
|
|
449
|
+
return _.max(Object.values(store).map((v) => v.length)) ?? 0
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export const concatColumnStores = (
|
|
453
|
+
stores: CoreColumnStore[],
|
|
454
|
+
slugsToKeep?: ColumnSlug[]
|
|
455
|
+
): CoreColumnStore => {
|
|
456
|
+
if (!stores.length) return {}
|
|
457
|
+
|
|
458
|
+
const lengths = stores.map(getColumnStoreLength)
|
|
459
|
+
const slugs = slugsToKeep ?? Object.keys(R.first(stores)!)
|
|
460
|
+
|
|
461
|
+
const newColumnStore: CoreColumnStore = {}
|
|
462
|
+
|
|
463
|
+
// The below code is performance-critical.
|
|
464
|
+
// That's why it's written using for loops and mutable arrays rather than using map or flatMap:
|
|
465
|
+
// To this day, that's still faster in JS.
|
|
466
|
+
slugs.forEach((slug) => {
|
|
467
|
+
let newColumnValues: CoreValueType[] = []
|
|
468
|
+
for (const [i, store] of stores.entries()) {
|
|
469
|
+
const values = store[slug] ?? []
|
|
470
|
+
const toFill = Math.max(0, lengths[i] - values.length)
|
|
471
|
+
|
|
472
|
+
newColumnValues = newColumnValues.concat(values)
|
|
473
|
+
if (toFill > 0) {
|
|
474
|
+
newColumnValues = newColumnValues.concat(
|
|
475
|
+
new Array(toFill).fill(
|
|
476
|
+
ErrorValueTypes.MissingValuePlaceholder
|
|
477
|
+
)
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
newColumnStore[slug] = newColumnValues
|
|
482
|
+
})
|
|
483
|
+
return newColumnStore
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export const rowsToColumnStore = (rows: CoreRow[]): CoreColumnStore => {
|
|
487
|
+
const columnsObject: CoreColumnStore = {}
|
|
488
|
+
if (!rows.length) return columnsObject
|
|
489
|
+
|
|
490
|
+
Object.keys(rows[0]).forEach((slug) => {
|
|
491
|
+
columnsObject[slug] = rows.map((row) => row[slug])
|
|
492
|
+
})
|
|
493
|
+
return columnsObject
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const guessColumnDefsFromRows = (
|
|
497
|
+
rows: CoreRow[],
|
|
498
|
+
definedSlugs: Map<ColumnSlug, any>
|
|
499
|
+
): CoreColumnDef[] => {
|
|
500
|
+
if (!rows[0]) return []
|
|
501
|
+
return Object.keys(rows[0])
|
|
502
|
+
.filter((slug) => !definedSlugs.has(slug))
|
|
503
|
+
.map((slug) => {
|
|
504
|
+
const firstRowWithValue = rows.find(
|
|
505
|
+
(row) =>
|
|
506
|
+
row[slug] !== undefined &&
|
|
507
|
+
row[slug] !== null &&
|
|
508
|
+
row[slug] !== ""
|
|
509
|
+
)
|
|
510
|
+
const firstValue = firstRowWithValue
|
|
511
|
+
? firstRowWithValue[slug]
|
|
512
|
+
: undefined
|
|
513
|
+
|
|
514
|
+
return guessColumnDefFromSlugAndRow(slug, firstValue)
|
|
515
|
+
})
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export const autodetectColumnDefs = (
|
|
519
|
+
rowsOrColumnStore: CoreColumnStore | CoreRow[],
|
|
520
|
+
definedSlugs: Map<ColumnSlug, any>
|
|
521
|
+
): CoreColumnDef[] => {
|
|
522
|
+
if (!Array.isArray(rowsOrColumnStore)) {
|
|
523
|
+
const columnStore = rowsOrColumnStore as CoreColumnStore
|
|
524
|
+
return Object.keys(columnStore)
|
|
525
|
+
.filter((slug) => !definedSlugs.has(slug))
|
|
526
|
+
.map((slug) => {
|
|
527
|
+
return guessColumnDefFromSlugAndRow(
|
|
528
|
+
slug,
|
|
529
|
+
columnStore[slug].find(
|
|
530
|
+
(val) => val !== undefined && val !== null
|
|
531
|
+
)
|
|
532
|
+
)
|
|
533
|
+
})
|
|
534
|
+
}
|
|
535
|
+
return guessColumnDefsFromRows(rowsOrColumnStore, definedSlugs)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Convenience method when you are replacing columns
|
|
539
|
+
export const replaceDef = <ColumnDef extends CoreColumnDef>(
|
|
540
|
+
defs: ColumnDef[],
|
|
541
|
+
newDefs: ColumnDef[]
|
|
542
|
+
): ColumnDef[] =>
|
|
543
|
+
defs.map((def) => {
|
|
544
|
+
const newDef = newDefs.find((newDef) => newDef.slug === def.slug)
|
|
545
|
+
return newDef ?? def
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
export const renameColumnStore = (
|
|
549
|
+
columnStore: CoreColumnStore,
|
|
550
|
+
columnRenameMap: { [columnSlug: string]: ColumnSlug }
|
|
551
|
+
): CoreColumnStore => {
|
|
552
|
+
const newStore: CoreColumnStore = {}
|
|
553
|
+
Object.keys(columnStore).forEach((slug) => {
|
|
554
|
+
if (columnRenameMap[slug])
|
|
555
|
+
newStore[columnRenameMap[slug]] = columnStore[slug]
|
|
556
|
+
else newStore[slug] = columnStore[slug]
|
|
557
|
+
})
|
|
558
|
+
return newStore
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Returns a Set of random indexes to drop in an array, preserving the order of the array
|
|
562
|
+
export const getDropIndexes = (
|
|
563
|
+
arrayLength: number,
|
|
564
|
+
howMany: number,
|
|
565
|
+
seed = Date.now()
|
|
566
|
+
): Set<number> => new Set(sampleFrom(_.range(0, arrayLength), howMany, seed))
|
|
567
|
+
|
|
568
|
+
export const replaceRandomCellsInColumnStore = (
|
|
569
|
+
columnStore: CoreColumnStore,
|
|
570
|
+
howMany = 1,
|
|
571
|
+
columnSlugs: ColumnSlug[] = [],
|
|
572
|
+
seed = Date.now(),
|
|
573
|
+
replacementGenerator: () => any = (): DroppedForTesting =>
|
|
574
|
+
ErrorValueTypes.DroppedForTesting
|
|
575
|
+
): CoreColumnStore => {
|
|
576
|
+
const newStore: CoreColumnStore = Object.assign({}, columnStore)
|
|
577
|
+
columnSlugs.forEach((slug) => {
|
|
578
|
+
const values = newStore[slug]
|
|
579
|
+
const indexesToDrop = getDropIndexes(values.length, howMany, seed)
|
|
580
|
+
newStore[slug] = values.map((value, index) =>
|
|
581
|
+
indexesToDrop.has(index) ? replacementGenerator() : value
|
|
582
|
+
)
|
|
583
|
+
})
|
|
584
|
+
return newStore
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export class Timer {
|
|
588
|
+
constructor() {
|
|
589
|
+
this._tickTime = Date.now()
|
|
590
|
+
this._firstTickTime = this._tickTime
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
private _tickTime: number
|
|
594
|
+
private _firstTickTime: number
|
|
595
|
+
|
|
596
|
+
tick(msg?: string): number {
|
|
597
|
+
const elapsed = Date.now() - this._tickTime
|
|
598
|
+
// eslint-disable-next-line no-console
|
|
599
|
+
if (msg) console.log(`${elapsed}ms ${msg}`)
|
|
600
|
+
this._tickTime = Date.now()
|
|
601
|
+
return elapsed
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
getTotalElapsedTime(): number {
|
|
605
|
+
return Date.now() - this._firstTickTime
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export const rowsFromMatrix = (matrix: CoreMatrix): any[] => {
|
|
610
|
+
const table = trimMatrix(matrix)
|
|
611
|
+
const header = table[0]
|
|
612
|
+
return table.slice(1).map((row) => {
|
|
613
|
+
const newRow: any = {}
|
|
614
|
+
header.forEach((col, index) => {
|
|
615
|
+
newRow[col] = row[index]
|
|
616
|
+
})
|
|
617
|
+
return newRow
|
|
618
|
+
})
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const trimEmptyColumns = (matrix: CoreMatrix): CoreMatrix =>
|
|
622
|
+
matrix.map(trimArray)
|
|
623
|
+
export const trimMatrix = (matrix: CoreMatrix): CoreMatrix =>
|
|
624
|
+
trimEmptyColumns(trimEmptyRows(matrix))
|
|
625
|
+
|
|
626
|
+
export const matrixToDelimited = (
|
|
627
|
+
table: CoreMatrix,
|
|
628
|
+
delimiter = "\t"
|
|
629
|
+
): string => {
|
|
630
|
+
return table
|
|
631
|
+
.map((row: any) =>
|
|
632
|
+
row
|
|
633
|
+
.map((cell: any) =>
|
|
634
|
+
cell === null || cell === undefined ? "" : cell
|
|
635
|
+
)
|
|
636
|
+
.join(delimiter)
|
|
637
|
+
)
|
|
638
|
+
.join("\n")
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* An array object representing all parsed rows. The array is enhanced with a property listing
|
|
643
|
+
* the names of the parsed columns.
|
|
644
|
+
*/
|
|
645
|
+
export interface DSVParsedArray<T> extends Array<T> {
|
|
646
|
+
/**
|
|
647
|
+
* List of column names.
|
|
648
|
+
*/
|
|
649
|
+
columns: Array<keyof T>
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
export const parseDelimited = (
|
|
653
|
+
str: string,
|
|
654
|
+
delimiter?: string,
|
|
655
|
+
parseFn?: (rawRow: RawRow) => ParsedRow
|
|
656
|
+
): DSVParsedArray<Record<string, any>> => {
|
|
657
|
+
// Convert PapaParse result to D3 format for backwards compatibility
|
|
658
|
+
const papaParseToD3 = ({
|
|
659
|
+
data,
|
|
660
|
+
meta,
|
|
661
|
+
}: Papa.ParseResult<any>): DSVParsedArray<Record<string, any>> => {
|
|
662
|
+
const dsvParsed = data as DSVParsedArray<Record<string, any>>
|
|
663
|
+
dsvParsed.columns = meta.fields || []
|
|
664
|
+
|
|
665
|
+
// Some downstream methods expect all rows to have fields for all columns,
|
|
666
|
+
// even if they are missing in that row. This loop ensures that.
|
|
667
|
+
for (const row of dsvParsed) {
|
|
668
|
+
for (const col of dsvParsed.columns) {
|
|
669
|
+
if (!(col in row)) row[col] = ""
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return dsvParsed
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const result = Papa.parse(str, {
|
|
677
|
+
delimiter: delimiter ?? detectDelimiter(str),
|
|
678
|
+
header: true,
|
|
679
|
+
skipEmptyLines: true,
|
|
680
|
+
transformHeader: (header: string) => header.trim(),
|
|
681
|
+
transform: (value: string) => value.trim(),
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
if (parseFn) {
|
|
685
|
+
result.data = result.data.map((rawRow) => parseFn(rawRow as RawRow))
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return papaParseToD3(result)
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
export const detectDelimiter = (str: string): "\t" | "," | " " =>
|
|
692
|
+
str.includes("\t") ? "\t" : str.includes(",") ? "," : " "
|
|
693
|
+
|
|
694
|
+
export const rowsToMatrix = (rows: any[]): CoreMatrix | undefined =>
|
|
695
|
+
rows.length
|
|
696
|
+
? [Object.keys(rows[0]), ...rows.map((row) => Object.values(row))]
|
|
697
|
+
: undefined
|
|
698
|
+
|
|
699
|
+
const isRowEmpty = (row: any[]): boolean => row.every(isCellEmpty)
|
|
700
|
+
|
|
701
|
+
export const isCellEmpty = (cell: unknown): boolean =>
|
|
702
|
+
cell === null || cell === undefined || cell === ""
|
|
703
|
+
|
|
704
|
+
export const trimEmptyRows = (matrix: CoreMatrix): CoreMatrix => {
|
|
705
|
+
let trimAt = undefined
|
|
706
|
+
for (let rowIndex = matrix.length - 1; rowIndex >= 0; rowIndex--) {
|
|
707
|
+
if (!isRowEmpty(matrix[rowIndex])) break
|
|
708
|
+
trimAt = rowIndex
|
|
709
|
+
}
|
|
710
|
+
return trimAt === undefined ? matrix : matrix.slice(0, trimAt)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
export const trimArray = (arr: any[]): any[] => {
|
|
714
|
+
let rightIndex: number
|
|
715
|
+
for (rightIndex = arr.length - 1; rightIndex >= 0; rightIndex--) {
|
|
716
|
+
if (!isCellEmpty(arr[rightIndex])) break
|
|
717
|
+
}
|
|
718
|
+
return arr.slice(0, rightIndex + 1)
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const applyNewSortOrder = (arr: any[], newOrder: number[]): any[] => {
|
|
722
|
+
const newArr = new Array(arr.length)
|
|
723
|
+
for (let i = 0; i < newOrder.length; i++) {
|
|
724
|
+
const index = newOrder[i]
|
|
725
|
+
newArr[i] = arr[index]
|
|
726
|
+
}
|
|
727
|
+
return newArr
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
export const sortColumnStore = (
|
|
731
|
+
columnStore: CoreColumnStore,
|
|
732
|
+
slugs: ColumnSlug[]
|
|
733
|
+
): CoreColumnStore => {
|
|
734
|
+
const firstCol = Object.values(columnStore)[0]
|
|
735
|
+
if (!firstCol) return {}
|
|
736
|
+
const len = firstCol.length
|
|
737
|
+
const sortFn = makeSortByFn(columnStore, slugs)
|
|
738
|
+
|
|
739
|
+
// Check if column store is already sorted.
|
|
740
|
+
// If it's not sorted, we will detect that within the first few iterations usually.
|
|
741
|
+
let isSorted = true
|
|
742
|
+
for (let i = 0; i < len - 1; i++) {
|
|
743
|
+
if (sortFn(i, i + 1) > 0) {
|
|
744
|
+
isSorted = false
|
|
745
|
+
break
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
// Column store is already sorted; return existing store unchanged
|
|
749
|
+
if (isSorted) return columnStore
|
|
750
|
+
|
|
751
|
+
const newStore: CoreColumnStore = {}
|
|
752
|
+
// Compute an array of the new sort order, i.e. [0, 1, 2, ...] -> [2, 0, 1]
|
|
753
|
+
const newOrder = _.range(0, len).sort(sortFn)
|
|
754
|
+
Object.entries(columnStore).forEach(([slug, colValues]) => {
|
|
755
|
+
newStore[slug] = applyNewSortOrder(colValues, newOrder)
|
|
756
|
+
})
|
|
757
|
+
|
|
758
|
+
return newStore
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const makeSortByFn = (
|
|
762
|
+
columnStore: CoreColumnStore,
|
|
763
|
+
columnSlugs: ColumnSlug[]
|
|
764
|
+
): ((indexA: number, indexB: number) => 1 | 0 | -1) => {
|
|
765
|
+
const cols = columnSlugs.map((slug) => columnStore[slug])
|
|
766
|
+
|
|
767
|
+
return (indexA: number, indexB: number): 1 | 0 | -1 => {
|
|
768
|
+
const nodeAFirst = -1
|
|
769
|
+
const nodeBFirst = 1
|
|
770
|
+
|
|
771
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
772
|
+
for (let colIndex = 0; colIndex < cols.length; colIndex++) {
|
|
773
|
+
const col = cols[colIndex]
|
|
774
|
+
const av = col[indexA]
|
|
775
|
+
const bv = col[indexB]
|
|
776
|
+
if (av < bv) return nodeAFirst
|
|
777
|
+
if (av > bv) return nodeBFirst
|
|
778
|
+
// todo: handle ErrorValues
|
|
779
|
+
}
|
|
780
|
+
return 0
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
export const emptyColumnsInFirstRowInDelimited = (str: string): string[] => {
|
|
785
|
+
// todo: don't split a big string here, just do a faster first line scan
|
|
786
|
+
const shortCsv = parseDelimited(str.split("\n").slice(0, 2).join("\n"))
|
|
787
|
+
const firstRow: any = shortCsv[0] ?? {}
|
|
788
|
+
const emptySlugs: string[] = []
|
|
789
|
+
Object.keys(firstRow).forEach((slug) => {
|
|
790
|
+
if (firstRow[slug] === "") emptySlugs.push(slug)
|
|
791
|
+
})
|
|
792
|
+
return emptySlugs
|
|
793
|
+
}
|