@cdc/chart 4.26.6 → 4.26.10
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/CONFIG.md +504 -368
- package/dist/cdcchart-CYc-Kayd.es.js +22488 -0
- package/dist/cdcchart-DCKn8NmI.es.js +20695 -0
- package/dist/cdcchart-Dyui9nk_.es.js +45828 -0
- package/dist/cdcchart.js +4 -96498
- package/examples/__data__/vega-remote-data.json +6 -0
- package/examples/dendrogram.json +18 -0
- package/examples/feature/bar/bar-chart-race-annual-change.json +68 -0
- package/examples/feature/heatmap/average-age-categorical.json +2 -2
- package/examples/feature/heatmap/calendar-heatmap.json +2 -2
- package/examples/feature/heatmap/cell-values-bottom-axis.json +2 -2
- package/examples/feature/heatmap/sparse-aggregation.json +2 -2
- package/examples/feature/line/line-chart-race.json +50 -0
- package/examples/feature/sankey/sankey-example-data.json +40 -1441
- package/examples/feature/sankey/sankey_chart_data.csv +9 -20
- package/examples/network.json +31 -0
- package/examples/private/box-plot.json +573 -0
- package/examples/private/custom-color-issue.json +625 -0
- package/examples/private/custom-colors.json +522 -0
- package/examples/private/cutoff.json +10750 -0
- package/examples/private/flueview.json +22051 -0
- package/examples/private/overlay-legend.json +485 -0
- package/examples/private/pie-test.json +469 -0
- package/examples/vega-remote.json +22 -0
- package/package.json +11 -13
- package/src/CdcChart.tsx +2 -2
- package/src/CdcChartComponent.tsx +444 -217
- package/src/_stories/Chart.BarRace.stories.tsx +443 -0
- package/src/_stories/Chart.BoxPlotNumericSort.stories.tsx +189 -0
- package/src/_stories/Chart.BoxPlotStatsParity.stories.tsx +207 -0
- package/src/_stories/Chart.ColorDistribution.stories.tsx +299 -0
- package/src/_stories/Chart.DataCutoff.stories.tsx +147 -0
- package/src/_stories/Chart.DataTableDownloadFilename.stories.tsx +60 -0
- package/src/_stories/Chart.Dendrogram.stories.tsx +373 -0
- package/src/_stories/Chart.Forecast.CILegend.stories.tsx +236 -0
- package/src/_stories/Chart.HeatMap.stories.tsx +170 -4
- package/src/_stories/Chart.Legend.AxisAlign.stories.tsx +119 -0
- package/src/_stories/Chart.LineRace.stories.tsx +149 -0
- package/src/_stories/Chart.Network.stories.tsx +432 -0
- package/src/_stories/Chart.PaletteConversion.Editor.stories.tsx +208 -0
- package/src/_stories/Chart.Patterns.smoke.stories.tsx +31 -0
- package/src/_stories/Chart.PercentageValue.stories.tsx +311 -0
- package/src/_stories/Chart.PieLabels.stories.tsx +49 -0
- package/src/_stories/Chart.RankByValueMutation.stories.tsx +206 -0
- package/src/_stories/Chart.RequiredFields.stories.tsx +681 -0
- package/src/_stories/Chart.Sankey.stories.tsx +417 -0
- package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +47 -0
- package/src/_stories/Chart.SmallMultiplesNumericTileOrder.stories.tsx +137 -0
- package/src/_stories/Chart.TooltipSeriesLabels.stories.tsx +43 -0
- package/src/_stories/Chart.VegaRemoteData.stories.tsx +31 -0
- package/src/_stories/Chart.smoke.stories.tsx +14 -1
- package/src/_stories/ChartAnnotation.Editor.stories.tsx +78 -0
- package/src/_stories/ChartAxisTitles.smoke.stories.tsx +48 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +499 -57
- package/src/_stories/ChartBrush.Editor.stories.tsx +46 -67
- package/src/_stories/ChartBrush.Regression.stories.tsx +135 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +33 -3
- package/src/_stories/ChartLine.EffectCompatibility.stories.tsx +120 -0
- package/src/_stories/ChartPrefixSuffix.smoke.stories.tsx +28 -1
- package/src/_stories/_mock/annotation_event-line_mock.json +2 -2
- package/src/_stories/_mock/editor-test-configs.json +20 -10
- package/src/_stories/_mock/editor-test-datasets.json +38 -42
- package/src/_stories/_mock/heatmap-average-age-categorical.json +2 -2
- package/src/_stories/_mock/heatmap.json +2 -2
- package/src/_stories/_mock/legend-axis-align-stacked-bar.json +125 -0
- package/src/_stories/_mock/sankey_mock.json +23 -27
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +52 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +639 -139
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +44 -4
- package/src/components/Annotations/components/AnnotationDropdown.tsx +10 -32
- package/src/components/Annotations/helpers/resolveAnnotationLayout.ts +212 -0
- package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +612 -19
- package/src/components/Annotations/tests/AnnotationDropdown.test.tsx +50 -0
- package/src/components/Annotations/tests/resolveAnnotationLayout.test.ts +203 -0
- package/src/components/Axis/BottomAxis.tsx +1 -1
- package/src/components/Axis/Categorical.Axis.tsx +3 -3
- package/src/components/Axis/RightAxis.tsx +21 -13
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +39 -8
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +13 -4
- package/src/components/BarChart/components/BarChart.StackedVertical.test.ts +28 -0
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +30 -26
- package/src/components/BarChart/components/BarChart.Vertical.tsx +38 -28
- package/src/components/BarChart/components/PortionPatternOverlay.tsx +61 -0
- package/src/components/BarChart/components/context.tsx +6 -0
- package/src/components/BarChart/helpers/getBarData.ts +28 -28
- package/src/components/BarChart/helpers/getBarHeights.ts +2 -2
- package/src/components/BarChart/helpers/getPatternUrl.ts +2 -5
- package/src/components/BarChart/helpers/index.ts +8 -0
- package/src/components/BarChart/helpers/portionPattern.ts +243 -0
- package/src/components/BarChart/helpers/tests/addMinimumBarHeights.test.ts +42 -0
- package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +13 -0
- package/src/components/BarChart/helpers/tests/portionPattern.test.ts +280 -0
- package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +202 -0
- package/src/components/BarChart/helpers/useBarChart.ts +60 -60
- package/src/components/BarChartRace/BarChartRace.test.tsx +199 -0
- package/src/components/BarChartRace/BarChartRace.tsx +230 -0
- package/src/components/BarChartRace/BarChartRaceFallback.test.tsx +61 -0
- package/src/components/BarChartRace/BarChartRaceFallback.tsx +48 -0
- package/src/components/BarChartRace/bar-chart-race.css +160 -0
- package/src/components/BarChartRace/helpers.test.ts +114 -0
- package/src/components/BarChartRace/helpers.ts +185 -0
- package/src/components/BarChartRace/index.ts +3 -0
- package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +7 -6
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +9 -7
- package/src/components/BoxPlot/helpers/index.ts +13 -83
- package/src/components/Brush/BrushSelector.tsx +10 -24
- package/src/components/Brush/MiniChartPreview.tsx +33 -1
- package/src/components/Dendrogram/components/Dendrogram.tsx +374 -0
- package/src/components/Dendrogram/dendrogram.css +113 -0
- package/src/components/Dendrogram/helpers/layoutDendrogram.test.ts +95 -0
- package/src/components/Dendrogram/helpers/layoutDendrogram.ts +56 -0
- package/src/components/Dendrogram/helpers/prepareDendrogramData.test.ts +135 -0
- package/src/components/Dendrogram/helpers/prepareDendrogramData.ts +166 -0
- package/src/components/Dendrogram/helpers/resolveDendrogramLabels.test.ts +196 -0
- package/src/components/Dendrogram/helpers/resolveDendrogramLabels.ts +418 -0
- package/src/components/Dendrogram/index.tsx +1 -0
- package/src/components/Dendrogram/types/index.ts +66 -0
- package/src/components/EditorPanel/EditorPanel.tsx +1111 -805
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +50 -5
- package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.css +10 -0
- package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.tsx +211 -0
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +19 -8
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +104 -61
- package/src/components/EditorPanel/components/Panels/Panel.Network.css +9 -0
- package/src/components/EditorPanel/components/Panels/Panel.Network.tsx +202 -0
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +236 -36
- package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +6 -1
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +89 -105
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +21 -6
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +8 -66
- package/src/components/EditorPanel/components/Panels/RacingControls.test.tsx +131 -0
- package/src/components/EditorPanel/components/Panels/RacingControls.tsx +185 -0
- package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/index.tsx +4 -0
- package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +20 -7
- package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +25 -2
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +5 -4
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +94 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +75 -25
- package/src/components/Forecasting/Forecasting.tsx +16 -7
- package/src/components/HeatMap/components/HeatMap.tsx +511 -178
- package/src/components/HeatMap/heatmap.constants.ts +7 -3
- package/src/components/HeatMap/heatmap.css +101 -2
- package/src/components/HeatMap/helpers/layout.ts +80 -0
- package/src/components/HeatMap/tests/HeatMap.test.tsx +339 -16
- package/src/components/HeatMap/tests/helpers.test.ts +79 -0
- package/src/components/Legend/Legend.Component.tsx +163 -148
- package/src/components/Legend/Legend.Suppression.tsx +3 -3
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +7 -0
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +47 -35
- package/src/components/Legend/helpers/createFormatLabels.test.ts +226 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +45 -51
- package/src/components/LineChart/components/LineChart.Circle.tsx +3 -3
- package/src/components/LineChart/helpers.test.ts +126 -38
- package/src/components/LineChart/helpers.ts +9 -6
- package/src/components/LineChart/index.tsx +71 -27
- package/src/components/LineChart/tests/LineChart.test.tsx +630 -0
- package/src/components/LineChartRace/LineChartRace.test.tsx +275 -0
- package/src/components/LineChartRace/LineChartRace.tsx +134 -0
- package/src/components/LineChartRace/LineChartRaceFallback.tsx +28 -0
- package/src/components/LineChartRace/helpers.test.ts +88 -0
- package/src/components/LineChartRace/helpers.ts +157 -0
- package/src/components/LineChartRace/index.ts +3 -0
- package/src/components/LineChartRace/line-chart-race.css +16 -0
- package/src/components/LinearChart/ValueAxisAnchors.tsx +58 -0
- package/src/components/LinearChart/VisualizationRenderer.tsx +18 -23
- package/src/components/LinearChart/tests/LinearChart.test.tsx +412 -13
- package/src/components/LinearChart/tests/VisualizationRenderer.test.tsx +141 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +2 -1
- package/src/components/LinearChart.tsx +85 -54
- package/src/components/Network/components/Network.tsx +372 -0
- package/src/components/Network/helpers/layoutNetwork.test.ts +68 -0
- package/src/components/Network/helpers/layoutNetwork.ts +87 -0
- package/src/components/Network/helpers/prepareNetworkData.test.ts +116 -0
- package/src/components/Network/helpers/prepareNetworkData.ts +101 -0
- package/src/components/Network/helpers/resolveNetworkLabels.test.ts +112 -0
- package/src/components/Network/helpers/resolveNetworkLabels.ts +206 -0
- package/src/components/Network/index.tsx +3 -0
- package/src/components/Network/network.css +107 -0
- package/src/components/Network/types/index.ts +56 -0
- package/src/components/PairedBarChart.jsx +5 -10
- package/src/components/PairedBarChart.test.tsx +71 -0
- package/src/components/PieChart/PieChart.tsx +121 -13
- package/src/components/PieChart/helpers/labelPlacement.test.ts +88 -0
- package/src/components/PieChart/helpers/labelPlacement.ts +95 -0
- package/src/components/RacingChartRenderer.tsx +44 -0
- package/src/components/Regions/components/Regions.test.tsx +179 -0
- package/src/components/Regions/components/Regions.tsx +104 -69
- package/src/components/Regions/index.tsx +2 -3
- package/src/components/Sankey/components/Sankey.tsx +264 -437
- package/src/components/Sankey/helpers/getSankeyTooltip.ts +12 -0
- package/src/components/Sankey/helpers/prepareSankeyData.test.ts +71 -0
- package/src/components/Sankey/helpers/prepareSankeyData.ts +90 -0
- package/src/components/Sankey/sankey.scss +57 -116
- package/src/components/Sankey/types/index.ts +41 -4
- package/src/components/ScatterPlot/ScatterPlot.jsx +28 -12
- package/src/components/ScatterPlot/ScatterPlot.test.tsx +60 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +12 -12
- package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +7 -5
- package/src/components/raceTiming.ts +18 -0
- package/src/data/initial-state.js +79 -31
- package/src/helpers/applyChartTypeContract.ts +71 -0
- package/src/helpers/axisLabelFontSize.ts +2 -2
- package/src/helpers/boxPlotStats.ts +60 -0
- package/src/helpers/bumpChartTooltip.ts +2 -1
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.test.ts +37 -0
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +3 -2
- package/src/helpers/calculateRightYAxisWidth.ts +60 -0
- package/src/helpers/categoryOrder.ts +60 -0
- package/src/helpers/colorAssignmentHelpers.ts +2 -2
- package/src/helpers/createNewChartConfig.ts +56 -0
- package/src/helpers/dataDrivenYAxisCategories.ts +18 -0
- package/src/helpers/forecastLegend.ts +88 -0
- package/src/helpers/getBoxPlotConfig.ts +26 -38
- package/src/helpers/getChartTypeDefaultPalette.ts +29 -0
- package/src/helpers/getColorCodeCategoryColorMap.ts +26 -0
- package/src/helpers/getColorScale.ts +31 -22
- package/src/helpers/getComboChartConfig.ts +0 -1
- package/src/helpers/getDynamicYAxisCategories.ts +69 -0
- package/src/helpers/getExcludedData.ts +0 -1
- package/src/helpers/getHasBoundarySuppression.ts +4 -3
- package/src/helpers/getMinMax.ts +11 -8
- package/src/helpers/getNewRuntime.ts +4 -3
- package/src/helpers/getPiePercent.ts +3 -3
- package/src/helpers/getStackedSeriesMax.ts +51 -0
- package/src/helpers/getV21ChartDistributionColors.ts +57 -0
- package/src/helpers/handleRankByValue.ts +15 -15
- package/src/helpers/isConvertLineToBarGraph.ts +2 -2
- package/src/helpers/seriesColumnSettings.ts +31 -3
- package/src/helpers/sizeHelpers.ts +4 -5
- package/src/helpers/smallMultiplesHelpers.ts +10 -13
- package/src/helpers/tests/applyChartTypeContract.test.ts +37 -0
- package/src/helpers/tests/boxPlotStats.test.ts +31 -0
- package/src/helpers/tests/bumpChartTooltip.test.ts +29 -0
- package/src/helpers/tests/calculateRightYAxisWidth.test.ts +105 -0
- package/src/helpers/tests/categoryOrder.test.ts +57 -0
- package/src/helpers/tests/countNumOfTicks.test.ts +11 -0
- package/src/helpers/tests/createNewChartConfig.test.ts +182 -0
- package/src/helpers/tests/getBoxPlotConfig.test.ts +70 -0
- package/src/helpers/tests/getColorCodeCategoryColorMap.test.ts +113 -0
- package/src/helpers/tests/getColorScale.test.ts +226 -1
- package/src/helpers/tests/getDynamicYAxisCategories.test.ts +61 -0
- package/src/helpers/tests/getMinMax.test.ts +41 -0
- package/src/helpers/tests/getStackedSeriesMax.test.ts +26 -0
- package/src/helpers/tests/handleRankByValue.test.ts +55 -37
- package/src/helpers/tests/seriesColumnSettings.test.ts +83 -0
- package/src/helpers/tests/smallMultiplesHelpers.test.ts +37 -1
- package/src/helpers/tests/tooltipHelpers.test.ts +80 -0
- package/src/helpers/tooltipHelpers.ts +16 -0
- package/src/hooks/tests/useRightAxis.test.tsx +143 -2
- package/src/hooks/tests/useScales.test.tsx +3 -6
- package/src/hooks/tests/useTooltip.test.tsx +484 -0
- package/src/hooks/usePrefersReducedMotion.ts +22 -0
- package/src/hooks/useReduceData.ts +4 -36
- package/src/hooks/useRightAxis.ts +19 -26
- package/src/hooks/useScales.ts +15 -0
- package/src/hooks/useTooltip.tsx +106 -71
- package/src/scss/main.scss +74 -2
- package/src/store/chart.reducer.ts +4 -4
- package/src/test/CdcChart.dataTable.test.tsx +582 -14
- package/src/test/CdcChart.dendrogram.test.tsx +277 -0
- package/src/test/CdcChart.finalization.test.tsx +143 -0
- package/src/test/CdcChart.network.test.tsx +271 -0
- package/src/types/ChartConfig.ts +91 -54
- package/src/types/ChartContext.ts +5 -0
- package/src/types/Horizon.ts +1 -1
- package/LICENSE +0 -201
- package/dist/cdcchart-BWVIUcAF.es.js +0 -4802
- package/src/_stories/Chart.Sankey.smoke.stories.tsx +0 -62
- package/src/components/Sankey/components/ColumnList.tsx +0 -19
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +0 -34
- package/src/components/Sankey/useSankeyAlert.tsx +0 -60
- package/src/data/legacy-defaults.ts +0 -18
- package/src/helpers/ensureSpecialChartAxisTypes.ts +0 -11
- package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +0 -30
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
Suspense,
|
|
3
|
+
lazy,
|
|
4
|
+
useState,
|
|
5
|
+
useEffect,
|
|
6
|
+
useCallback,
|
|
7
|
+
useRef,
|
|
8
|
+
useId,
|
|
9
|
+
useContext,
|
|
10
|
+
useReducer,
|
|
11
|
+
useMemo
|
|
12
|
+
} from 'react'
|
|
2
13
|
|
|
3
14
|
// IE11
|
|
4
15
|
import ResizeObserver from 'resize-observer-polyfill'
|
|
@@ -6,7 +17,7 @@ import 'whatwg-fetch'
|
|
|
6
17
|
// Core components
|
|
7
18
|
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
8
19
|
import { VisualizationContainer, VisualizationContent } from '@cdc/core/components/Layout'
|
|
9
|
-
import
|
|
20
|
+
import Alert from '@cdc/core/components/Alert'
|
|
10
21
|
import Error from '@cdc/core/components/elements/Error'
|
|
11
22
|
import SkipTo from '@cdc/core/components/elements/SkipTo'
|
|
12
23
|
import Title from '@cdc/core/components/ui/Title'
|
|
@@ -25,9 +36,6 @@ import ParentSize from '@visx/responsive/lib/components/ParentSize'
|
|
|
25
36
|
import { timeParse } from 'd3-time-format'
|
|
26
37
|
import parse from 'html-react-parser'
|
|
27
38
|
import cloneDeep from 'lodash/cloneDeep'
|
|
28
|
-
import defaultsDeep from 'lodash/defaultsDeep'
|
|
29
|
-
import lodashDefaults from 'lodash/defaults'
|
|
30
|
-
import forEach from 'lodash/forEach'
|
|
31
39
|
import get from 'lodash/get'
|
|
32
40
|
import isEmpty from 'lodash/isEmpty'
|
|
33
41
|
import isEqual from 'lodash/isEqual'
|
|
@@ -35,7 +43,6 @@ import isString from 'lodash/isString'
|
|
|
35
43
|
import kebabCase from 'lodash/kebabCase'
|
|
36
44
|
import pick from 'lodash/pick'
|
|
37
45
|
import remove from 'lodash/remove'
|
|
38
|
-
import set from 'lodash/set'
|
|
39
46
|
import uniq from 'lodash/uniq'
|
|
40
47
|
import xor from 'lodash/xor'
|
|
41
48
|
// Primary Components
|
|
@@ -43,8 +50,13 @@ import ConfigContext, { ChartDispatchContext } from './ConfigContext'
|
|
|
43
50
|
import PieChart from './components/PieChart'
|
|
44
51
|
import RadarChart from './components/RadarChart'
|
|
45
52
|
import SankeyChart from './components/Sankey'
|
|
53
|
+
import NetworkChart from './components/Network'
|
|
54
|
+
import DendrogramChart from './components/Dendrogram'
|
|
46
55
|
import HeatMap, { HeatMapGradientLegend } from './components/HeatMap'
|
|
47
56
|
import LinearChart from './components/LinearChart'
|
|
57
|
+
import { getBarRaceEligibility, type BarRaceEligibility } from './components/BarChartRace'
|
|
58
|
+
import { getLineRaceEligibility, type LineRaceEligibility } from './components/LineChartRace'
|
|
59
|
+
import RacingChartRenderer from './components/RacingChartRenderer'
|
|
48
60
|
import { isDateScale, formatDate as coreFormatDate } from '@cdc/core/helpers/cove/date'
|
|
49
61
|
|
|
50
62
|
import { twoColorPalette } from '@cdc/core/data/colorPalettes'
|
|
@@ -54,8 +66,6 @@ import SparkLine from './components/Sparkline'
|
|
|
54
66
|
import Legend from './components/Legend'
|
|
55
67
|
import WarmingStripesGradientLegend from './components/WarmingStripes/WarmingStripesGradientLegend'
|
|
56
68
|
import defaults from './data/initial-state'
|
|
57
|
-
import { LEGACY_CHART_DEFAULTS } from './data/legacy-defaults'
|
|
58
|
-
import EditorPanel from './components/EditorPanel'
|
|
59
69
|
import { abbreviateNumber } from './helpers/abbreviateNumber'
|
|
60
70
|
import { handleChartTabbing } from './helpers/handleChartTabbing'
|
|
61
71
|
|
|
@@ -72,10 +82,14 @@ import Annotation from './components/Annotations'
|
|
|
72
82
|
import { getVisibleAnnotations } from './components/Annotations/helpers/getVisibleAnnotations'
|
|
73
83
|
// Core Helpers
|
|
74
84
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
75
|
-
import {
|
|
76
|
-
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
85
|
+
import { applyConfigDefaults } from '@cdc/core/helpers/applyConfigDefaults'
|
|
86
|
+
import { isLegendWrapViewport, isMobileAnnotationViewport } from '@cdc/core/helpers/viewports'
|
|
77
87
|
import { getAxisLabelFontSize } from './helpers/axisLabelFontSize'
|
|
78
|
-
import {
|
|
88
|
+
import {
|
|
89
|
+
getMissingRequiredFields,
|
|
90
|
+
missingRequiredSections,
|
|
91
|
+
type MissingRequiredField
|
|
92
|
+
} from '@cdc/core/helpers/missingRequiredSections'
|
|
79
93
|
import { filterVizData } from '@cdc/core/helpers/filterVizData'
|
|
80
94
|
import { addValuesToFilters } from '@cdc/core/helpers/addValuesToFilters'
|
|
81
95
|
import { hasVisibleVizFilters } from '@cdc/core/helpers/filterVisibility'
|
|
@@ -87,7 +101,6 @@ import isNumber from '@cdc/core/helpers/isNumber'
|
|
|
87
101
|
import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
|
|
88
102
|
import EditorContext from '@cdc/core/contexts/EditorContext'
|
|
89
103
|
import { EDITOR_WIDTH } from '@cdc/core/helpers/constants'
|
|
90
|
-
import { extractCoveData, updateVegaData } from '@cdc/core/helpers/vegaConfig'
|
|
91
104
|
// Local helpers
|
|
92
105
|
import { isConvertLineToBarGraph } from './helpers/isConvertLineToBarGraph'
|
|
93
106
|
import { getBoxPlotConfig } from './helpers/getBoxPlotConfig'
|
|
@@ -99,8 +112,7 @@ import { getLegendHighlightKey, shouldResetSeriesHighlight } from './helpers/ser
|
|
|
99
112
|
import { getPiePercent } from './helpers/getPiePercent'
|
|
100
113
|
import { prepareSmallMultiplesDataTable } from './helpers/smallMultiplesHelpers'
|
|
101
114
|
import { calcInitialHeight } from './helpers/sizeHelpers'
|
|
102
|
-
import {
|
|
103
|
-
import { findColumnConfigByName } from './helpers/seriesColumnSettings'
|
|
115
|
+
import { sortByCategoryOrder } from './helpers/categoryOrder'
|
|
104
116
|
|
|
105
117
|
// styles
|
|
106
118
|
import './scss/main.scss'
|
|
@@ -112,9 +124,12 @@ import { Datasets } from '@cdc/core/types/DataSet'
|
|
|
112
124
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
113
125
|
import cloneConfig from '@cdc/core/helpers/cloneConfig'
|
|
114
126
|
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
127
|
+
import { getSeriesName } from '@cdc/core/helpers/getSeriesName'
|
|
115
128
|
import { ENABLE_CHART_MAP_TP5_TREATMENT, ENABLE_CHART_VISUAL_SETTINGS } from '@cdc/core/helpers/constants'
|
|
116
129
|
import CalloutFlag from '@cdc/core/assets/callout-flag.svg?url'
|
|
117
130
|
|
|
131
|
+
const EditorPanel = lazy(() => import('./components/EditorPanel'))
|
|
132
|
+
|
|
118
133
|
interface CdcChartProps {
|
|
119
134
|
config?: ChartConfig
|
|
120
135
|
isEditor?: boolean
|
|
@@ -130,6 +145,16 @@ interface CdcChartProps {
|
|
|
130
145
|
datasets?: Datasets
|
|
131
146
|
interactionLabel: string
|
|
132
147
|
}
|
|
148
|
+
|
|
149
|
+
const hasDataCutoff = (value: unknown): boolean => value !== undefined && value !== null && value !== ''
|
|
150
|
+
|
|
151
|
+
const getDataCutoffDecimalPlaces = (value: unknown): number => {
|
|
152
|
+
const normalizedValue = String(value).trim().replace(/,/g, '')
|
|
153
|
+
const decimalPart = normalizedValue.match(/^-?(?:\d+)?\.(\d+)/)?.[1]
|
|
154
|
+
|
|
155
|
+
return decimalPart?.length ?? 0
|
|
156
|
+
}
|
|
157
|
+
|
|
133
158
|
const CdcChart: React.FC<CdcChartProps> = ({
|
|
134
159
|
config: configObj,
|
|
135
160
|
isEditor = false,
|
|
@@ -167,11 +192,23 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
167
192
|
const svgRef = useRef(null)
|
|
168
193
|
const editorContext = useContext(EditorContext)
|
|
169
194
|
const [externalFilters, setExternalFilters] = useState<any[]>()
|
|
195
|
+
const [raceTiming, setRaceTiming] = useState<{
|
|
196
|
+
elapsedSeconds: number
|
|
197
|
+
frameKey: string
|
|
198
|
+
isPlaying: boolean
|
|
199
|
+
totalSeconds: number
|
|
200
|
+
} | null>(null)
|
|
170
201
|
|
|
171
202
|
const setConfig = (newConfig: ChartConfig): void => {
|
|
172
|
-
|
|
203
|
+
const shouldFinalizeNewChart =
|
|
204
|
+
isEditor && !isDashboard && newConfig.newViz && getMissingRequiredFields(newConfig).length === 0
|
|
205
|
+
const finalizedConfig = shouldFinalizeNewChart ? { ...newConfig } : newConfig
|
|
206
|
+
|
|
207
|
+
if (shouldFinalizeNewChart) delete finalizedConfig.newViz
|
|
208
|
+
|
|
209
|
+
dispatch({ type: 'SET_CONFIG', payload: finalizedConfig })
|
|
173
210
|
if (isEditor && !isDashboard) {
|
|
174
|
-
editorContext.setTempConfig(
|
|
211
|
+
editorContext.setTempConfig(finalizedConfig)
|
|
175
212
|
}
|
|
176
213
|
}
|
|
177
214
|
|
|
@@ -231,6 +268,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
231
268
|
config.enableMarkupVariables,
|
|
232
269
|
config.markupVariables,
|
|
233
270
|
config.data,
|
|
271
|
+
config.dataMetadata,
|
|
234
272
|
config.filters,
|
|
235
273
|
title,
|
|
236
274
|
config.superTitle,
|
|
@@ -246,11 +284,13 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
246
284
|
const processedIntroText = processedTextFields.introText
|
|
247
285
|
const processedLegacyFootnotes = processedTextFields.legacyFootnotes
|
|
248
286
|
const processedDescription = processedTextFields.description
|
|
249
|
-
const
|
|
250
|
-
Boolean(config.table?.show) &&
|
|
287
|
+
const chartSupportsDataTable =
|
|
251
288
|
config.visualizationType !== 'Spark Line' &&
|
|
252
|
-
(
|
|
289
|
+
(Boolean(config.xAxis?.dataKey) || ['Sankey', 'Network', 'Dendrogram'].includes(config.visualizationType))
|
|
253
290
|
// Note: Axis labels are processed within updateConfig to ensure they use the correct data
|
|
291
|
+
const showDataTable = Boolean(config.table?.show) && chartSupportsDataTable
|
|
292
|
+
const showDataDownload = Boolean(config.table?.download) && chartSupportsDataTable
|
|
293
|
+
const renderDataTableArea = showDataTable || showDataDownload
|
|
254
294
|
|
|
255
295
|
// set defaults on titles if blank AND only in editor
|
|
256
296
|
if (isEditor) {
|
|
@@ -263,96 +303,45 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
263
303
|
const legendId = useId()
|
|
264
304
|
|
|
265
305
|
const convertLineToBarGraph = isConvertLineToBarGraph(config, filteredData)
|
|
306
|
+
const xAxisDataKey = config.xAxis?.dataKey
|
|
307
|
+
const xAxisType = config.xAxis?.type
|
|
308
|
+
const xAxisCategoryOrderType = config.xAxis?.categoryOrderType
|
|
309
|
+
const xAxisCategoryOrder = config.xAxis?.categoryOrder
|
|
310
|
+
const categoryOrderConfig = useMemo(
|
|
311
|
+
() =>
|
|
312
|
+
({
|
|
313
|
+
xAxis: {
|
|
314
|
+
dataKey: xAxisDataKey,
|
|
315
|
+
type: xAxisType,
|
|
316
|
+
categoryOrderType: xAxisCategoryOrderType,
|
|
317
|
+
categoryOrder: xAxisCategoryOrder
|
|
318
|
+
}
|
|
319
|
+
} as Pick<ChartConfig, 'xAxis'>),
|
|
320
|
+
[xAxisDataKey, xAxisType, xAxisCategoryOrderType, xAxisCategoryOrder]
|
|
321
|
+
)
|
|
266
322
|
|
|
267
323
|
// Declaratively calculate series keys for pie charts based on filtered data
|
|
268
324
|
const pieSeriesKeys = useMemo(() => {
|
|
269
|
-
if (
|
|
325
|
+
if (visualizationType !== 'Pie' || !xAxisDataKey) return null
|
|
270
326
|
const data = filteredData?.length > 0 ? filteredData : excludedData
|
|
271
327
|
if (!data) return null
|
|
272
|
-
return uniq(data.map(d => d[
|
|
273
|
-
}, [
|
|
328
|
+
return uniq(sortByCategoryOrder(data, categoryOrderConfig).map(d => d[xAxisDataKey]))
|
|
329
|
+
}, [visualizationType, xAxisDataKey, categoryOrderConfig, filteredData, excludedData])
|
|
274
330
|
|
|
275
331
|
const prepareConfig = (loadedConfig: ChartConfig) => {
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// New configs and v2 configs should get the v2 palette defaults
|
|
281
|
-
if (loadedConfig?.general?.palette || (!loadedConfig?.general && !loadedConfig?.color)) {
|
|
282
|
-
// Keep palette defaults for:
|
|
283
|
-
// 1. Configs that already have general.palette (v2 configs)
|
|
284
|
-
// 2. New configs (no general section and no legacy color property)
|
|
285
|
-
} else {
|
|
286
|
-
// Remove palette defaults for legacy configs that have color but no general.palette
|
|
287
|
-
delete defaultsWithoutPalette.general?.palette
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Override palette defaults for Line charts specifically
|
|
291
|
-
if (loadedConfig?.visualizationType === 'Line' && !loadedConfig?.general?.palette) {
|
|
292
|
-
if (!defaultsWithoutPalette.general) {
|
|
293
|
-
defaultsWithoutPalette.general = {}
|
|
294
|
-
}
|
|
295
|
-
defaultsWithoutPalette.general.palette = {
|
|
296
|
-
isReversed: false,
|
|
297
|
-
version: '2.0',
|
|
298
|
-
name: 'divergent_blue_cyan'
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Override palette defaults for Horizon Chart specifically
|
|
303
|
-
if (loadedConfig?.visualizationType === 'Horizon Chart' && !loadedConfig?.general?.palette) {
|
|
304
|
-
if (!defaultsWithoutPalette.general) {
|
|
305
|
-
defaultsWithoutPalette.general = {}
|
|
306
|
-
}
|
|
307
|
-
defaultsWithoutPalette.general.palette = {
|
|
308
|
-
isReversed: false,
|
|
309
|
-
version: '2.0',
|
|
310
|
-
name: 'sequential_blue'
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (loadedConfig?.visualizationType === 'HeatMap' && !loadedConfig?.general?.palette) {
|
|
315
|
-
if (!defaultsWithoutPalette.general) {
|
|
316
|
-
defaultsWithoutPalette.general = {}
|
|
317
|
-
}
|
|
318
|
-
defaultsWithoutPalette.general.palette = {
|
|
319
|
-
isReversed: false,
|
|
320
|
-
version: '2.0',
|
|
321
|
-
name: 'sequential_blue'
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
let newConfig = { ...defaultsWithoutPalette, ...loadedConfig }
|
|
326
|
-
|
|
327
|
-
// Ensure Horizon Chart has enough palette colors for all layers
|
|
328
|
-
if (newConfig.visualizationType === 'Horizon Chart') {
|
|
329
|
-
const numLayers = newConfig.horizon?.numLayers ?? 4
|
|
330
|
-
const currentCount = get(newConfig, 'general.paletteColorCount', 4)
|
|
331
|
-
set(newConfig, 'general.paletteColorCount', Math.max(currentCount, numLayers))
|
|
332
|
+
const migratedConfig = isDashboard ? loadedConfig : coveUpdateWorker(loadedConfig)
|
|
333
|
+
const packageDefaults = {
|
|
334
|
+
...defaults,
|
|
335
|
+
table: { ...defaults.table, show: !isDashboard }
|
|
332
336
|
}
|
|
333
|
-
|
|
334
|
-
defaultsDeep(newConfig, {
|
|
335
|
-
table: { showVertical: false }
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
set(newConfig, 'table.show', get(newConfig, 'table.show', !isDashboard))
|
|
339
|
-
|
|
340
|
-
forEach(newConfig.series, series => {
|
|
341
|
-
lodashDefaults(series, {
|
|
342
|
-
tooltip: true,
|
|
343
|
-
axis: 'Left'
|
|
344
|
-
})
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
ensureSpecialChartAxisTypes(newConfig)
|
|
348
|
-
if (!isDashboard) return coveUpdateWorker(newConfig)
|
|
349
|
-
return newConfig
|
|
337
|
+
return applyConfigDefaults(migratedConfig, packageDefaults)
|
|
350
338
|
}
|
|
351
339
|
|
|
352
340
|
const getProcessedAxisLabels = useCallback(
|
|
353
341
|
(targetConfig: AllChartsConfig, dataSource: any[] = []) => {
|
|
354
342
|
let processedXAxis = targetConfig.xAxis?.label
|
|
355
343
|
let processedYAxis = targetConfig.yAxis?.label
|
|
344
|
+
let processedRightYAxis = targetConfig.yAxis?.rightLabel
|
|
356
345
|
|
|
357
346
|
if (targetConfig.enableMarkupVariables && targetConfig.markupVariables?.length) {
|
|
358
347
|
const axisMarkupOptions = {
|
|
@@ -377,6 +366,14 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
377
366
|
axisMarkupOptions
|
|
378
367
|
).processedContent
|
|
379
368
|
}
|
|
369
|
+
if (targetConfig.yAxis?.rightLabel) {
|
|
370
|
+
processedRightYAxis = processMarkupVariables(
|
|
371
|
+
targetConfig.yAxis.rightLabel,
|
|
372
|
+
dataSource || [],
|
|
373
|
+
targetConfig.markupVariables,
|
|
374
|
+
axisMarkupOptions
|
|
375
|
+
).processedContent
|
|
376
|
+
}
|
|
380
377
|
}
|
|
381
378
|
|
|
382
379
|
const isHorizontalVariant =
|
|
@@ -385,14 +382,23 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
385
382
|
['Deviation Bar', 'Paired Bar', 'Forest Plot'].includes(targetConfig.visualizationType)
|
|
386
383
|
|
|
387
384
|
const runtimeXAxisLabel = isHorizontalVariant
|
|
388
|
-
? processedYAxis ??
|
|
385
|
+
? processedYAxis ?? targetConfig.yAxis?.label
|
|
389
386
|
: processedXAxis ?? targetConfig.xAxis?.label
|
|
390
387
|
|
|
391
388
|
const runtimeYAxisLabel = isHorizontalVariant
|
|
392
|
-
? processedXAxis ??
|
|
389
|
+
? processedXAxis ?? targetConfig.xAxis?.label
|
|
393
390
|
: processedYAxis ?? targetConfig.yAxis?.label
|
|
391
|
+
const runtimeRightYAxisLabel = processedRightYAxis ?? targetConfig.yAxis?.rightLabel
|
|
394
392
|
|
|
395
|
-
return {
|
|
393
|
+
return {
|
|
394
|
+
processedXAxis,
|
|
395
|
+
processedYAxis,
|
|
396
|
+
processedRightYAxis,
|
|
397
|
+
runtimeXAxisLabel,
|
|
398
|
+
runtimeYAxisLabel,
|
|
399
|
+
runtimeRightYAxisLabel,
|
|
400
|
+
isHorizontalVariant
|
|
401
|
+
}
|
|
396
402
|
},
|
|
397
403
|
[isEditor]
|
|
398
404
|
)
|
|
@@ -400,40 +406,29 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
400
406
|
const updateConfig = (_config: AllChartsConfig, dataOverride?: any[]) => {
|
|
401
407
|
const newConfig = cloneConfig(_config)
|
|
402
408
|
let data = dataOverride || stateData
|
|
403
|
-
const shouldUseHeatMapSideTitlePlacement =
|
|
404
|
-
newConfig.visualizationType === 'HeatMap' && !newConfig.yAxis?.titlePlacement
|
|
405
|
-
|
|
406
|
-
ensureSpecialChartAxisTypes(newConfig)
|
|
407
|
-
|
|
408
409
|
data = handleRankByValue(data, newConfig)
|
|
409
410
|
|
|
410
|
-
const {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Auto-populate table.defaultSort for date-axis charts if not already set by user
|
|
420
|
-
const hasDateAxisType = ['date-time', 'date'].includes(newConfig.xAxis?.type)
|
|
421
|
-
if (hasDateAxisType && newConfig.xAxis?.dataKey && !newConfig.table?.defaultSort?.column) {
|
|
422
|
-
newConfig.table = {
|
|
423
|
-
...newConfig.table,
|
|
424
|
-
defaultSort: { column: newConfig.xAxis.dataKey, sortDirection: 'desc' }
|
|
425
|
-
}
|
|
426
|
-
}
|
|
411
|
+
const {
|
|
412
|
+
processedXAxis,
|
|
413
|
+
processedYAxis,
|
|
414
|
+
runtimeXAxisLabel,
|
|
415
|
+
runtimeYAxisLabel,
|
|
416
|
+
runtimeRightYAxisLabel,
|
|
417
|
+
isHorizontalVariant
|
|
418
|
+
} = getProcessedAxisLabels(newConfig, data || [])
|
|
427
419
|
|
|
428
|
-
const newExcludedData: any[] = getExcludedData(newConfig,
|
|
420
|
+
const newExcludedData: any[] = getExcludedData(newConfig, data)
|
|
429
421
|
dispatch({ type: 'SET_EXCLUDED_DATA', payload: newExcludedData })
|
|
430
422
|
|
|
431
423
|
// After data is grabbed, loop through and generate filter column values if there are any
|
|
432
424
|
let currentData: any[] = []
|
|
433
|
-
|
|
425
|
+
const isBarRaceConfig = newConfig.visualizationType === 'Bar' && newConfig.visualizationSubType === 'racing'
|
|
426
|
+
if (newConfig.filters && !isBarRaceConfig) {
|
|
434
427
|
const filtersWithValues = addValuesToFilters(newConfig.filters, newExcludedData)
|
|
435
428
|
currentData = filterVizData(filtersWithValues, newExcludedData)
|
|
436
429
|
dispatch({ type: 'SET_FILTERED_DATA', payload: currentData })
|
|
430
|
+
} else if (isBarRaceConfig) {
|
|
431
|
+
dispatch({ type: 'SET_FILTERED_DATA', payload: newExcludedData })
|
|
437
432
|
}
|
|
438
433
|
|
|
439
434
|
if (newConfig.xAxis.type === 'date-time' && config.orientation === 'horizontal') {
|
|
@@ -458,13 +453,15 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
458
453
|
if (newConfig.visualizationType === 'Pie') {
|
|
459
454
|
// Use the same data that will be passed to PieChart (after exclusions and filters)
|
|
460
455
|
const pieData = currentData.length > 0 ? currentData : newExcludedData
|
|
461
|
-
newConfig.runtime.seriesKeys = uniq(pieData.map(d => d[newConfig.xAxis.dataKey]))
|
|
456
|
+
newConfig.runtime.seriesKeys = uniq(sortByCategoryOrder(pieData, newConfig).map(d => d[newConfig.xAxis.dataKey]))
|
|
462
457
|
newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
|
|
463
458
|
newConfig.runtime.isPieChart = true // Flag to know when to use derived keys
|
|
464
459
|
} else if (newConfig.visualizationType === 'Radar') {
|
|
465
460
|
// Radar chart: seriesKeys are the entity names from xAxis.dataKey
|
|
466
461
|
const radarData = currentData.length > 0 ? currentData : newExcludedData
|
|
467
|
-
newConfig.runtime.seriesKeys = uniq(
|
|
462
|
+
newConfig.runtime.seriesKeys = uniq(
|
|
463
|
+
sortByCategoryOrder(radarData, newConfig).map(d => d[newConfig.xAxis.dataKey])
|
|
464
|
+
)
|
|
468
465
|
newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
|
|
469
466
|
} else {
|
|
470
467
|
const finalData = dataOverride || newConfig.formattedData || newConfig.data
|
|
@@ -492,8 +489,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
492
489
|
// return the series keys
|
|
493
490
|
return seriesKeys
|
|
494
491
|
} else {
|
|
495
|
-
|
|
496
|
-
newConfig.runtime.
|
|
492
|
+
const seriesName = getSeriesName(series.dataKey, { series: [series] })
|
|
493
|
+
newConfig.runtime.seriesLabels[series.dataKey] = seriesName
|
|
494
|
+
newConfig.runtime.seriesLabelsAll.push(seriesName)
|
|
497
495
|
// return the series keys
|
|
498
496
|
return [series.dataKey]
|
|
499
497
|
}
|
|
@@ -501,10 +499,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
501
499
|
}
|
|
502
500
|
|
|
503
501
|
if (newConfig.visualizationType === 'Box Plot' && newConfig.series) {
|
|
504
|
-
const [plots, categories] = getBoxPlotConfig(newConfig,
|
|
502
|
+
const [plots, categories] = getBoxPlotConfig(newConfig, data || [])
|
|
505
503
|
newConfig.boxplot['categories'] = categories
|
|
506
504
|
newConfig.boxplot.plots = plots
|
|
507
|
-
newConfig.yAxis.labelPlacement = 'On Date/Category Axis'
|
|
508
505
|
}
|
|
509
506
|
if (newConfig.visualizationType === 'Combo' && newConfig.series) {
|
|
510
507
|
newConfig.runtime = getComboChartConfig(newConfig)
|
|
@@ -518,18 +515,6 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
518
515
|
newConfig.runtime.forecastingSeriesKeys.push(series)
|
|
519
516
|
}
|
|
520
517
|
})
|
|
521
|
-
|
|
522
|
-
// Default to date scaling type for Forecasting charts
|
|
523
|
-
if (newConfig.xAxis.type === 'categorical') {
|
|
524
|
-
newConfig.xAxis.type = 'date'
|
|
525
|
-
// Initialize date parsing formats if they don't exist
|
|
526
|
-
if (!newConfig.xAxis.dateParseFormat) {
|
|
527
|
-
newConfig.xAxis.dateParseFormat = '%Y-%m-%d'
|
|
528
|
-
}
|
|
529
|
-
if (!newConfig.xAxis.dateDisplayFormat) {
|
|
530
|
-
newConfig.xAxis.dateDisplayFormat = '%Y-%m-%d'
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
518
|
}
|
|
534
519
|
|
|
535
520
|
if (newConfig.visualizationType === 'Area Chart' && newConfig.series) {
|
|
@@ -541,43 +526,14 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
541
526
|
newConfig.visualizationSubType = 'stacked'
|
|
542
527
|
}
|
|
543
528
|
|
|
544
|
-
if (newConfig.visualizationType === 'Horizon Chart' && newConfig.series) {
|
|
545
|
-
// Apply horizon defaults if not set
|
|
546
|
-
newConfig.horizon = {
|
|
547
|
-
numLayers: 4,
|
|
548
|
-
mode: 'offset', // Always offset for now, mirror hidden from UI
|
|
549
|
-
bandGap: 15,
|
|
550
|
-
bottomPadding: 15,
|
|
551
|
-
...newConfig.horizon
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// Set categorical as default xAxis type for horizon charts if not already set
|
|
555
|
-
if (!newConfig.xAxis.type) {
|
|
556
|
-
newConfig.xAxis.type = 'categorical'
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
529
|
if (newConfig.visualizationType === 'HeatMap') {
|
|
561
|
-
const
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
const hasCustomColumnLabel = configuredColumnLabel && configuredColumnLabel !== series.dataKey
|
|
566
|
-
acc[series.dataKey] = hasCustomColumnLabel ? configuredColumnLabel : series.name || series.dataKey
|
|
530
|
+
const heatMapSeries = Array.isArray(newConfig.series) ? newConfig.series : []
|
|
531
|
+
const heatMapSeriesKeys = heatMapSeries.map(series => series.dataKey)
|
|
532
|
+
const heatMapSeriesLabels = heatMapSeries.reduce<Record<string, string>>((acc, series) => {
|
|
533
|
+
acc[series.dataKey] = getSeriesName(series.dataKey, { series: [series] })
|
|
567
534
|
return acc
|
|
568
535
|
}, {})
|
|
569
536
|
|
|
570
|
-
newConfig.legend = {
|
|
571
|
-
...newConfig.legend,
|
|
572
|
-
position: newConfig.legend?.position || 'top',
|
|
573
|
-
style: newConfig.legend?.style || 'gradient',
|
|
574
|
-
subStyle:
|
|
575
|
-
newConfig.legend?.subStyle === 'smooth' ? 'linear blocks' : newConfig.legend?.subStyle || 'linear blocks'
|
|
576
|
-
}
|
|
577
|
-
newConfig.yAxis = {
|
|
578
|
-
...newConfig.yAxis,
|
|
579
|
-
type: 'categorical'
|
|
580
|
-
}
|
|
581
537
|
newConfig.runtime.seriesKeys = heatMapSeriesKeys
|
|
582
538
|
newConfig.runtime.seriesLabelsAll = heatMapSeriesKeys
|
|
583
539
|
newConfig.runtime.seriesLabels = heatMapSeriesLabels
|
|
@@ -585,19 +541,22 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
585
541
|
|
|
586
542
|
if (isHorizontalVariant) {
|
|
587
543
|
// For horizontal charts, axes are swapped, so processedYAxis goes to runtime.xAxis and vice versa
|
|
588
|
-
const horizontalXAxisSource = cloneDeep(
|
|
589
|
-
const horizontalYAxisSource = cloneDeep(
|
|
544
|
+
const horizontalXAxisSource = cloneDeep(newConfig.yAxis)
|
|
545
|
+
const horizontalYAxisSource = cloneDeep(newConfig.xAxis)
|
|
590
546
|
newConfig.runtime.xAxis = {
|
|
591
547
|
...horizontalXAxisSource,
|
|
592
548
|
label: runtimeXAxisLabel ?? horizontalXAxisSource?.label
|
|
593
549
|
}
|
|
594
550
|
newConfig.runtime.yAxis = {
|
|
595
551
|
...horizontalYAxisSource,
|
|
596
|
-
label: runtimeYAxisLabel ?? horizontalYAxisSource?.label
|
|
552
|
+
label: runtimeYAxisLabel ?? horizontalYAxisSource?.label,
|
|
553
|
+
rightLabel: runtimeRightYAxisLabel ?? newConfig.yAxis?.rightLabel
|
|
597
554
|
}
|
|
598
555
|
|
|
599
556
|
newConfig.runtime.horizontal = false
|
|
600
|
-
newConfig.
|
|
557
|
+
if (newConfig.visualizationType === 'Forest Plot') {
|
|
558
|
+
newConfig.orientation = 'horizontal'
|
|
559
|
+
}
|
|
601
560
|
// remove after COVE supports categorical axis on horizonatal bars
|
|
602
561
|
newConfig.yAxis.type = newConfig.yAxis.type === 'categorical' ? 'linear' : newConfig.yAxis.type
|
|
603
562
|
} else if (
|
|
@@ -605,12 +564,20 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
605
564
|
!convertLineToBarGraph
|
|
606
565
|
) {
|
|
607
566
|
newConfig.runtime.xAxis = { ...newConfig.xAxis, label: runtimeXAxisLabel ?? newConfig.xAxis.label }
|
|
608
|
-
newConfig.runtime.yAxis = {
|
|
567
|
+
newConfig.runtime.yAxis = {
|
|
568
|
+
...newConfig.yAxis,
|
|
569
|
+
label: runtimeYAxisLabel ?? newConfig.yAxis.label,
|
|
570
|
+
rightLabel: runtimeRightYAxisLabel ?? newConfig.yAxis.rightLabel
|
|
571
|
+
}
|
|
609
572
|
newConfig.runtime.horizontal = false
|
|
610
573
|
newConfig.orientation = 'vertical'
|
|
611
574
|
} else {
|
|
612
575
|
newConfig.runtime.xAxis = { ...newConfig.xAxis, label: runtimeXAxisLabel ?? newConfig.xAxis.label }
|
|
613
|
-
newConfig.runtime.yAxis = {
|
|
576
|
+
newConfig.runtime.yAxis = {
|
|
577
|
+
...newConfig.yAxis,
|
|
578
|
+
label: runtimeYAxisLabel ?? newConfig.yAxis.label,
|
|
579
|
+
rightLabel: runtimeRightYAxisLabel ?? newConfig.yAxis.rightLabel
|
|
580
|
+
}
|
|
614
581
|
newConfig.runtime.horizontal = false
|
|
615
582
|
}
|
|
616
583
|
|
|
@@ -745,6 +712,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
745
712
|
newConfig.dataMetadata = dataMetadata
|
|
746
713
|
|
|
747
714
|
if (newConfig.vegaConfig) {
|
|
715
|
+
const { extractCoveData, updateVegaData } = await import('@cdc/core/helpers/vegaRuntimeData')
|
|
748
716
|
newData = extractCoveData(updateVegaData(newConfig.vegaConfig, newData))
|
|
749
717
|
}
|
|
750
718
|
|
|
@@ -777,6 +745,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
777
745
|
}
|
|
778
746
|
|
|
779
747
|
const configDataDependency = configObj?.data?.length ? configObj.data : null
|
|
748
|
+
const configDataMetadataDependency = configObj?.dataMetadata ? JSON.stringify(configObj.dataMetadata) : null
|
|
780
749
|
const configExclusionsDependency = [
|
|
781
750
|
configObj?.xAxis?.type,
|
|
782
751
|
configObj?.xAxis?.dataKey,
|
|
@@ -807,7 +776,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
807
776
|
}
|
|
808
777
|
|
|
809
778
|
load()
|
|
810
|
-
}, [configDataDependency, configExclusionsDependency])
|
|
779
|
+
}, [configDataDependency, configDataMetadataDependency, configExclusionsDependency])
|
|
811
780
|
|
|
812
781
|
/**
|
|
813
782
|
* When cove has a config and container ref publish the cove_loaded event.
|
|
@@ -916,7 +885,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
916
885
|
}
|
|
917
886
|
|
|
918
887
|
const dataSource = (stateData && stateData.length ? stateData : config.data) || []
|
|
919
|
-
const { runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant } =
|
|
888
|
+
const { runtimeXAxisLabel, runtimeYAxisLabel, runtimeRightYAxisLabel, isHorizontalVariant } =
|
|
889
|
+
getProcessedAxisLabels(config, dataSource)
|
|
920
890
|
|
|
921
891
|
const runtimeClone = cloneDeep(config.runtime)
|
|
922
892
|
|
|
@@ -936,6 +906,11 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
936
906
|
shouldUpdateLabels = true
|
|
937
907
|
}
|
|
938
908
|
|
|
909
|
+
if (typeof runtimeRightYAxisLabel !== 'undefined' && runtimeClone.yAxis.rightLabel !== runtimeRightYAxisLabel) {
|
|
910
|
+
runtimeClone.yAxis = { ...runtimeClone.yAxis, rightLabel: runtimeRightYAxisLabel }
|
|
911
|
+
shouldUpdateLabels = true
|
|
912
|
+
}
|
|
913
|
+
|
|
939
914
|
if (shouldUpdateLabels) {
|
|
940
915
|
runtimeClone.uniqueId = Date.now()
|
|
941
916
|
const updatedConfig = { ...config, runtime: runtimeClone } as ChartConfig
|
|
@@ -1033,7 +1008,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1033
1008
|
) => {
|
|
1034
1009
|
if (num === '') return 'N/A'
|
|
1035
1010
|
// if num is NaN return num
|
|
1036
|
-
if (
|
|
1011
|
+
if (num === undefined || num === null) return num
|
|
1012
|
+
if (isNaN(num)) return num
|
|
1037
1013
|
// Check if the input number is negative
|
|
1038
1014
|
const isNegative = num < 0
|
|
1039
1015
|
|
|
@@ -1058,6 +1034,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1058
1034
|
rightSuffix,
|
|
1059
1035
|
bottomPrefix,
|
|
1060
1036
|
bottomSuffix,
|
|
1037
|
+
bottomCommas,
|
|
1061
1038
|
bottomAbbreviated,
|
|
1062
1039
|
preserveOriginalDecimals
|
|
1063
1040
|
}
|
|
@@ -1135,11 +1112,19 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1135
1112
|
}
|
|
1136
1113
|
|
|
1137
1114
|
if (!config.dataFormat) return num
|
|
1138
|
-
|
|
1115
|
+
let isBelowDataCutoff = false
|
|
1116
|
+
if (hasDataCutoff(config.dataCutoff)) {
|
|
1139
1117
|
let cutoff = numberFromString(config.dataCutoff)
|
|
1140
1118
|
|
|
1141
|
-
if (num < cutoff) {
|
|
1119
|
+
if (typeof cutoff === 'number' && num < cutoff) {
|
|
1142
1120
|
num = cutoff
|
|
1121
|
+
isBelowDataCutoff = true
|
|
1122
|
+
const cutoffDecimalPlaces = getDataCutoffDecimalPlaces(config.dataCutoff)
|
|
1123
|
+
stringFormattingOptions = {
|
|
1124
|
+
...stringFormattingOptions,
|
|
1125
|
+
minimumFractionDigits: cutoffDecimalPlaces,
|
|
1126
|
+
maximumFractionDigits: cutoffDecimalPlaces
|
|
1127
|
+
}
|
|
1143
1128
|
}
|
|
1144
1129
|
}
|
|
1145
1130
|
|
|
@@ -1153,7 +1138,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1153
1138
|
|
|
1154
1139
|
if (
|
|
1155
1140
|
(axis === 'left' && commas && abbreviated && shouldAbbreviate) ||
|
|
1156
|
-
(axis === 'bottom' &&
|
|
1141
|
+
(axis === 'bottom' && bottomCommas && bottomAbbreviated && shouldAbbreviate)
|
|
1157
1142
|
) {
|
|
1158
1143
|
num = num // eslint-disable-line
|
|
1159
1144
|
} else {
|
|
@@ -1203,6 +1188,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1203
1188
|
if (bottomSuffix && axis === 'bottom') {
|
|
1204
1189
|
result += bottomSuffix
|
|
1205
1190
|
}
|
|
1191
|
+
if (isBelowDataCutoff) {
|
|
1192
|
+
result = '<' + result
|
|
1193
|
+
}
|
|
1206
1194
|
if (isNegative) {
|
|
1207
1195
|
result = '-' + result
|
|
1208
1196
|
}
|
|
@@ -1228,24 +1216,68 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1228
1216
|
}
|
|
1229
1217
|
}
|
|
1230
1218
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1219
|
+
const restoreSuppressedBarValues = (cleanedData, rawData) => {
|
|
1220
|
+
const suppressionRules =
|
|
1221
|
+
config.preliminaryData?.filter(
|
|
1222
|
+
rule => rule.type === 'suppression' && rule.value !== null && rule.value !== undefined && rule.value !== ''
|
|
1223
|
+
) ?? []
|
|
1224
|
+
|
|
1225
|
+
if (!suppressionRules.length) return cleanedData
|
|
1226
|
+
|
|
1227
|
+
const dynamicSeries = config.series.find(series => series.dynamicCategory)
|
|
1228
|
+
const seriesDataKeys = new Set(config.series.map(series => series.dataKey))
|
|
1229
|
+
const confidenceDataKeys = new Set(Object.values(config.confidenceKeys ?? {}))
|
|
1230
|
+
|
|
1231
|
+
// Suppression markers must remain strings after numeric bar values are cleaned.
|
|
1232
|
+
return cleanedData.map((row, rowIndex) => {
|
|
1233
|
+
const rawRow = rawData[rowIndex] ?? {}
|
|
1234
|
+
|
|
1235
|
+
return Object.fromEntries(
|
|
1236
|
+
Object.entries(row).map(([key, value]) => {
|
|
1237
|
+
if (!seriesDataKeys.has(key) || confidenceDataKeys.has(key)) return [key, value]
|
|
1238
|
+
|
|
1239
|
+
const rawValue = rawRow[key]
|
|
1240
|
+
const isSuppressed = suppressionRules.some(rule => {
|
|
1241
|
+
const matchesStaticColumn = rule.column === key
|
|
1242
|
+
const matchesDynamicColumn =
|
|
1243
|
+
dynamicSeries?.dataKey === key && String(rawRow[dynamicSeries.dynamicCategory]) === String(rule.column)
|
|
1244
|
+
const matchesColumn = !rule.column || matchesStaticColumn || matchesDynamicColumn
|
|
1245
|
+
|
|
1246
|
+
return matchesColumn && String(rule.value) === String(rawValue)
|
|
1247
|
+
})
|
|
1248
|
+
|
|
1249
|
+
return [key, isSuppressed ? rawValue : value]
|
|
1250
|
+
})
|
|
1251
|
+
)
|
|
1252
|
+
})
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
const cleanChartData = data => {
|
|
1234
1256
|
if (!Array.isArray(data)) return []
|
|
1235
1257
|
if (config.visualizationType === 'Forecasting') return data
|
|
1236
|
-
|
|
1237
|
-
const
|
|
1258
|
+
|
|
1259
|
+
const xAxisKey = config.xAxis?.dataKey
|
|
1260
|
+
if (!xAxisKey) return data
|
|
1261
|
+
|
|
1262
|
+
const confidenceKeys: string[] = Object.values(get(config, 'confidenceKeys', {})) as string[]
|
|
1238
1263
|
const seriesKeys: string[] = get(config, 'series', []).map((s: any) => s.dataKey)
|
|
1239
|
-
const keysToClean: string[] = [...
|
|
1264
|
+
const keysToClean: string[] = [...seriesKeys, ...confidenceKeys]
|
|
1265
|
+
|
|
1266
|
+
const stripTrailingPercentage = config.visualizationType === 'Bar'
|
|
1267
|
+
const cleanedData = transform.cleanData(data, xAxisKey, keysToClean, stripTrailingPercentage)
|
|
1240
1268
|
|
|
1241
|
-
|
|
1242
|
-
const excludedKey = config.xAxis.dataKey
|
|
1243
|
-
return config?.xAxis?.dataKey ? transform.cleanData(data, excludedKey, keysToClean) : data
|
|
1269
|
+
return stripTrailingPercentage ? restoreSuppressedBarValues(cleanedData, data) : cleanedData
|
|
1244
1270
|
}
|
|
1245
1271
|
|
|
1272
|
+
const orderedTableData = useMemo(
|
|
1273
|
+
() => sortByCategoryOrder(filteredData || excludedData, categoryOrderConfig),
|
|
1274
|
+
[filteredData, excludedData, categoryOrderConfig]
|
|
1275
|
+
)
|
|
1276
|
+
|
|
1246
1277
|
const getTableRuntimeData = () => {
|
|
1247
|
-
if (visualizationType === 'Sankey') return config?.data
|
|
1248
|
-
|
|
1278
|
+
if (visualizationType === 'Sankey') return config?.data
|
|
1279
|
+
if (['Network', 'Dendrogram'].includes(visualizationType)) return orderedTableData
|
|
1280
|
+
const data = orderedTableData
|
|
1249
1281
|
if (config.visualizationType === 'Pie' && !config.dataFormat?.showPiePercent) {
|
|
1250
1282
|
return getPiePercent(data, config?.yAxis?.dataKey)
|
|
1251
1283
|
}
|
|
@@ -1271,18 +1303,32 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1271
1303
|
}
|
|
1272
1304
|
|
|
1273
1305
|
// Transform and clean data for chart rendering
|
|
1274
|
-
const transformedData =
|
|
1306
|
+
const transformedData = sortByCategoryOrder(
|
|
1307
|
+
getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean: cleanChartData }),
|
|
1308
|
+
config
|
|
1309
|
+
)
|
|
1310
|
+
const barRaceEligibility: BarRaceEligibility =
|
|
1311
|
+
config.visualizationType === 'Bar' && config.visualizationSubType === 'racing'
|
|
1312
|
+
? getBarRaceEligibility(config, transformedData)
|
|
1313
|
+
: { eligible: false, competitorCount: 0, hasDuplicateRows: false, frames: [], globalMax: 0 }
|
|
1314
|
+
const lineRaceEligibility: LineRaceEligibility =
|
|
1315
|
+
config.visualizationType === 'Line' && config.visualizationSubType === 'racing'
|
|
1316
|
+
? getLineRaceEligibility(config, transformedData)
|
|
1317
|
+
: { eligible: false, frames: [] }
|
|
1275
1318
|
const configYAxisDomainData = (config as ChartConfig).yAxisDomainData
|
|
1276
1319
|
const yAxisDomainData = useMemo(() => {
|
|
1277
1320
|
if (Array.isArray(configYAxisDomainData) && configYAxisDomainData.length > 0) {
|
|
1278
|
-
return
|
|
1321
|
+
return cleanChartData(getExcludedData(config, configYAxisDomainData))
|
|
1279
1322
|
}
|
|
1280
1323
|
|
|
1281
|
-
return
|
|
1324
|
+
return cleanChartData(excludedData)
|
|
1282
1325
|
}, [config, configYAxisDomainData, excludedData])
|
|
1283
1326
|
|
|
1284
1327
|
// Filter annotations to only those visible in current data view
|
|
1285
1328
|
const visibleAnnotations = getVisibleAnnotations(config.annotations, transformedData, config.xAxis?.dataKey)
|
|
1329
|
+
const showAnnotationDropdown =
|
|
1330
|
+
config.general.showAnnotationDropdown ||
|
|
1331
|
+
(isMobileAnnotationViewport(currentViewport) && config.general.mobileAnnotationDisplay !== 'text')
|
|
1286
1332
|
const isHeatMap = config.visualizationType === 'HeatMap'
|
|
1287
1333
|
const isTp5Treatment = ENABLE_CHART_MAP_TP5_TREATMENT && config.visual?.tp5Treatment
|
|
1288
1334
|
const visualSettingClasses = new Set([
|
|
@@ -1369,12 +1415,67 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1369
1415
|
!config.smallMultiples?.mode &&
|
|
1370
1416
|
!config.hideYAxisLabel &&
|
|
1371
1417
|
Boolean(config.runtime?.yAxis?.label)
|
|
1418
|
+
const showTopRightYAxisTitle =
|
|
1419
|
+
config.yAxis?.rightTitlePlacement === 'top' &&
|
|
1420
|
+
!config.smallMultiples?.mode &&
|
|
1421
|
+
Boolean(config.runtime?.yAxis?.rightLabel ?? config.yAxis?.rightLabel)
|
|
1422
|
+
const topRightYAxisTitle = config.runtime?.yAxis?.rightLabel ?? config.yAxis?.rightLabel
|
|
1372
1423
|
const topYAxisTitleFontSize = getAxisLabelFontSize(vizViewport)
|
|
1424
|
+
const missingRequiredFields = getMissingRequiredFields(config)
|
|
1425
|
+
const raceEligibility =
|
|
1426
|
+
config.visualizationType === 'Bar'
|
|
1427
|
+
? barRaceEligibility
|
|
1428
|
+
: config.visualizationType === 'Line'
|
|
1429
|
+
? lineRaceEligibility
|
|
1430
|
+
: undefined
|
|
1431
|
+
const shouldShowRaceEligibilityAlert = Boolean(
|
|
1432
|
+
isEditor && config.visualizationSubType === 'racing' && raceEligibility && !raceEligibility.eligible
|
|
1433
|
+
)
|
|
1434
|
+
const shouldRenderChart = isEditor
|
|
1435
|
+
? missingRequiredFields.length === 0
|
|
1436
|
+
: !missingRequiredSections(config) && !config.newViz
|
|
1437
|
+
|
|
1438
|
+
const revealRequiredField = ({ target, sectionTarget }: MissingRequiredField) => {
|
|
1439
|
+
const revealTarget = () => {
|
|
1440
|
+
const sectionButton = container?.querySelector<HTMLElement>(
|
|
1441
|
+
`[data-required-field-section='${sectionTarget || target}']`
|
|
1442
|
+
)
|
|
1443
|
+
|
|
1444
|
+
if (sectionButton?.getAttribute('aria-expanded') !== 'true') {
|
|
1445
|
+
sectionButton?.click()
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
window.requestAnimationFrame(() => {
|
|
1449
|
+
const fieldControl = container?.querySelector<HTMLElement>(`[data-required-field-control='${target}']`)
|
|
1450
|
+
fieldControl?.focus()
|
|
1451
|
+
fieldControl?.scrollIntoView?.({ block: 'nearest' })
|
|
1452
|
+
})
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
const collapsedEditorToggle = container?.querySelector<HTMLButtonElement>('.editor-panel__toggle.collapsed')
|
|
1456
|
+
|
|
1457
|
+
if (collapsedEditorToggle) {
|
|
1458
|
+
collapsedEditorToggle.click()
|
|
1459
|
+
window.requestAnimationFrame(revealTarget)
|
|
1460
|
+
} else {
|
|
1461
|
+
revealTarget()
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1373
1464
|
|
|
1374
|
-
const
|
|
1375
|
-
showTopYAxisTitle ? (
|
|
1376
|
-
<div
|
|
1377
|
-
{
|
|
1465
|
+
const renderTopYAxisTitles = () =>
|
|
1466
|
+
showTopYAxisTitle || showTopRightYAxisTitle ? (
|
|
1467
|
+
<div
|
|
1468
|
+
className={`y-axis-top-title-row ${
|
|
1469
|
+
showTopYAxisTitle && showTopRightYAxisTitle ? 'y-axis-top-title-row--split' : ''
|
|
1470
|
+
}`}
|
|
1471
|
+
style={{ fontSize: `${topYAxisTitleFontSize}px` }}
|
|
1472
|
+
>
|
|
1473
|
+
{showTopYAxisTitle && (
|
|
1474
|
+
<div className='y-axis-top-title y-axis-top-title--left'>{parse(config.runtime.yAxis.label)}</div>
|
|
1475
|
+
)}
|
|
1476
|
+
{showTopRightYAxisTitle && (
|
|
1477
|
+
<div className='y-axis-top-title y-axis-top-title--right'>{parse(topRightYAxisTitle)}</div>
|
|
1478
|
+
)}
|
|
1378
1479
|
</div>
|
|
1379
1480
|
) : null
|
|
1380
1481
|
|
|
@@ -1383,7 +1484,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1383
1484
|
wrapperStyle: React.CSSProperties = { width: '100%' }
|
|
1384
1485
|
) => (
|
|
1385
1486
|
<>
|
|
1386
|
-
{
|
|
1487
|
+
{renderTopYAxisTitles()}
|
|
1387
1488
|
<div ref={parentRef} style={wrapperStyle}>
|
|
1388
1489
|
<ParentSize>
|
|
1389
1490
|
{parent => <LinearChart ref={svgRef} parentWidth={getParentWidth(parent)} parentHeight={parent.height} />}
|
|
@@ -1400,15 +1501,50 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1400
1501
|
)
|
|
1401
1502
|
body = (
|
|
1402
1503
|
<>
|
|
1403
|
-
{
|
|
1404
|
-
|
|
1504
|
+
{isEditor && (missingRequiredFields.length > 0 || shouldShowRaceEligibilityAlert) && (
|
|
1505
|
+
<section className='chart-required-fields-alerts' aria-label='Chart alerts'>
|
|
1506
|
+
{missingRequiredFields.map(missingField => (
|
|
1507
|
+
<Alert
|
|
1508
|
+
key={missingField.target}
|
|
1509
|
+
type='info'
|
|
1510
|
+
message={
|
|
1511
|
+
<span>
|
|
1512
|
+
Missing field: <strong>{missingField.field}</strong>.{' '}
|
|
1513
|
+
<button
|
|
1514
|
+
type='button'
|
|
1515
|
+
className='chart-required-fields-alerts__link'
|
|
1516
|
+
aria-label={`Open ${missingField.section} and focus ${missingField.field}`}
|
|
1517
|
+
onClick={() => revealRequiredField(missingField)}
|
|
1518
|
+
>
|
|
1519
|
+
More information
|
|
1520
|
+
</button>
|
|
1521
|
+
</span>
|
|
1522
|
+
}
|
|
1523
|
+
showCloseButton={false}
|
|
1524
|
+
/>
|
|
1525
|
+
))}
|
|
1526
|
+
{shouldShowRaceEligibilityAlert && (
|
|
1527
|
+
<Alert
|
|
1528
|
+
type='info'
|
|
1529
|
+
message={`Racing mode cannot render this configuration. ${raceEligibility?.reason} A regular ${
|
|
1530
|
+
config.visualizationType === 'Bar' ? 'horizontal bar' : 'Line'
|
|
1531
|
+
} chart is shown instead.`}
|
|
1532
|
+
showCloseButton={false}
|
|
1533
|
+
/>
|
|
1534
|
+
)}
|
|
1535
|
+
</section>
|
|
1536
|
+
)}
|
|
1537
|
+
{shouldRenderChart && (
|
|
1405
1538
|
<VisualizationContent
|
|
1406
1539
|
innerClassName={`type-${makeClassName(config.visualizationType)}`}
|
|
1407
1540
|
innerProps={{ tabIndex: 0 }}
|
|
1408
1541
|
bodyClassName={bodyClasses.join(' ')}
|
|
1409
1542
|
bodyWrapClassName={bodyWrapClasses}
|
|
1410
1543
|
filters={
|
|
1411
|
-
hasVisibleVizFilters(config.filters) &&
|
|
1544
|
+
hasVisibleVizFilters(config.filters) &&
|
|
1545
|
+
!externalFilters &&
|
|
1546
|
+
config.visualizationType !== 'Spark Line' &&
|
|
1547
|
+
!(config.visualizationType === 'Bar' && config.visualizationSubType === 'racing') ? (
|
|
1412
1548
|
<Filters
|
|
1413
1549
|
config={config}
|
|
1414
1550
|
setFilters={setFilters}
|
|
@@ -1423,19 +1559,26 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1423
1559
|
<div className={`cove-prose ${getChartSubTextClasses().join(' ')}`}>{parse(processedDescription)}</div>
|
|
1424
1560
|
) : null
|
|
1425
1561
|
}
|
|
1562
|
+
bodyAnnotations={
|
|
1563
|
+
visibleAnnotations.length > 0 &&
|
|
1564
|
+
showAnnotationDropdown &&
|
|
1565
|
+
!(config.visualizationType === 'Bar' && config.visualizationSubType === 'racing') ? (
|
|
1566
|
+
<Annotation.Dropdown />
|
|
1567
|
+
) : null
|
|
1568
|
+
}
|
|
1426
1569
|
bodyFooter={
|
|
1427
1570
|
<>
|
|
1428
1571
|
{isDashboard && config.table && config.table.show && config.table.showDataTableLink
|
|
1429
1572
|
? tableLink
|
|
1430
1573
|
: link && link}
|
|
1431
|
-
{
|
|
1574
|
+
{renderDataTableArea
|
|
1432
1575
|
? (() => {
|
|
1433
1576
|
let dataTableConfig = pivotDynamicSeries(config)
|
|
1434
1577
|
let dataTableColumns = config.columns
|
|
1435
1578
|
let dataTableRuntimeData = getTableRuntimeData()
|
|
1436
1579
|
let dataTableRawData =
|
|
1437
1580
|
config.visualizationType === 'Sankey'
|
|
1438
|
-
? config?.data
|
|
1581
|
+
? config?.data
|
|
1439
1582
|
: config.table.customTableConfig
|
|
1440
1583
|
? filterVizData(config.filters, config.data)
|
|
1441
1584
|
: config.data
|
|
@@ -1468,6 +1611,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1468
1611
|
tabbingId={handleChartTabbing(config, legendId)}
|
|
1469
1612
|
colorScale={colorScale}
|
|
1470
1613
|
imageRef={imageId}
|
|
1614
|
+
showTable={showDataTable}
|
|
1471
1615
|
showDownloadImgButton={config.table.showDownloadImgButton}
|
|
1472
1616
|
showDownloadPdfButton={config.table.showDownloadPdfButton}
|
|
1473
1617
|
includeContextInDownload={config.table?.includeContextInDownload}
|
|
@@ -1502,7 +1646,6 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1502
1646
|
</MediaControls.Section>
|
|
1503
1647
|
</div>
|
|
1504
1648
|
)}
|
|
1505
|
-
{visibleAnnotations.length > 0 && <Annotation.Dropdown />}
|
|
1506
1649
|
{processedLegacyFootnotes && (
|
|
1507
1650
|
<section className='footnotes cove-prose pt-2 mt-4'>{parse(processedLegacyFootnotes)}</section>
|
|
1508
1651
|
)}
|
|
@@ -1541,11 +1684,13 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1541
1684
|
<LegendWrapper>
|
|
1542
1685
|
<div
|
|
1543
1686
|
className={
|
|
1544
|
-
legend.hide || isLegendWrapViewport(currentViewport)
|
|
1687
|
+
legend.hide || barRaceEligibility.eligible || isLegendWrapViewport(currentViewport)
|
|
1545
1688
|
? 'w-100'
|
|
1546
1689
|
: legend.position === 'bottom' ||
|
|
1547
1690
|
legend.position === 'top' ||
|
|
1548
1691
|
visualizationType === 'Sankey' ||
|
|
1692
|
+
visualizationType === 'Network' ||
|
|
1693
|
+
visualizationType === 'Dendrogram' ||
|
|
1549
1694
|
visualizationType === 'Spark Line'
|
|
1550
1695
|
? 'w-100'
|
|
1551
1696
|
: 'w-75'
|
|
@@ -1554,16 +1699,38 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1554
1699
|
{/* Check if there is data to display */}
|
|
1555
1700
|
{(!filteredData || filteredData.length === 0) && (
|
|
1556
1701
|
<div className='no-data-message' style={{ padding: '2rem', textAlign: 'center', color: '#666' }}>
|
|
1557
|
-
{config.chartMessage?.noData ||
|
|
1702
|
+
{config.chartMessage?.noData ||
|
|
1703
|
+
(config.visualizationType === 'Network'
|
|
1704
|
+
? 'No network data is available. Import edge-list rows and select source and target columns.'
|
|
1705
|
+
: config.visualizationType === 'Dendrogram'
|
|
1706
|
+
? 'No dendrogram data is available. Import hierarchy rows and select node and parent columns.'
|
|
1707
|
+
: 'No Data Available')}
|
|
1558
1708
|
</div>
|
|
1559
1709
|
)}
|
|
1560
1710
|
|
|
1561
1711
|
{/* All charts with LinearChart */}
|
|
1562
1712
|
{filteredData &&
|
|
1563
1713
|
filteredData.length > 0 &&
|
|
1564
|
-
!['Spark Line', 'Line', 'Sankey', 'Pie', 'Radar', 'HeatMap'].includes(
|
|
1714
|
+
!['Spark Line', 'Line', 'Sankey', 'Network', 'Dendrogram', 'Pie', 'Radar', 'HeatMap'].includes(
|
|
1715
|
+
config.visualizationType
|
|
1716
|
+
) &&
|
|
1717
|
+
!(config.visualizationType === 'Bar' && config.visualizationSubType === 'racing') &&
|
|
1565
1718
|
renderLinearChartWithParentSize()}
|
|
1566
1719
|
|
|
1720
|
+
{filteredData &&
|
|
1721
|
+
filteredData.length > 0 &&
|
|
1722
|
+
(config.visualizationType === 'Bar' || config.visualizationType === 'Line') &&
|
|
1723
|
+
config.visualizationSubType === 'racing' && (
|
|
1724
|
+
<RacingChartRenderer
|
|
1725
|
+
family={config.visualizationType}
|
|
1726
|
+
barRace={barRaceEligibility}
|
|
1727
|
+
lineRace={lineRaceEligibility}
|
|
1728
|
+
parentRef={parentRef}
|
|
1729
|
+
svgRef={svgRef}
|
|
1730
|
+
renderTopYAxisTitles={renderTopYAxisTitles}
|
|
1731
|
+
/>
|
|
1732
|
+
)}
|
|
1733
|
+
|
|
1567
1734
|
{filteredData && filteredData.length > 0 && config.visualizationType === 'Pie' && (
|
|
1568
1735
|
<ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
|
|
1569
1736
|
{parent => (
|
|
@@ -1603,6 +1770,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1603
1770
|
{filteredData &&
|
|
1604
1771
|
filteredData.length > 0 &&
|
|
1605
1772
|
config.visualizationType === 'Line' &&
|
|
1773
|
+
config.visualizationSubType !== 'racing' &&
|
|
1606
1774
|
(convertLineToBarGraph
|
|
1607
1775
|
? renderLinearChartWithParentSize()
|
|
1608
1776
|
: renderLinearChartWithParentSize(parent => {
|
|
@@ -1641,15 +1809,66 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1641
1809
|
)}
|
|
1642
1810
|
{/* Sankey */}
|
|
1643
1811
|
{config.visualizationType === 'Sankey' && (
|
|
1644
|
-
<ParentSize
|
|
1812
|
+
<ParentSize>
|
|
1645
1813
|
{parent => <SankeyChart runtime={config.runtime} width={parent.width} height={parent.height} />}
|
|
1646
1814
|
</ParentSize>
|
|
1647
1815
|
)}
|
|
1816
|
+
{/* Network */}
|
|
1817
|
+
{filteredData && filteredData.length > 0 && config.visualizationType === 'Network' && (
|
|
1818
|
+
<div
|
|
1819
|
+
style={{
|
|
1820
|
+
width: '100%',
|
|
1821
|
+
height: `${
|
|
1822
|
+
Number.isFinite(Number(config.network?.height))
|
|
1823
|
+
? Math.max(160, Number(config.network?.height))
|
|
1824
|
+
: 500
|
|
1825
|
+
}px`
|
|
1826
|
+
}}
|
|
1827
|
+
>
|
|
1828
|
+
<ParentSize>
|
|
1829
|
+
{parent => (
|
|
1830
|
+
<NetworkChart
|
|
1831
|
+
data={filteredData}
|
|
1832
|
+
runtime={config.runtime}
|
|
1833
|
+
width={parent.width}
|
|
1834
|
+
height={parent.height}
|
|
1835
|
+
/>
|
|
1836
|
+
)}
|
|
1837
|
+
</ParentSize>
|
|
1838
|
+
</div>
|
|
1839
|
+
)}
|
|
1840
|
+
{/* Dendrogram */}
|
|
1841
|
+
{filteredData && filteredData.length > 0 && config.visualizationType === 'Dendrogram' && (
|
|
1842
|
+
<div
|
|
1843
|
+
style={{
|
|
1844
|
+
width: '100%',
|
|
1845
|
+
height: `${
|
|
1846
|
+
Number.isFinite(Number(config.dendrogram?.height))
|
|
1847
|
+
? Math.max(160, Number(config.dendrogram?.height))
|
|
1848
|
+
: 500
|
|
1849
|
+
}px`
|
|
1850
|
+
}}
|
|
1851
|
+
>
|
|
1852
|
+
<ParentSize>
|
|
1853
|
+
{parent => (
|
|
1854
|
+
<DendrogramChart
|
|
1855
|
+
data={filteredData}
|
|
1856
|
+
runtime={config.runtime}
|
|
1857
|
+
width={parent.width}
|
|
1858
|
+
height={parent.height}
|
|
1859
|
+
/>
|
|
1860
|
+
)}
|
|
1861
|
+
</ParentSize>
|
|
1862
|
+
</div>
|
|
1863
|
+
)}
|
|
1648
1864
|
</div>
|
|
1649
1865
|
{/* Legend */}
|
|
1650
1866
|
{!config.legend.hide &&
|
|
1867
|
+
!(config.visualizationType === 'Bar' && config.visualizationSubType === 'racing') &&
|
|
1651
1868
|
config.visualizationType !== 'Spark Line' &&
|
|
1652
1869
|
config.visualizationType !== 'Sankey' &&
|
|
1870
|
+
config.visualizationType !== 'Network' &&
|
|
1871
|
+
config.visualizationType !== 'Dendrogram' &&
|
|
1653
1872
|
config.visualizationType !== 'HeatMap' &&
|
|
1654
1873
|
!(config.visualizationType === 'Warming Stripes' && config.legend?.style === 'gradient') &&
|
|
1655
1874
|
!(config.visualizationType === 'Warming Stripes' && config.smallMultiples?.mode) && (
|
|
@@ -1688,7 +1907,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1688
1907
|
...state,
|
|
1689
1908
|
capitalize,
|
|
1690
1909
|
convertLineToBarGraph,
|
|
1691
|
-
clean,
|
|
1910
|
+
clean: cleanChartData,
|
|
1692
1911
|
colorPalettes,
|
|
1693
1912
|
dashboardConfig,
|
|
1694
1913
|
debugSvg: isDebug,
|
|
@@ -1712,6 +1931,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1712
1931
|
legendId,
|
|
1713
1932
|
legendRef,
|
|
1714
1933
|
lineOptions,
|
|
1934
|
+
raceTiming,
|
|
1715
1935
|
missingRequiredSections,
|
|
1716
1936
|
outerContainerRef,
|
|
1717
1937
|
parentRef,
|
|
@@ -1719,11 +1939,12 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1719
1939
|
rawData: stateData ?? {},
|
|
1720
1940
|
setConfig,
|
|
1721
1941
|
setEditing,
|
|
1942
|
+
setRaceTiming,
|
|
1722
1943
|
setParentConfig,
|
|
1723
1944
|
setSharedFilter,
|
|
1724
1945
|
setSharedFilterValue,
|
|
1725
1946
|
svgRef,
|
|
1726
|
-
tableData:
|
|
1947
|
+
tableData: orderedTableData,
|
|
1727
1948
|
transformedData,
|
|
1728
1949
|
twoColorPalette,
|
|
1729
1950
|
unfilteredData: stateData,
|
|
@@ -1741,7 +1962,13 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1741
1962
|
currentViewport={currentViewport}
|
|
1742
1963
|
ref={outerContainerRef}
|
|
1743
1964
|
imageId={imageId}
|
|
1744
|
-
editorPanel={
|
|
1965
|
+
editorPanel={
|
|
1966
|
+
isEditor && !isLoading ? (
|
|
1967
|
+
<Suspense fallback={null}>
|
|
1968
|
+
<EditorPanel datasets={datasets} />
|
|
1969
|
+
</Suspense>
|
|
1970
|
+
) : null
|
|
1971
|
+
}
|
|
1745
1972
|
>
|
|
1746
1973
|
{body}
|
|
1747
1974
|
</VisualizationContainer>
|