@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,1355 @@
|
|
|
1
|
+
import * as _ from "lodash-es"
|
|
2
|
+
import {
|
|
3
|
+
csvEscape,
|
|
4
|
+
intersection,
|
|
5
|
+
isPresent,
|
|
6
|
+
ColumnSlug,
|
|
7
|
+
PrimitiveType,
|
|
8
|
+
imemo,
|
|
9
|
+
} from "../utils/index.js"
|
|
10
|
+
import {
|
|
11
|
+
CoreColumn,
|
|
12
|
+
ColumnTypeMap,
|
|
13
|
+
MissingColumn,
|
|
14
|
+
TimeColumn,
|
|
15
|
+
} from "./CoreTableColumns.js"
|
|
16
|
+
import {
|
|
17
|
+
CoreColumnStore,
|
|
18
|
+
CoreRow,
|
|
19
|
+
CoreTableInputOption,
|
|
20
|
+
Time,
|
|
21
|
+
TransformType,
|
|
22
|
+
ValueRange,
|
|
23
|
+
CoreQuery,
|
|
24
|
+
CoreValueType,
|
|
25
|
+
InputType,
|
|
26
|
+
CoreMatrix,
|
|
27
|
+
TableSlug,
|
|
28
|
+
ColumnTypeNames,
|
|
29
|
+
CoreColumnDef,
|
|
30
|
+
JsTypes,
|
|
31
|
+
OwidTableSlugs,
|
|
32
|
+
OwidColumnDef,
|
|
33
|
+
} from "../types/index.js"
|
|
34
|
+
import {
|
|
35
|
+
makeAutoTypeFn,
|
|
36
|
+
columnStoreToRows,
|
|
37
|
+
makeKeyFn,
|
|
38
|
+
makeRowFromColumnStore,
|
|
39
|
+
standardizeSlugs,
|
|
40
|
+
concatColumnStores,
|
|
41
|
+
rowsToColumnStore,
|
|
42
|
+
autodetectColumnDefs,
|
|
43
|
+
renameColumnStore,
|
|
44
|
+
replaceRandomCellsInColumnStore,
|
|
45
|
+
parseDelimited,
|
|
46
|
+
rowsFromMatrix,
|
|
47
|
+
sortColumnStore,
|
|
48
|
+
emptyColumnsInFirstRowInDelimited,
|
|
49
|
+
truncate,
|
|
50
|
+
} from "./CoreTableUtils.js"
|
|
51
|
+
import {
|
|
52
|
+
ErrorValueTypes,
|
|
53
|
+
isNotErrorValue,
|
|
54
|
+
DroppedForTesting,
|
|
55
|
+
} from "./ErrorValues.js"
|
|
56
|
+
import { applyTransforms, extractTransformNameAndParams } from "./Transforms.js"
|
|
57
|
+
|
|
58
|
+
interface AdvancedOptions {
|
|
59
|
+
tableDescription?: string
|
|
60
|
+
transformCategory?: TransformType
|
|
61
|
+
parent?: CoreTable
|
|
62
|
+
filterMask?: FilterMask
|
|
63
|
+
tableSlug?: TableSlug
|
|
64
|
+
forceReuseColumnStore?: boolean
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The complex generic with default here just enables you to optionally specify a more
|
|
68
|
+
// narrow interface for the input rows. This is helpful for OwidTable.
|
|
69
|
+
export class CoreTable<
|
|
70
|
+
ROW_TYPE extends CoreRow = CoreRow,
|
|
71
|
+
COL_DEF_TYPE extends CoreColumnDef = CoreColumnDef,
|
|
72
|
+
> {
|
|
73
|
+
private _columns: Map<ColumnSlug, CoreColumn> = new Map()
|
|
74
|
+
protected parent?: this
|
|
75
|
+
tableDescription: string
|
|
76
|
+
private timeToLoad = 0
|
|
77
|
+
private initTime = Date.now()
|
|
78
|
+
|
|
79
|
+
private originalInput: CoreTableInputOption
|
|
80
|
+
private advancedOptions: AdvancedOptions
|
|
81
|
+
|
|
82
|
+
private inputColumnDefs: COL_DEF_TYPE[]
|
|
83
|
+
constructor(
|
|
84
|
+
input: CoreTableInputOption = [],
|
|
85
|
+
inputColumnDefs: COL_DEF_TYPE[] | string = [],
|
|
86
|
+
advancedOptions: AdvancedOptions = {}
|
|
87
|
+
) {
|
|
88
|
+
const start = Date.now() // Perf aid
|
|
89
|
+
const { parent, tableDescription = "" } = advancedOptions
|
|
90
|
+
|
|
91
|
+
this.originalInput = input
|
|
92
|
+
this.tableDescription = tableDescription
|
|
93
|
+
this.parent = parent as this
|
|
94
|
+
this.inputColumnDefs =
|
|
95
|
+
typeof inputColumnDefs === "string"
|
|
96
|
+
? columnDefinitionsFromInput<COL_DEF_TYPE>(inputColumnDefs)
|
|
97
|
+
: inputColumnDefs
|
|
98
|
+
|
|
99
|
+
// Column definitions with a "duplicate" transform are merged with the column definition of the specified source column
|
|
100
|
+
this.inputColumnDefs = this.inputColumnDefs.map((def) => {
|
|
101
|
+
if (!def.transform) return def
|
|
102
|
+
const transform = extractTransformNameAndParams(def.transform)
|
|
103
|
+
if (transform?.transformName !== "duplicate") return def
|
|
104
|
+
|
|
105
|
+
const sourceSlug = transform.params[0]
|
|
106
|
+
const sourceDef = this.inputColumnDefs.find(
|
|
107
|
+
(def) => def.slug === sourceSlug
|
|
108
|
+
)
|
|
109
|
+
return { ...sourceDef, ...def }
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// If any values were passed in, copy those to column store now and then remove them from column definitions.
|
|
113
|
+
// todo: remove values property entirely? may be an anti-pattern.
|
|
114
|
+
this.inputColumnDefs = this.inputColumnDefs.map((def) => {
|
|
115
|
+
if (!def.values) return def
|
|
116
|
+
this.valuesFromColumnDefs[def.slug] = def.values
|
|
117
|
+
const copy = {
|
|
118
|
+
...def,
|
|
119
|
+
}
|
|
120
|
+
delete copy.values
|
|
121
|
+
return copy
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
this.inputColumnDefs.forEach((def) => this.setColumn(def))
|
|
125
|
+
|
|
126
|
+
this.advancedOptions = advancedOptions
|
|
127
|
+
|
|
128
|
+
// If this has a parent table, than we expect all defs. This makes "deletes" and "renames" fast.
|
|
129
|
+
// If this is the first input table, then we do a simple check to generate any missing column defs.
|
|
130
|
+
if (!parent)
|
|
131
|
+
autodetectColumnDefs(this.inputColumnStore, this._columns).forEach(
|
|
132
|
+
(def) => this.setColumn(def as COL_DEF_TYPE)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
this.timeToLoad = Date.now() - start // Perf aid
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private valuesFromColumnDefs: CoreColumnStore = {}
|
|
139
|
+
|
|
140
|
+
// A method currently used just in debugging but may be useful in the author backend.
|
|
141
|
+
// If your charts look funny, a good thing to check is if the autodetected columns are wrong.
|
|
142
|
+
get autodetectedColumnDefs(): CoreTable {
|
|
143
|
+
const providedSlugs = new Set(
|
|
144
|
+
this.inputColumnDefs.map((def) => def.slug)
|
|
145
|
+
)
|
|
146
|
+
return new CoreTable(
|
|
147
|
+
this.defs.filter((def) => !providedSlugs.has(def.slug))
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@imemo get transformCategory(): TransformType {
|
|
152
|
+
const { advancedOptions, inputType } = this
|
|
153
|
+
if (advancedOptions.transformCategory)
|
|
154
|
+
return advancedOptions.transformCategory
|
|
155
|
+
|
|
156
|
+
if (inputType === InputType.Delimited)
|
|
157
|
+
return TransformType.LoadFromDelimited
|
|
158
|
+
if (inputType === InputType.Matrix) return TransformType.LoadFromMatrix
|
|
159
|
+
if (inputType === InputType.RowStore)
|
|
160
|
+
return TransformType.LoadFromRowStore
|
|
161
|
+
return TransformType.LoadFromColumnStore
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// If the input is a column store, returns that. If it is DSV, parses that and turns it into a column store.
|
|
165
|
+
// If it is a Rows[], turns it into a column store.
|
|
166
|
+
@imemo private get inputColumnStore(): CoreColumnStore {
|
|
167
|
+
const { originalInput, inputType } = this
|
|
168
|
+
|
|
169
|
+
if (inputType === InputType.Delimited)
|
|
170
|
+
return this.delimitedAsColumnStore
|
|
171
|
+
else if (inputType === InputType.Matrix)
|
|
172
|
+
return rowsToColumnStore(
|
|
173
|
+
rowsFromMatrix(originalInput as CoreMatrix)
|
|
174
|
+
)
|
|
175
|
+
else if (inputType === InputType.RowStore)
|
|
176
|
+
return rowsToColumnStore(originalInput as CoreRow[])
|
|
177
|
+
return originalInput as CoreColumnStore
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// This can, in theory, be used to detect whether we can reuse the input column store, which can save us time parsing.
|
|
181
|
+
// However, I'm not entirely sure whether the conditions included here are complete, so we don't currently use it.
|
|
182
|
+
// We do use `advancedOptions.forceReuseColumnStore`, however.
|
|
183
|
+
@imemo get canReuseInputColumnStore(): boolean {
|
|
184
|
+
const { inputColumnDefs, valuesFromColumnDefs, columnSlugs } = this
|
|
185
|
+
|
|
186
|
+
const storeHasAllColumns = columnSlugs.every(
|
|
187
|
+
(slug) => slug in this.inputColumnStore
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
const columnsFromTransforms = inputColumnDefs.filter(
|
|
191
|
+
(def) => def.transform && !def.transformHasRun
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
!this.colsToParse.length &&
|
|
196
|
+
storeHasAllColumns &&
|
|
197
|
+
!Object.keys(valuesFromColumnDefs).length &&
|
|
198
|
+
!columnsFromTransforms.length
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@imemo get columnStore(): CoreColumnStore {
|
|
203
|
+
const { inputColumnStore, advancedOptions } = this
|
|
204
|
+
|
|
205
|
+
if (advancedOptions.forceReuseColumnStore) {
|
|
206
|
+
if (advancedOptions.filterMask)
|
|
207
|
+
return advancedOptions.filterMask.apply(inputColumnStore)
|
|
208
|
+
else return inputColumnStore
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const {
|
|
212
|
+
valuesFromColumnDefs,
|
|
213
|
+
inputColumnsToParsedColumnStore,
|
|
214
|
+
inputColumnDefs,
|
|
215
|
+
} = this
|
|
216
|
+
|
|
217
|
+
// Set blank columns
|
|
218
|
+
let columnStore = Object.assign(
|
|
219
|
+
{},
|
|
220
|
+
this.blankColumnStore,
|
|
221
|
+
inputColumnStore,
|
|
222
|
+
valuesFromColumnDefs
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
// Overwrite any non-parsed columns with parsed values
|
|
226
|
+
if (Object.keys(inputColumnsToParsedColumnStore).length)
|
|
227
|
+
columnStore = Object.assign(
|
|
228
|
+
columnStore,
|
|
229
|
+
inputColumnsToParsedColumnStore
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
// If we ever pass Mobx observable arrays, we need to convert them to regular arrays.
|
|
233
|
+
// Otherwise, operations like `.concat()` will break in unexpected ways.
|
|
234
|
+
// See https://github.com/mobxjs/mobx/blob/mobx4and5/docs/best/pitfalls.md
|
|
235
|
+
// Also, see https://github.com/owid/owid-grapher/issues/2948 for an issue caused by this problem.
|
|
236
|
+
type CoreValueArrayThatMayBeMobxProxy = CoreValueType[] & {
|
|
237
|
+
toJS?: () => CoreValueType[]
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
for (const [slug, values] of Object.entries(columnStore)) {
|
|
241
|
+
const valuesThatMayBeMobxProxy =
|
|
242
|
+
values as CoreValueArrayThatMayBeMobxProxy
|
|
243
|
+
if (typeof valuesThatMayBeMobxProxy.toJS === "function") {
|
|
244
|
+
columnStore[slug] = valuesThatMayBeMobxProxy.toJS()
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const columnsFromTransforms = inputColumnDefs.filter(
|
|
249
|
+
(def) => def.transform && !def.transformHasRun
|
|
250
|
+
) // todo: sort by graph dependency order
|
|
251
|
+
if (columnsFromTransforms.length) {
|
|
252
|
+
columnStore = applyTransforms(columnStore, columnsFromTransforms)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return advancedOptions.filterMask
|
|
256
|
+
? advancedOptions.filterMask.apply(columnStore)
|
|
257
|
+
: columnStore
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private get blankColumnStore(): CoreColumnStore {
|
|
261
|
+
const columnsObject: CoreColumnStore = {}
|
|
262
|
+
this.columnSlugs.forEach((slug) => {
|
|
263
|
+
columnsObject[slug] = []
|
|
264
|
+
})
|
|
265
|
+
return columnsObject
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
@imemo private get delimitedAsColumnStore(): CoreColumnStore {
|
|
269
|
+
const { originalInput, _numericColumnSlugs } = this
|
|
270
|
+
const parsed = parseDelimited(
|
|
271
|
+
originalInput as string,
|
|
272
|
+
undefined,
|
|
273
|
+
makeAutoTypeFn(_numericColumnSlugs)
|
|
274
|
+
) as any
|
|
275
|
+
// Remove the columns object from the parsed object
|
|
276
|
+
delete parsed.columns
|
|
277
|
+
|
|
278
|
+
const renamedRows = standardizeSlugs(parsed) // todo: pass renamed defs back in.
|
|
279
|
+
return rowsToColumnStore(renamedRows ? renamedRows.rows : parsed)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
get tableSlug(): string | undefined {
|
|
283
|
+
return this.advancedOptions.tableSlug
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private get inputColumnsToParsedColumnStore(): CoreColumnStore {
|
|
287
|
+
const { inputColumnStore, colsToParse } = this
|
|
288
|
+
const columnsObject: CoreColumnStore = {}
|
|
289
|
+
if (!colsToParse.length) return columnsObject
|
|
290
|
+
const missingCols: CoreColumn[] = []
|
|
291
|
+
let len = 0
|
|
292
|
+
colsToParse.forEach((col) => {
|
|
293
|
+
const { slug } = col
|
|
294
|
+
const unparsedVals = inputColumnStore[slug]
|
|
295
|
+
if (!unparsedVals) {
|
|
296
|
+
missingCols.push(col)
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
columnsObject[slug] = unparsedVals.map((val) => col.parse(val))
|
|
300
|
+
len = columnsObject[slug].length
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
// If column defs were provided but there were no values provided for those columns, create blank columns the same size
|
|
304
|
+
// as the filled columns.
|
|
305
|
+
missingCols.forEach(
|
|
306
|
+
(col) =>
|
|
307
|
+
(columnsObject[col.slug] = _.range(0, len).map(() =>
|
|
308
|
+
col.parse(undefined)
|
|
309
|
+
))
|
|
310
|
+
)
|
|
311
|
+
return columnsObject
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private get colsToParse(): CoreColumn[] {
|
|
315
|
+
const { inputType, columnsAsArray, inputColumnStore } = this
|
|
316
|
+
const columnsToMaybeParse = columnsAsArray.filter(
|
|
317
|
+
(col) => !col.def.skipParsing
|
|
318
|
+
)
|
|
319
|
+
const firstInputRow = makeRowFromColumnStore(0, inputColumnStore)
|
|
320
|
+
if (inputType === InputType.Delimited) {
|
|
321
|
+
const missingTypes = new Set(
|
|
322
|
+
this.getColumns(
|
|
323
|
+
emptyColumnsInFirstRowInDelimited(
|
|
324
|
+
this.originalInput as string
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
) // Our autotyping is poor if the first value in a column is empty
|
|
328
|
+
return columnsToMaybeParse.filter(
|
|
329
|
+
(col) =>
|
|
330
|
+
col.needsParsing(firstInputRow[col.slug]) ||
|
|
331
|
+
missingTypes.has(col)
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (this.parent || !firstInputRow) return []
|
|
336
|
+
|
|
337
|
+
// The default behavior is to assume some missing or bad data in user data, so we always parse the full input the first time we load
|
|
338
|
+
// user data, with the exception of columns that have values passed directly.
|
|
339
|
+
// Todo: measure the perf hit and add a parameter to opt out of this this if you know the data is complete?
|
|
340
|
+
const alreadyTypedSlugs = new Set(
|
|
341
|
+
Object.keys(this.valuesFromColumnDefs)
|
|
342
|
+
)
|
|
343
|
+
if (this.isRoot) {
|
|
344
|
+
return columnsToMaybeParse.filter(
|
|
345
|
+
(col) => !alreadyTypedSlugs.has(col.slug)
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return columnsToMaybeParse.filter(
|
|
350
|
+
(col) =>
|
|
351
|
+
!alreadyTypedSlugs.has(col.slug) ||
|
|
352
|
+
col.needsParsing(firstInputRow[col.slug])
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private setColumn(def: COL_DEF_TYPE): void {
|
|
357
|
+
const { type, slug } = def
|
|
358
|
+
const ColumnType = (type && ColumnTypeMap[type]) || ColumnTypeMap.String
|
|
359
|
+
this._columns.set(slug, new ColumnType(this, def))
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
protected transform(
|
|
363
|
+
rowsOrColumnStore: ROW_TYPE[] | CoreColumnStore | CoreMatrix,
|
|
364
|
+
defs: COL_DEF_TYPE[],
|
|
365
|
+
tableDescription: string,
|
|
366
|
+
transformCategory: TransformType,
|
|
367
|
+
filterMask?: FilterMask
|
|
368
|
+
): this {
|
|
369
|
+
// The combo of the "this" return type and then casting this to any allows subclasses to create transforms of the
|
|
370
|
+
// same type. The "any" typing is very brief (the returned type will have the same type as the instance being transformed).
|
|
371
|
+
return new (this.constructor as any)(rowsOrColumnStore, defs, {
|
|
372
|
+
parent: this,
|
|
373
|
+
tableDescription,
|
|
374
|
+
transformCategory,
|
|
375
|
+
filterMask,
|
|
376
|
+
} as AdvancedOptions)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
protected noopTransform(tableDescription: string): this {
|
|
380
|
+
return new (this.constructor as any)(this.columnStore, this.defs, {
|
|
381
|
+
parent: this,
|
|
382
|
+
tableDescription,
|
|
383
|
+
transformCategory: TransformType.Noop,
|
|
384
|
+
forceReuseColumnStore: true,
|
|
385
|
+
} as AdvancedOptions)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Time between when the parent table finished loading and this table started constructing.
|
|
389
|
+
// A large time may just be due to a transform only happening after a user action, or it
|
|
390
|
+
// could be do to other sync code executing between transforms.
|
|
391
|
+
private get betweenTime(): number {
|
|
392
|
+
return this.parent
|
|
393
|
+
? this.initTime - (this.parent.initTime + this.parent.timeToLoad)
|
|
394
|
+
: 0
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@imemo get rows(): ROW_TYPE[] {
|
|
398
|
+
return columnStoreToRows(this.columnStore) as ROW_TYPE[]
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
@imemo get indices(): number[] {
|
|
402
|
+
return _.range(0, this.numRows)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
*[Symbol.iterator](): Generator<CoreRow, void, unknown> {
|
|
406
|
+
const { columnStore, numRows } = this
|
|
407
|
+
for (let index = 0; index < numRows; index++) {
|
|
408
|
+
yield makeRowFromColumnStore(index, columnStore)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
getTimesAtIndices(indices: number[]): number[] {
|
|
413
|
+
if (!indices.length) return []
|
|
414
|
+
return this.getValuesAtIndices(this.timeColumn!.slug, indices) as Time[]
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
getValuesAtIndices(
|
|
418
|
+
columnSlug: ColumnSlug,
|
|
419
|
+
indices: number[]
|
|
420
|
+
): CoreValueType[] {
|
|
421
|
+
const values = this.get(columnSlug).valuesIncludingErrorValues
|
|
422
|
+
return indices.map((index) => values[index])
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
@imemo get firstRow(): ROW_TYPE {
|
|
426
|
+
return makeRowFromColumnStore(0, this.columnStore) as ROW_TYPE
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
@imemo get lastRow(): ROW_TYPE {
|
|
430
|
+
return makeRowFromColumnStore(
|
|
431
|
+
this.numRows - 1,
|
|
432
|
+
this.columnStore
|
|
433
|
+
) as ROW_TYPE
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
@imemo get numRows(): number {
|
|
437
|
+
const firstColValues = Object.values(this.columnStore)[0]
|
|
438
|
+
return firstColValues ? firstColValues.length : 0
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
@imemo get numColumns(): number {
|
|
442
|
+
return this.columnSlugs.length
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
get(columnSlug: ColumnSlug | undefined): CoreColumn {
|
|
446
|
+
if (columnSlug === undefined)
|
|
447
|
+
return new MissingColumn(this, {
|
|
448
|
+
slug: `undefined_slug`,
|
|
449
|
+
})
|
|
450
|
+
return (
|
|
451
|
+
this._columns.get(columnSlug) ??
|
|
452
|
+
new MissingColumn(this, {
|
|
453
|
+
slug: columnSlug,
|
|
454
|
+
})
|
|
455
|
+
)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
has(columnSlug: ColumnSlug | undefined): boolean {
|
|
459
|
+
if (columnSlug === undefined) return false
|
|
460
|
+
return this._columns.has(columnSlug)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
getFirstColumnWithType(
|
|
464
|
+
columnTypeName: ColumnTypeNames
|
|
465
|
+
): CoreColumn | undefined {
|
|
466
|
+
return this.columnsAsArray.find(
|
|
467
|
+
(col) => col.def.type === columnTypeName
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// todo: move this. time methods should not be in CoreTable, in OwidTable instead (which is really TimeSeriesTable).
|
|
472
|
+
// TODO: remove this. Currently we use this to get the right day/year time formatting. For now a chart is either a "day chart" or a "year chart".
|
|
473
|
+
// But we can have charts with multiple time columns. Ideally each place that needs access to the timeColumn, would get the specific column
|
|
474
|
+
// and not the first time column from the table.
|
|
475
|
+
@imemo get timeColumn(): TimeColumn | MissingColumn {
|
|
476
|
+
// "time" is the canonical time column slug.
|
|
477
|
+
// See LegacyToOwidTable where this column is injected for all Graphers.
|
|
478
|
+
const maybeTimeColumn = this.get(OwidTableSlugs.time)
|
|
479
|
+
if (maybeTimeColumn instanceof ColumnTypeMap.Time)
|
|
480
|
+
return maybeTimeColumn
|
|
481
|
+
// If a valid "time" column doesn't exist, find _some_ time column to use.
|
|
482
|
+
// This is somewhat unreliable and currently only used to infer the time
|
|
483
|
+
// column on explorers.
|
|
484
|
+
return (this.columnsAsArray.find(
|
|
485
|
+
(col) => col instanceof ColumnTypeMap.Day
|
|
486
|
+
) ??
|
|
487
|
+
this.columnsAsArray.find(
|
|
488
|
+
(col) => col instanceof ColumnTypeMap.Date
|
|
489
|
+
) ??
|
|
490
|
+
this.columnsAsArray.find(
|
|
491
|
+
(col) => col instanceof ColumnTypeMap.Year
|
|
492
|
+
) ??
|
|
493
|
+
this.columnsAsArray.find(
|
|
494
|
+
(col) => col instanceof ColumnTypeMap.Quarter
|
|
495
|
+
) ??
|
|
496
|
+
maybeTimeColumn) as TimeColumn | MissingColumn
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// todo: should be on owidtable
|
|
500
|
+
@imemo get entityNameColumn(): CoreColumn {
|
|
501
|
+
return (
|
|
502
|
+
this.getFirstColumnWithType(ColumnTypeNames.EntityName) ??
|
|
503
|
+
this.get(OwidTableSlugs.entityName)
|
|
504
|
+
)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// todo: should be on owidtable
|
|
508
|
+
@imemo get entityNameSlug(): string {
|
|
509
|
+
return this.entityNameColumn.slug
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
@imemo private get columnsWithParseErrors(): CoreColumn[] {
|
|
513
|
+
return this.columnsAsArray.filter((col) => col.numErrorValues)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
@imemo get numColumnsWithErrorValues(): number {
|
|
517
|
+
return this.columnsWithParseErrors.length
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
@imemo get numErrorValues(): number {
|
|
521
|
+
return _.sum(this.columnsAsArray.map((col) => col.numErrorValues))
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
@imemo get numValidCells(): number {
|
|
525
|
+
return _.sum(this.columnsAsArray.map((col) => col.numValues))
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
@imemo get colStoreIsEqualToParent(): boolean {
|
|
529
|
+
return this.parent
|
|
530
|
+
? this.columnStore === this.parent.columnStore
|
|
531
|
+
: false
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
get rootTable(): this {
|
|
535
|
+
return this.parent ? this.parent.rootTable : this
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Returns a string map (aka index) where the keys are the combined string values of columnSlug[], and the values
|
|
540
|
+
* are the indices for the rows that match.
|
|
541
|
+
*
|
|
542
|
+
* {country: "USA", population: 100}
|
|
543
|
+
*
|
|
544
|
+
* So `table.rowIndex(["country", "population"]).get("USA 100")` would return [0].
|
|
545
|
+
*
|
|
546
|
+
*/
|
|
547
|
+
rowIndex(columnSlugs: ColumnSlug[]): Map<string, number[]> {
|
|
548
|
+
const index = new Map<string, number[]>()
|
|
549
|
+
const keyFn = makeKeyFn(this.columnStore, columnSlugs)
|
|
550
|
+
for (let i = 0; i < this.numRows; i++) {
|
|
551
|
+
const key = keyFn(i)
|
|
552
|
+
if (index.has(key)) index.get(key)!.push(i)
|
|
553
|
+
else index.set(key, [i])
|
|
554
|
+
}
|
|
555
|
+
return index
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Returns a map (aka index) where the keys are the values of the indexColumnSlug, and the values
|
|
560
|
+
* are the values of the valueColumnSlug.
|
|
561
|
+
*
|
|
562
|
+
* {country: "USA", population: 100}
|
|
563
|
+
*
|
|
564
|
+
* So `table.valueIndex("country", "population").get("USA")` would return 100.
|
|
565
|
+
*
|
|
566
|
+
*/
|
|
567
|
+
protected valueIndex(
|
|
568
|
+
indexColumnSlug: ColumnSlug,
|
|
569
|
+
valueColumnSlug: ColumnSlug
|
|
570
|
+
): Map<PrimitiveType, PrimitiveType> {
|
|
571
|
+
const indexCol = this.get(indexColumnSlug)
|
|
572
|
+
const valueCol = this.get(valueColumnSlug)
|
|
573
|
+
const indexValues = indexCol.valuesIncludingErrorValues
|
|
574
|
+
const valueValues = valueCol.valuesIncludingErrorValues
|
|
575
|
+
const valueIndices = new Set(valueCol.validRowIndices)
|
|
576
|
+
const intersection = indexCol.validRowIndices.filter((index) =>
|
|
577
|
+
valueIndices.has(index)
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
const map = new Map<PrimitiveType, PrimitiveType>()
|
|
581
|
+
intersection.forEach((index) => {
|
|
582
|
+
map.set(
|
|
583
|
+
indexValues[index] as PrimitiveType,
|
|
584
|
+
valueValues[index] as PrimitiveType
|
|
585
|
+
)
|
|
586
|
+
})
|
|
587
|
+
return map
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
grep(searchStringOrRegex: string | RegExp): this {
|
|
591
|
+
return this.rowFilter((row) => {
|
|
592
|
+
const line = Object.values(row).join(" ")
|
|
593
|
+
return typeof searchStringOrRegex === "string"
|
|
594
|
+
? line.includes(searchStringOrRegex)
|
|
595
|
+
: searchStringOrRegex.test(line)
|
|
596
|
+
}, `Kept rows that matched '${searchStringOrRegex.toString()}'`)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
rowFilter(
|
|
600
|
+
predicate: (row: ROW_TYPE, index: number) => boolean,
|
|
601
|
+
opName: string
|
|
602
|
+
): this {
|
|
603
|
+
const mask = new FilterMask(this.numRows, this.rows.map(predicate)) // Warning: this will be slow
|
|
604
|
+
if (mask.isNoop()) return this.noopTransform(opName)
|
|
605
|
+
|
|
606
|
+
return this.transform(
|
|
607
|
+
this.columnStore,
|
|
608
|
+
this.defs,
|
|
609
|
+
opName,
|
|
610
|
+
TransformType.FilterRows,
|
|
611
|
+
mask
|
|
612
|
+
)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
columnFilter(
|
|
616
|
+
columnSlug: ColumnSlug,
|
|
617
|
+
predicate: (value: CoreValueType, index: number) => boolean,
|
|
618
|
+
opName: string
|
|
619
|
+
): this {
|
|
620
|
+
const mask = new FilterMask(
|
|
621
|
+
this.numRows,
|
|
622
|
+
this.get(columnSlug).valuesIncludingErrorValues.map(predicate)
|
|
623
|
+
)
|
|
624
|
+
if (mask.isNoop()) return this.noopTransform(opName)
|
|
625
|
+
|
|
626
|
+
return this.transform(
|
|
627
|
+
this.columnStore,
|
|
628
|
+
this.defs,
|
|
629
|
+
opName,
|
|
630
|
+
TransformType.FilterRows,
|
|
631
|
+
mask
|
|
632
|
+
)
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
sortBy(slugs: ColumnSlug[]): this {
|
|
636
|
+
const description = `Sort by ${slugs.join(",")}`
|
|
637
|
+
const sorted = sortColumnStore(this.columnStore, slugs)
|
|
638
|
+
|
|
639
|
+
if (sorted === this.columnStore) return this.noopTransform(description)
|
|
640
|
+
else
|
|
641
|
+
return this.transform(
|
|
642
|
+
sorted,
|
|
643
|
+
this.defs,
|
|
644
|
+
description,
|
|
645
|
+
TransformType.SortRows
|
|
646
|
+
)
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Assumes table is sorted by columnSlug. Returns an array representing the starting index of each new group.
|
|
650
|
+
protected groupBoundaries(columnSlug: ColumnSlug): number[] {
|
|
651
|
+
const values = this.get(columnSlug).valuesIncludingErrorValues
|
|
652
|
+
const arr: number[] = []
|
|
653
|
+
let last: CoreValueType | undefined = undefined
|
|
654
|
+
for (let i = 0; i < values.length; i++) {
|
|
655
|
+
const val = values[i]
|
|
656
|
+
if (val !== last) {
|
|
657
|
+
arr.push(i)
|
|
658
|
+
last = val
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
// Include the end of the last group, which doesn't result in a change in value above.
|
|
662
|
+
if (values && values.length) {
|
|
663
|
+
arr.push(values.length)
|
|
664
|
+
}
|
|
665
|
+
return arr
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
@imemo get defs(): COL_DEF_TYPE[] {
|
|
669
|
+
return this.columnsAsArray.map((col) => col.def) as COL_DEF_TYPE[]
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
@imemo get columnNames(): string[] {
|
|
673
|
+
return this.columnsAsArray.map((col) => col.name)
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
@imemo get columnTypes(): (ColumnTypeNames | undefined)[] {
|
|
677
|
+
return this.columnsAsArray.map((col) => col.def.type)
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
@imemo get columnJsTypes(): JsTypes[] {
|
|
681
|
+
return this.columnsAsArray.map((col) => col.jsType)
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
@imemo get columnSlugs(): string[] {
|
|
685
|
+
return Array.from(this._columns.keys())
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
@imemo get numericColumnSlugs(): string[] {
|
|
689
|
+
return this._numericColumnSlugs
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
private get _numericColumnSlugs(): string[] {
|
|
693
|
+
return this._columnsAsArray
|
|
694
|
+
.filter((col) => col instanceof ColumnTypeMap.Numeric)
|
|
695
|
+
.map((col) => col.slug)
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
private get _columnsAsArray(): CoreColumn[] {
|
|
699
|
+
return Array.from(this._columns.values())
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
@imemo get columnsAsArray(): CoreColumn[] {
|
|
703
|
+
return this._columnsAsArray
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
getColumns(slugs: ColumnSlug[]): CoreColumn[] {
|
|
707
|
+
return slugs.map((slug) => this.get(slug))
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Get the min and max for multiple columns at once
|
|
711
|
+
domainFor(slugs: ColumnSlug[]): ValueRange {
|
|
712
|
+
const cols = this.getColumns(slugs)
|
|
713
|
+
const mins = cols.map((col) => col.minValue)
|
|
714
|
+
const maxes = cols.map((col) => col.maxValue)
|
|
715
|
+
return [_.min(mins), _.max(maxes)]
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
private get isRoot(): boolean {
|
|
719
|
+
return !this.parent
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
dump(rowLimit = 30): void {
|
|
723
|
+
this.dumpPipeline()
|
|
724
|
+
this.dumpColumns()
|
|
725
|
+
this.dumpRows(rowLimit)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
dumpPipeline(): void {
|
|
729
|
+
// eslint-disable-next-line no-console
|
|
730
|
+
console.table(this.ancestors.map((tb) => tb.explanation))
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
dumpColumns(): void {
|
|
734
|
+
// eslint-disable-next-line no-console
|
|
735
|
+
console.table(this.explainColumns)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
rowsFrom(start: number, end: number): any {
|
|
739
|
+
if (start >= this.numRows) return []
|
|
740
|
+
if (end > this.numRows) end = this.numRows
|
|
741
|
+
return _.range(start, end).map((index) =>
|
|
742
|
+
makeRowFromColumnStore(index, this.columnStore)
|
|
743
|
+
)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
dumpRows(rowLimit = 30): void {
|
|
747
|
+
// eslint-disable-next-line no-console
|
|
748
|
+
console.table(this.rowsFrom(0, rowLimit), this.columnSlugs)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
dumpInputTable(): void {
|
|
752
|
+
// eslint-disable-next-line no-console
|
|
753
|
+
console.table(this.inputAsTable)
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
@imemo private get inputType(): InputType {
|
|
757
|
+
const { originalInput } = this
|
|
758
|
+
if (typeof originalInput === "string") return InputType.Delimited
|
|
759
|
+
if (Array.isArray(originalInput))
|
|
760
|
+
return Array.isArray(originalInput[0])
|
|
761
|
+
? InputType.Matrix
|
|
762
|
+
: InputType.RowStore
|
|
763
|
+
return InputType.ColumnStore
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
@imemo private get inputColumnStoreToRows(): Record<
|
|
767
|
+
string,
|
|
768
|
+
CoreValueType
|
|
769
|
+
>[] {
|
|
770
|
+
return columnStoreToRows(this.inputColumnStore)
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
@imemo private get inputAsTable():
|
|
774
|
+
| Record<string, CoreValueType>[]
|
|
775
|
+
| CoreTableInputOption {
|
|
776
|
+
const { inputType } = this
|
|
777
|
+
return inputType === InputType.ColumnStore
|
|
778
|
+
? this.inputColumnStoreToRows
|
|
779
|
+
: inputType === InputType.Matrix
|
|
780
|
+
? rowsFromMatrix(this.originalInput as CoreMatrix)
|
|
781
|
+
: this.originalInput
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
@imemo private get explainColumns(): Record<string, unknown>[] {
|
|
785
|
+
return this.columnsAsArray.map((col) => {
|
|
786
|
+
const {
|
|
787
|
+
slug,
|
|
788
|
+
jsType,
|
|
789
|
+
name,
|
|
790
|
+
numValues,
|
|
791
|
+
numErrorValues,
|
|
792
|
+
displayName,
|
|
793
|
+
def,
|
|
794
|
+
} = col
|
|
795
|
+
return {
|
|
796
|
+
slug,
|
|
797
|
+
type: def.type,
|
|
798
|
+
jsType,
|
|
799
|
+
name,
|
|
800
|
+
numValues,
|
|
801
|
+
numErrorValues,
|
|
802
|
+
displayName,
|
|
803
|
+
color: def.color,
|
|
804
|
+
}
|
|
805
|
+
})
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
get ancestors(): this[] {
|
|
809
|
+
return this.parent ? [...this.parent.ancestors, this] : [this]
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
@imemo private get numColsToParse(): number {
|
|
813
|
+
return this.colsToParse.length
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
private static guids = 0
|
|
817
|
+
private guid = ++CoreTable.guids
|
|
818
|
+
|
|
819
|
+
private get explanation(): Record<string, unknown> {
|
|
820
|
+
// todo: is there a better way to do this in JS?
|
|
821
|
+
const {
|
|
822
|
+
tableDescription,
|
|
823
|
+
transformCategory,
|
|
824
|
+
guid,
|
|
825
|
+
numColumns,
|
|
826
|
+
numRows,
|
|
827
|
+
betweenTime,
|
|
828
|
+
timeToLoad,
|
|
829
|
+
numColsToParse,
|
|
830
|
+
numValidCells,
|
|
831
|
+
numErrorValues,
|
|
832
|
+
numColumnsWithErrorValues,
|
|
833
|
+
colStoreIsEqualToParent,
|
|
834
|
+
} = this
|
|
835
|
+
return {
|
|
836
|
+
tableDescription: truncate(tableDescription, 40),
|
|
837
|
+
transformCategory,
|
|
838
|
+
guid,
|
|
839
|
+
numColumns,
|
|
840
|
+
numRows,
|
|
841
|
+
betweenTime,
|
|
842
|
+
timeToLoad,
|
|
843
|
+
numColsToParse,
|
|
844
|
+
numValidCells,
|
|
845
|
+
numErrorValues,
|
|
846
|
+
numColumnsWithErrorValues,
|
|
847
|
+
colStoreIsEqualToParent,
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
toCsvWithColumnNames(useShortNames: boolean = false): string {
|
|
852
|
+
const delimiter = ","
|
|
853
|
+
const header =
|
|
854
|
+
this.columnsAsArray
|
|
855
|
+
.map((col) =>
|
|
856
|
+
csvEscape(
|
|
857
|
+
useShortNames && (col.def as OwidColumnDef).shortName
|
|
858
|
+
? (col.def as OwidColumnDef).shortName
|
|
859
|
+
: col.name
|
|
860
|
+
)
|
|
861
|
+
)
|
|
862
|
+
.join(delimiter) + "\n"
|
|
863
|
+
const body = this.rows
|
|
864
|
+
.map((row) =>
|
|
865
|
+
this.columnsAsArray.map(
|
|
866
|
+
(col) => col.formatForCsv(row[col.slug]) ?? ""
|
|
867
|
+
)
|
|
868
|
+
)
|
|
869
|
+
.map((row) => row.join(delimiter))
|
|
870
|
+
.join("\n")
|
|
871
|
+
return header + body
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
rowsAt(indices: number[]): ROW_TYPE[] {
|
|
875
|
+
const { columnStore } = this
|
|
876
|
+
return indices.map(
|
|
877
|
+
(index) => makeRowFromColumnStore(index, columnStore) as ROW_TYPE
|
|
878
|
+
)
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
findRows(query: CoreQuery): ROW_TYPE[] {
|
|
882
|
+
return this.rowsAt(this.findRowsIndices(query))
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
findRowsIndices(query: CoreQuery): any {
|
|
886
|
+
const slugs = Object.keys(query)
|
|
887
|
+
if (!slugs.length) return this.indices
|
|
888
|
+
const arrs = this.getColumns(slugs).map((col) =>
|
|
889
|
+
col.indicesWhere(query[col.slug])
|
|
890
|
+
)
|
|
891
|
+
return intersection(...arrs)
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
indexOf(row: ROW_TYPE): any {
|
|
895
|
+
return this.findRowsIndices(row)[0] ?? -1
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
where(query: CoreQuery): this {
|
|
899
|
+
const rows = this.findRows(query)
|
|
900
|
+
const queryDescription = Object.entries(query)
|
|
901
|
+
.map(([col, value]) => `${col}=${value}`)
|
|
902
|
+
.join("&")
|
|
903
|
+
|
|
904
|
+
return this.transform(
|
|
905
|
+
rows,
|
|
906
|
+
this.defs,
|
|
907
|
+
`Selecting ${rows.length} rows where ${queryDescription}`,
|
|
908
|
+
TransformType.FilterRows
|
|
909
|
+
)
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
appendRows(rows: ROW_TYPE[], opDescription: string): this {
|
|
913
|
+
return this.concat(
|
|
914
|
+
[
|
|
915
|
+
new (this.constructor as typeof CoreTable)(rows, this.defs, {
|
|
916
|
+
parent: this,
|
|
917
|
+
}),
|
|
918
|
+
],
|
|
919
|
+
opDescription
|
|
920
|
+
)
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
updateDefs(fn: (def: COL_DEF_TYPE) => COL_DEF_TYPE): this {
|
|
924
|
+
return this.transform(
|
|
925
|
+
this.columnStore,
|
|
926
|
+
this.defs.map(fn),
|
|
927
|
+
`Updated column defs`,
|
|
928
|
+
TransformType.UpdateColumnDefs
|
|
929
|
+
)
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
select(slugs: ColumnSlug[]): this {
|
|
933
|
+
const columnsToKeep = new Set(slugs)
|
|
934
|
+
const newStore: CoreColumnStore = {}
|
|
935
|
+
const defs = this.columnsAsArray
|
|
936
|
+
.filter((col) => columnsToKeep.has(col.slug))
|
|
937
|
+
.map((col) => col.def) as COL_DEF_TYPE[]
|
|
938
|
+
|
|
939
|
+
Object.keys(this.columnStore)
|
|
940
|
+
.filter((slug) => columnsToKeep.has(slug))
|
|
941
|
+
.forEach((slug) => {
|
|
942
|
+
newStore[slug] = this.columnStore[slug]
|
|
943
|
+
})
|
|
944
|
+
|
|
945
|
+
return this.transform(
|
|
946
|
+
newStore,
|
|
947
|
+
defs,
|
|
948
|
+
`Kept columns '${slugs}'`,
|
|
949
|
+
TransformType.FilterColumns
|
|
950
|
+
)
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
dropColumns(slugs: ColumnSlug[], message?: string): this {
|
|
954
|
+
const columnsToDrop = new Set(slugs)
|
|
955
|
+
const newStore = {
|
|
956
|
+
...this.columnStore,
|
|
957
|
+
}
|
|
958
|
+
const defs = this.columnsAsArray
|
|
959
|
+
.filter((col) => !columnsToDrop.has(col.slug))
|
|
960
|
+
.map((col) => col.def) as COL_DEF_TYPE[]
|
|
961
|
+
slugs.forEach((slug) => {
|
|
962
|
+
delete newStore[slug]
|
|
963
|
+
})
|
|
964
|
+
return this.transform(
|
|
965
|
+
newStore,
|
|
966
|
+
defs,
|
|
967
|
+
message ?? `Dropped columns '${slugs}'`,
|
|
968
|
+
TransformType.FilterColumns
|
|
969
|
+
)
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
isRowEmpty(index: number): boolean {
|
|
973
|
+
const { columnStore } = this
|
|
974
|
+
return (
|
|
975
|
+
this.columnSlugs
|
|
976
|
+
.map((slug) => columnStore[slug][index])
|
|
977
|
+
.filter((value) => isNotErrorValue(value) && value !== "")
|
|
978
|
+
.length === 0
|
|
979
|
+
)
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
dropEmptyRows(): this {
|
|
983
|
+
return this.dropRowsAt(
|
|
984
|
+
this.indices
|
|
985
|
+
.map((index) => (this.isRowEmpty(index) ? index : null))
|
|
986
|
+
.filter(isPresent)
|
|
987
|
+
)
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
renameColumn(oldSlug: ColumnSlug, newSlug: ColumnSlug): this {
|
|
991
|
+
return this.renameColumns({ [oldSlug]: newSlug })
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Todo: improve typings. After renaming a column the row interface should change. Applies to some other methods as well.
|
|
995
|
+
renameColumns(columnRenameMap: { [columnSlug: string]: ColumnSlug }): this {
|
|
996
|
+
const oldSlugs = Object.keys(columnRenameMap)
|
|
997
|
+
const newSlugs = Object.values(columnRenameMap)
|
|
998
|
+
|
|
999
|
+
const message =
|
|
1000
|
+
`Renamed ` +
|
|
1001
|
+
oldSlugs
|
|
1002
|
+
.map((name, index) => `'${name}' to '${newSlugs[index]}'`)
|
|
1003
|
+
.join(" and ")
|
|
1004
|
+
|
|
1005
|
+
return this.transform(
|
|
1006
|
+
renameColumnStore(this.columnStore, columnRenameMap),
|
|
1007
|
+
this.defs.map((def) =>
|
|
1008
|
+
oldSlugs.indexOf(def.slug) > -1
|
|
1009
|
+
? {
|
|
1010
|
+
...def,
|
|
1011
|
+
slug: newSlugs[oldSlugs.indexOf(def.slug)],
|
|
1012
|
+
}
|
|
1013
|
+
: def
|
|
1014
|
+
),
|
|
1015
|
+
message,
|
|
1016
|
+
TransformType.RenameColumns
|
|
1017
|
+
)
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
dropRowsAt(indices: number[], message?: string): this {
|
|
1021
|
+
const mask = new FilterMask(this.numRows, indices, false)
|
|
1022
|
+
if (mask.isNoop())
|
|
1023
|
+
return this.noopTransform(message ?? `Dropping 0 rows`)
|
|
1024
|
+
|
|
1025
|
+
return this.transform(
|
|
1026
|
+
this.columnStore,
|
|
1027
|
+
this.defs,
|
|
1028
|
+
message ?? `Dropping ${indices.length} rows`,
|
|
1029
|
+
TransformType.FilterRows,
|
|
1030
|
+
mask
|
|
1031
|
+
)
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
replaceCells(
|
|
1035
|
+
columnSlugs: ColumnSlug[],
|
|
1036
|
+
replaceFn: (val: CoreValueType) => CoreValueType
|
|
1037
|
+
): this {
|
|
1038
|
+
const newStore: CoreColumnStore = { ...this.columnStore }
|
|
1039
|
+
columnSlugs.forEach((slug) => {
|
|
1040
|
+
newStore[slug] = newStore[slug].map(replaceFn)
|
|
1041
|
+
})
|
|
1042
|
+
return this.transform(
|
|
1043
|
+
newStore,
|
|
1044
|
+
this.defs,
|
|
1045
|
+
`Replaced all cells across columns ${columnSlugs.join(" and ")}`,
|
|
1046
|
+
TransformType.UpdateRows
|
|
1047
|
+
)
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
combineColumns(
|
|
1051
|
+
columnSlugs: ColumnSlug[],
|
|
1052
|
+
def: COL_DEF_TYPE,
|
|
1053
|
+
combineFn: (
|
|
1054
|
+
row: Record<ColumnSlug, { value: CoreValueType; time: Time }>,
|
|
1055
|
+
time: Time
|
|
1056
|
+
) => CoreValueType
|
|
1057
|
+
): this {
|
|
1058
|
+
if (columnSlugs.length === 0) return this
|
|
1059
|
+
const newStore: CoreColumnStore = { ...this.columnStore }
|
|
1060
|
+
newStore[def.slug] = this.indices.map((index) => {
|
|
1061
|
+
const time = this.timeColumn.valuesIncludingErrorValues[index]
|
|
1062
|
+
|
|
1063
|
+
const row: Record<
|
|
1064
|
+
ColumnSlug,
|
|
1065
|
+
{ value: CoreValueType; time: Time }
|
|
1066
|
+
> = {}
|
|
1067
|
+
columnSlugs.forEach((slug) => {
|
|
1068
|
+
row[slug] = {
|
|
1069
|
+
value: this.get(slug).valuesIncludingErrorValues[index],
|
|
1070
|
+
time: this.get(slug).originalTimeColumn
|
|
1071
|
+
.valuesIncludingErrorValues[index] as Time,
|
|
1072
|
+
}
|
|
1073
|
+
})
|
|
1074
|
+
|
|
1075
|
+
return combineFn(row, time as Time)
|
|
1076
|
+
})
|
|
1077
|
+
return this.transform(
|
|
1078
|
+
newStore,
|
|
1079
|
+
[...this.defs, def],
|
|
1080
|
+
`Combined columns '${columnSlugs.join(", ")}' into '${def.slug}'`,
|
|
1081
|
+
TransformType.CombineColumns
|
|
1082
|
+
)
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
replaceNonPositiveCellsForLogScale(columnSlugs: ColumnSlug[] = []): this {
|
|
1086
|
+
return this.replaceCells(columnSlugs, (val) =>
|
|
1087
|
+
typeof val !== "number" || val <= 0
|
|
1088
|
+
? ErrorValueTypes.InvalidOnALogScale
|
|
1089
|
+
: val
|
|
1090
|
+
)
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
replaceNegativeCellsWithErrorValues(columnSlugs: ColumnSlug[] = []): this {
|
|
1094
|
+
return this.replaceCells(columnSlugs, (val) =>
|
|
1095
|
+
typeof val !== "number" || val < 0
|
|
1096
|
+
? ErrorValueTypes.InvalidNegativeValue
|
|
1097
|
+
: val
|
|
1098
|
+
)
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
replaceNonNumericCellsWithErrorValues(columnSlugs: ColumnSlug[]): this {
|
|
1102
|
+
return this.replaceCells(columnSlugs, (val) =>
|
|
1103
|
+
!_.isNumber(val) ? ErrorValueTypes.NaNButShouldBeNumber : val
|
|
1104
|
+
)
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
replaceRandomCells(
|
|
1108
|
+
howMany = 1,
|
|
1109
|
+
columnSlugs: ColumnSlug[] = [],
|
|
1110
|
+
seed = Date.now(),
|
|
1111
|
+
replacementGenerator: () => any = (): DroppedForTesting =>
|
|
1112
|
+
ErrorValueTypes.DroppedForTesting
|
|
1113
|
+
): this {
|
|
1114
|
+
return this.transform(
|
|
1115
|
+
replaceRandomCellsInColumnStore(
|
|
1116
|
+
this.columnStore,
|
|
1117
|
+
howMany,
|
|
1118
|
+
columnSlugs,
|
|
1119
|
+
seed,
|
|
1120
|
+
replacementGenerator
|
|
1121
|
+
),
|
|
1122
|
+
this.defs,
|
|
1123
|
+
`Replaced a random ${howMany} cells in ${columnSlugs.join(
|
|
1124
|
+
" and "
|
|
1125
|
+
)}`,
|
|
1126
|
+
TransformType.UpdateRows
|
|
1127
|
+
)
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
appendColumns(defs: COL_DEF_TYPE[]): this {
|
|
1131
|
+
return this.transform(
|
|
1132
|
+
this.columnStore,
|
|
1133
|
+
this.defs.concat(defs),
|
|
1134
|
+
`Appended columns ${defs
|
|
1135
|
+
.map((def) => `'${def.slug}'`)
|
|
1136
|
+
.join(" and ")}`,
|
|
1137
|
+
TransformType.AppendColumns
|
|
1138
|
+
)
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
duplicateColumn(slug: ColumnSlug, overrides: COL_DEF_TYPE): this {
|
|
1142
|
+
return this.transform(
|
|
1143
|
+
{
|
|
1144
|
+
...this.columnStore,
|
|
1145
|
+
[overrides.slug]: this.columnStore[slug],
|
|
1146
|
+
},
|
|
1147
|
+
this.defs.concat([
|
|
1148
|
+
{
|
|
1149
|
+
...this.get(slug).def,
|
|
1150
|
+
...overrides,
|
|
1151
|
+
},
|
|
1152
|
+
]),
|
|
1153
|
+
`Duplicated column '${slug}' to column '${overrides.slug}'`,
|
|
1154
|
+
TransformType.AppendColumns
|
|
1155
|
+
)
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
appendColumnsIfNew(defs: COL_DEF_TYPE[]): this {
|
|
1159
|
+
return this.appendColumns(defs.filter((def) => !this.has(def.slug)))
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
toMatrix(): any[][] {
|
|
1163
|
+
const slugs = this.columnSlugs
|
|
1164
|
+
const rows = this.rows.map((row) =>
|
|
1165
|
+
slugs.map((slug) =>
|
|
1166
|
+
isNotErrorValue(row[slug]) ? row[slug] : undefined
|
|
1167
|
+
)
|
|
1168
|
+
)
|
|
1169
|
+
return [this.columnSlugs, ...rows]
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// Same as toMatrix, but preserves error types
|
|
1173
|
+
toTypedMatrix(): any[][] {
|
|
1174
|
+
const slugs = this.columnSlugs
|
|
1175
|
+
const rows = this.rows.map((row) => slugs.map((slug) => row[slug]))
|
|
1176
|
+
return [this.columnSlugs, ...rows]
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
concat(tables: CoreTable[], message: string = `Combined tables`): this {
|
|
1180
|
+
const all = [this, ...tables] as CoreTable[]
|
|
1181
|
+
const defs = all.flatMap((table) => table.defs) as COL_DEF_TYPE[]
|
|
1182
|
+
const uniqDefs = _.uniqBy(defs, (def) => def.slug)
|
|
1183
|
+
return this.transform(
|
|
1184
|
+
concatColumnStores(
|
|
1185
|
+
all.map((table) => table.columnStore),
|
|
1186
|
+
uniqDefs.map((def) => def.slug)
|
|
1187
|
+
),
|
|
1188
|
+
uniqDefs,
|
|
1189
|
+
message,
|
|
1190
|
+
TransformType.Concat
|
|
1191
|
+
)
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* Ensure a row exists for all values in columnSlug1 × columnSlug2 × ...
|
|
1196
|
+
*
|
|
1197
|
+
* For example, if we have a table:
|
|
1198
|
+
*
|
|
1199
|
+
* ```
|
|
1200
|
+
* entityName, year, …
|
|
1201
|
+
* UK, 2000, …
|
|
1202
|
+
* UK, 2005, …
|
|
1203
|
+
* USA, 2003, …
|
|
1204
|
+
* ```
|
|
1205
|
+
*
|
|
1206
|
+
* After `complete(["entityName", "year"])`, we'd get:
|
|
1207
|
+
*
|
|
1208
|
+
* ```
|
|
1209
|
+
* entityName, year, …
|
|
1210
|
+
* UK, 2000, …
|
|
1211
|
+
* UK, 2003, …
|
|
1212
|
+
* UK, 2005, …
|
|
1213
|
+
* USA, 2000, …
|
|
1214
|
+
* USA, 2003, …
|
|
1215
|
+
* USA, 2005, …
|
|
1216
|
+
* ```
|
|
1217
|
+
*
|
|
1218
|
+
*/
|
|
1219
|
+
complete(columnSlugs: [ColumnSlug, ColumnSlug]): this {
|
|
1220
|
+
if (columnSlugs.length !== 2)
|
|
1221
|
+
throw new Error("Can only run complete() for exactly 2 columns")
|
|
1222
|
+
|
|
1223
|
+
const [slug1, slug2] = columnSlugs
|
|
1224
|
+
const col1 = this.get(slug1)
|
|
1225
|
+
const col2 = this.get(slug2)
|
|
1226
|
+
|
|
1227
|
+
// The output table will have exactly this many rows, since we assume that [col1, col2] are primary keys
|
|
1228
|
+
// (i.e. there are no two rows with the same key), and every combination that doesn't exist yet we will add.
|
|
1229
|
+
const cartesianProductSize = col1.numUniqs * col2.numUniqs
|
|
1230
|
+
if (this.numRows >= cartesianProductSize) {
|
|
1231
|
+
if (this.numRows > cartesianProductSize)
|
|
1232
|
+
throw new Error("Table has more rows than expected")
|
|
1233
|
+
|
|
1234
|
+
// Table is already complete
|
|
1235
|
+
return this
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// Map that points from a value in col1 to a set of values in col2.
|
|
1239
|
+
// It's filled with all the values that already exist in the table, so we
|
|
1240
|
+
// can later take the difference.
|
|
1241
|
+
const existingRowValues = new Map<CoreValueType, Set<CoreValueType>>()
|
|
1242
|
+
for (const index of this.indices) {
|
|
1243
|
+
const val1 = col1.values[index]
|
|
1244
|
+
const val2 = col2.values[index]
|
|
1245
|
+
if (!existingRowValues.has(val1))
|
|
1246
|
+
existingRowValues.set(val1, new Set([val2]))
|
|
1247
|
+
else existingRowValues.get(val1)!.add(val2)
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// The below code should be as performant as possible, since it's often iterating over hundreds of thousands of rows.
|
|
1251
|
+
// The below implementation has been benchmarked against a few alternatives (using flatMap, map, and Array.from), and
|
|
1252
|
+
// is the fastest.
|
|
1253
|
+
// See https://jsperf.app/zudoye.
|
|
1254
|
+
const rowsToAddCol1: CoreValueType[] = []
|
|
1255
|
+
const rowsToAddCol2: CoreValueType[] = []
|
|
1256
|
+
const col2UniqValuesCount = col2.uniqValuesAsSet.size
|
|
1257
|
+
// Add rows for all combinations of values that are not contained in `existingRowValues`.
|
|
1258
|
+
for (const val1 of col1.uniqValuesAsSet) {
|
|
1259
|
+
const existingVals2 = existingRowValues.get(val1)
|
|
1260
|
+
|
|
1261
|
+
// perf: if all values in col2 are already present for this value in col1, skip
|
|
1262
|
+
// this iteration. This is a relatively common case, so we can save some time.
|
|
1263
|
+
if (existingVals2?.size === col2UniqValuesCount) continue
|
|
1264
|
+
|
|
1265
|
+
// Find the values in col2 that need to be inserted for this value in col1: col2.uniqValuesAsSet - existingVals2
|
|
1266
|
+
const diff = new Set(col2.uniqValuesAsSet)
|
|
1267
|
+
for (const val2 of existingVals2 || []) diff.delete(val2)
|
|
1268
|
+
|
|
1269
|
+
for (const val2 of diff) {
|
|
1270
|
+
rowsToAddCol1.push(val1)
|
|
1271
|
+
rowsToAddCol2.push(val2)
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
const appendColumnStore: CoreColumnStore = {
|
|
1275
|
+
[slug1]: rowsToAddCol1,
|
|
1276
|
+
[slug2]: rowsToAddCol2,
|
|
1277
|
+
}
|
|
1278
|
+
const appendTable = new (this.constructor as typeof CoreTable)(
|
|
1279
|
+
appendColumnStore,
|
|
1280
|
+
this.defs,
|
|
1281
|
+
{ parent: this }
|
|
1282
|
+
)
|
|
1283
|
+
|
|
1284
|
+
return this.concat(
|
|
1285
|
+
[appendTable],
|
|
1286
|
+
`Append missing combos of ${columnSlugs}`
|
|
1287
|
+
)
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
static getPreposition(col: TimeColumn | CoreColumn): string {
|
|
1291
|
+
return col instanceof TimeColumn ? col.preposition : "in"
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
class FilterMask {
|
|
1296
|
+
private mask: boolean[]
|
|
1297
|
+
private numRows: number
|
|
1298
|
+
constructor(
|
|
1299
|
+
numRows: number,
|
|
1300
|
+
input: boolean[] | number[],
|
|
1301
|
+
keepThese = true
|
|
1302
|
+
) {
|
|
1303
|
+
this.numRows = numRows
|
|
1304
|
+
if (typeof input[0] === "boolean") this.mask = input as boolean[]
|
|
1305
|
+
else {
|
|
1306
|
+
const set = new Set(input as number[])
|
|
1307
|
+
this.mask = _.range(0, numRows).map((index) =>
|
|
1308
|
+
set.has(index) ? keepThese : !keepThese
|
|
1309
|
+
)
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
isNoop(): boolean {
|
|
1314
|
+
return this.mask.every((value) => value)
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
apply(columnStore: CoreColumnStore): CoreColumnStore {
|
|
1318
|
+
const columnsObject: CoreColumnStore = {}
|
|
1319
|
+
const keepIndexes: number[] = []
|
|
1320
|
+
for (let i = 0; i < this.numRows; i++) {
|
|
1321
|
+
if (this.mask[i]) keepIndexes.push(i)
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// Optimization: early return if we're keeping all rows
|
|
1325
|
+
if (keepIndexes.length === this.numRows) {
|
|
1326
|
+
return columnStore
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
Object.keys(columnStore).forEach((slug) => {
|
|
1330
|
+
const originalColumn = columnStore[slug]
|
|
1331
|
+
const newColumn: CoreValueType[] = new Array(keepIndexes.length)
|
|
1332
|
+
for (let i = 0; i < keepIndexes.length; i++) {
|
|
1333
|
+
newColumn[i] = originalColumn[keepIndexes[i]]
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
columnsObject[slug] = newColumn
|
|
1337
|
+
})
|
|
1338
|
+
return columnsObject
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* Allows you to store your column definitions in CSV/TSV like:
|
|
1344
|
+
* slug,name,type etc.
|
|
1345
|
+
*
|
|
1346
|
+
* todo: define all column def property types
|
|
1347
|
+
*/
|
|
1348
|
+
export const columnDefinitionsFromInput = <T extends CoreRow>(
|
|
1349
|
+
input: CoreTableInputOption
|
|
1350
|
+
): T[] =>
|
|
1351
|
+
new CoreTable<T>(input).columnFilter(
|
|
1352
|
+
"slug",
|
|
1353
|
+
(value) => !!value,
|
|
1354
|
+
"Keep only column defs with a slug"
|
|
1355
|
+
).rows
|