@cdc/chart 4.26.7 → 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 +502 -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 -97406
- 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/overlay-legend.json +485 -0
- package/examples/private/pie-test.json +469 -0
- package/examples/vega-remote.json +22 -0
- package/package.json +11 -5
- package/src/CdcChartComponent.tsx +405 -208
- 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 +4 -2
- 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/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/_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 +38 -6
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -3
- package/src/components/BarChart/components/BarChart.StackedVertical.test.ts +28 -0
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +29 -25
- package/src/components/BarChart/components/BarChart.Vertical.tsx +37 -26
- package/src/components/BarChart/components/PortionPatternOverlay.tsx +61 -0
- package/src/components/BarChart/components/context.tsx +6 -0
- 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 +104 -0
- package/src/components/BarChart/helpers/useBarChart.ts +60 -59
- 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/helpers/index.ts +11 -82
- package/src/components/Brush/BrushSelector.tsx +9 -23
- 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 +1005 -760
- 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 +88 -54
- 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 +233 -33
- 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 +2 -2
- 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 +2 -1
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +62 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +70 -24
- package/src/components/Forecasting/Forecasting.tsx +15 -6
- 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 +110 -90
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +7 -0
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +47 -34
- 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 -4
- package/src/components/LineChart/index.tsx +69 -24
- 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 +81 -51
- 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 +9 -8
- 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 -4
- 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 +77 -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/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 +18 -37
- package/src/helpers/getChartTypeDefaultPalette.ts +29 -0
- package/src/helpers/getColorCodeCategoryColorMap.ts +26 -0
- package/src/helpers/getColorScale.ts +31 -22
- package/src/helpers/getDynamicYAxisCategories.ts +69 -0
- package/src/helpers/getMinMax.ts +9 -7
- package/src/helpers/getNewRuntime.ts +5 -2
- package/src/helpers/getStackedSeriesMax.ts +51 -0
- package/src/helpers/getV21ChartDistributionColors.ts +57 -0
- package/src/helpers/handleRankByValue.ts +15 -15
- package/src/helpers/seriesColumnSettings.ts +10 -0
- package/src/helpers/sizeHelpers.ts +4 -3
- 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/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 +23 -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/useTooltip.test.tsx +353 -1
- package/src/hooks/usePrefersReducedMotion.ts +22 -0
- package/src/hooks/useReduceData.ts +4 -36
- package/src/hooks/useScales.ts +15 -0
- package/src/hooks/useTooltip.tsx +95 -64
- package/src/scss/main.scss +74 -2
- package/src/store/chart.reducer.ts +4 -3
- package/src/test/CdcChart.dataTable.test.tsx +490 -16
- 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,6 @@ 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 { ensureSpecialChartAxisTypes } from './helpers/ensureSpecialChartAxisTypes'
|
|
103
|
-
import { findColumnConfigByName } from './helpers/seriesColumnSettings'
|
|
104
115
|
import { sortByCategoryOrder } from './helpers/categoryOrder'
|
|
105
116
|
|
|
106
117
|
// styles
|
|
@@ -113,9 +124,12 @@ import { Datasets } from '@cdc/core/types/DataSet'
|
|
|
113
124
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
114
125
|
import cloneConfig from '@cdc/core/helpers/cloneConfig'
|
|
115
126
|
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
127
|
+
import { getSeriesName } from '@cdc/core/helpers/getSeriesName'
|
|
116
128
|
import { ENABLE_CHART_MAP_TP5_TREATMENT, ENABLE_CHART_VISUAL_SETTINGS } from '@cdc/core/helpers/constants'
|
|
117
129
|
import CalloutFlag from '@cdc/core/assets/callout-flag.svg?url'
|
|
118
130
|
|
|
131
|
+
const EditorPanel = lazy(() => import('./components/EditorPanel'))
|
|
132
|
+
|
|
119
133
|
interface CdcChartProps {
|
|
120
134
|
config?: ChartConfig
|
|
121
135
|
isEditor?: boolean
|
|
@@ -131,6 +145,16 @@ interface CdcChartProps {
|
|
|
131
145
|
datasets?: Datasets
|
|
132
146
|
interactionLabel: string
|
|
133
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
|
+
|
|
134
158
|
const CdcChart: React.FC<CdcChartProps> = ({
|
|
135
159
|
config: configObj,
|
|
136
160
|
isEditor = false,
|
|
@@ -168,11 +192,23 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
168
192
|
const svgRef = useRef(null)
|
|
169
193
|
const editorContext = useContext(EditorContext)
|
|
170
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)
|
|
171
201
|
|
|
172
202
|
const setConfig = (newConfig: ChartConfig): void => {
|
|
173
|
-
|
|
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 })
|
|
174
210
|
if (isEditor && !isDashboard) {
|
|
175
|
-
editorContext.setTempConfig(
|
|
211
|
+
editorContext.setTempConfig(finalizedConfig)
|
|
176
212
|
}
|
|
177
213
|
}
|
|
178
214
|
|
|
@@ -248,11 +284,13 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
248
284
|
const processedIntroText = processedTextFields.introText
|
|
249
285
|
const processedLegacyFootnotes = processedTextFields.legacyFootnotes
|
|
250
286
|
const processedDescription = processedTextFields.description
|
|
251
|
-
const
|
|
252
|
-
Boolean(config.table?.show) &&
|
|
287
|
+
const chartSupportsDataTable =
|
|
253
288
|
config.visualizationType !== 'Spark Line' &&
|
|
254
|
-
(
|
|
289
|
+
(Boolean(config.xAxis?.dataKey) || ['Sankey', 'Network', 'Dendrogram'].includes(config.visualizationType))
|
|
255
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
|
|
256
294
|
|
|
257
295
|
// set defaults on titles if blank AND only in editor
|
|
258
296
|
if (isEditor) {
|
|
@@ -291,86 +329,19 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
291
329
|
}, [visualizationType, xAxisDataKey, categoryOrderConfig, filteredData, excludedData])
|
|
292
330
|
|
|
293
331
|
const prepareConfig = (loadedConfig: ChartConfig) => {
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
// New configs and v2 configs should get the v2 palette defaults
|
|
299
|
-
if (loadedConfig?.general?.palette || (!loadedConfig?.general && !loadedConfig?.color)) {
|
|
300
|
-
// Keep palette defaults for:
|
|
301
|
-
// 1. Configs that already have general.palette (v2 configs)
|
|
302
|
-
// 2. New configs (no general section and no legacy color property)
|
|
303
|
-
} else {
|
|
304
|
-
// Remove palette defaults for legacy configs that have color but no general.palette
|
|
305
|
-
delete defaultsWithoutPalette.general?.palette
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Override palette defaults for Line charts specifically
|
|
309
|
-
if (loadedConfig?.visualizationType === 'Line' && !loadedConfig?.general?.palette) {
|
|
310
|
-
if (!defaultsWithoutPalette.general) {
|
|
311
|
-
defaultsWithoutPalette.general = {}
|
|
312
|
-
}
|
|
313
|
-
defaultsWithoutPalette.general.palette = {
|
|
314
|
-
isReversed: false,
|
|
315
|
-
version: '2.0',
|
|
316
|
-
name: 'divergent_blue_cyan'
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Override palette defaults for Horizon Chart specifically
|
|
321
|
-
if (loadedConfig?.visualizationType === 'Horizon Chart' && !loadedConfig?.general?.palette) {
|
|
322
|
-
if (!defaultsWithoutPalette.general) {
|
|
323
|
-
defaultsWithoutPalette.general = {}
|
|
324
|
-
}
|
|
325
|
-
defaultsWithoutPalette.general.palette = {
|
|
326
|
-
isReversed: false,
|
|
327
|
-
version: '2.0',
|
|
328
|
-
name: 'sequential_blue'
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (loadedConfig?.visualizationType === 'HeatMap' && !loadedConfig?.general?.palette) {
|
|
333
|
-
if (!defaultsWithoutPalette.general) {
|
|
334
|
-
defaultsWithoutPalette.general = {}
|
|
335
|
-
}
|
|
336
|
-
defaultsWithoutPalette.general.palette = {
|
|
337
|
-
isReversed: false,
|
|
338
|
-
version: '2.0',
|
|
339
|
-
name: 'sequential_blue'
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
let newConfig = { ...defaultsWithoutPalette, ...loadedConfig }
|
|
344
|
-
|
|
345
|
-
// Ensure Horizon Chart has enough palette colors for all layers
|
|
346
|
-
if (newConfig.visualizationType === 'Horizon Chart') {
|
|
347
|
-
const numLayers = newConfig.horizon?.numLayers ?? 4
|
|
348
|
-
const currentCount = get(newConfig, 'general.paletteColorCount', 4)
|
|
349
|
-
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 }
|
|
350
336
|
}
|
|
351
|
-
|
|
352
|
-
defaultsDeep(newConfig, {
|
|
353
|
-
table: { showVertical: false }
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
set(newConfig, 'table.show', get(newConfig, 'table.show', !isDashboard))
|
|
357
|
-
|
|
358
|
-
forEach(newConfig.series, series => {
|
|
359
|
-
lodashDefaults(series, {
|
|
360
|
-
tooltip: true,
|
|
361
|
-
axis: 'Left'
|
|
362
|
-
})
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
ensureSpecialChartAxisTypes(newConfig)
|
|
366
|
-
if (!isDashboard) return coveUpdateWorker(newConfig)
|
|
367
|
-
return newConfig
|
|
337
|
+
return applyConfigDefaults(migratedConfig, packageDefaults)
|
|
368
338
|
}
|
|
369
339
|
|
|
370
340
|
const getProcessedAxisLabels = useCallback(
|
|
371
341
|
(targetConfig: AllChartsConfig, dataSource: any[] = []) => {
|
|
372
342
|
let processedXAxis = targetConfig.xAxis?.label
|
|
373
343
|
let processedYAxis = targetConfig.yAxis?.label
|
|
344
|
+
let processedRightYAxis = targetConfig.yAxis?.rightLabel
|
|
374
345
|
|
|
375
346
|
if (targetConfig.enableMarkupVariables && targetConfig.markupVariables?.length) {
|
|
376
347
|
const axisMarkupOptions = {
|
|
@@ -395,6 +366,14 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
395
366
|
axisMarkupOptions
|
|
396
367
|
).processedContent
|
|
397
368
|
}
|
|
369
|
+
if (targetConfig.yAxis?.rightLabel) {
|
|
370
|
+
processedRightYAxis = processMarkupVariables(
|
|
371
|
+
targetConfig.yAxis.rightLabel,
|
|
372
|
+
dataSource || [],
|
|
373
|
+
targetConfig.markupVariables,
|
|
374
|
+
axisMarkupOptions
|
|
375
|
+
).processedContent
|
|
376
|
+
}
|
|
398
377
|
}
|
|
399
378
|
|
|
400
379
|
const isHorizontalVariant =
|
|
@@ -403,14 +382,23 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
403
382
|
['Deviation Bar', 'Paired Bar', 'Forest Plot'].includes(targetConfig.visualizationType)
|
|
404
383
|
|
|
405
384
|
const runtimeXAxisLabel = isHorizontalVariant
|
|
406
|
-
? processedYAxis ??
|
|
385
|
+
? processedYAxis ?? targetConfig.yAxis?.label
|
|
407
386
|
: processedXAxis ?? targetConfig.xAxis?.label
|
|
408
387
|
|
|
409
388
|
const runtimeYAxisLabel = isHorizontalVariant
|
|
410
|
-
? processedXAxis ??
|
|
389
|
+
? processedXAxis ?? targetConfig.xAxis?.label
|
|
411
390
|
: processedYAxis ?? targetConfig.yAxis?.label
|
|
391
|
+
const runtimeRightYAxisLabel = processedRightYAxis ?? targetConfig.yAxis?.rightLabel
|
|
412
392
|
|
|
413
|
-
return {
|
|
393
|
+
return {
|
|
394
|
+
processedXAxis,
|
|
395
|
+
processedYAxis,
|
|
396
|
+
processedRightYAxis,
|
|
397
|
+
runtimeXAxisLabel,
|
|
398
|
+
runtimeYAxisLabel,
|
|
399
|
+
runtimeRightYAxisLabel,
|
|
400
|
+
isHorizontalVariant
|
|
401
|
+
}
|
|
414
402
|
},
|
|
415
403
|
[isEditor]
|
|
416
404
|
)
|
|
@@ -418,40 +406,29 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
418
406
|
const updateConfig = (_config: AllChartsConfig, dataOverride?: any[]) => {
|
|
419
407
|
const newConfig = cloneConfig(_config)
|
|
420
408
|
let data = dataOverride || stateData
|
|
421
|
-
const shouldUseHeatMapSideTitlePlacement =
|
|
422
|
-
newConfig.visualizationType === 'HeatMap' && !newConfig.yAxis?.titlePlacement
|
|
423
|
-
|
|
424
|
-
ensureSpecialChartAxisTypes(newConfig)
|
|
425
|
-
|
|
426
409
|
data = handleRankByValue(data, newConfig)
|
|
427
410
|
|
|
428
|
-
const {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Auto-populate table.defaultSort for date-axis charts if not already set by user
|
|
438
|
-
const hasDateAxisType = ['date-time', 'date'].includes(newConfig.xAxis?.type)
|
|
439
|
-
if (hasDateAxisType && newConfig.xAxis?.dataKey && !newConfig.table?.defaultSort?.column) {
|
|
440
|
-
newConfig.table = {
|
|
441
|
-
...newConfig.table,
|
|
442
|
-
defaultSort: { column: newConfig.xAxis.dataKey, sortDirection: 'desc' }
|
|
443
|
-
}
|
|
444
|
-
}
|
|
411
|
+
const {
|
|
412
|
+
processedXAxis,
|
|
413
|
+
processedYAxis,
|
|
414
|
+
runtimeXAxisLabel,
|
|
415
|
+
runtimeYAxisLabel,
|
|
416
|
+
runtimeRightYAxisLabel,
|
|
417
|
+
isHorizontalVariant
|
|
418
|
+
} = getProcessedAxisLabels(newConfig, data || [])
|
|
445
419
|
|
|
446
|
-
const newExcludedData: any[] = getExcludedData(newConfig,
|
|
420
|
+
const newExcludedData: any[] = getExcludedData(newConfig, data)
|
|
447
421
|
dispatch({ type: 'SET_EXCLUDED_DATA', payload: newExcludedData })
|
|
448
422
|
|
|
449
423
|
// After data is grabbed, loop through and generate filter column values if there are any
|
|
450
424
|
let currentData: any[] = []
|
|
451
|
-
|
|
425
|
+
const isBarRaceConfig = newConfig.visualizationType === 'Bar' && newConfig.visualizationSubType === 'racing'
|
|
426
|
+
if (newConfig.filters && !isBarRaceConfig) {
|
|
452
427
|
const filtersWithValues = addValuesToFilters(newConfig.filters, newExcludedData)
|
|
453
428
|
currentData = filterVizData(filtersWithValues, newExcludedData)
|
|
454
429
|
dispatch({ type: 'SET_FILTERED_DATA', payload: currentData })
|
|
430
|
+
} else if (isBarRaceConfig) {
|
|
431
|
+
dispatch({ type: 'SET_FILTERED_DATA', payload: newExcludedData })
|
|
455
432
|
}
|
|
456
433
|
|
|
457
434
|
if (newConfig.xAxis.type === 'date-time' && config.orientation === 'horizontal') {
|
|
@@ -512,8 +489,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
512
489
|
// return the series keys
|
|
513
490
|
return seriesKeys
|
|
514
491
|
} else {
|
|
515
|
-
|
|
516
|
-
newConfig.runtime.
|
|
492
|
+
const seriesName = getSeriesName(series.dataKey, { series: [series] })
|
|
493
|
+
newConfig.runtime.seriesLabels[series.dataKey] = seriesName
|
|
494
|
+
newConfig.runtime.seriesLabelsAll.push(seriesName)
|
|
517
495
|
// return the series keys
|
|
518
496
|
return [series.dataKey]
|
|
519
497
|
}
|
|
@@ -521,10 +499,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
521
499
|
}
|
|
522
500
|
|
|
523
501
|
if (newConfig.visualizationType === 'Box Plot' && newConfig.series) {
|
|
524
|
-
const [plots, categories] = getBoxPlotConfig(newConfig,
|
|
502
|
+
const [plots, categories] = getBoxPlotConfig(newConfig, data || [])
|
|
525
503
|
newConfig.boxplot['categories'] = categories
|
|
526
504
|
newConfig.boxplot.plots = plots
|
|
527
|
-
newConfig.yAxis.labelPlacement = 'On Date/Category Axis'
|
|
528
505
|
}
|
|
529
506
|
if (newConfig.visualizationType === 'Combo' && newConfig.series) {
|
|
530
507
|
newConfig.runtime = getComboChartConfig(newConfig)
|
|
@@ -538,18 +515,6 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
538
515
|
newConfig.runtime.forecastingSeriesKeys.push(series)
|
|
539
516
|
}
|
|
540
517
|
})
|
|
541
|
-
|
|
542
|
-
// Default to date scaling type for Forecasting charts
|
|
543
|
-
if (newConfig.xAxis.type === 'categorical') {
|
|
544
|
-
newConfig.xAxis.type = 'date'
|
|
545
|
-
// Initialize date parsing formats if they don't exist
|
|
546
|
-
if (!newConfig.xAxis.dateParseFormat) {
|
|
547
|
-
newConfig.xAxis.dateParseFormat = '%Y-%m-%d'
|
|
548
|
-
}
|
|
549
|
-
if (!newConfig.xAxis.dateDisplayFormat) {
|
|
550
|
-
newConfig.xAxis.dateDisplayFormat = '%Y-%m-%d'
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
518
|
}
|
|
554
519
|
|
|
555
520
|
if (newConfig.visualizationType === 'Area Chart' && newConfig.series) {
|
|
@@ -561,43 +526,14 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
561
526
|
newConfig.visualizationSubType = 'stacked'
|
|
562
527
|
}
|
|
563
528
|
|
|
564
|
-
if (newConfig.visualizationType === 'Horizon Chart' && newConfig.series) {
|
|
565
|
-
// Apply horizon defaults if not set
|
|
566
|
-
newConfig.horizon = {
|
|
567
|
-
numLayers: 4,
|
|
568
|
-
mode: 'offset', // Always offset for now, mirror hidden from UI
|
|
569
|
-
bandGap: 15,
|
|
570
|
-
bottomPadding: 15,
|
|
571
|
-
...newConfig.horizon
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// Set categorical as default xAxis type for horizon charts if not already set
|
|
575
|
-
if (!newConfig.xAxis.type) {
|
|
576
|
-
newConfig.xAxis.type = 'categorical'
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
529
|
if (newConfig.visualizationType === 'HeatMap') {
|
|
581
|
-
const
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const hasCustomColumnLabel = configuredColumnLabel && configuredColumnLabel !== series.dataKey
|
|
586
|
-
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] })
|
|
587
534
|
return acc
|
|
588
535
|
}, {})
|
|
589
536
|
|
|
590
|
-
newConfig.legend = {
|
|
591
|
-
...newConfig.legend,
|
|
592
|
-
position: newConfig.legend?.position || 'top',
|
|
593
|
-
style: newConfig.legend?.style || 'gradient',
|
|
594
|
-
subStyle:
|
|
595
|
-
newConfig.legend?.subStyle === 'smooth' ? 'linear blocks' : newConfig.legend?.subStyle || 'linear blocks'
|
|
596
|
-
}
|
|
597
|
-
newConfig.yAxis = {
|
|
598
|
-
...newConfig.yAxis,
|
|
599
|
-
type: 'categorical'
|
|
600
|
-
}
|
|
601
537
|
newConfig.runtime.seriesKeys = heatMapSeriesKeys
|
|
602
538
|
newConfig.runtime.seriesLabelsAll = heatMapSeriesKeys
|
|
603
539
|
newConfig.runtime.seriesLabels = heatMapSeriesLabels
|
|
@@ -605,19 +541,22 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
605
541
|
|
|
606
542
|
if (isHorizontalVariant) {
|
|
607
543
|
// For horizontal charts, axes are swapped, so processedYAxis goes to runtime.xAxis and vice versa
|
|
608
|
-
const horizontalXAxisSource = cloneDeep(
|
|
609
|
-
const horizontalYAxisSource = cloneDeep(
|
|
544
|
+
const horizontalXAxisSource = cloneDeep(newConfig.yAxis)
|
|
545
|
+
const horizontalYAxisSource = cloneDeep(newConfig.xAxis)
|
|
610
546
|
newConfig.runtime.xAxis = {
|
|
611
547
|
...horizontalXAxisSource,
|
|
612
548
|
label: runtimeXAxisLabel ?? horizontalXAxisSource?.label
|
|
613
549
|
}
|
|
614
550
|
newConfig.runtime.yAxis = {
|
|
615
551
|
...horizontalYAxisSource,
|
|
616
|
-
label: runtimeYAxisLabel ?? horizontalYAxisSource?.label
|
|
552
|
+
label: runtimeYAxisLabel ?? horizontalYAxisSource?.label,
|
|
553
|
+
rightLabel: runtimeRightYAxisLabel ?? newConfig.yAxis?.rightLabel
|
|
617
554
|
}
|
|
618
555
|
|
|
619
556
|
newConfig.runtime.horizontal = false
|
|
620
|
-
newConfig.
|
|
557
|
+
if (newConfig.visualizationType === 'Forest Plot') {
|
|
558
|
+
newConfig.orientation = 'horizontal'
|
|
559
|
+
}
|
|
621
560
|
// remove after COVE supports categorical axis on horizonatal bars
|
|
622
561
|
newConfig.yAxis.type = newConfig.yAxis.type === 'categorical' ? 'linear' : newConfig.yAxis.type
|
|
623
562
|
} else if (
|
|
@@ -625,12 +564,20 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
625
564
|
!convertLineToBarGraph
|
|
626
565
|
) {
|
|
627
566
|
newConfig.runtime.xAxis = { ...newConfig.xAxis, label: runtimeXAxisLabel ?? newConfig.xAxis.label }
|
|
628
|
-
newConfig.runtime.yAxis = {
|
|
567
|
+
newConfig.runtime.yAxis = {
|
|
568
|
+
...newConfig.yAxis,
|
|
569
|
+
label: runtimeYAxisLabel ?? newConfig.yAxis.label,
|
|
570
|
+
rightLabel: runtimeRightYAxisLabel ?? newConfig.yAxis.rightLabel
|
|
571
|
+
}
|
|
629
572
|
newConfig.runtime.horizontal = false
|
|
630
573
|
newConfig.orientation = 'vertical'
|
|
631
574
|
} else {
|
|
632
575
|
newConfig.runtime.xAxis = { ...newConfig.xAxis, label: runtimeXAxisLabel ?? newConfig.xAxis.label }
|
|
633
|
-
newConfig.runtime.yAxis = {
|
|
576
|
+
newConfig.runtime.yAxis = {
|
|
577
|
+
...newConfig.yAxis,
|
|
578
|
+
label: runtimeYAxisLabel ?? newConfig.yAxis.label,
|
|
579
|
+
rightLabel: runtimeRightYAxisLabel ?? newConfig.yAxis.rightLabel
|
|
580
|
+
}
|
|
634
581
|
newConfig.runtime.horizontal = false
|
|
635
582
|
}
|
|
636
583
|
|
|
@@ -765,6 +712,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
765
712
|
newConfig.dataMetadata = dataMetadata
|
|
766
713
|
|
|
767
714
|
if (newConfig.vegaConfig) {
|
|
715
|
+
const { extractCoveData, updateVegaData } = await import('@cdc/core/helpers/vegaRuntimeData')
|
|
768
716
|
newData = extractCoveData(updateVegaData(newConfig.vegaConfig, newData))
|
|
769
717
|
}
|
|
770
718
|
|
|
@@ -937,7 +885,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
937
885
|
}
|
|
938
886
|
|
|
939
887
|
const dataSource = (stateData && stateData.length ? stateData : config.data) || []
|
|
940
|
-
const { runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant } =
|
|
888
|
+
const { runtimeXAxisLabel, runtimeYAxisLabel, runtimeRightYAxisLabel, isHorizontalVariant } =
|
|
889
|
+
getProcessedAxisLabels(config, dataSource)
|
|
941
890
|
|
|
942
891
|
const runtimeClone = cloneDeep(config.runtime)
|
|
943
892
|
|
|
@@ -957,6 +906,11 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
957
906
|
shouldUpdateLabels = true
|
|
958
907
|
}
|
|
959
908
|
|
|
909
|
+
if (typeof runtimeRightYAxisLabel !== 'undefined' && runtimeClone.yAxis.rightLabel !== runtimeRightYAxisLabel) {
|
|
910
|
+
runtimeClone.yAxis = { ...runtimeClone.yAxis, rightLabel: runtimeRightYAxisLabel }
|
|
911
|
+
shouldUpdateLabels = true
|
|
912
|
+
}
|
|
913
|
+
|
|
960
914
|
if (shouldUpdateLabels) {
|
|
961
915
|
runtimeClone.uniqueId = Date.now()
|
|
962
916
|
const updatedConfig = { ...config, runtime: runtimeClone } as ChartConfig
|
|
@@ -1054,7 +1008,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1054
1008
|
) => {
|
|
1055
1009
|
if (num === '') return 'N/A'
|
|
1056
1010
|
// if num is NaN return num
|
|
1057
|
-
if (
|
|
1011
|
+
if (num === undefined || num === null) return num
|
|
1012
|
+
if (isNaN(num)) return num
|
|
1058
1013
|
// Check if the input number is negative
|
|
1059
1014
|
const isNegative = num < 0
|
|
1060
1015
|
|
|
@@ -1157,11 +1112,19 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1157
1112
|
}
|
|
1158
1113
|
|
|
1159
1114
|
if (!config.dataFormat) return num
|
|
1160
|
-
|
|
1115
|
+
let isBelowDataCutoff = false
|
|
1116
|
+
if (hasDataCutoff(config.dataCutoff)) {
|
|
1161
1117
|
let cutoff = numberFromString(config.dataCutoff)
|
|
1162
1118
|
|
|
1163
|
-
if (num < cutoff) {
|
|
1119
|
+
if (typeof cutoff === 'number' && num < cutoff) {
|
|
1164
1120
|
num = cutoff
|
|
1121
|
+
isBelowDataCutoff = true
|
|
1122
|
+
const cutoffDecimalPlaces = getDataCutoffDecimalPlaces(config.dataCutoff)
|
|
1123
|
+
stringFormattingOptions = {
|
|
1124
|
+
...stringFormattingOptions,
|
|
1125
|
+
minimumFractionDigits: cutoffDecimalPlaces,
|
|
1126
|
+
maximumFractionDigits: cutoffDecimalPlaces
|
|
1127
|
+
}
|
|
1165
1128
|
}
|
|
1166
1129
|
}
|
|
1167
1130
|
|
|
@@ -1225,6 +1188,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1225
1188
|
if (bottomSuffix && axis === 'bottom') {
|
|
1226
1189
|
result += bottomSuffix
|
|
1227
1190
|
}
|
|
1191
|
+
if (isBelowDataCutoff) {
|
|
1192
|
+
result = '<' + result
|
|
1193
|
+
}
|
|
1228
1194
|
if (isNegative) {
|
|
1229
1195
|
result = '-' + result
|
|
1230
1196
|
}
|
|
@@ -1250,19 +1216,57 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1250
1216
|
}
|
|
1251
1217
|
}
|
|
1252
1218
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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 => {
|
|
1256
1256
|
if (!Array.isArray(data)) return []
|
|
1257
1257
|
if (config.visualizationType === 'Forecasting') return data
|
|
1258
|
-
|
|
1259
|
-
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[]
|
|
1260
1263
|
const seriesKeys: string[] = get(config, 'series', []).map((s: any) => s.dataKey)
|
|
1261
|
-
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)
|
|
1262
1268
|
|
|
1263
|
-
|
|
1264
|
-
const excludedKey = config.xAxis.dataKey
|
|
1265
|
-
return config?.xAxis?.dataKey ? transform.cleanData(data, excludedKey, keysToClean) : data
|
|
1269
|
+
return stripTrailingPercentage ? restoreSuppressedBarValues(cleanedData, data) : cleanedData
|
|
1266
1270
|
}
|
|
1267
1271
|
|
|
1268
1272
|
const orderedTableData = useMemo(
|
|
@@ -1271,7 +1275,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1271
1275
|
)
|
|
1272
1276
|
|
|
1273
1277
|
const getTableRuntimeData = () => {
|
|
1274
|
-
if (visualizationType === 'Sankey') return config?.data
|
|
1278
|
+
if (visualizationType === 'Sankey') return config?.data
|
|
1279
|
+
if (['Network', 'Dendrogram'].includes(visualizationType)) return orderedTableData
|
|
1275
1280
|
const data = orderedTableData
|
|
1276
1281
|
if (config.visualizationType === 'Pie' && !config.dataFormat?.showPiePercent) {
|
|
1277
1282
|
return getPiePercent(data, config?.yAxis?.dataKey)
|
|
@@ -1299,20 +1304,31 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1299
1304
|
|
|
1300
1305
|
// Transform and clean data for chart rendering
|
|
1301
1306
|
const transformedData = sortByCategoryOrder(
|
|
1302
|
-
getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean }),
|
|
1307
|
+
getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean: cleanChartData }),
|
|
1303
1308
|
config
|
|
1304
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: [] }
|
|
1305
1318
|
const configYAxisDomainData = (config as ChartConfig).yAxisDomainData
|
|
1306
1319
|
const yAxisDomainData = useMemo(() => {
|
|
1307
1320
|
if (Array.isArray(configYAxisDomainData) && configYAxisDomainData.length > 0) {
|
|
1308
|
-
return
|
|
1321
|
+
return cleanChartData(getExcludedData(config, configYAxisDomainData))
|
|
1309
1322
|
}
|
|
1310
1323
|
|
|
1311
|
-
return
|
|
1324
|
+
return cleanChartData(excludedData)
|
|
1312
1325
|
}, [config, configYAxisDomainData, excludedData])
|
|
1313
1326
|
|
|
1314
1327
|
// Filter annotations to only those visible in current data view
|
|
1315
1328
|
const visibleAnnotations = getVisibleAnnotations(config.annotations, transformedData, config.xAxis?.dataKey)
|
|
1329
|
+
const showAnnotationDropdown =
|
|
1330
|
+
config.general.showAnnotationDropdown ||
|
|
1331
|
+
(isMobileAnnotationViewport(currentViewport) && config.general.mobileAnnotationDisplay !== 'text')
|
|
1316
1332
|
const isHeatMap = config.visualizationType === 'HeatMap'
|
|
1317
1333
|
const isTp5Treatment = ENABLE_CHART_MAP_TP5_TREATMENT && config.visual?.tp5Treatment
|
|
1318
1334
|
const visualSettingClasses = new Set([
|
|
@@ -1399,12 +1415,67 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1399
1415
|
!config.smallMultiples?.mode &&
|
|
1400
1416
|
!config.hideYAxisLabel &&
|
|
1401
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
|
|
1402
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
|
+
}
|
|
1403
1454
|
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
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
|
+
}
|
|
1464
|
+
|
|
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
|
+
)}
|
|
1408
1479
|
</div>
|
|
1409
1480
|
) : null
|
|
1410
1481
|
|
|
@@ -1413,7 +1484,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1413
1484
|
wrapperStyle: React.CSSProperties = { width: '100%' }
|
|
1414
1485
|
) => (
|
|
1415
1486
|
<>
|
|
1416
|
-
{
|
|
1487
|
+
{renderTopYAxisTitles()}
|
|
1417
1488
|
<div ref={parentRef} style={wrapperStyle}>
|
|
1418
1489
|
<ParentSize>
|
|
1419
1490
|
{parent => <LinearChart ref={svgRef} parentWidth={getParentWidth(parent)} parentHeight={parent.height} />}
|
|
@@ -1430,15 +1501,50 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1430
1501
|
)
|
|
1431
1502
|
body = (
|
|
1432
1503
|
<>
|
|
1433
|
-
{
|
|
1434
|
-
|
|
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 && (
|
|
1435
1538
|
<VisualizationContent
|
|
1436
1539
|
innerClassName={`type-${makeClassName(config.visualizationType)}`}
|
|
1437
1540
|
innerProps={{ tabIndex: 0 }}
|
|
1438
1541
|
bodyClassName={bodyClasses.join(' ')}
|
|
1439
1542
|
bodyWrapClassName={bodyWrapClasses}
|
|
1440
1543
|
filters={
|
|
1441
|
-
hasVisibleVizFilters(config.filters) &&
|
|
1544
|
+
hasVisibleVizFilters(config.filters) &&
|
|
1545
|
+
!externalFilters &&
|
|
1546
|
+
config.visualizationType !== 'Spark Line' &&
|
|
1547
|
+
!(config.visualizationType === 'Bar' && config.visualizationSubType === 'racing') ? (
|
|
1442
1548
|
<Filters
|
|
1443
1549
|
config={config}
|
|
1444
1550
|
setFilters={setFilters}
|
|
@@ -1453,19 +1559,26 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1453
1559
|
<div className={`cove-prose ${getChartSubTextClasses().join(' ')}`}>{parse(processedDescription)}</div>
|
|
1454
1560
|
) : null
|
|
1455
1561
|
}
|
|
1562
|
+
bodyAnnotations={
|
|
1563
|
+
visibleAnnotations.length > 0 &&
|
|
1564
|
+
showAnnotationDropdown &&
|
|
1565
|
+
!(config.visualizationType === 'Bar' && config.visualizationSubType === 'racing') ? (
|
|
1566
|
+
<Annotation.Dropdown />
|
|
1567
|
+
) : null
|
|
1568
|
+
}
|
|
1456
1569
|
bodyFooter={
|
|
1457
1570
|
<>
|
|
1458
1571
|
{isDashboard && config.table && config.table.show && config.table.showDataTableLink
|
|
1459
1572
|
? tableLink
|
|
1460
1573
|
: link && link}
|
|
1461
|
-
{
|
|
1574
|
+
{renderDataTableArea
|
|
1462
1575
|
? (() => {
|
|
1463
1576
|
let dataTableConfig = pivotDynamicSeries(config)
|
|
1464
1577
|
let dataTableColumns = config.columns
|
|
1465
1578
|
let dataTableRuntimeData = getTableRuntimeData()
|
|
1466
1579
|
let dataTableRawData =
|
|
1467
1580
|
config.visualizationType === 'Sankey'
|
|
1468
|
-
? config?.data
|
|
1581
|
+
? config?.data
|
|
1469
1582
|
: config.table.customTableConfig
|
|
1470
1583
|
? filterVizData(config.filters, config.data)
|
|
1471
1584
|
: config.data
|
|
@@ -1498,6 +1611,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1498
1611
|
tabbingId={handleChartTabbing(config, legendId)}
|
|
1499
1612
|
colorScale={colorScale}
|
|
1500
1613
|
imageRef={imageId}
|
|
1614
|
+
showTable={showDataTable}
|
|
1501
1615
|
showDownloadImgButton={config.table.showDownloadImgButton}
|
|
1502
1616
|
showDownloadPdfButton={config.table.showDownloadPdfButton}
|
|
1503
1617
|
includeContextInDownload={config.table?.includeContextInDownload}
|
|
@@ -1532,7 +1646,6 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1532
1646
|
</MediaControls.Section>
|
|
1533
1647
|
</div>
|
|
1534
1648
|
)}
|
|
1535
|
-
{visibleAnnotations.length > 0 && <Annotation.Dropdown />}
|
|
1536
1649
|
{processedLegacyFootnotes && (
|
|
1537
1650
|
<section className='footnotes cove-prose pt-2 mt-4'>{parse(processedLegacyFootnotes)}</section>
|
|
1538
1651
|
)}
|
|
@@ -1571,11 +1684,13 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1571
1684
|
<LegendWrapper>
|
|
1572
1685
|
<div
|
|
1573
1686
|
className={
|
|
1574
|
-
legend.hide || isLegendWrapViewport(currentViewport)
|
|
1687
|
+
legend.hide || barRaceEligibility.eligible || isLegendWrapViewport(currentViewport)
|
|
1575
1688
|
? 'w-100'
|
|
1576
1689
|
: legend.position === 'bottom' ||
|
|
1577
1690
|
legend.position === 'top' ||
|
|
1578
1691
|
visualizationType === 'Sankey' ||
|
|
1692
|
+
visualizationType === 'Network' ||
|
|
1693
|
+
visualizationType === 'Dendrogram' ||
|
|
1579
1694
|
visualizationType === 'Spark Line'
|
|
1580
1695
|
? 'w-100'
|
|
1581
1696
|
: 'w-75'
|
|
@@ -1584,16 +1699,38 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1584
1699
|
{/* Check if there is data to display */}
|
|
1585
1700
|
{(!filteredData || filteredData.length === 0) && (
|
|
1586
1701
|
<div className='no-data-message' style={{ padding: '2rem', textAlign: 'center', color: '#666' }}>
|
|
1587
|
-
{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')}
|
|
1588
1708
|
</div>
|
|
1589
1709
|
)}
|
|
1590
1710
|
|
|
1591
1711
|
{/* All charts with LinearChart */}
|
|
1592
1712
|
{filteredData &&
|
|
1593
1713
|
filteredData.length > 0 &&
|
|
1594
|
-
!['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') &&
|
|
1595
1718
|
renderLinearChartWithParentSize()}
|
|
1596
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
|
+
|
|
1597
1734
|
{filteredData && filteredData.length > 0 && config.visualizationType === 'Pie' && (
|
|
1598
1735
|
<ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
|
|
1599
1736
|
{parent => (
|
|
@@ -1633,6 +1770,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1633
1770
|
{filteredData &&
|
|
1634
1771
|
filteredData.length > 0 &&
|
|
1635
1772
|
config.visualizationType === 'Line' &&
|
|
1773
|
+
config.visualizationSubType !== 'racing' &&
|
|
1636
1774
|
(convertLineToBarGraph
|
|
1637
1775
|
? renderLinearChartWithParentSize()
|
|
1638
1776
|
: renderLinearChartWithParentSize(parent => {
|
|
@@ -1671,15 +1809,66 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1671
1809
|
)}
|
|
1672
1810
|
{/* Sankey */}
|
|
1673
1811
|
{config.visualizationType === 'Sankey' && (
|
|
1674
|
-
<ParentSize
|
|
1812
|
+
<ParentSize>
|
|
1675
1813
|
{parent => <SankeyChart runtime={config.runtime} width={parent.width} height={parent.height} />}
|
|
1676
1814
|
</ParentSize>
|
|
1677
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
|
+
)}
|
|
1678
1864
|
</div>
|
|
1679
1865
|
{/* Legend */}
|
|
1680
1866
|
{!config.legend.hide &&
|
|
1867
|
+
!(config.visualizationType === 'Bar' && config.visualizationSubType === 'racing') &&
|
|
1681
1868
|
config.visualizationType !== 'Spark Line' &&
|
|
1682
1869
|
config.visualizationType !== 'Sankey' &&
|
|
1870
|
+
config.visualizationType !== 'Network' &&
|
|
1871
|
+
config.visualizationType !== 'Dendrogram' &&
|
|
1683
1872
|
config.visualizationType !== 'HeatMap' &&
|
|
1684
1873
|
!(config.visualizationType === 'Warming Stripes' && config.legend?.style === 'gradient') &&
|
|
1685
1874
|
!(config.visualizationType === 'Warming Stripes' && config.smallMultiples?.mode) && (
|
|
@@ -1718,7 +1907,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1718
1907
|
...state,
|
|
1719
1908
|
capitalize,
|
|
1720
1909
|
convertLineToBarGraph,
|
|
1721
|
-
clean,
|
|
1910
|
+
clean: cleanChartData,
|
|
1722
1911
|
colorPalettes,
|
|
1723
1912
|
dashboardConfig,
|
|
1724
1913
|
debugSvg: isDebug,
|
|
@@ -1742,6 +1931,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1742
1931
|
legendId,
|
|
1743
1932
|
legendRef,
|
|
1744
1933
|
lineOptions,
|
|
1934
|
+
raceTiming,
|
|
1745
1935
|
missingRequiredSections,
|
|
1746
1936
|
outerContainerRef,
|
|
1747
1937
|
parentRef,
|
|
@@ -1749,6 +1939,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1749
1939
|
rawData: stateData ?? {},
|
|
1750
1940
|
setConfig,
|
|
1751
1941
|
setEditing,
|
|
1942
|
+
setRaceTiming,
|
|
1752
1943
|
setParentConfig,
|
|
1753
1944
|
setSharedFilter,
|
|
1754
1945
|
setSharedFilterValue,
|
|
@@ -1771,7 +1962,13 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1771
1962
|
currentViewport={currentViewport}
|
|
1772
1963
|
ref={outerContainerRef}
|
|
1773
1964
|
imageId={imageId}
|
|
1774
|
-
editorPanel={
|
|
1965
|
+
editorPanel={
|
|
1966
|
+
isEditor && !isLoading ? (
|
|
1967
|
+
<Suspense fallback={null}>
|
|
1968
|
+
<EditorPanel datasets={datasets} />
|
|
1969
|
+
</Suspense>
|
|
1970
|
+
) : null
|
|
1971
|
+
}
|
|
1775
1972
|
>
|
|
1776
1973
|
{body}
|
|
1777
1974
|
</VisualizationContainer>
|