@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,973 @@
|
|
|
1
|
+
import * as _ from "lodash-es"
|
|
2
|
+
import { scaleLog, scaleLinear, ScaleLinear, ScaleLogarithmic } from "d3-scale"
|
|
3
|
+
import { observable, computed, makeObservable } from "mobx"
|
|
4
|
+
import {
|
|
5
|
+
rollingMap,
|
|
6
|
+
numberMagnitude,
|
|
7
|
+
Bounds,
|
|
8
|
+
AxisAlign,
|
|
9
|
+
HorizontalAlign,
|
|
10
|
+
Position,
|
|
11
|
+
ScaleType,
|
|
12
|
+
VerticalAlign,
|
|
13
|
+
TickFormattingOptions,
|
|
14
|
+
Tickmark,
|
|
15
|
+
ValueRange,
|
|
16
|
+
OwidVariableRoundingMode,
|
|
17
|
+
} from "../../utils/index.js"
|
|
18
|
+
import { ComparisonLineConfig } from "../../types/index.js"
|
|
19
|
+
import { AxisConfig, AxisManager } from "./AxisConfig"
|
|
20
|
+
import { MarkdownTextWrap } from "../../components/index.js"
|
|
21
|
+
import { CoreColumn } from "../../core-table/index.js"
|
|
22
|
+
import {
|
|
23
|
+
DEFAULT_GRAPHER_BOUNDS,
|
|
24
|
+
GRAPHER_FONT_SCALE_10_5,
|
|
25
|
+
GRAPHER_FONT_SCALE_11,
|
|
26
|
+
GRAPHER_FONT_SCALE_12,
|
|
27
|
+
} from "../core/GrapherConstants.js"
|
|
28
|
+
import { makeAxisLabel } from "../chart/ChartUtils"
|
|
29
|
+
import * as R from "remeda"
|
|
30
|
+
import { isValidVerticalComparisonLineConfig } from "../comparisonLine/ComparisonLineHelpers"
|
|
31
|
+
|
|
32
|
+
interface TickLabelPlacement {
|
|
33
|
+
value: number
|
|
34
|
+
formattedValue: string
|
|
35
|
+
x: number
|
|
36
|
+
y: number
|
|
37
|
+
width: number
|
|
38
|
+
height: number
|
|
39
|
+
xAlign?: HorizontalAlign
|
|
40
|
+
yAlign?: VerticalAlign
|
|
41
|
+
isHidden: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type Scale = ScaleLinear<number, number> | ScaleLogarithmic<number, number>
|
|
45
|
+
|
|
46
|
+
const OUTER_PADDING = 4
|
|
47
|
+
|
|
48
|
+
const doIntersect = (bounds: Bounds, bounds2: Bounds): boolean => {
|
|
49
|
+
return bounds.intersects(bounds2)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const boundsFromLabelPlacement = (label: TickLabelPlacement): Bounds => {
|
|
53
|
+
const { x, y, width, height, xAlign, yAlign } = label
|
|
54
|
+
const xShift =
|
|
55
|
+
xAlign === HorizontalAlign.center
|
|
56
|
+
? -width / 2
|
|
57
|
+
: xAlign === HorizontalAlign.right
|
|
58
|
+
? -width
|
|
59
|
+
: 0
|
|
60
|
+
const yShift =
|
|
61
|
+
yAlign === VerticalAlign.middle
|
|
62
|
+
? -height / 2
|
|
63
|
+
: yAlign === VerticalAlign.bottom
|
|
64
|
+
? -height
|
|
65
|
+
: 0
|
|
66
|
+
return new Bounds(x + xShift, y + yShift, width, height)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
abstract class AbstractAxis {
|
|
70
|
+
config: AxisConfig
|
|
71
|
+
axisManager?: AxisManager
|
|
72
|
+
|
|
73
|
+
domain: ValueRange
|
|
74
|
+
formatColumn: CoreColumn | undefined = undefined // Pass the column purely for formatting reasons. Might be a better way to do this.
|
|
75
|
+
hideFractionalTicks = false
|
|
76
|
+
range: ValueRange = [0, 0]
|
|
77
|
+
private _scaleType: ScaleType | undefined = undefined
|
|
78
|
+
private _label: string | undefined = undefined
|
|
79
|
+
|
|
80
|
+
constructor(config: AxisConfig, axisManager?: AxisManager) {
|
|
81
|
+
makeObservable<AbstractAxis, "_scaleType" | "_label">(this, {
|
|
82
|
+
domain: observable.ref,
|
|
83
|
+
formatColumn: observable,
|
|
84
|
+
hideFractionalTicks: observable,
|
|
85
|
+
range: observable.struct,
|
|
86
|
+
_scaleType: observable,
|
|
87
|
+
_label: observable,
|
|
88
|
+
})
|
|
89
|
+
this.config = config
|
|
90
|
+
this.domain = [config.domain[0], config.domain[1]]
|
|
91
|
+
this.axisManager = axisManager
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The orthogonal size of the axis.
|
|
96
|
+
* For horizontal axes, this is the height.
|
|
97
|
+
* For vertical axes, this is the width.
|
|
98
|
+
*/
|
|
99
|
+
abstract get size(): number
|
|
100
|
+
abstract get orient(): Position
|
|
101
|
+
abstract get labelMaxWidth(): number
|
|
102
|
+
|
|
103
|
+
abstract placeTickLabel(value: number): TickLabelPlacement
|
|
104
|
+
abstract get tickLabels(): TickLabelPlacement[]
|
|
105
|
+
|
|
106
|
+
@computed get hideAxis(): boolean {
|
|
107
|
+
return this.config.hideAxis ?? false
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@computed get hideGridlines(): boolean {
|
|
111
|
+
return this.config.hideGridlines ?? false
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@computed get tickPadding(): number {
|
|
115
|
+
return this.config.tickPadding ?? 5
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@computed get labelPadding(): number {
|
|
119
|
+
return this.config.labelPadding ?? 10
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@computed get nice(): boolean {
|
|
123
|
+
return this.config.nice ?? false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@computed get fontSize(): number {
|
|
127
|
+
return this.config.fontSize
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@computed protected get minTicks(): number {
|
|
131
|
+
return 2
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@computed private get maxTicks(): number {
|
|
135
|
+
return this.config.maxTicks ?? 6
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@computed get canChangeScaleType(): boolean | undefined {
|
|
139
|
+
return this.config.canChangeScaleType
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@computed get scaleType(): ScaleType {
|
|
143
|
+
return this._scaleType ?? this.config.scaleType ?? ScaleType.linear
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@computed get isLogScale(): boolean {
|
|
147
|
+
return this.scaleType === ScaleType.log
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
set scaleType(value: ScaleType) {
|
|
151
|
+
this._scaleType = value
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@computed get label(): string {
|
|
155
|
+
return this._label ?? this.config.label ?? ""
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
set label(value: string) {
|
|
159
|
+
this._label = value
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// This will expand the domain but never shrink.
|
|
163
|
+
// This will change the min unless the user's min setting is less
|
|
164
|
+
// This will change the max unless the user's max setting is greater
|
|
165
|
+
// Undefined values are ignored
|
|
166
|
+
updateDomainPreservingUserSettings(
|
|
167
|
+
domain: [number | undefined, number | undefined]
|
|
168
|
+
): this {
|
|
169
|
+
const left =
|
|
170
|
+
domain[0] !== undefined
|
|
171
|
+
? _.min([this.domain[0], domain[0]])
|
|
172
|
+
: this.domain[0]
|
|
173
|
+
const right =
|
|
174
|
+
domain[1] !== undefined
|
|
175
|
+
? _.max([this.domain[1], domain[1]])
|
|
176
|
+
: this.domain[1]
|
|
177
|
+
this.domain = [left ?? 0, right ?? 0]
|
|
178
|
+
return this
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// todo: refactor. switch to a parent pattern?
|
|
182
|
+
_update(parentAxis: AbstractAxis): this {
|
|
183
|
+
this.formatColumn = parentAxis.formatColumn
|
|
184
|
+
this.domain = parentAxis.domain.slice() as ValueRange
|
|
185
|
+
this.hideFractionalTicks = parentAxis.hideFractionalTicks
|
|
186
|
+
this.range = parentAxis.range.slice() as ValueRange
|
|
187
|
+
this._scaleType = parentAxis._scaleType
|
|
188
|
+
this._label = parentAxis._label
|
|
189
|
+
return this
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private static calculateBandWidth({
|
|
193
|
+
values,
|
|
194
|
+
scale,
|
|
195
|
+
}: {
|
|
196
|
+
values: number[]
|
|
197
|
+
scale: Scale
|
|
198
|
+
}): number {
|
|
199
|
+
const range = scale.range()
|
|
200
|
+
const rangeSize = Math.abs(range[1] - range[0])
|
|
201
|
+
const maxBandWidth = 0.4 * rangeSize
|
|
202
|
+
|
|
203
|
+
if (values.length < 2) return maxBandWidth
|
|
204
|
+
|
|
205
|
+
// the band width is the smallest distance between
|
|
206
|
+
// two adjacent values placed on the axis
|
|
207
|
+
const sortedValues = _.sortBy(values)
|
|
208
|
+
const positions = sortedValues.map((value) => scale(value))
|
|
209
|
+
const diffs = positions
|
|
210
|
+
.slice(1)
|
|
211
|
+
.map((pos, index) => pos - positions[index])
|
|
212
|
+
const bandWidth = _.min(diffs) ?? 0
|
|
213
|
+
|
|
214
|
+
return _.min([bandWidth, maxBandWidth]) ?? 0
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Maximum width a single value can take up on the axis.
|
|
219
|
+
* Not meaningful if no domain values are given.
|
|
220
|
+
*/
|
|
221
|
+
@computed get bandWidth(): number | undefined {
|
|
222
|
+
const { domainValues } = this.config
|
|
223
|
+
if (!domainValues) return undefined
|
|
224
|
+
return AbstractAxis.calculateBandWidth({
|
|
225
|
+
values: domainValues,
|
|
226
|
+
scale: this.d3_scale,
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private static makeScaleNice(
|
|
231
|
+
scale: ScaleLinear<number, number>,
|
|
232
|
+
totalTicksTarget: number
|
|
233
|
+
): { scale: ScaleLinear<number, number>; ticks?: number[] } {
|
|
234
|
+
let ticks = scale.ticks(totalTicksTarget)
|
|
235
|
+
|
|
236
|
+
// use d3's nice function when there is only one tick
|
|
237
|
+
if (ticks.length < 2) return { scale: scale.nice(totalTicksTarget) }
|
|
238
|
+
|
|
239
|
+
const tickStep = ticks[1] - ticks[0]
|
|
240
|
+
const firstTick = ticks[0]
|
|
241
|
+
const lastTick = R.last(ticks)!
|
|
242
|
+
|
|
243
|
+
// if the the max or min value exceeds the last grid line by more than 25%,
|
|
244
|
+
// expand the domain to include an additional grid line
|
|
245
|
+
const [minValue, maxValue] = scale.domain()
|
|
246
|
+
if (maxValue > lastTick + 0.25 * tickStep) {
|
|
247
|
+
scale.domain([scale.domain()[0], lastTick + tickStep])
|
|
248
|
+
ticks = [...ticks, lastTick + tickStep]
|
|
249
|
+
}
|
|
250
|
+
if (minValue < firstTick - 0.25 * tickStep) {
|
|
251
|
+
scale.domain([firstTick - tickStep, scale.domain()[1]])
|
|
252
|
+
ticks = [firstTick - tickStep, ...ticks]
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return { scale, ticks }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private niceTicks?: number[]
|
|
259
|
+
@computed private get d3_scale(): Scale {
|
|
260
|
+
const d3Scale = this.isLogScale ? scaleLog : scaleLinear
|
|
261
|
+
let scale = d3Scale().domain(this.domain).range(this.range)
|
|
262
|
+
|
|
263
|
+
if (this.nice && !this.isLogScale) {
|
|
264
|
+
const { scale: niceScale, ticks: niceTicks } =
|
|
265
|
+
AbstractAxis.makeScaleNice(scale, this.totalTicksTarget)
|
|
266
|
+
scale = niceScale
|
|
267
|
+
this.niceTicks = niceTicks
|
|
268
|
+
} else {
|
|
269
|
+
this.niceTicks = undefined
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (this.config.domainValues) {
|
|
273
|
+
// compute bandwidth and adjust the scale
|
|
274
|
+
const bandWidth = AbstractAxis.calculateBandWidth({
|
|
275
|
+
values: this.config.domainValues,
|
|
276
|
+
scale,
|
|
277
|
+
})
|
|
278
|
+
const offset = bandWidth / 2 + OUTER_PADDING
|
|
279
|
+
const r = scale.range()
|
|
280
|
+
return scale.range([r[0] + offset, r[1] - offset])
|
|
281
|
+
} else {
|
|
282
|
+
return scale
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@computed get rangeSize(): number {
|
|
287
|
+
return Math.abs(this.range[1] - this.range[0])
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@computed get rangeMax(): number {
|
|
291
|
+
return Math.max(this.range[1], this.range[0])
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@computed get rangeMin(): number {
|
|
295
|
+
return Math.min(this.range[1], this.range[0])
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@computed get rangeCenter(): number {
|
|
299
|
+
return this.rangeMin + this.rangeSize / 2
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** The number of ticks we should _aim_ to show, not necessarily a strict target. */
|
|
303
|
+
@computed private get totalTicksTarget(): number {
|
|
304
|
+
// Chose 1.8 here by trying a bunch of different faceted charts and figuring out what
|
|
305
|
+
// a reasonable lower bound is.
|
|
306
|
+
// NOTE: This setting is used between both log & linear axes, check both when tweaking.
|
|
307
|
+
// -@danielgavrilov, 2021-06-15
|
|
308
|
+
return Math.round(
|
|
309
|
+
R.clamp(this.rangeSize / (this.fontSize * 1.8), {
|
|
310
|
+
min: this.minTicks,
|
|
311
|
+
max: this.maxTicks,
|
|
312
|
+
})
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
getTickValues(): Tickmark[] {
|
|
317
|
+
const { d3_scale } = this
|
|
318
|
+
|
|
319
|
+
let ticks: Tickmark[]
|
|
320
|
+
|
|
321
|
+
if (this.config.ticks) {
|
|
322
|
+
// If custom ticks are supplied, use them without any transformations or additions.
|
|
323
|
+
const [minValue, maxValue] = d3_scale.domain()
|
|
324
|
+
return (
|
|
325
|
+
this.config.ticks
|
|
326
|
+
// replace ±Infinity with minimum/maximum
|
|
327
|
+
.map((tick) => {
|
|
328
|
+
if (tick.value === -Infinity)
|
|
329
|
+
return { ...tick, value: minValue }
|
|
330
|
+
if (tick.value === Infinity)
|
|
331
|
+
return { ...tick, value: maxValue }
|
|
332
|
+
return tick
|
|
333
|
+
})
|
|
334
|
+
// filter out custom ticks outside the plottable area
|
|
335
|
+
.filter(
|
|
336
|
+
(tick) =>
|
|
337
|
+
tick.value >= minValue && tick.value <= maxValue
|
|
338
|
+
)
|
|
339
|
+
)
|
|
340
|
+
} else if (this.isLogScale) {
|
|
341
|
+
// Show a bit more ticks for log axes
|
|
342
|
+
const maxLabelledTicks = Math.round(this.totalTicksTarget * 1.25)
|
|
343
|
+
const maxTicks = Math.round(this.totalTicksTarget * 3)
|
|
344
|
+
|
|
345
|
+
// This is a wild heuristic that decides how many tick lines and grid lines we want to
|
|
346
|
+
// show for log charts.
|
|
347
|
+
//
|
|
348
|
+
// It tries to achive multiple goals:
|
|
349
|
+
// * make it obvious for the user which values they're looking at
|
|
350
|
+
// * ideally, make it very clear that this is a log axis by looking like log paper
|
|
351
|
+
// * (but) don't overwhelm the user
|
|
352
|
+
// * avoid cases where only one tick is shown for the whole axis (we had those!)
|
|
353
|
+
//
|
|
354
|
+
// This code roughly works as follows:
|
|
355
|
+
// First, we let d3 generate ticks for the axis. d3 gives values of the form `y * 10^x`,
|
|
356
|
+
// with 0 < y < 10.
|
|
357
|
+
// We then assign priorities to these values:
|
|
358
|
+
// * priority 1 (highest) to values of the form `1 * 10^x` (e.g. 100)
|
|
359
|
+
// * priority 2 to values of the form `2 * 10^x` or `5 * 10^x` (e.g. 5, 2000)
|
|
360
|
+
// * priority 3 (lowest) to all other ("in-between") values (e.g. 70, 300)
|
|
361
|
+
//
|
|
362
|
+
// We then decide depending on the number of tick candidates what to do:
|
|
363
|
+
// * if we have less than `maxLabelledTicks`, just show all
|
|
364
|
+
// * if we have between `maxLabelledTicks` and `maxTicks`, show all "in-between" lines
|
|
365
|
+
// as faint grid lines without labels to give the chart that log paper look.
|
|
366
|
+
// We also show all priority 1 and 2 lines with labels, because there aren't too many
|
|
367
|
+
// of them.
|
|
368
|
+
// * otherwise, remove priority 3 and, if necessary, priority 2 labels until we're below
|
|
369
|
+
// `maxLabelledTicks` labels overall
|
|
370
|
+
//
|
|
371
|
+
// -@MarcelGerber, 2020-08-07
|
|
372
|
+
const tickCandidates = d3_scale.ticks(maxLabelledTicks)
|
|
373
|
+
ticks = tickCandidates.map((value) => {
|
|
374
|
+
// 10^x
|
|
375
|
+
if (Math.fround(Math.log10(value)) % 1 === 0)
|
|
376
|
+
return { value, priority: 1 }
|
|
377
|
+
// 5 * 10^x
|
|
378
|
+
else if (Math.fround(Math.log10(value * 2)) % 1 === 0)
|
|
379
|
+
return { value, priority: 2 }
|
|
380
|
+
// 2 * 10^x
|
|
381
|
+
else if (Math.fround(Math.log10(value / 2)) % 1 === 0)
|
|
382
|
+
return { value, priority: 2 }
|
|
383
|
+
return { value, priority: 3 }
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
if (ticks.length > maxLabelledTicks) {
|
|
387
|
+
if (ticks.length <= maxTicks) {
|
|
388
|
+
// Convert all "in-between" lines to faint grid lines without labels
|
|
389
|
+
ticks = ticks.map((tick) => {
|
|
390
|
+
if (tick.priority === 3)
|
|
391
|
+
tick = {
|
|
392
|
+
...tick,
|
|
393
|
+
faint: true,
|
|
394
|
+
gridLineOnly: true,
|
|
395
|
+
}
|
|
396
|
+
return tick
|
|
397
|
+
})
|
|
398
|
+
} else {
|
|
399
|
+
// Remove some tickmarks again because the chart would get too overwhelming
|
|
400
|
+
// otherwise
|
|
401
|
+
for (let priority = 3; priority > 1; priority--) {
|
|
402
|
+
if (ticks.length > maxLabelledTicks)
|
|
403
|
+
ticks = ticks.filter(
|
|
404
|
+
(tick) => tick.priority < priority
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
const d3_ticks =
|
|
411
|
+
this.niceTicks ?? d3_scale.ticks(this.totalTicksTarget)
|
|
412
|
+
|
|
413
|
+
// Only use priority 2 here because we want the start / end ticks
|
|
414
|
+
// to be priority 1
|
|
415
|
+
ticks = d3_ticks.map((tickValue) => ({
|
|
416
|
+
value: tickValue,
|
|
417
|
+
priority: 2,
|
|
418
|
+
}))
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (this.hideFractionalTicks)
|
|
422
|
+
ticks = ticks.filter((t) => t.value % 1 === 0)
|
|
423
|
+
|
|
424
|
+
// mark value=0 ticks as solid for non-time columns
|
|
425
|
+
if (!this.formatColumn?.isTimeColumn) {
|
|
426
|
+
ticks = ticks.map((tick) =>
|
|
427
|
+
tick.value === 0 ? { ...tick, solid: true } : tick
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return _.uniq(ticks)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private getTickFormattingOptions(): TickFormattingOptions {
|
|
435
|
+
const options: TickFormattingOptions = {
|
|
436
|
+
...this.config.tickFormattingOptions,
|
|
437
|
+
roundingMode: OwidVariableRoundingMode.decimalPlaces,
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// The chart's tick formatting function is used by default to format axis ticks. This means
|
|
441
|
+
// that the chart's `numDecimalPlaces` is also used by default to format the axis ticks.
|
|
442
|
+
//
|
|
443
|
+
// However, the author-specified decimal places are not always appropriate for rendering
|
|
444
|
+
// ticks, because:
|
|
445
|
+
// 1. Subsets of the data may require higher fidelity, e.g. users can use the timeline to
|
|
446
|
+
// end up in a subset of the dataset where values happen to be much lower than usual.
|
|
447
|
+
// 2. Ticks may be rendered at granularities that may not exist in the data, e.g. the data
|
|
448
|
+
// may only contain 0 and 1, but we may show ticks in between those values.
|
|
449
|
+
//
|
|
450
|
+
// Therefore, when formatting ticks, we determine the `numDecimalPlaces` automatically, by
|
|
451
|
+
// finding the smallest difference between any pair of ticks and making sure that we have
|
|
452
|
+
// sufficient decimal places to express the difference to the first significant figure (the
|
|
453
|
+
// first non-zero digit).
|
|
454
|
+
//
|
|
455
|
+
// One significant figure is sufficient because we use D3's ticks() and that creates
|
|
456
|
+
// "uniformly-spaced, nicely-rounded values [...] where each value is a power of ten
|
|
457
|
+
// multiplied by 1, 2 or 5"
|
|
458
|
+
// See: https://github.com/d3/d3-array/blob/master/README.md#ticks
|
|
459
|
+
//
|
|
460
|
+
// -@danielgavrilov, 2020-05-27
|
|
461
|
+
const minDist = _.min(
|
|
462
|
+
rollingMap(this.baseTicks, (a, b) => Math.abs(a.value - b.value))
|
|
463
|
+
)
|
|
464
|
+
if (minDist !== undefined) {
|
|
465
|
+
// Find the decimal places required to reach the first non-zero digit
|
|
466
|
+
const dp = -numberMagnitude(minDist) + 1
|
|
467
|
+
if (dp >= 0) {
|
|
468
|
+
options.numDecimalPlaces = dp
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return options
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
place(value: number): number {
|
|
475
|
+
if (!this.range) {
|
|
476
|
+
console.error(
|
|
477
|
+
"Can't place value on scale without a defined output range"
|
|
478
|
+
)
|
|
479
|
+
return value
|
|
480
|
+
} else if (this.isLogScale && value <= 0) {
|
|
481
|
+
console.error(`Can't have ${value} which is <= 0 on a log scale`)
|
|
482
|
+
return value
|
|
483
|
+
} else if (this.domain[0] === this.domain[1]) {
|
|
484
|
+
// When the domain is a single value, the D3 scale will by default place
|
|
485
|
+
// the value at the middle of the range.
|
|
486
|
+
// We instead want to customize what happens - sometimes we want to place the point
|
|
487
|
+
// at the start of the range instead.
|
|
488
|
+
// see https://github.com/owid/owid-grapher/pull/1367#issuecomment-1090845181.
|
|
489
|
+
//
|
|
490
|
+
// -@marcelgerber, 2022-04-12
|
|
491
|
+
switch (this.config.singleValueAxisPointAlign) {
|
|
492
|
+
case AxisAlign.start:
|
|
493
|
+
return this.range[0]
|
|
494
|
+
case AxisAlign.end:
|
|
495
|
+
return this.range[1]
|
|
496
|
+
case AxisAlign.middle:
|
|
497
|
+
default:
|
|
498
|
+
return (this.range[0] + this.range[1]) / 2
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const placedValue = this.d3_scale(value)
|
|
502
|
+
if (placedValue === undefined) {
|
|
503
|
+
console.error(`Placed value is undefined for ${value}`)
|
|
504
|
+
return value
|
|
505
|
+
}
|
|
506
|
+
return parseFloat(placedValue.toFixed(1))
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** This function returns the inverse of place - i.e. given a screen space
|
|
510
|
+
* coordinate, it returns the corresponding domain value. This is useful
|
|
511
|
+
* for cases where you want to make sure that something is at least one pixel high.
|
|
512
|
+
*/
|
|
513
|
+
invert(value: number): number {
|
|
514
|
+
return this.d3_scale.invert(value)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
@computed get tickFontSize(): number {
|
|
518
|
+
return Math.floor(GRAPHER_FONT_SCALE_12 * this.fontSize)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
@computed protected get baseTicks(): Tickmark[] {
|
|
522
|
+
return this.getTickValues().filter((tick) => !tick.gridLineOnly)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
formatTick(
|
|
526
|
+
tick: number,
|
|
527
|
+
formattingOptionsOverride?: TickFormattingOptions
|
|
528
|
+
): string {
|
|
529
|
+
const tickFormattingOptions: TickFormattingOptions = {
|
|
530
|
+
...this.getTickFormattingOptions(),
|
|
531
|
+
...formattingOptionsOverride,
|
|
532
|
+
}
|
|
533
|
+
return (
|
|
534
|
+
this.formatColumn?.formatForTick(tick, tickFormattingOptions) ??
|
|
535
|
+
tick.toString()
|
|
536
|
+
)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
@computed get labelFontSize(): number {
|
|
540
|
+
return Math.floor(GRAPHER_FONT_SCALE_12 * this.fontSize)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
@computed get labelTextWrap(): MarkdownTextWrap | undefined {
|
|
544
|
+
if (!this.label) return
|
|
545
|
+
|
|
546
|
+
const textWrapProps = {
|
|
547
|
+
maxWidth: this.labelMaxWidth,
|
|
548
|
+
fontSize: this.labelFontSize,
|
|
549
|
+
lineHeight: 1,
|
|
550
|
+
detailsOrderedByReference:
|
|
551
|
+
this.axisManager?.detailsOrderedByReference,
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const axisLabel = makeAxisLabel({
|
|
555
|
+
label: this.label,
|
|
556
|
+
displayUnit: this.formatColumn?.displayUnit,
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
const logScaleNotice = "plotted on a logarithmic axis"
|
|
560
|
+
|
|
561
|
+
if (axisLabel.unit) {
|
|
562
|
+
const secondaryText = this.isLogScale
|
|
563
|
+
? `(${axisLabel.unit}; ${logScaleNotice})`
|
|
564
|
+
: `(${axisLabel.unit})`
|
|
565
|
+
return MarkdownTextWrap.fromFragments({
|
|
566
|
+
main: { text: axisLabel.mainLabel, bold: true },
|
|
567
|
+
secondary: { text: secondaryText },
|
|
568
|
+
newLine: "avoid-wrap",
|
|
569
|
+
textWrapProps,
|
|
570
|
+
})
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (this.isLogScale) {
|
|
574
|
+
return MarkdownTextWrap.fromFragments({
|
|
575
|
+
main: { text: axisLabel.mainLabel, bold: true },
|
|
576
|
+
secondary: { text: `(${logScaleNotice})` },
|
|
577
|
+
newLine: "avoid-wrap",
|
|
578
|
+
textWrapProps,
|
|
579
|
+
})
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return new MarkdownTextWrap({
|
|
583
|
+
text: axisLabel.mainLabel,
|
|
584
|
+
fontWeight: 700,
|
|
585
|
+
...textWrapProps,
|
|
586
|
+
})
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
@computed get labelHeight(): number {
|
|
590
|
+
return this.labelTextWrap
|
|
591
|
+
? this.labelTextWrap.height + this.labelPadding
|
|
592
|
+
: 0
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export class HorizontalAxis extends AbstractAxis {
|
|
597
|
+
constructor(config: AxisConfig, axisManager?: AxisManager) {
|
|
598
|
+
super(config, axisManager)
|
|
599
|
+
|
|
600
|
+
makeObservable(this)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
clone(): HorizontalAxis {
|
|
604
|
+
return new HorizontalAxis(this.config, this.axisManager)._update(this)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
@computed get orient(): Position {
|
|
608
|
+
// Default to `bottom` unless overriden to `top`.
|
|
609
|
+
return this.config.orient === Position.top
|
|
610
|
+
? Position.top
|
|
611
|
+
: Position.bottom
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
@computed get labelOffset(): number {
|
|
615
|
+
return this.labelHeight
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
@computed get labelMaxWidth(): number {
|
|
619
|
+
return this.rangeSize
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// note that we intentionally don't take `hideAxisLabels` into account here.
|
|
623
|
+
// tick labels might be hidden in faceted charts. when faceted, it's important
|
|
624
|
+
// the axis size doesn't change as a result of hiding the axis labels, or else
|
|
625
|
+
// we might end up with misaligned axes.
|
|
626
|
+
@computed get height(): number {
|
|
627
|
+
if (this.hideAxis) return 0
|
|
628
|
+
const { labelOffset, tickPadding } = this
|
|
629
|
+
const maxTickHeight = _.max(this.tickLabels.map((tick) => tick.height))
|
|
630
|
+
const tickHeight = maxTickHeight ? maxTickHeight + tickPadding : 0
|
|
631
|
+
return Math.max(tickHeight + labelOffset, this.config.minSize ?? 0)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
@computed get size(): number {
|
|
635
|
+
return this.height
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
protected override get baseTicks(): Tickmark[] {
|
|
639
|
+
let ticks = this.getTickValues().filter(
|
|
640
|
+
(tick): boolean => !tick.gridLineOnly
|
|
641
|
+
)
|
|
642
|
+
const { domain } = this
|
|
643
|
+
|
|
644
|
+
// Make sure the start and end values are present, if they're whole numbers
|
|
645
|
+
const startEndPrio = this.isLogScale ? 2 : 1
|
|
646
|
+
if (domain[0] % 1 === 0)
|
|
647
|
+
ticks = [
|
|
648
|
+
{
|
|
649
|
+
value: domain[0],
|
|
650
|
+
priority: startEndPrio,
|
|
651
|
+
},
|
|
652
|
+
...ticks,
|
|
653
|
+
]
|
|
654
|
+
if (domain[1] % 1 === 0 && this.hideFractionalTicks)
|
|
655
|
+
ticks = [
|
|
656
|
+
...ticks,
|
|
657
|
+
{
|
|
658
|
+
value: domain[1],
|
|
659
|
+
priority: startEndPrio,
|
|
660
|
+
},
|
|
661
|
+
]
|
|
662
|
+
|
|
663
|
+
// sort by value, then priority.
|
|
664
|
+
// this way, we don't end up with two ticks of the same value but different priorities.
|
|
665
|
+
// instead, we deduplicate by choosing the highest priority (i.e. lowest priority value).
|
|
666
|
+
const sortedTicks = _.sortBy(ticks, [
|
|
667
|
+
(t): number => t.value,
|
|
668
|
+
(t): number => t.priority,
|
|
669
|
+
])
|
|
670
|
+
return _.sortedUniqBy(sortedTicks, (t) => t.value)
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
@computed get tickLabels(): TickLabelPlacement[] {
|
|
674
|
+
// Get ticks with coordinates, sorted by priority
|
|
675
|
+
const tickLabels = _.sortBy(
|
|
676
|
+
this.baseTicks,
|
|
677
|
+
(tick) => tick.priority
|
|
678
|
+
).map((tick) => this.placeTickLabel(tick.value))
|
|
679
|
+
const visibleTickLabels = hideOverlappingTickLabels(tickLabels, {
|
|
680
|
+
padding: 3,
|
|
681
|
+
})
|
|
682
|
+
return visibleTickLabels
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
placeTickLabel(value: number): TickLabelPlacement {
|
|
686
|
+
const formattedValue = this.formatTick(value)
|
|
687
|
+
const { width, height } = Bounds.forText(formattedValue, {
|
|
688
|
+
fontSize: this.tickFontSize,
|
|
689
|
+
})
|
|
690
|
+
let x = this.place(value)
|
|
691
|
+
let xAlign = HorizontalAlign.center
|
|
692
|
+
const left = x - width / 2
|
|
693
|
+
const right = x + width / 2
|
|
694
|
+
const offset = this.bandWidth ? this.bandWidth / 2 + OUTER_PADDING : 0
|
|
695
|
+
if (left < this.rangeMin - offset) {
|
|
696
|
+
x = this.rangeMin
|
|
697
|
+
xAlign = HorizontalAlign.left
|
|
698
|
+
}
|
|
699
|
+
if (right > this.rangeMax + offset) {
|
|
700
|
+
x = this.rangeMax
|
|
701
|
+
xAlign = HorizontalAlign.right
|
|
702
|
+
}
|
|
703
|
+
return {
|
|
704
|
+
value,
|
|
705
|
+
formattedValue,
|
|
706
|
+
x,
|
|
707
|
+
y: 0,
|
|
708
|
+
width,
|
|
709
|
+
height,
|
|
710
|
+
xAlign,
|
|
711
|
+
isHidden: false,
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Add some padding before checking for intersection
|
|
716
|
+
protected doIntersect(bounds: Bounds, bounds2: Bounds): boolean {
|
|
717
|
+
return bounds.intersects(bounds2.padWidth(-5))
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
export class VerticalAxis extends AbstractAxis {
|
|
722
|
+
constructor(config: AxisConfig, axisManager?: AxisManager) {
|
|
723
|
+
super(config, axisManager)
|
|
724
|
+
|
|
725
|
+
makeObservable(this)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
clone(): VerticalAxis {
|
|
729
|
+
return new VerticalAxis(this.config, this.axisManager)._update(this)
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
@computed get orient(): Position {
|
|
733
|
+
return Position.left
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
@computed get labelMaxWidth(): number {
|
|
737
|
+
return this.axisManager?.axisBounds?.width ?? Infinity
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
@computed get labelOffsetTop(): number {
|
|
741
|
+
return this.labelHeight
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// note that we intentionally don't take `hideAxisLabels` into account here.
|
|
745
|
+
// tick labels might be hidden in faceted charts. when faceted, it's important
|
|
746
|
+
// the axis size doesn't change as a result of hiding the axis labels, or else
|
|
747
|
+
// we might end up with misaligned axes.
|
|
748
|
+
@computed get width(): number {
|
|
749
|
+
if (this.hideAxis) return 0
|
|
750
|
+
const { tickPadding } = this
|
|
751
|
+
const maxTickWidth = _.max(this.tickLabels.map((tick) => tick.width))
|
|
752
|
+
const tickWidth =
|
|
753
|
+
maxTickWidth !== undefined ? maxTickWidth + tickPadding : 0
|
|
754
|
+
return Math.max(tickWidth, this.config.minSize ?? 0)
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
@computed get height(): number {
|
|
758
|
+
return this.rangeSize
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
@computed get size(): number {
|
|
762
|
+
return this.width
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
@computed get tickLabels(): TickLabelPlacement[] {
|
|
766
|
+
const { domain } = this
|
|
767
|
+
|
|
768
|
+
const tickLabels = _.sortBy(
|
|
769
|
+
this.baseTicks,
|
|
770
|
+
(tick) => tick.priority
|
|
771
|
+
).map((tick) => this.placeTickLabel(tick.value))
|
|
772
|
+
|
|
773
|
+
// hide overlapping ticks, and allow for some padding
|
|
774
|
+
let visibleTicks = hideOverlappingTickLabels(tickLabels, { padding: 3 })
|
|
775
|
+
|
|
776
|
+
// if we end up with too few ticks, try again with less padding
|
|
777
|
+
if (visibleTicks.length < this.minTicks) {
|
|
778
|
+
visibleTicks = hideOverlappingTickLabels(tickLabels, { padding: 1 })
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// if we still have too few ticks, de-prioritize the zero tick
|
|
782
|
+
// if it's a start or end value and drawn as a solid line
|
|
783
|
+
if (visibleTicks.length < this.minTicks) {
|
|
784
|
+
const updatedBaseTicks = _.cloneDeep(this.baseTicks)
|
|
785
|
+
if (domain[0] === 0 || domain[1] === 0) {
|
|
786
|
+
const zeroIndex = updatedBaseTicks
|
|
787
|
+
.map((tick) => tick.value)
|
|
788
|
+
.indexOf(0)
|
|
789
|
+
if (zeroIndex >= 0 && updatedBaseTicks[zeroIndex].solid) {
|
|
790
|
+
updatedBaseTicks[zeroIndex] = {
|
|
791
|
+
value: 0,
|
|
792
|
+
priority: 3,
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const tickLabels = _.sortBy(
|
|
798
|
+
updatedBaseTicks,
|
|
799
|
+
(tick) => tick.priority
|
|
800
|
+
).map((tick) => this.placeTickLabel(tick.value))
|
|
801
|
+
visibleTicks = hideOverlappingTickLabels(tickLabels, { padding: 1 })
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
return visibleTicks
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
placeTickLabel(value: number): TickLabelPlacement {
|
|
808
|
+
const formattedValue = this.formatTick(value)
|
|
809
|
+
const { width, height } = Bounds.forText(formattedValue, {
|
|
810
|
+
fontSize: this.tickFontSize,
|
|
811
|
+
})
|
|
812
|
+
const y = this.place(value)
|
|
813
|
+
return {
|
|
814
|
+
value,
|
|
815
|
+
formattedValue,
|
|
816
|
+
x: 0,
|
|
817
|
+
y,
|
|
818
|
+
width,
|
|
819
|
+
height,
|
|
820
|
+
xAlign: HorizontalAlign.right,
|
|
821
|
+
yAlign: VerticalAlign.middle,
|
|
822
|
+
isHidden: false,
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
@computed get shouldShowLogNotice(): boolean {
|
|
827
|
+
return this.isLogScale && !this.labelTextWrap
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
@computed get logNoticeTextWrap(): MarkdownTextWrap | undefined {
|
|
831
|
+
if (!this.shouldShowLogNotice) return undefined
|
|
832
|
+
|
|
833
|
+
const fontSize = Math.floor(GRAPHER_FONT_SCALE_11 * this.fontSize)
|
|
834
|
+
|
|
835
|
+
return new MarkdownTextWrap({
|
|
836
|
+
text: "log axis",
|
|
837
|
+
maxWidth: Infinity, // No line breaks
|
|
838
|
+
fontSize,
|
|
839
|
+
})
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
@computed get logNoticeWidth(): number {
|
|
843
|
+
return this.logNoticeTextWrap ? this.logNoticeTextWrap.width : 0
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
@computed get logNoticeHeight(): number {
|
|
847
|
+
const padding = this.tickFontSize
|
|
848
|
+
return this.logNoticeTextWrap
|
|
849
|
+
? this.logNoticeTextWrap.height + padding
|
|
850
|
+
: 0
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
interface DualAxisProps {
|
|
855
|
+
bounds?: Bounds
|
|
856
|
+
horizontalAxis: HorizontalAxis
|
|
857
|
+
verticalAxis: VerticalAxis
|
|
858
|
+
comparisonLines?: ComparisonLineConfig[]
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// DualAxis has the important task of coordinating two axes so that they work together!
|
|
862
|
+
// There is a *two-way dependency* between the bounding size of each axis.
|
|
863
|
+
// e.g. if the y axis becomes wider because a label is present, the x axis then has less
|
|
864
|
+
// space to work with, and vice versa
|
|
865
|
+
export class DualAxis {
|
|
866
|
+
private props: DualAxisProps
|
|
867
|
+
constructor(props: DualAxisProps) {
|
|
868
|
+
makeObservable(this)
|
|
869
|
+
this.props = props
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
@computed get horizontalAxis(): HorizontalAxis {
|
|
873
|
+
const axis = this.props.horizontalAxis.clone()
|
|
874
|
+
axis.range = this.innerBounds.xRange()
|
|
875
|
+
return axis
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
@computed get verticalAxis(): VerticalAxis {
|
|
879
|
+
const axis = this.props.verticalAxis.clone()
|
|
880
|
+
axis.range = this.innerBounds.yRange()
|
|
881
|
+
return axis
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// We calculate an initial height from the range of the input bounds
|
|
885
|
+
@computed private get horizontalAxisSize(): number {
|
|
886
|
+
const axis = this.props.horizontalAxis.clone()
|
|
887
|
+
axis.range = [0, this.bounds.width]
|
|
888
|
+
return axis.size
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// We calculate an initial width from the range of the input bounds
|
|
892
|
+
@computed private get verticalAxisSize(): number {
|
|
893
|
+
const axis = this.props.verticalAxis.clone()
|
|
894
|
+
axis.range = [0, this.bounds.height]
|
|
895
|
+
return axis.size
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Now we can determine the "true" inner bounds of the dual axis
|
|
899
|
+
@computed get innerBounds(): Bounds {
|
|
900
|
+
return (
|
|
901
|
+
this.bounds
|
|
902
|
+
// Add padding to account for the width of the vertical axis
|
|
903
|
+
// and the height of the horizontal axis
|
|
904
|
+
.pad({
|
|
905
|
+
[this.props.horizontalAxis.orient]: this.horizontalAxisSize,
|
|
906
|
+
[this.props.verticalAxis.orient]: this.verticalAxisSize,
|
|
907
|
+
})
|
|
908
|
+
// Make space for the y-axis label if plotted above the axis
|
|
909
|
+
.padTop(this.props.verticalAxis.labelOffsetTop)
|
|
910
|
+
// Make space for vertical comparison line labels if any
|
|
911
|
+
.padTop(this.comparisonLineLabelOffset)
|
|
912
|
+
.padTop(
|
|
913
|
+
this.shouldShowLogNotice
|
|
914
|
+
? this.props.verticalAxis.logNoticeHeight
|
|
915
|
+
: 0
|
|
916
|
+
)
|
|
917
|
+
)
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
@computed get bounds(): Bounds {
|
|
921
|
+
return this.props.bounds ?? DEFAULT_GRAPHER_BOUNDS
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
@computed get comparisonLines(): ComparisonLineConfig[] {
|
|
925
|
+
return this.props.comparisonLines ?? []
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
@computed get comparisonLineLabelFontSize(): number {
|
|
929
|
+
return Math.floor(
|
|
930
|
+
GRAPHER_FONT_SCALE_10_5 * this.props.verticalAxis.fontSize
|
|
931
|
+
)
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
@computed private get comparisonLineLabelOffset(): number {
|
|
935
|
+
const hasVerticalComparisonLines = this.comparisonLines.some((line) =>
|
|
936
|
+
isValidVerticalComparisonLineConfig(line)
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
if (!hasVerticalComparisonLines) return 0
|
|
940
|
+
|
|
941
|
+
return this.comparisonLineLabelFontSize
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
@computed private get shouldShowLogNotice(): boolean {
|
|
945
|
+
return (
|
|
946
|
+
this.props.verticalAxis.shouldShowLogNotice &&
|
|
947
|
+
// Only show the notice if it fits in the margin
|
|
948
|
+
this.props.verticalAxis.logNoticeWidth <= this.verticalAxisSize
|
|
949
|
+
)
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function hideOverlappingTickLabels(
|
|
954
|
+
tickLabels: TickLabelPlacement[],
|
|
955
|
+
{ padding = 0 }: { padding?: number } = {}
|
|
956
|
+
): TickLabelPlacement[] {
|
|
957
|
+
for (let i = 0; i < tickLabels.length; i++) {
|
|
958
|
+
for (let j = i + 1; j < tickLabels.length; j++) {
|
|
959
|
+
const t1 = tickLabels[i],
|
|
960
|
+
t2 = tickLabels[j]
|
|
961
|
+
if (t1 === t2 || t1.isHidden || t2.isHidden) continue
|
|
962
|
+
if (
|
|
963
|
+
doIntersect(
|
|
964
|
+
// Expand bounds so that labels aren't too close together.
|
|
965
|
+
boundsFromLabelPlacement(t1).expand(padding),
|
|
966
|
+
boundsFromLabelPlacement(t2).expand(padding)
|
|
967
|
+
)
|
|
968
|
+
)
|
|
969
|
+
t2.isHidden = true
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return tickLabels.filter((tick) => !tick.isHidden)
|
|
973
|
+
}
|