@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,923 @@
|
|
|
1
|
+
import * as _ from "lodash-es"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import * as R from "remeda"
|
|
4
|
+
import { computed, makeObservable } from "mobx"
|
|
5
|
+
import { observer } from "mobx-react"
|
|
6
|
+
import {
|
|
7
|
+
dyFromAlign,
|
|
8
|
+
removeAllWhitespace,
|
|
9
|
+
Bounds,
|
|
10
|
+
HorizontalAlign,
|
|
11
|
+
VerticalAlign,
|
|
12
|
+
makeIdForHumanConsumption,
|
|
13
|
+
} from "../../utils/index.js"
|
|
14
|
+
import { TextWrap } from "../../components/index.js"
|
|
15
|
+
import {
|
|
16
|
+
ColorScaleBin,
|
|
17
|
+
NumericBin,
|
|
18
|
+
CategoricalBin,
|
|
19
|
+
} from "../color/ColorScaleBin"
|
|
20
|
+
import {
|
|
21
|
+
BASE_FONT_SIZE,
|
|
22
|
+
GRAPHER_FONT_SCALE_12,
|
|
23
|
+
GRAPHER_FONT_SCALE_12_8,
|
|
24
|
+
GRAPHER_FONT_SCALE_14,
|
|
25
|
+
} from "../core/GrapherConstants"
|
|
26
|
+
import { darkenColorForLine } from "../color/ColorUtils"
|
|
27
|
+
import {
|
|
28
|
+
LegendInteractionState,
|
|
29
|
+
LegendStyleConfig,
|
|
30
|
+
LegendTextStyle,
|
|
31
|
+
LegendMarkerStyle,
|
|
32
|
+
} from "../legend/LegendInteractionState"
|
|
33
|
+
import { GRAPHER_DARK_TEXT } from "../color/ColorConstants"
|
|
34
|
+
|
|
35
|
+
export interface PositionedBin {
|
|
36
|
+
x: number
|
|
37
|
+
width: number
|
|
38
|
+
bin: ColorScaleBin
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface NumericLabel {
|
|
42
|
+
text: string
|
|
43
|
+
fontSize: number
|
|
44
|
+
bounds: Bounds
|
|
45
|
+
priority?: boolean
|
|
46
|
+
hidden: boolean
|
|
47
|
+
raised: boolean
|
|
48
|
+
bin: ColorScaleBin
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface CategoricalMark {
|
|
52
|
+
x: number
|
|
53
|
+
y: number
|
|
54
|
+
rectSize: number
|
|
55
|
+
width: number
|
|
56
|
+
label: {
|
|
57
|
+
text: string
|
|
58
|
+
bounds: Bounds
|
|
59
|
+
fontSize: number
|
|
60
|
+
}
|
|
61
|
+
bin: CategoricalBin
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface MarkLine {
|
|
65
|
+
totalWidth: number
|
|
66
|
+
marks: CategoricalMark[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// TODO unify properties across categorical & numeric legend.
|
|
70
|
+
// This would make multiple legends per chart less convenient (only used in Map), but we shouldn't
|
|
71
|
+
// be using multiple anyway – instead the numeric should also handle categorical bins too.
|
|
72
|
+
export interface HorizontalColorLegendManager {
|
|
73
|
+
fontSize?: number
|
|
74
|
+
legendX?: number
|
|
75
|
+
legendAlign?: HorizontalAlign
|
|
76
|
+
legendTitle?: string
|
|
77
|
+
categoryLegendY?: number
|
|
78
|
+
numericLegendY?: number
|
|
79
|
+
legendWidth?: number
|
|
80
|
+
legendMaxWidth?: number
|
|
81
|
+
legendHeight?: number
|
|
82
|
+
legendTickSize?: number
|
|
83
|
+
legendCursor?: "pointer" | "default"
|
|
84
|
+
categoricalLegendData?: CategoricalBin[]
|
|
85
|
+
numericLegendData?: ColorScaleBin[]
|
|
86
|
+
numericBinSize?: number
|
|
87
|
+
onLegendMouseEnter?: (d: ColorScaleBin) => void
|
|
88
|
+
onLegendMouseLeave?: () => void
|
|
89
|
+
onLegendMouseOver?: (d: ColorScaleBin) => void
|
|
90
|
+
onLegendClick?: (d: ColorScaleBin) => void
|
|
91
|
+
isStatic?: boolean
|
|
92
|
+
getLegendBinState?: (bin: ColorScaleBin) => LegendInteractionState
|
|
93
|
+
legendStyleConfig?: LegendStyleConfig
|
|
94
|
+
categoricalLegendStyleConfig?: LegendStyleConfig
|
|
95
|
+
numericLegendStyleConfig?: LegendStyleConfig
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const DEFAULT_NUMERIC_BIN_SIZE = 10
|
|
99
|
+
const DEFAULT_NUMERIC_BIN_STROKE = "#333"
|
|
100
|
+
const DEFAULT_NUMERIC_BIN_STROKE_WIDTH = 0.3
|
|
101
|
+
const DEFAULT_TEXT_COLOR = "#111"
|
|
102
|
+
const DEFAULT_TICK_SIZE = 3
|
|
103
|
+
|
|
104
|
+
const CATEGORICAL_BIN_MIN_WIDTH = 20
|
|
105
|
+
const SPACE_BETWEEN_CATEGORICAL_BINS = 7
|
|
106
|
+
const MINIMUM_LABEL_DISTANCE = 5
|
|
107
|
+
|
|
108
|
+
export abstract class HorizontalColorLegend extends React.Component<{
|
|
109
|
+
manager: HorizontalColorLegendManager
|
|
110
|
+
}> {
|
|
111
|
+
constructor(props: { manager: HorizontalColorLegendManager }) {
|
|
112
|
+
super(props)
|
|
113
|
+
makeObservable(this)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@computed protected get manager(): HorizontalColorLegendManager {
|
|
117
|
+
return this.props.manager
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@computed protected get legendX(): number {
|
|
121
|
+
return this.manager.legendX ?? 0
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@computed protected get categoryLegendY(): number {
|
|
125
|
+
return this.manager.categoryLegendY ?? 0
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@computed protected get numericLegendY(): number {
|
|
129
|
+
return this.manager.numericLegendY ?? 0
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@computed protected get legendMaxWidth(): number | undefined {
|
|
133
|
+
return this.manager.legendMaxWidth
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@computed protected get legendHeight(): number {
|
|
137
|
+
return this.manager.legendHeight ?? 200
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@computed protected get legendAlign(): HorizontalAlign {
|
|
141
|
+
// Assume center alignment if none specified, for backwards-compatibility
|
|
142
|
+
return this.manager.legendAlign ?? HorizontalAlign.center
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@computed protected get fontSize(): number {
|
|
146
|
+
return this.manager.fontSize ?? BASE_FONT_SIZE
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@computed protected get legendTickSize(): number {
|
|
150
|
+
return this.manager.legendTickSize ?? DEFAULT_TICK_SIZE
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
protected getBinState(bin: ColorScaleBin): LegendInteractionState {
|
|
154
|
+
return (
|
|
155
|
+
this.manager.getLegendBinState?.(bin) ??
|
|
156
|
+
LegendInteractionState.Default
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
abstract get height(): number
|
|
161
|
+
abstract get width(): number
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@observer
|
|
165
|
+
export class HorizontalNumericColorLegend extends HorizontalColorLegend {
|
|
166
|
+
base = React.createRef<SVGGElement>()
|
|
167
|
+
|
|
168
|
+
constructor(props: { manager: HorizontalColorLegendManager }) {
|
|
169
|
+
super(props)
|
|
170
|
+
|
|
171
|
+
makeObservable(this)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@computed private get numericLegendData(): ColorScaleBin[] {
|
|
175
|
+
return this.manager.numericLegendData ?? []
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@computed private get visibleBins(): ColorScaleBin[] {
|
|
179
|
+
return this.numericLegendData.filter((bin) => !bin.isHidden)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@computed private get numericBins(): NumericBin[] {
|
|
183
|
+
return this.visibleBins.filter(
|
|
184
|
+
(bin): bin is NumericBin => bin instanceof NumericBin
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@computed private get numericBinSize(): number {
|
|
189
|
+
return this.props.manager.numericBinSize ?? DEFAULT_NUMERIC_BIN_SIZE
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@computed private get tickFontSize(): number {
|
|
193
|
+
return GRAPHER_FONT_SCALE_12 * this.fontSize
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@computed private get itemMargin(): number {
|
|
197
|
+
return Math.round(this.fontSize * 1.125)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@computed private get maxWidth(): number {
|
|
201
|
+
return this.manager.legendMaxWidth ?? this.manager.legendWidth ?? 200
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private getTickLabelWidth(label: string): number {
|
|
205
|
+
return Bounds.forText(label, {
|
|
206
|
+
fontSize: this.tickFontSize,
|
|
207
|
+
}).width
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private getCategoricalBinWidth(bin: ColorScaleBin): number {
|
|
211
|
+
return Math.max(
|
|
212
|
+
this.getTickLabelWidth(bin.text),
|
|
213
|
+
CATEGORICAL_BIN_MIN_WIDTH
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@computed private get totalCategoricalWidth(): number {
|
|
218
|
+
const { visibleBins, itemMargin } = this
|
|
219
|
+
const widths = visibleBins.map((bin) =>
|
|
220
|
+
bin instanceof CategoricalBin && !bin.isHidden
|
|
221
|
+
? this.getCategoricalBinWidth(bin) + itemMargin
|
|
222
|
+
: 0
|
|
223
|
+
)
|
|
224
|
+
return _.sum(widths)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@computed private get isAutoWidth(): boolean {
|
|
228
|
+
return (
|
|
229
|
+
this.manager.legendWidth === undefined &&
|
|
230
|
+
this.manager.legendMaxWidth !== undefined
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private getNumericLabelMinWidth(bin: NumericBin): number {
|
|
235
|
+
if (bin.text) {
|
|
236
|
+
const tickLabelWidth = this.getTickLabelWidth(bin.text)
|
|
237
|
+
return tickLabelWidth + MINIMUM_LABEL_DISTANCE
|
|
238
|
+
} else {
|
|
239
|
+
const combinedLabelWidths = _.sum(
|
|
240
|
+
[bin.minText, bin.maxText].map(
|
|
241
|
+
(text) =>
|
|
242
|
+
// because labels are center-aligned, only half the label space is required
|
|
243
|
+
this.getTickLabelWidth(text) / 2
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
return combinedLabelWidths + MINIMUM_LABEL_DISTANCE * 2
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Overstretched legends don't look good.
|
|
251
|
+
// If the manager provides `legendMaxWidth`, then we calculate an _ideal_ width for the legend.
|
|
252
|
+
@computed private get idealNumericWidth(): number {
|
|
253
|
+
const binCount = this.numericBins.length
|
|
254
|
+
const spaceRequirements = this.numericBins.map((bin) => ({
|
|
255
|
+
labelSpace: this.getNumericLabelMinWidth(bin),
|
|
256
|
+
}))
|
|
257
|
+
// Make sure the legend is big enough to avoid overlapping labels (including `raisedMode`)
|
|
258
|
+
|
|
259
|
+
// Try to keep the minimum close to the size of the "No data" bin,
|
|
260
|
+
// so they look visually balanced somewhat.
|
|
261
|
+
const minBinWidth = this.fontSize * 3.25
|
|
262
|
+
const maxBinWidth =
|
|
263
|
+
_.max(
|
|
264
|
+
spaceRequirements.map(({ labelSpace }) =>
|
|
265
|
+
Math.max(labelSpace, minBinWidth)
|
|
266
|
+
)
|
|
267
|
+
) ?? 0
|
|
268
|
+
return Math.round(maxBinWidth * binCount)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@computed get width(): number {
|
|
272
|
+
if (this.isAutoWidth) {
|
|
273
|
+
return Math.min(
|
|
274
|
+
this.maxWidth,
|
|
275
|
+
this.legendTitleWidth +
|
|
276
|
+
this.totalCategoricalWidth +
|
|
277
|
+
this.idealNumericWidth
|
|
278
|
+
)
|
|
279
|
+
} else {
|
|
280
|
+
return this.maxWidth
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
@computed private get availableNumericWidth(): number {
|
|
285
|
+
return this.width - this.totalCategoricalWidth - this.legendTitleWidth
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Since we calculate the width automatically in some cases (when `isAutoWidth` is true),
|
|
289
|
+
// we need to shift X to align the legend horizontally (`legendAlign`).
|
|
290
|
+
@computed private get x(): number {
|
|
291
|
+
const { width, maxWidth, legendAlign, legendX } = this
|
|
292
|
+
const widthDiff = maxWidth - width
|
|
293
|
+
if (legendAlign === HorizontalAlign.center) {
|
|
294
|
+
return legendX + widthDiff / 2
|
|
295
|
+
} else if (legendAlign === HorizontalAlign.right) {
|
|
296
|
+
return legendX + widthDiff
|
|
297
|
+
} else {
|
|
298
|
+
return legendX // left align
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@computed private get positionedBins(): PositionedBin[] {
|
|
303
|
+
const {
|
|
304
|
+
availableNumericWidth,
|
|
305
|
+
visibleBins,
|
|
306
|
+
numericBins,
|
|
307
|
+
legendTitleWidth,
|
|
308
|
+
x,
|
|
309
|
+
} = this
|
|
310
|
+
|
|
311
|
+
let xOffset = x + legendTitleWidth
|
|
312
|
+
let prevBin: ColorScaleBin | undefined
|
|
313
|
+
|
|
314
|
+
return visibleBins.map((bin, index) => {
|
|
315
|
+
const isFirst = index === 0
|
|
316
|
+
let width: number = this.getCategoricalBinWidth(bin)
|
|
317
|
+
let marginLeft: number = isFirst ? 0 : this.itemMargin
|
|
318
|
+
|
|
319
|
+
if (bin instanceof NumericBin) {
|
|
320
|
+
width = availableNumericWidth / numericBins.length
|
|
321
|
+
|
|
322
|
+
// Don't add any margin between numeric bins
|
|
323
|
+
if (prevBin instanceof NumericBin) {
|
|
324
|
+
marginLeft = 0
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const x = xOffset + marginLeft
|
|
329
|
+
xOffset = x + width
|
|
330
|
+
prevBin = bin
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
x,
|
|
334
|
+
width,
|
|
335
|
+
bin,
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
@computed private get legendTitleFontSize(): number {
|
|
341
|
+
return this.fontSize * GRAPHER_FONT_SCALE_14
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
@computed private get legendTitle(): TextWrap | undefined {
|
|
345
|
+
const { legendTitle } = this.manager
|
|
346
|
+
return legendTitle
|
|
347
|
+
? new TextWrap({
|
|
348
|
+
text: legendTitle,
|
|
349
|
+
fontSize: this.legendTitleFontSize,
|
|
350
|
+
fontWeight: 700,
|
|
351
|
+
maxWidth: this.maxWidth / 3,
|
|
352
|
+
lineHeight: 1,
|
|
353
|
+
})
|
|
354
|
+
: undefined
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
@computed private get legendTitleWidth(): number {
|
|
358
|
+
return this.legendTitle ? this.legendTitle.width + this.itemMargin : 0
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
@computed private get numericLabels(): NumericLabel[] {
|
|
362
|
+
const { numericBinSize, positionedBins, tickFontSize } = this
|
|
363
|
+
|
|
364
|
+
const makeBoundaryLabel = (
|
|
365
|
+
bin: PositionedBin,
|
|
366
|
+
minOrMax: "min" | "max",
|
|
367
|
+
text: string
|
|
368
|
+
): NumericLabel => {
|
|
369
|
+
const labelBounds = Bounds.forText(text, { fontSize: tickFontSize })
|
|
370
|
+
const x =
|
|
371
|
+
bin.x +
|
|
372
|
+
(minOrMax === "min" ? 0 : bin.width) -
|
|
373
|
+
labelBounds.width / 2
|
|
374
|
+
const y = -numericBinSize - labelBounds.height - this.legendTickSize
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
text: text,
|
|
378
|
+
fontSize: tickFontSize,
|
|
379
|
+
bounds: labelBounds.set({ x: x, y: y }),
|
|
380
|
+
hidden: false,
|
|
381
|
+
raised: false,
|
|
382
|
+
bin: bin.bin,
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const makeRangeLabel = (bin: PositionedBin): NumericLabel => {
|
|
387
|
+
const labelBounds = Bounds.forText(bin.bin.text, {
|
|
388
|
+
fontSize: tickFontSize,
|
|
389
|
+
})
|
|
390
|
+
const x = bin.x + bin.width / 2 - labelBounds.width / 2
|
|
391
|
+
const y = -numericBinSize - labelBounds.height - this.legendTickSize
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
text: bin.bin.text,
|
|
395
|
+
fontSize: tickFontSize,
|
|
396
|
+
bounds: labelBounds.set({ x: x, y: y }),
|
|
397
|
+
priority: true,
|
|
398
|
+
hidden: false,
|
|
399
|
+
raised: false,
|
|
400
|
+
bin: bin.bin,
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
let labels: NumericLabel[] = []
|
|
405
|
+
for (const bin of positionedBins) {
|
|
406
|
+
if (bin.bin.text) labels.push(makeRangeLabel(bin))
|
|
407
|
+
else if (bin.bin instanceof NumericBin) {
|
|
408
|
+
if (bin.bin.minText)
|
|
409
|
+
labels.push(makeBoundaryLabel(bin, "min", bin.bin.minText))
|
|
410
|
+
if (bin === R.last(positionedBins) && bin.bin.maxText)
|
|
411
|
+
labels.push(makeBoundaryLabel(bin, "max", bin.bin.maxText))
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
for (let index = 0; index < labels.length; index++) {
|
|
416
|
+
const l1 = labels[index]
|
|
417
|
+
if (l1.hidden) continue
|
|
418
|
+
|
|
419
|
+
for (let j = index + 1; j < labels.length; j++) {
|
|
420
|
+
const l2 = labels[j]
|
|
421
|
+
if (
|
|
422
|
+
l1.bounds.right + MINIMUM_LABEL_DISTANCE >
|
|
423
|
+
l2.bounds.centerX ||
|
|
424
|
+
(l2.bounds.left - MINIMUM_LABEL_DISTANCE <
|
|
425
|
+
l1.bounds.centerX &&
|
|
426
|
+
!l2.priority)
|
|
427
|
+
)
|
|
428
|
+
l2.hidden = true
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
labels = labels.filter((label) => !label.hidden)
|
|
433
|
+
|
|
434
|
+
// If labels overlap, first we try alternating raised labels
|
|
435
|
+
let raisedMode = false
|
|
436
|
+
for (let index = 1; index < labels.length; index++) {
|
|
437
|
+
const l1 = labels[index - 1],
|
|
438
|
+
l2 = labels[index]
|
|
439
|
+
if (l1.bounds.right + MINIMUM_LABEL_DISTANCE > l2.bounds.left) {
|
|
440
|
+
raisedMode = true
|
|
441
|
+
break
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (raisedMode) {
|
|
446
|
+
for (let index = 1; index < labels.length; index++) {
|
|
447
|
+
const label = labels[index]
|
|
448
|
+
if (index % 2 !== 0) {
|
|
449
|
+
label.bounds = label.bounds.set({
|
|
450
|
+
y: label.bounds.y - label.bounds.height - 1,
|
|
451
|
+
})
|
|
452
|
+
label.raised = true
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return labels
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
@computed get height(): number {
|
|
461
|
+
return Math.abs(
|
|
462
|
+
_.min(this.numericLabels.map((label) => label.bounds.y)) ?? 0
|
|
463
|
+
)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
@computed private get legendStyleConfig(): LegendStyleConfig | undefined {
|
|
467
|
+
return (
|
|
468
|
+
this.manager.numericLegendStyleConfig ??
|
|
469
|
+
this.manager.legendStyleConfig
|
|
470
|
+
)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
private getTextStyleConfig(bin: ColorScaleBin): LegendTextStyle {
|
|
474
|
+
const state = this.getBinState(bin)
|
|
475
|
+
const styleConfig = this.legendStyleConfig?.text
|
|
476
|
+
const defaultStyle = styleConfig?.default
|
|
477
|
+
const currentStyle = styleConfig?.[state]
|
|
478
|
+
return { color: GRAPHER_DARK_TEXT, ...defaultStyle, ...currentStyle }
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
private getMarkerStyleConfig(bin: ColorScaleBin): LegendMarkerStyle {
|
|
482
|
+
const state = this.getBinState(bin)
|
|
483
|
+
const styleConfig = this.legendStyleConfig?.marker
|
|
484
|
+
const defaultStyle = styleConfig?.default
|
|
485
|
+
const current = styleConfig?.[state]
|
|
486
|
+
return {
|
|
487
|
+
fill: bin.color,
|
|
488
|
+
stroke: DEFAULT_NUMERIC_BIN_STROKE,
|
|
489
|
+
strokeWidth: DEFAULT_NUMERIC_BIN_STROKE_WIDTH,
|
|
490
|
+
...defaultStyle,
|
|
491
|
+
...current,
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
override render(): React.ReactElement {
|
|
496
|
+
const { numericLabels, numericBinSize, positionedBins, height } = this
|
|
497
|
+
|
|
498
|
+
const bottomY = this.numericLegendY + height
|
|
499
|
+
|
|
500
|
+
const textColor =
|
|
501
|
+
this.legendStyleConfig?.text?.default?.color ?? DEFAULT_TEXT_COLOR
|
|
502
|
+
|
|
503
|
+
return (
|
|
504
|
+
<g
|
|
505
|
+
ref={this.base}
|
|
506
|
+
id={makeIdForHumanConsumption("numeric-color-legend")}
|
|
507
|
+
className="numericColorLegend"
|
|
508
|
+
>
|
|
509
|
+
<g id={makeIdForHumanConsumption("lines")}>
|
|
510
|
+
{numericLabels.map((label, index) => {
|
|
511
|
+
const style = this.getMarkerStyleConfig(label.bin)
|
|
512
|
+
return (
|
|
513
|
+
<line
|
|
514
|
+
key={index}
|
|
515
|
+
id={makeIdForHumanConsumption(label.text)}
|
|
516
|
+
x1={label.bounds.x + label.bounds.width / 2}
|
|
517
|
+
y1={bottomY - numericBinSize}
|
|
518
|
+
x2={label.bounds.x + label.bounds.width / 2}
|
|
519
|
+
y2={
|
|
520
|
+
bottomY +
|
|
521
|
+
label.bounds.y +
|
|
522
|
+
label.bounds.height
|
|
523
|
+
}
|
|
524
|
+
// if we use a light color for stroke (e.g. white), we want it to stay
|
|
525
|
+
// "invisible", except for raised labels, where we want *some* contrast.
|
|
526
|
+
stroke={
|
|
527
|
+
label.raised && style.stroke
|
|
528
|
+
? darkenColorForLine(style.stroke)
|
|
529
|
+
: style.stroke
|
|
530
|
+
}
|
|
531
|
+
strokeWidth={style.strokeWidth}
|
|
532
|
+
/>
|
|
533
|
+
)
|
|
534
|
+
})}
|
|
535
|
+
</g>
|
|
536
|
+
<g id={makeIdForHumanConsumption("swatches")}>
|
|
537
|
+
{_.sortBy(
|
|
538
|
+
positionedBins.map((positionedBin, index) => {
|
|
539
|
+
const bin = positionedBin.bin
|
|
540
|
+
const style = this.getMarkerStyleConfig(bin)
|
|
541
|
+
|
|
542
|
+
const fill = bin.patternRef
|
|
543
|
+
? `url(#${bin.patternRef})`
|
|
544
|
+
: style.fill
|
|
545
|
+
|
|
546
|
+
return (
|
|
547
|
+
<NumericBinRect
|
|
548
|
+
key={index}
|
|
549
|
+
x={positionedBin.x}
|
|
550
|
+
y={bottomY - numericBinSize}
|
|
551
|
+
width={positionedBin.width}
|
|
552
|
+
height={numericBinSize}
|
|
553
|
+
fill={fill}
|
|
554
|
+
stroke={style.stroke}
|
|
555
|
+
strokeWidth={style.strokeWidth}
|
|
556
|
+
opacity={style.opacity}
|
|
557
|
+
isOpenLeft={
|
|
558
|
+
bin instanceof NumericBin
|
|
559
|
+
? bin.props.isOpenLeft
|
|
560
|
+
: false
|
|
561
|
+
}
|
|
562
|
+
isOpenRight={
|
|
563
|
+
bin instanceof NumericBin
|
|
564
|
+
? bin.props.isOpenRight
|
|
565
|
+
: false
|
|
566
|
+
}
|
|
567
|
+
onMouseEnter={() => {
|
|
568
|
+
this.manager.onLegendMouseEnter?.(bin)
|
|
569
|
+
this.manager.onLegendMouseOver?.(bin)
|
|
570
|
+
}}
|
|
571
|
+
onMouseLeave={() =>
|
|
572
|
+
this.manager.onLegendMouseLeave?.()
|
|
573
|
+
}
|
|
574
|
+
/>
|
|
575
|
+
)
|
|
576
|
+
}),
|
|
577
|
+
(rect) => rect.props["strokeWidth"]
|
|
578
|
+
)}
|
|
579
|
+
</g>
|
|
580
|
+
<g id={makeIdForHumanConsumption("labels")}>
|
|
581
|
+
{numericLabels.map((label, index) => {
|
|
582
|
+
const style = this.getTextStyleConfig(label.bin)
|
|
583
|
+
return (
|
|
584
|
+
<text
|
|
585
|
+
key={index}
|
|
586
|
+
x={label.bounds.x}
|
|
587
|
+
y={bottomY + label.bounds.y}
|
|
588
|
+
// we can't use dominant-baseline to do proper alignment since our svg-to-png library Sharp
|
|
589
|
+
// doesn't support that (https://github.com/lovell/sharp/issues/1996), so we'll have to make
|
|
590
|
+
// do with some rough positioning.
|
|
591
|
+
dy={dyFromAlign(VerticalAlign.bottom)}
|
|
592
|
+
fontSize={label.fontSize}
|
|
593
|
+
style={{ fill: style.color, ...style }}
|
|
594
|
+
>
|
|
595
|
+
{label.text}
|
|
596
|
+
</text>
|
|
597
|
+
)
|
|
598
|
+
})}
|
|
599
|
+
</g>
|
|
600
|
+
{this.legendTitle?.renderSVG(
|
|
601
|
+
this.x,
|
|
602
|
+
// Align legend title baseline with bottom of color bins
|
|
603
|
+
this.numericLegendY +
|
|
604
|
+
height -
|
|
605
|
+
this.legendTitle.height +
|
|
606
|
+
this.legendTitleFontSize * 0.2,
|
|
607
|
+
{ textProps: { fill: textColor } }
|
|
608
|
+
)}
|
|
609
|
+
</g>
|
|
610
|
+
)
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
interface NumericBinRectProps extends React.SVGAttributes<SVGElement> {
|
|
615
|
+
x: number
|
|
616
|
+
y: number
|
|
617
|
+
width: number
|
|
618
|
+
height: number
|
|
619
|
+
isOpenLeft?: boolean
|
|
620
|
+
isOpenRight?: boolean
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** The width of the arrowhead for open-ended bins (left or right) */
|
|
624
|
+
const ARROW_SIZE = 5
|
|
625
|
+
|
|
626
|
+
const NumericBinRect = (props: NumericBinRectProps) => {
|
|
627
|
+
const { isOpenLeft, isOpenRight, x, y, width, height, ...restProps } = props
|
|
628
|
+
if (isOpenRight) {
|
|
629
|
+
const a = ARROW_SIZE
|
|
630
|
+
const w = width - a
|
|
631
|
+
const d = removeAllWhitespace(`
|
|
632
|
+
M ${x}, ${y}
|
|
633
|
+
l ${w}, 0
|
|
634
|
+
l ${a}, ${height / 2}
|
|
635
|
+
l ${-a}, ${height / 2}
|
|
636
|
+
l ${-w}, 0
|
|
637
|
+
z
|
|
638
|
+
`)
|
|
639
|
+
return <path d={d} {...restProps} />
|
|
640
|
+
} else if (isOpenLeft) {
|
|
641
|
+
const a = ARROW_SIZE
|
|
642
|
+
const w = width - a
|
|
643
|
+
const d = removeAllWhitespace(`
|
|
644
|
+
M ${x + a}, ${y}
|
|
645
|
+
l ${w}, 0
|
|
646
|
+
l 0, ${height}
|
|
647
|
+
l ${-w}, 0
|
|
648
|
+
l ${-a}, ${-height / 2}
|
|
649
|
+
z
|
|
650
|
+
`)
|
|
651
|
+
return <path d={d} {...restProps} />
|
|
652
|
+
} else {
|
|
653
|
+
return <rect x={x} y={y} width={width} height={height} {...restProps} />
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
@observer
|
|
658
|
+
export class HorizontalCategoricalColorLegend extends HorizontalColorLegend {
|
|
659
|
+
private rectPadding = 5
|
|
660
|
+
private markPadding = 5
|
|
661
|
+
|
|
662
|
+
constructor(props: { manager: HorizontalColorLegendManager }) {
|
|
663
|
+
super(props)
|
|
664
|
+
|
|
665
|
+
makeObservable(this)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
@computed get width(): number {
|
|
669
|
+
return this.manager.legendWidth ?? this.manager.legendMaxWidth ?? 200
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
@computed private get categoricalLegendData(): CategoricalBin[] {
|
|
673
|
+
return this.manager.categoricalLegendData ?? []
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
@computed private get visibleCategoricalBins(): CategoricalBin[] {
|
|
677
|
+
return this.categoricalLegendData.filter((bin) => !bin.isHidden)
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
@computed private get markLines(): MarkLine[] {
|
|
681
|
+
const fontSize = this.fontSize * GRAPHER_FONT_SCALE_12_8
|
|
682
|
+
const rectSize = this.fontSize * 0.75
|
|
683
|
+
|
|
684
|
+
const lines: MarkLine[] = []
|
|
685
|
+
let marks: CategoricalMark[] = []
|
|
686
|
+
let xOffset = 0
|
|
687
|
+
let yOffset = 0
|
|
688
|
+
this.visibleCategoricalBins.forEach((bin) => {
|
|
689
|
+
const labelBounds = Bounds.forText(bin.text, { fontSize })
|
|
690
|
+
const markWidth =
|
|
691
|
+
rectSize +
|
|
692
|
+
this.rectPadding +
|
|
693
|
+
labelBounds.width +
|
|
694
|
+
this.markPadding
|
|
695
|
+
|
|
696
|
+
if (xOffset + markWidth > this.width && marks.length > 0) {
|
|
697
|
+
lines.push({
|
|
698
|
+
totalWidth: xOffset - this.markPadding,
|
|
699
|
+
marks: marks,
|
|
700
|
+
})
|
|
701
|
+
marks = []
|
|
702
|
+
xOffset = 0
|
|
703
|
+
yOffset += rectSize + this.rectPadding
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const markX = xOffset
|
|
707
|
+
const markY = yOffset
|
|
708
|
+
|
|
709
|
+
const label = {
|
|
710
|
+
text: bin.text,
|
|
711
|
+
bounds: labelBounds.set({
|
|
712
|
+
x: markX + rectSize + this.rectPadding,
|
|
713
|
+
y: markY + rectSize / 2,
|
|
714
|
+
}),
|
|
715
|
+
fontSize,
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
marks.push({
|
|
719
|
+
x: markX,
|
|
720
|
+
y: markY,
|
|
721
|
+
width: markWidth,
|
|
722
|
+
rectSize,
|
|
723
|
+
label,
|
|
724
|
+
bin,
|
|
725
|
+
})
|
|
726
|
+
|
|
727
|
+
xOffset += markWidth + SPACE_BETWEEN_CATEGORICAL_BINS
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
if (marks.length > 0)
|
|
731
|
+
lines.push({ totalWidth: xOffset - this.markPadding, marks: marks })
|
|
732
|
+
|
|
733
|
+
return lines
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
@computed private get contentWidth(): number {
|
|
737
|
+
return _.max(this.markLines.map((l) => l.totalWidth)) as number
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
@computed private get containerWidth(): number {
|
|
741
|
+
return this.width ?? this.contentWidth
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
@computed private get marks(): CategoricalMark[] {
|
|
745
|
+
const lines = this.markLines
|
|
746
|
+
const align = this.legendAlign
|
|
747
|
+
const width = this.containerWidth
|
|
748
|
+
|
|
749
|
+
// Center each line
|
|
750
|
+
lines.forEach((line) => {
|
|
751
|
+
// TODO abstract this
|
|
752
|
+
const xShift =
|
|
753
|
+
align === HorizontalAlign.center
|
|
754
|
+
? (width - line.totalWidth) / 2
|
|
755
|
+
: align === HorizontalAlign.right
|
|
756
|
+
? width - line.totalWidth
|
|
757
|
+
: 0
|
|
758
|
+
line.marks.forEach((mark) => {
|
|
759
|
+
mark.x += xShift
|
|
760
|
+
mark.label.bounds = mark.label.bounds.set({
|
|
761
|
+
x: mark.label.bounds.x + xShift,
|
|
762
|
+
})
|
|
763
|
+
})
|
|
764
|
+
})
|
|
765
|
+
|
|
766
|
+
return lines.flatMap((l) => l.marks)
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
@computed get height(): number {
|
|
770
|
+
return _.max(this.marks.map((mark) => mark.y + mark.rectSize)) ?? 0
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
@computed private get legendStyleConfig(): LegendStyleConfig | undefined {
|
|
774
|
+
return (
|
|
775
|
+
this.manager.categoricalLegendStyleConfig ??
|
|
776
|
+
this.manager.legendStyleConfig
|
|
777
|
+
)
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
private getTextStyleConfig(bin: ColorScaleBin): LegendTextStyle {
|
|
781
|
+
const state = this.getBinState(bin)
|
|
782
|
+
const styleConfig = this.legendStyleConfig?.text
|
|
783
|
+
const defaultStyle = styleConfig?.default
|
|
784
|
+
const currentStyle = styleConfig?.[state]
|
|
785
|
+
return { color: GRAPHER_DARK_TEXT, ...defaultStyle, ...currentStyle }
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
protected getMarkerStyleConfig(bin: ColorScaleBin): LegendMarkerStyle {
|
|
789
|
+
const state = this.getBinState(bin)
|
|
790
|
+
const styleConfig = this.legendStyleConfig?.marker
|
|
791
|
+
const defaultStyle = styleConfig?.default
|
|
792
|
+
const currentStyle = styleConfig?.[state]
|
|
793
|
+
return {
|
|
794
|
+
fill: bin.color,
|
|
795
|
+
strokeWidth: 0.4,
|
|
796
|
+
...defaultStyle,
|
|
797
|
+
...currentStyle,
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
renderLabels(): React.ReactElement {
|
|
802
|
+
const { marks } = this
|
|
803
|
+
|
|
804
|
+
return (
|
|
805
|
+
<g id={makeIdForHumanConsumption("labels")}>
|
|
806
|
+
{marks.map((mark, index) => {
|
|
807
|
+
const style = this.getTextStyleConfig(mark.bin)
|
|
808
|
+
|
|
809
|
+
return (
|
|
810
|
+
<text
|
|
811
|
+
key={`${mark.label}-${index}`}
|
|
812
|
+
x={this.legendX + mark.label.bounds.x}
|
|
813
|
+
y={this.categoryLegendY + mark.label.bounds.y}
|
|
814
|
+
// we can't use dominant-baseline to do proper alignment since our svg-to-png library Sharp
|
|
815
|
+
// doesn't support that (https://github.com/lovell/sharp/issues/1996), so we'll have to make
|
|
816
|
+
// do with some rough positioning.
|
|
817
|
+
dy={dyFromAlign(VerticalAlign.middle)}
|
|
818
|
+
fontSize={mark.label.fontSize}
|
|
819
|
+
fontWeight={style.fontWeight}
|
|
820
|
+
style={{ fill: style.color, ...style }}
|
|
821
|
+
>
|
|
822
|
+
{mark.label.text}
|
|
823
|
+
</text>
|
|
824
|
+
)
|
|
825
|
+
})}
|
|
826
|
+
</g>
|
|
827
|
+
)
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
renderSwatches(): React.ReactElement {
|
|
831
|
+
const { marks } = this
|
|
832
|
+
|
|
833
|
+
return (
|
|
834
|
+
<g id={makeIdForHumanConsumption("swatches")}>
|
|
835
|
+
{marks.map((mark, index) => {
|
|
836
|
+
const style = this.getMarkerStyleConfig(mark.bin)
|
|
837
|
+
|
|
838
|
+
const fill = mark.bin.patternRef
|
|
839
|
+
? `url(#${mark.bin.patternRef})`
|
|
840
|
+
: style.fill
|
|
841
|
+
|
|
842
|
+
return (
|
|
843
|
+
<rect
|
|
844
|
+
id={makeIdForHumanConsumption(mark.label.text)}
|
|
845
|
+
key={`${mark.label}-${index}`}
|
|
846
|
+
x={this.legendX + mark.x}
|
|
847
|
+
y={this.categoryLegendY + mark.y}
|
|
848
|
+
width={mark.rectSize}
|
|
849
|
+
height={mark.rectSize}
|
|
850
|
+
style={{ ...style, fill }}
|
|
851
|
+
/>
|
|
852
|
+
)
|
|
853
|
+
})}
|
|
854
|
+
</g>
|
|
855
|
+
)
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
renderInteractiveElements(): React.ReactElement {
|
|
859
|
+
const { manager, marks } = this
|
|
860
|
+
|
|
861
|
+
return (
|
|
862
|
+
<g>
|
|
863
|
+
{marks.map((mark, index) => {
|
|
864
|
+
const mouseEnter = (): void =>
|
|
865
|
+
manager.onLegendMouseEnter
|
|
866
|
+
? manager.onLegendMouseEnter(mark.bin)
|
|
867
|
+
: undefined
|
|
868
|
+
const mouseOver = (): void =>
|
|
869
|
+
manager.onLegendMouseOver
|
|
870
|
+
? manager.onLegendMouseOver(mark.bin)
|
|
871
|
+
: undefined
|
|
872
|
+
const mouseLeave = (): void =>
|
|
873
|
+
manager.onLegendMouseLeave
|
|
874
|
+
? manager.onLegendMouseLeave()
|
|
875
|
+
: undefined
|
|
876
|
+
const click = manager.onLegendClick
|
|
877
|
+
? (): void => manager.onLegendClick?.(mark.bin)
|
|
878
|
+
: undefined
|
|
879
|
+
|
|
880
|
+
return (
|
|
881
|
+
<g
|
|
882
|
+
key={`${mark.label}-${index}`}
|
|
883
|
+
onMouseEnter={mouseEnter}
|
|
884
|
+
onMouseOver={mouseOver}
|
|
885
|
+
onMouseLeave={mouseLeave}
|
|
886
|
+
onClick={click}
|
|
887
|
+
style={{ cursor: manager.legendCursor }}
|
|
888
|
+
>
|
|
889
|
+
{/* for hover interaction */}
|
|
890
|
+
<rect
|
|
891
|
+
x={this.legendX + mark.x}
|
|
892
|
+
y={
|
|
893
|
+
this.categoryLegendY +
|
|
894
|
+
mark.y -
|
|
895
|
+
this.rectPadding / 2
|
|
896
|
+
}
|
|
897
|
+
height={mark.rectSize + this.rectPadding}
|
|
898
|
+
width={
|
|
899
|
+
mark.width + SPACE_BETWEEN_CATEGORICAL_BINS
|
|
900
|
+
}
|
|
901
|
+
fill="#fff"
|
|
902
|
+
opacity={0}
|
|
903
|
+
/>
|
|
904
|
+
</g>
|
|
905
|
+
)
|
|
906
|
+
})}
|
|
907
|
+
</g>
|
|
908
|
+
)
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
override render(): React.ReactElement {
|
|
912
|
+
return (
|
|
913
|
+
<g
|
|
914
|
+
id={makeIdForHumanConsumption("categorical-color-legend")}
|
|
915
|
+
className="categoricalColorLegend"
|
|
916
|
+
>
|
|
917
|
+
{this.renderSwatches()}
|
|
918
|
+
{this.renderLabels()}
|
|
919
|
+
{!this.manager.isStatic && this.renderInteractiveElements()}
|
|
920
|
+
</g>
|
|
921
|
+
)
|
|
922
|
+
}
|
|
923
|
+
}
|