@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,84 @@
|
|
|
1
|
+
# Binning Strategies
|
|
2
|
+
|
|
3
|
+
Grapher supports several automatic binning strategies, and can also automatically choose the best strategy to use based on data.
|
|
4
|
+
One key input into choosing a suiting strategy is the (log10) magnitude difference, i.e. $\log_{10}(maxValue) - \log_{10}(minValue)$.
|
|
5
|
+
For example, the magnitude difference between 4 and 400 is 2, because $4 \cdot 10^{\mathbf 2} = 400$.
|
|
6
|
+
|
|
7
|
+
In general, if we have a high magnitude difference, we want to use a logarithmic binning strategy, and if we have a low one we want to use an equal-sized binning strategy.
|
|
8
|
+
We only compute the magnitude difference based on positive values, and don't take the lowest and highest values directly, but rather some low and high quantiles.
|
|
9
|
+
|
|
10
|
+
```mermaid
|
|
11
|
+
flowchart TD
|
|
12
|
+
A -->|small magnitude diff| E
|
|
13
|
+
S --> E[equalSizeBins]
|
|
14
|
+
S(Binning Strategy) --> A[auto]
|
|
15
|
+
S --> L[logarithmic]
|
|
16
|
+
A -->|large magnitude diff| L
|
|
17
|
+
|
|
18
|
+
E --> E1(Compute min, max value based on quantiles)
|
|
19
|
+
E1 --> E2(Find fitting, nice step size that produces a decent number of bins)
|
|
20
|
+
E2 -->|e.g. 10| E3(Compute bins; e.g. 0, 10, 20, 30, 40)
|
|
21
|
+
|
|
22
|
+
L --> L1(Compute min, max value based on quantiles)
|
|
23
|
+
L1 --> L2(Decide specific strategy based on magnitude difference)
|
|
24
|
+
L2 -->|log-1-2-5| L3(Compute bins; e.g. 0, 10, 20, 50, 100)
|
|
25
|
+
L2 -->|log-1-3| L3
|
|
26
|
+
L2 -->|log-1-10| L3
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Finding min & max values
|
|
30
|
+
|
|
31
|
+
To find the min and max values for binning, we use low and high quantiles instead of the raw min and max values.
|
|
32
|
+
This naturally removes outliers and will produce open-ended bins more often.
|
|
33
|
+
|
|
34
|
+
Min & max values can also be overridden manually in the admin.
|
|
35
|
+
|
|
36
|
+
Note that min & max values need to be non-zero positive numbers for log strategies, but can be any number for equal-size strategies.
|
|
37
|
+
|
|
38
|
+
## Equal-size strategy
|
|
39
|
+
|
|
40
|
+
For the equal-size strategy, we are differentiating between four different strategies:
|
|
41
|
+
|
|
42
|
+
- `equalSizeBins-normal` (default): creates 5-9 bins
|
|
43
|
+
- `equalSizeBins-few-bins`: creates fewer bins than that
|
|
44
|
+
- `equalSizeBins-many-bins`: creates more bins than that
|
|
45
|
+
- `equalSizeBins-percent`: creates bins of size 10% (0-10%, 10-20%, ...)
|
|
46
|
+
|
|
47
|
+
Note that `equalSizeBins-percent` is chosen automatically when we detect that the data is a percentage that's roughly in the 0-100% range.
|
|
48
|
+
|
|
49
|
+
Once we have resolved the strategy to use, we can then proceed to pick the step size to use. This process is based on a list of "known good" step sizes, which we choose in such a way that we arrive at a reasonable number of bins that is within the target range of bins.
|
|
50
|
+
|
|
51
|
+
## Logarithmic strategy
|
|
52
|
+
|
|
53
|
+
For logarithmic binning, we also differentiate between several strategies:
|
|
54
|
+
|
|
55
|
+
- `log-auto`: automatically chooses the best log binning strategy based on data, useful for when you want to force using a log strategy
|
|
56
|
+
- `log-1-2-5`: follows the 1, 2, 5, 10, 20, ... pattern
|
|
57
|
+
- `log-1-3`: follows the 1, 3, 10, 30, 100, ... pattern
|
|
58
|
+
- `log-1-10`: follows the 1, 10, 100, ... pattern
|
|
59
|
+
|
|
60
|
+
After picking out one of these strategies (either manually or automatically), the log strategy then produces bins such that they fill the range between `minValue` and `maxValue`.
|
|
61
|
+
|
|
62
|
+
## Midpoints & midpoint modes
|
|
63
|
+
|
|
64
|
+
Some of our charts also have natural midpoints, where we want to create a diverging color scale around the midpoint.
|
|
65
|
+
The most common natural midpoint is 0.
|
|
66
|
+
|
|
67
|
+
We have a bunch of different midpoint modes:
|
|
68
|
+
|
|
69
|
+
- `undefined` (automatic): chooses `symmetric` if there are values on both sides of the midpoint, otherwise chooses `none`
|
|
70
|
+
- `none`: disregards the midpoint
|
|
71
|
+
- `symmetric`: creates bins around the midpoint that are fully symmetric, i.e. there's the same number of bins on both sides, and they have the same distance from the midpoint
|
|
72
|
+
- example: `-20, -10, -5, 0, 5, 10, 20`
|
|
73
|
+
- `same-num-bins`: creates (roughly) the same number of bins on both sides, but creates them independently, without regard to the distance from the midpoint
|
|
74
|
+
- example: `-10, -5, -2, 0, 10, 20, 50`
|
|
75
|
+
- `asymmetric`: like `symmetric`, but then prunes any bins that are not below `minValue` or above `maxValue`
|
|
76
|
+
- example: `-10, -5, 0, 5, 10, 20`
|
|
77
|
+
|
|
78
|
+
### NO integration with diverging color schemes
|
|
79
|
+
|
|
80
|
+
Note that currently, midpoints are not integrated with color scales.
|
|
81
|
+
This means that modes other than `symmetric` will not pick "good/bad" colors from a _diverging color scheme_ for the bins on both sides of the midpoint.
|
|
82
|
+
`symmetric` mode supports this, by virtue of it picking the same number of bins on both sides of the midpoint.
|
|
83
|
+
|
|
84
|
+
This is a feature that's clearly relevant, and it shouldn't be too hard to implement this in the future.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DualAxis } from "../axis/Axis"
|
|
2
|
+
import {
|
|
3
|
+
Color,
|
|
4
|
+
ComparisonLineConfig,
|
|
5
|
+
VerticalComparisonLineConfig,
|
|
6
|
+
} from "../../types/index.js"
|
|
7
|
+
import { VerticalComparisonLine } from "./VerticalComparisonLine"
|
|
8
|
+
import { CustomComparisonLine } from "./CustomComparisonLine"
|
|
9
|
+
import { isValidVerticalComparisonLineConfig } from "./ComparisonLineHelpers"
|
|
10
|
+
|
|
11
|
+
export interface ComparisonLineProps<LineConfig extends ComparisonLineConfig> {
|
|
12
|
+
dualAxis: DualAxis
|
|
13
|
+
comparisonLine: LineConfig
|
|
14
|
+
backgroundColor?: Color
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ComparisonLine = <LineConfig extends ComparisonLineConfig>(
|
|
18
|
+
props: ComparisonLineProps<LineConfig>
|
|
19
|
+
) => {
|
|
20
|
+
if (isVerticalComparisonLineProps(props)) {
|
|
21
|
+
return <VerticalComparisonLine {...props} />
|
|
22
|
+
} else {
|
|
23
|
+
return <CustomComparisonLine {...props} />
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isVerticalComparisonLineProps(
|
|
28
|
+
props: ComparisonLineProps<ComparisonLineConfig>
|
|
29
|
+
): props is ComparisonLineProps<VerticalComparisonLineConfig> {
|
|
30
|
+
return isValidVerticalComparisonLineConfig(props.comparisonLine)
|
|
31
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Formula from "fparser"
|
|
2
|
+
import { scaleLinear, scaleLog } from "d3-scale"
|
|
3
|
+
import { ScaleType } from "../../types/index.js"
|
|
4
|
+
|
|
5
|
+
export function generateComparisonLinePoints(
|
|
6
|
+
lineFunction: string = "x",
|
|
7
|
+
xScaleDomain: [number, number],
|
|
8
|
+
yScaleDomain: [number, number],
|
|
9
|
+
xScaleType: ScaleType,
|
|
10
|
+
yScaleType: ScaleType
|
|
11
|
+
): [number, number][] {
|
|
12
|
+
const formula = parseEquation(lineFunction)
|
|
13
|
+
const yFunc = (x: number): number | undefined =>
|
|
14
|
+
evalFormula(formula, { x }, undefined)
|
|
15
|
+
|
|
16
|
+
// Construct control data by running the equation across sample points
|
|
17
|
+
const numPoints = 500
|
|
18
|
+
|
|
19
|
+
const scaleFunction = xScaleType === ScaleType.log ? scaleLog : scaleLinear
|
|
20
|
+
const scale = scaleFunction().domain(xScaleDomain).range([0, numPoints])
|
|
21
|
+
|
|
22
|
+
const controlData: Array<[number, number]> = []
|
|
23
|
+
for (let i = 0; i < numPoints; i++) {
|
|
24
|
+
const x = scale.invert(i)
|
|
25
|
+
const y = yFunc(x)
|
|
26
|
+
|
|
27
|
+
if (y === undefined || Number.isNaN(x) || Number.isNaN(y)) continue
|
|
28
|
+
if (xScaleType === ScaleType.log && x <= 0) continue
|
|
29
|
+
if (yScaleType === ScaleType.log && y <= 0) continue
|
|
30
|
+
if (y > yScaleDomain[1]) continue
|
|
31
|
+
controlData.push([x, y])
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return controlData
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function evalFormula<D>(
|
|
38
|
+
expr: Formula | undefined,
|
|
39
|
+
context: Record<string, number>,
|
|
40
|
+
defaultOnError: D
|
|
41
|
+
): number | D {
|
|
42
|
+
if (expr === undefined) return defaultOnError
|
|
43
|
+
try {
|
|
44
|
+
return expr.evaluate(context) as number
|
|
45
|
+
} catch {
|
|
46
|
+
return defaultOnError
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function parseEquation(equation: string): Formula | undefined {
|
|
51
|
+
try {
|
|
52
|
+
const formula = new Formula(equation)
|
|
53
|
+
formula.ln = Math.log
|
|
54
|
+
formula.e = Math.E
|
|
55
|
+
return formula
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error(e)
|
|
58
|
+
return undefined
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComparisonLineConfig,
|
|
3
|
+
VerticalComparisonLineConfig,
|
|
4
|
+
} from "../../types/index.js"
|
|
5
|
+
|
|
6
|
+
export function isValidVerticalComparisonLineConfig(
|
|
7
|
+
lineConfig: ComparisonLineConfig
|
|
8
|
+
): lineConfig is VerticalComparisonLineConfig {
|
|
9
|
+
return "xEquals" in lineConfig && lineConfig.xEquals !== undefined
|
|
10
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { computed, makeObservable } from "mobx"
|
|
3
|
+
import { observer } from "mobx-react"
|
|
4
|
+
import { line as d3_line, curveLinear } from "d3-shape"
|
|
5
|
+
import {
|
|
6
|
+
guid,
|
|
7
|
+
PointVector,
|
|
8
|
+
makeIdForHumanConsumption,
|
|
9
|
+
} from "../../utils/index.js"
|
|
10
|
+
import { CustomComparisonLineConfig } from "../../types/index.js"
|
|
11
|
+
import { generateComparisonLinePoints } from "./ComparisonLineGenerator"
|
|
12
|
+
import { Halo } from "../../components/index.js"
|
|
13
|
+
import { GRAPHER_TEXT_OUTLINE_FACTOR } from "../core/GrapherConstants"
|
|
14
|
+
import { ClipPath, makeClipPath } from "../chart/ChartUtils"
|
|
15
|
+
import {
|
|
16
|
+
COMPARISON_LINE_STYLE,
|
|
17
|
+
COMPARISON_LINE_LABEL_STYLE,
|
|
18
|
+
} from "./ComparisonLineConstants"
|
|
19
|
+
import { ComparisonLineProps } from "./ComparisonLine"
|
|
20
|
+
|
|
21
|
+
@observer
|
|
22
|
+
export class CustomComparisonLine extends React.Component<
|
|
23
|
+
ComparisonLineProps<CustomComparisonLineConfig>
|
|
24
|
+
> {
|
|
25
|
+
private renderUid = guid()
|
|
26
|
+
private pathId = `path-${this.renderUid}`
|
|
27
|
+
|
|
28
|
+
constructor(props: ComparisonLineProps<CustomComparisonLineConfig>) {
|
|
29
|
+
super(props)
|
|
30
|
+
makeObservable(this)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@computed private get fontSize(): number {
|
|
34
|
+
return this.props.dualAxis.comparisonLineLabelFontSize
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@computed private get haloOutlineWidth(): number {
|
|
38
|
+
return GRAPHER_TEXT_OUTLINE_FACTOR * this.fontSize
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@computed get clipPath(): ClipPath {
|
|
42
|
+
return makeClipPath({
|
|
43
|
+
name: "axisBounds",
|
|
44
|
+
renderUid: this.renderUid,
|
|
45
|
+
box: this.props.dualAxis.innerBounds,
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@computed private get controlData(): [number, number][] {
|
|
50
|
+
const { comparisonLine, dualAxis } = this.props
|
|
51
|
+
const { horizontalAxis, verticalAxis } = dualAxis
|
|
52
|
+
|
|
53
|
+
return generateComparisonLinePoints(
|
|
54
|
+
comparisonLine.yEquals,
|
|
55
|
+
horizontalAxis.domain,
|
|
56
|
+
verticalAxis.domain,
|
|
57
|
+
horizontalAxis.scaleType,
|
|
58
|
+
verticalAxis.scaleType
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@computed private get linePath(): string | null {
|
|
63
|
+
const { controlData } = this
|
|
64
|
+
const { horizontalAxis, verticalAxis } = this.props.dualAxis
|
|
65
|
+
const line = d3_line()
|
|
66
|
+
.curve(curveLinear)
|
|
67
|
+
.x((d) => horizontalAxis.place(d[0]))
|
|
68
|
+
.y((d) => verticalAxis.place(d[1]))
|
|
69
|
+
return line(controlData)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@computed private get placedLabel():
|
|
73
|
+
| { x: number; y: number; angle: number; text: string }
|
|
74
|
+
| undefined {
|
|
75
|
+
const { label } = this.props.comparisonLine
|
|
76
|
+
if (!label) return
|
|
77
|
+
|
|
78
|
+
const { controlData } = this
|
|
79
|
+
const { horizontalAxis, verticalAxis, innerBounds } =
|
|
80
|
+
this.props.dualAxis
|
|
81
|
+
|
|
82
|
+
// Find the points of the line that are actually placeable on the chart
|
|
83
|
+
const linePoints = controlData
|
|
84
|
+
.map(
|
|
85
|
+
(d) =>
|
|
86
|
+
new PointVector(
|
|
87
|
+
horizontalAxis.place(d[0]),
|
|
88
|
+
verticalAxis.place(d[1])
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
.filter((p) => innerBounds.contains(p))
|
|
92
|
+
if (!linePoints.length) return
|
|
93
|
+
|
|
94
|
+
const labelPosition = linePoints[Math.floor(linePoints.length / 2)]
|
|
95
|
+
const p1 = linePoints[0]
|
|
96
|
+
const p2 = linePoints[linePoints.length - 1]
|
|
97
|
+
const angle = (Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180) / Math.PI
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
x: labelPosition.x,
|
|
101
|
+
y: labelPosition.y,
|
|
102
|
+
angle: angle,
|
|
103
|
+
text: label,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private renderLabel(): React.ReactElement | null {
|
|
108
|
+
const { pathId, renderUid, placedLabel } = this
|
|
109
|
+
|
|
110
|
+
if (!placedLabel) return null
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<text
|
|
114
|
+
style={{
|
|
115
|
+
...COMPARISON_LINE_LABEL_STYLE,
|
|
116
|
+
textAnchor: "end",
|
|
117
|
+
fontSize: this.fontSize,
|
|
118
|
+
}}
|
|
119
|
+
clipPath={this.clipPath.id}
|
|
120
|
+
>
|
|
121
|
+
<Halo
|
|
122
|
+
id={`halo-${renderUid}`}
|
|
123
|
+
outlineWidth={this.haloOutlineWidth}
|
|
124
|
+
outlineColor={this.props.backgroundColor}
|
|
125
|
+
>
|
|
126
|
+
<textPath
|
|
127
|
+
baselineShift="-0.2rem"
|
|
128
|
+
href={`#${pathId}`}
|
|
129
|
+
startOffset="90%"
|
|
130
|
+
>
|
|
131
|
+
{placedLabel.text}
|
|
132
|
+
</textPath>
|
|
133
|
+
</Halo>
|
|
134
|
+
</text>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
override render(): React.ReactElement | null {
|
|
139
|
+
if (!this.linePath) return null
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<g
|
|
143
|
+
id={makeIdForHumanConsumption(
|
|
144
|
+
"comparison-line",
|
|
145
|
+
this.props.comparisonLine.label
|
|
146
|
+
)}
|
|
147
|
+
>
|
|
148
|
+
{this.clipPath.element}
|
|
149
|
+
<path
|
|
150
|
+
style={COMPARISON_LINE_STYLE}
|
|
151
|
+
id={this.pathId}
|
|
152
|
+
d={this.linePath}
|
|
153
|
+
clipPath={this.clipPath.id}
|
|
154
|
+
/>
|
|
155
|
+
{this.renderLabel()}
|
|
156
|
+
</g>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { computed, makeObservable } from "mobx"
|
|
3
|
+
import { observer } from "mobx-react"
|
|
4
|
+
import * as _ from "lodash-es"
|
|
5
|
+
import {
|
|
6
|
+
Bounds,
|
|
7
|
+
dyFromAlign,
|
|
8
|
+
makeIdForHumanConsumption,
|
|
9
|
+
VerticalAlign,
|
|
10
|
+
} from "../../utils/index.js"
|
|
11
|
+
import {
|
|
12
|
+
COMPARISON_LINE_STYLE,
|
|
13
|
+
COMPARISON_LINE_LABEL_STYLE,
|
|
14
|
+
} from "./ComparisonLineConstants"
|
|
15
|
+
import { ComparisonLineProps } from "./ComparisonLine"
|
|
16
|
+
import { VerticalComparisonLineConfig } from "../../types/index.js"
|
|
17
|
+
import { isValidVerticalComparisonLineConfig } from "./ComparisonLineHelpers"
|
|
18
|
+
|
|
19
|
+
@observer
|
|
20
|
+
export class VerticalComparisonLine extends React.Component<
|
|
21
|
+
ComparisonLineProps<VerticalComparisonLineConfig>
|
|
22
|
+
> {
|
|
23
|
+
constructor(props: ComparisonLineProps<VerticalComparisonLineConfig>) {
|
|
24
|
+
super(props)
|
|
25
|
+
makeObservable(this)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@computed private get fontSize(): number {
|
|
29
|
+
return this.props.dualAxis.comparisonLineLabelFontSize
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@computed private get labelHeight(): number {
|
|
33
|
+
// Since the label isn't wrapped, its height equals the font size
|
|
34
|
+
return this.fontSize
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@computed private get lineConfig(): VerticalComparisonLineConfig {
|
|
38
|
+
return this.props.comparisonLine
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@computed
|
|
42
|
+
private get otherVerticalComparisonLines(): VerticalComparisonLineConfig[] {
|
|
43
|
+
return this.props.dualAxis.comparisonLines.filter(
|
|
44
|
+
(lineConfig): lineConfig is VerticalComparisonLineConfig =>
|
|
45
|
+
isValidVerticalComparisonLineConfig(lineConfig) &&
|
|
46
|
+
lineConfig.xEquals !== this.props.comparisonLine.xEquals
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Nearest comparison line to the left of this line */
|
|
51
|
+
@computed private get closestLeftLine():
|
|
52
|
+
| VerticalComparisonLineConfig
|
|
53
|
+
| undefined {
|
|
54
|
+
const linesOnTheLeft = this.otherVerticalComparisonLines.filter(
|
|
55
|
+
(otherLine) => otherLine.xEquals < this.lineConfig.xEquals
|
|
56
|
+
)
|
|
57
|
+
return _.maxBy(linesOnTheLeft, (line) => line.xEquals)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Nearest comparison line to the right of this line */
|
|
61
|
+
@computed private get closestRightLine():
|
|
62
|
+
| VerticalComparisonLineConfig
|
|
63
|
+
| undefined {
|
|
64
|
+
const linesOnTheRight = this.otherVerticalComparisonLines.filter(
|
|
65
|
+
(otherLine) => otherLine.xEquals > this.lineConfig.xEquals
|
|
66
|
+
)
|
|
67
|
+
return _.minBy(linesOnTheRight, (line) => line.xEquals)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* X-coordinate of the leftmost boundary for label placement.
|
|
72
|
+
*
|
|
73
|
+
* Either the position of the nearest comparison line on the left
|
|
74
|
+
* or the chart's bound.
|
|
75
|
+
*/
|
|
76
|
+
@computed private get leftBoundX(): number {
|
|
77
|
+
const { closestLeftLine } = this
|
|
78
|
+
const { innerBounds, horizontalAxis } = this.props.dualAxis
|
|
79
|
+
|
|
80
|
+
if (!closestLeftLine) return innerBounds.left
|
|
81
|
+
|
|
82
|
+
return Math.max(
|
|
83
|
+
horizontalAxis.place(closestLeftLine.xEquals),
|
|
84
|
+
innerBounds.left
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* X-coordinate of the rightmost boundary for label placement.
|
|
90
|
+
*
|
|
91
|
+
* Either the position of the nearest comparison line on the right
|
|
92
|
+
* or the chart's bound.
|
|
93
|
+
*/
|
|
94
|
+
@computed private get rightBoundX(): number {
|
|
95
|
+
const { closestRightLine } = this
|
|
96
|
+
const { innerBounds, horizontalAxis } = this.props.dualAxis
|
|
97
|
+
|
|
98
|
+
if (!closestRightLine) return innerBounds.right
|
|
99
|
+
|
|
100
|
+
return Math.min(
|
|
101
|
+
horizontalAxis.place(closestRightLine.xEquals),
|
|
102
|
+
innerBounds.right
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Screen coordinates for rendering the vertical line */
|
|
107
|
+
@computed private get lineCoordinates():
|
|
108
|
+
| { x: number; y1: number; y2: number }
|
|
109
|
+
| undefined {
|
|
110
|
+
const { dualAxis } = this.props
|
|
111
|
+
const { horizontalAxis, innerBounds } = dualAxis
|
|
112
|
+
const xValue = this.lineConfig.xEquals
|
|
113
|
+
const [minX, maxX] = horizontalAxis.domain
|
|
114
|
+
|
|
115
|
+
// Only render if the line is within the chart bounds
|
|
116
|
+
const isWithinBounds = xValue >= minX && xValue <= maxX
|
|
117
|
+
if (!isWithinBounds) return undefined
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
x: horizontalAxis.place(xValue),
|
|
121
|
+
// The line extends outside of the chart area, so that the label
|
|
122
|
+
// isn't covered by any chart elements
|
|
123
|
+
y1: innerBounds.top - this.labelHeight,
|
|
124
|
+
y2: innerBounds.bottom,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Calculated position and alignment for the label text */
|
|
129
|
+
@computed private get labelPosition():
|
|
130
|
+
| { x: number; y: number; anchor: "start" | "end" }
|
|
131
|
+
| undefined {
|
|
132
|
+
const { fontSize, lineCoordinates, rightBoundX, leftBoundX } = this
|
|
133
|
+
const { label } = this.lineConfig
|
|
134
|
+
|
|
135
|
+
if (!label || !lineCoordinates) return undefined
|
|
136
|
+
|
|
137
|
+
const { x, y1: y } = lineCoordinates
|
|
138
|
+
|
|
139
|
+
// Calculate available space on both sides
|
|
140
|
+
const padding = 4 // padding between label and line
|
|
141
|
+
const availableSpaceRight = rightBoundX - x - 2 * padding
|
|
142
|
+
const availableSpaceLeft = x - leftBoundX - 2 * padding
|
|
143
|
+
|
|
144
|
+
// Determine label placement:
|
|
145
|
+
// - Prefer the right side if there's enough space
|
|
146
|
+
// - Otherwise, try placing the label on the left side
|
|
147
|
+
// - If neither side has enough space, hide the label
|
|
148
|
+
const labelWidth = Bounds.forText(label, { fontSize }).width
|
|
149
|
+
const side =
|
|
150
|
+
labelWidth <= availableSpaceRight
|
|
151
|
+
? "right"
|
|
152
|
+
: labelWidth <= availableSpaceLeft
|
|
153
|
+
? "left"
|
|
154
|
+
: null
|
|
155
|
+
|
|
156
|
+
// Can't fit the label
|
|
157
|
+
if (side === null) return undefined
|
|
158
|
+
|
|
159
|
+
// Add a bit of padding between label and line and determine text anchor
|
|
160
|
+
const labelX = side === "right" ? x + padding : x - padding
|
|
161
|
+
const anchor = side === "right" ? "start" : "end"
|
|
162
|
+
|
|
163
|
+
return { x: labelX, y, anchor }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private renderLabel(): React.ReactElement | null {
|
|
167
|
+
if (!this.labelPosition) return null
|
|
168
|
+
|
|
169
|
+
const { x, y, anchor } = this.labelPosition
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<text
|
|
173
|
+
{...COMPARISON_LINE_LABEL_STYLE}
|
|
174
|
+
x={x}
|
|
175
|
+
y={y}
|
|
176
|
+
fontSize={this.fontSize}
|
|
177
|
+
dy={dyFromAlign(VerticalAlign.bottom)}
|
|
178
|
+
textAnchor={anchor}
|
|
179
|
+
>
|
|
180
|
+
{this.lineConfig.label}
|
|
181
|
+
</text>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
override render(): React.ReactElement | null {
|
|
186
|
+
if (!this.lineCoordinates || !this.labelPosition) return null
|
|
187
|
+
|
|
188
|
+
const { x, y1, y2 } = this.lineCoordinates
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<g
|
|
192
|
+
id={makeIdForHumanConsumption(
|
|
193
|
+
"comparison-line",
|
|
194
|
+
this.lineConfig.label
|
|
195
|
+
)}
|
|
196
|
+
>
|
|
197
|
+
<line
|
|
198
|
+
x1={x}
|
|
199
|
+
y1={y1}
|
|
200
|
+
x2={x}
|
|
201
|
+
y2={y2}
|
|
202
|
+
style={COMPARISON_LINE_STYLE}
|
|
203
|
+
/>
|
|
204
|
+
{this.renderLabel()}
|
|
205
|
+
</g>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// keep in sync with constant values in ActionButtons.tsx
|
|
2
|
+
$actionButtonHeight: 32px; // keep in sync with BUTTON_HEIGHT
|
|
3
|
+
$paddingBetweenActionButtons: 8px; // keep in sync with PADDING_BETWEEN_BUTTONS
|
|
4
|
+
$paddingBetweenIconAndLabel: 8px; // keep in sync with PADDING_BETWEEN_ICON_AND_LABEL
|
|
5
|
+
$paddingX: 12px; // keep in sync with PADDING_X
|
|
6
|
+
|
|
7
|
+
.ActionButtons {
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 0;
|
|
10
|
+
white-space: nowrap;
|
|
11
|
+
|
|
12
|
+
ul {
|
|
13
|
+
display: flex;
|
|
14
|
+
list-style: none;
|
|
15
|
+
height: $actionButtonHeight;
|
|
16
|
+
padding: 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
li {
|
|
20
|
+
height: 100%;
|
|
21
|
+
display: inline-block;
|
|
22
|
+
position: relative;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
li + li {
|
|
26
|
+
margin-left: $paddingBetweenActionButtons;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
div.ActionButton {
|
|
31
|
+
--light-fill: #{$gray-10};
|
|
32
|
+
--hover-fill: #{$gray-20};
|
|
33
|
+
--active-fill: #{$blue-20};
|
|
34
|
+
--text-color: #{$dark-text};
|
|
35
|
+
|
|
36
|
+
&.ActionButton--exploreData {
|
|
37
|
+
--light-fill: #{$blue-20};
|
|
38
|
+
--hover-fill: #{$blue-20};
|
|
39
|
+
--active-fill: #{$blue-10};
|
|
40
|
+
--text-color: #{$blue-90};
|
|
41
|
+
|
|
42
|
+
--hover-decoration: underline;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&.ActionButton--donate {
|
|
46
|
+
--light-fill: #{rgba($vermillion, 0.15)};
|
|
47
|
+
--hover-fill: #{rgba(#f4a39f, 0.15)};
|
|
48
|
+
--active-fill: var(--hover-fill);
|
|
49
|
+
--text-color: #{$vermillion};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
height: 100%;
|
|
53
|
+
border-radius: 4px;
|
|
54
|
+
position: relative;
|
|
55
|
+
|
|
56
|
+
button,
|
|
57
|
+
a {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: $paddingBetweenIconAndLabel;
|
|
61
|
+
height: 100%;
|
|
62
|
+
width: 100%;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
color: var(--text-color);
|
|
65
|
+
font-size: 13px;
|
|
66
|
+
font-weight: 500;
|
|
67
|
+
padding: 0 $paddingX;
|
|
68
|
+
border-radius: inherit;
|
|
69
|
+
background-color: var(--light-fill);
|
|
70
|
+
position: relative;
|
|
71
|
+
letter-spacing: 0.01em;
|
|
72
|
+
|
|
73
|
+
svg {
|
|
74
|
+
font-size: 12px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
&.icon-only {
|
|
78
|
+
justify-content: center;
|
|
79
|
+
padding: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&:visited {
|
|
83
|
+
color: var(--text-color);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
&:hover {
|
|
87
|
+
background-color: var(--hover-fill);
|
|
88
|
+
text-decoration: var(--hover-decoration);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
&:active,
|
|
92
|
+
&.active {
|
|
93
|
+
color: $active-text;
|
|
94
|
+
background-color: var(--active-fill);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|