@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.
Files changed (255) hide show
  1. package/CONFIG.md +502 -368
  2. package/dist/cdcchart-CYc-Kayd.es.js +22488 -0
  3. package/dist/cdcchart-DCKn8NmI.es.js +20695 -0
  4. package/dist/cdcchart-Dyui9nk_.es.js +45828 -0
  5. package/dist/cdcchart.js +4 -97406
  6. package/examples/__data__/vega-remote-data.json +6 -0
  7. package/examples/dendrogram.json +18 -0
  8. package/examples/feature/bar/bar-chart-race-annual-change.json +68 -0
  9. package/examples/feature/heatmap/average-age-categorical.json +2 -2
  10. package/examples/feature/heatmap/calendar-heatmap.json +2 -2
  11. package/examples/feature/heatmap/cell-values-bottom-axis.json +2 -2
  12. package/examples/feature/heatmap/sparse-aggregation.json +2 -2
  13. package/examples/feature/line/line-chart-race.json +50 -0
  14. package/examples/feature/sankey/sankey-example-data.json +40 -1441
  15. package/examples/feature/sankey/sankey_chart_data.csv +9 -20
  16. package/examples/network.json +31 -0
  17. package/examples/private/box-plot.json +573 -0
  18. package/examples/private/custom-color-issue.json +625 -0
  19. package/examples/private/custom-colors.json +522 -0
  20. package/examples/private/cutoff.json +10750 -0
  21. package/examples/private/overlay-legend.json +485 -0
  22. package/examples/private/pie-test.json +469 -0
  23. package/examples/vega-remote.json +22 -0
  24. package/package.json +11 -5
  25. package/src/CdcChartComponent.tsx +405 -208
  26. package/src/_stories/Chart.BarRace.stories.tsx +443 -0
  27. package/src/_stories/Chart.BoxPlotNumericSort.stories.tsx +189 -0
  28. package/src/_stories/Chart.BoxPlotStatsParity.stories.tsx +207 -0
  29. package/src/_stories/Chart.ColorDistribution.stories.tsx +299 -0
  30. package/src/_stories/Chart.DataCutoff.stories.tsx +147 -0
  31. package/src/_stories/Chart.DataTableDownloadFilename.stories.tsx +60 -0
  32. package/src/_stories/Chart.Dendrogram.stories.tsx +373 -0
  33. package/src/_stories/Chart.Forecast.CILegend.stories.tsx +236 -0
  34. package/src/_stories/Chart.HeatMap.stories.tsx +170 -4
  35. package/src/_stories/Chart.Legend.AxisAlign.stories.tsx +119 -0
  36. package/src/_stories/Chart.LineRace.stories.tsx +149 -0
  37. package/src/_stories/Chart.Network.stories.tsx +432 -0
  38. package/src/_stories/Chart.PaletteConversion.Editor.stories.tsx +208 -0
  39. package/src/_stories/Chart.Patterns.smoke.stories.tsx +31 -0
  40. package/src/_stories/Chart.PercentageValue.stories.tsx +311 -0
  41. package/src/_stories/Chart.PieLabels.stories.tsx +4 -2
  42. package/src/_stories/Chart.RankByValueMutation.stories.tsx +206 -0
  43. package/src/_stories/Chart.RequiredFields.stories.tsx +681 -0
  44. package/src/_stories/Chart.Sankey.stories.tsx +417 -0
  45. package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +47 -0
  46. package/src/_stories/Chart.SmallMultiplesNumericTileOrder.stories.tsx +137 -0
  47. package/src/_stories/Chart.TooltipSeriesLabels.stories.tsx +43 -0
  48. package/src/_stories/Chart.VegaRemoteData.stories.tsx +31 -0
  49. package/src/_stories/ChartAnnotation.Editor.stories.tsx +78 -0
  50. package/src/_stories/ChartAxisTitles.smoke.stories.tsx +48 -0
  51. package/src/_stories/ChartBar.Editor.stories.tsx +499 -57
  52. package/src/_stories/ChartBrush.Editor.stories.tsx +46 -67
  53. package/src/_stories/ChartBrush.Regression.stories.tsx +135 -0
  54. package/src/_stories/ChartEditor.Editor.stories.tsx +33 -3
  55. package/src/_stories/ChartLine.EffectCompatibility.stories.tsx +120 -0
  56. package/src/_stories/_mock/annotation_event-line_mock.json +2 -2
  57. package/src/_stories/_mock/editor-test-configs.json +20 -10
  58. package/src/_stories/_mock/editor-test-datasets.json +38 -42
  59. package/src/_stories/_mock/heatmap-average-age-categorical.json +2 -2
  60. package/src/_stories/_mock/heatmap.json +2 -2
  61. package/src/_stories/_mock/legend-axis-align-stacked-bar.json +125 -0
  62. package/src/_stories/_mock/sankey_mock.json +23 -27
  63. package/src/components/Annotations/components/AnnotationDraggable.styles.css +52 -0
  64. package/src/components/Annotations/components/AnnotationDraggable.tsx +639 -139
  65. package/src/components/Annotations/components/AnnotationDropdown.styles.css +44 -4
  66. package/src/components/Annotations/components/AnnotationDropdown.tsx +10 -32
  67. package/src/components/Annotations/helpers/resolveAnnotationLayout.ts +212 -0
  68. package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +612 -19
  69. package/src/components/Annotations/tests/AnnotationDropdown.test.tsx +50 -0
  70. package/src/components/Annotations/tests/resolveAnnotationLayout.test.ts +203 -0
  71. package/src/components/Axis/BottomAxis.tsx +1 -1
  72. package/src/components/Axis/Categorical.Axis.tsx +3 -3
  73. package/src/components/Axis/RightAxis.tsx +21 -13
  74. package/src/components/BarChart/components/BarChart.Horizontal.tsx +38 -6
  75. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -3
  76. package/src/components/BarChart/components/BarChart.StackedVertical.test.ts +28 -0
  77. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +29 -25
  78. package/src/components/BarChart/components/BarChart.Vertical.tsx +37 -26
  79. package/src/components/BarChart/components/PortionPatternOverlay.tsx +61 -0
  80. package/src/components/BarChart/components/context.tsx +6 -0
  81. package/src/components/BarChart/helpers/getPatternUrl.ts +2 -5
  82. package/src/components/BarChart/helpers/index.ts +8 -0
  83. package/src/components/BarChart/helpers/portionPattern.ts +243 -0
  84. package/src/components/BarChart/helpers/tests/addMinimumBarHeights.test.ts +42 -0
  85. package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +13 -0
  86. package/src/components/BarChart/helpers/tests/portionPattern.test.ts +280 -0
  87. package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +104 -0
  88. package/src/components/BarChart/helpers/useBarChart.ts +60 -59
  89. package/src/components/BarChartRace/BarChartRace.test.tsx +199 -0
  90. package/src/components/BarChartRace/BarChartRace.tsx +230 -0
  91. package/src/components/BarChartRace/BarChartRaceFallback.test.tsx +61 -0
  92. package/src/components/BarChartRace/BarChartRaceFallback.tsx +48 -0
  93. package/src/components/BarChartRace/bar-chart-race.css +160 -0
  94. package/src/components/BarChartRace/helpers.test.ts +114 -0
  95. package/src/components/BarChartRace/helpers.ts +185 -0
  96. package/src/components/BarChartRace/index.ts +3 -0
  97. package/src/components/BoxPlot/helpers/index.ts +11 -82
  98. package/src/components/Brush/BrushSelector.tsx +9 -23
  99. package/src/components/Brush/MiniChartPreview.tsx +33 -1
  100. package/src/components/Dendrogram/components/Dendrogram.tsx +374 -0
  101. package/src/components/Dendrogram/dendrogram.css +113 -0
  102. package/src/components/Dendrogram/helpers/layoutDendrogram.test.ts +95 -0
  103. package/src/components/Dendrogram/helpers/layoutDendrogram.ts +56 -0
  104. package/src/components/Dendrogram/helpers/prepareDendrogramData.test.ts +135 -0
  105. package/src/components/Dendrogram/helpers/prepareDendrogramData.ts +166 -0
  106. package/src/components/Dendrogram/helpers/resolveDendrogramLabels.test.ts +196 -0
  107. package/src/components/Dendrogram/helpers/resolveDendrogramLabels.ts +418 -0
  108. package/src/components/Dendrogram/index.tsx +1 -0
  109. package/src/components/Dendrogram/types/index.ts +66 -0
  110. package/src/components/EditorPanel/EditorPanel.tsx +1005 -760
  111. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +50 -5
  112. package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.css +10 -0
  113. package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.tsx +211 -0
  114. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +19 -8
  115. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +88 -54
  116. package/src/components/EditorPanel/components/Panels/Panel.Network.css +9 -0
  117. package/src/components/EditorPanel/components/Panels/Panel.Network.tsx +202 -0
  118. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +233 -33
  119. package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +6 -1
  120. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +89 -105
  121. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -2
  122. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +1 -1
  123. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +8 -66
  124. package/src/components/EditorPanel/components/Panels/RacingControls.test.tsx +131 -0
  125. package/src/components/EditorPanel/components/Panels/RacingControls.tsx +185 -0
  126. package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +2 -2
  127. package/src/components/EditorPanel/components/Panels/index.tsx +4 -0
  128. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +20 -7
  129. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +25 -2
  130. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +2 -1
  131. package/src/components/EditorPanel/useEditorPermissions.test.tsx +62 -0
  132. package/src/components/EditorPanel/useEditorPermissions.ts +70 -24
  133. package/src/components/Forecasting/Forecasting.tsx +15 -6
  134. package/src/components/HeatMap/components/HeatMap.tsx +511 -178
  135. package/src/components/HeatMap/heatmap.constants.ts +7 -3
  136. package/src/components/HeatMap/heatmap.css +101 -2
  137. package/src/components/HeatMap/helpers/layout.ts +80 -0
  138. package/src/components/HeatMap/tests/HeatMap.test.tsx +339 -16
  139. package/src/components/HeatMap/tests/helpers.test.ts +79 -0
  140. package/src/components/Legend/Legend.Component.tsx +110 -90
  141. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +7 -0
  142. package/src/components/Legend/LegendGroup/LegendGroup.tsx +47 -34
  143. package/src/components/Legend/helpers/createFormatLabels.test.ts +226 -0
  144. package/src/components/Legend/helpers/createFormatLabels.tsx +45 -51
  145. package/src/components/LineChart/components/LineChart.Circle.tsx +3 -3
  146. package/src/components/LineChart/helpers.test.ts +126 -38
  147. package/src/components/LineChart/helpers.ts +9 -4
  148. package/src/components/LineChart/index.tsx +69 -24
  149. package/src/components/LineChart/tests/LineChart.test.tsx +630 -0
  150. package/src/components/LineChartRace/LineChartRace.test.tsx +275 -0
  151. package/src/components/LineChartRace/LineChartRace.tsx +134 -0
  152. package/src/components/LineChartRace/LineChartRaceFallback.tsx +28 -0
  153. package/src/components/LineChartRace/helpers.test.ts +88 -0
  154. package/src/components/LineChartRace/helpers.ts +157 -0
  155. package/src/components/LineChartRace/index.ts +3 -0
  156. package/src/components/LineChartRace/line-chart-race.css +16 -0
  157. package/src/components/LinearChart/ValueAxisAnchors.tsx +58 -0
  158. package/src/components/LinearChart/VisualizationRenderer.tsx +18 -23
  159. package/src/components/LinearChart/tests/LinearChart.test.tsx +412 -13
  160. package/src/components/LinearChart/tests/VisualizationRenderer.test.tsx +141 -0
  161. package/src/components/LinearChart/tests/mockConfigContext.ts +2 -1
  162. package/src/components/LinearChart.tsx +81 -51
  163. package/src/components/Network/components/Network.tsx +372 -0
  164. package/src/components/Network/helpers/layoutNetwork.test.ts +68 -0
  165. package/src/components/Network/helpers/layoutNetwork.ts +87 -0
  166. package/src/components/Network/helpers/prepareNetworkData.test.ts +116 -0
  167. package/src/components/Network/helpers/prepareNetworkData.ts +101 -0
  168. package/src/components/Network/helpers/resolveNetworkLabels.test.ts +112 -0
  169. package/src/components/Network/helpers/resolveNetworkLabels.ts +206 -0
  170. package/src/components/Network/index.tsx +3 -0
  171. package/src/components/Network/network.css +107 -0
  172. package/src/components/Network/types/index.ts +56 -0
  173. package/src/components/PairedBarChart.jsx +5 -10
  174. package/src/components/PairedBarChart.test.tsx +71 -0
  175. package/src/components/PieChart/PieChart.tsx +9 -8
  176. package/src/components/RacingChartRenderer.tsx +44 -0
  177. package/src/components/Regions/components/Regions.test.tsx +179 -0
  178. package/src/components/Regions/components/Regions.tsx +104 -69
  179. package/src/components/Regions/index.tsx +2 -3
  180. package/src/components/Sankey/components/Sankey.tsx +264 -437
  181. package/src/components/Sankey/helpers/getSankeyTooltip.ts +12 -0
  182. package/src/components/Sankey/helpers/prepareSankeyData.test.ts +71 -0
  183. package/src/components/Sankey/helpers/prepareSankeyData.ts +90 -0
  184. package/src/components/Sankey/sankey.scss +57 -116
  185. package/src/components/Sankey/types/index.ts +41 -4
  186. package/src/components/ScatterPlot/ScatterPlot.jsx +28 -4
  187. package/src/components/ScatterPlot/ScatterPlot.test.tsx +60 -0
  188. package/src/components/SmallMultiples/SmallMultiples.tsx +12 -12
  189. package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +7 -5
  190. package/src/components/raceTiming.ts +18 -0
  191. package/src/data/initial-state.js +77 -31
  192. package/src/helpers/applyChartTypeContract.ts +71 -0
  193. package/src/helpers/axisLabelFontSize.ts +2 -2
  194. package/src/helpers/boxPlotStats.ts +60 -0
  195. package/src/helpers/bumpChartTooltip.ts +2 -1
  196. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.test.ts +37 -0
  197. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +3 -2
  198. package/src/helpers/calculateRightYAxisWidth.ts +60 -0
  199. package/src/helpers/colorAssignmentHelpers.ts +2 -2
  200. package/src/helpers/createNewChartConfig.ts +56 -0
  201. package/src/helpers/dataDrivenYAxisCategories.ts +18 -0
  202. package/src/helpers/forecastLegend.ts +88 -0
  203. package/src/helpers/getBoxPlotConfig.ts +18 -37
  204. package/src/helpers/getChartTypeDefaultPalette.ts +29 -0
  205. package/src/helpers/getColorCodeCategoryColorMap.ts +26 -0
  206. package/src/helpers/getColorScale.ts +31 -22
  207. package/src/helpers/getDynamicYAxisCategories.ts +69 -0
  208. package/src/helpers/getMinMax.ts +9 -7
  209. package/src/helpers/getNewRuntime.ts +5 -2
  210. package/src/helpers/getStackedSeriesMax.ts +51 -0
  211. package/src/helpers/getV21ChartDistributionColors.ts +57 -0
  212. package/src/helpers/handleRankByValue.ts +15 -15
  213. package/src/helpers/seriesColumnSettings.ts +10 -0
  214. package/src/helpers/sizeHelpers.ts +4 -3
  215. package/src/helpers/smallMultiplesHelpers.ts +10 -13
  216. package/src/helpers/tests/applyChartTypeContract.test.ts +37 -0
  217. package/src/helpers/tests/boxPlotStats.test.ts +31 -0
  218. package/src/helpers/tests/bumpChartTooltip.test.ts +29 -0
  219. package/src/helpers/tests/calculateRightYAxisWidth.test.ts +105 -0
  220. package/src/helpers/tests/countNumOfTicks.test.ts +11 -0
  221. package/src/helpers/tests/createNewChartConfig.test.ts +182 -0
  222. package/src/helpers/tests/getBoxPlotConfig.test.ts +70 -0
  223. package/src/helpers/tests/getColorCodeCategoryColorMap.test.ts +113 -0
  224. package/src/helpers/tests/getColorScale.test.ts +226 -1
  225. package/src/helpers/tests/getDynamicYAxisCategories.test.ts +61 -0
  226. package/src/helpers/tests/getMinMax.test.ts +41 -0
  227. package/src/helpers/tests/getStackedSeriesMax.test.ts +26 -0
  228. package/src/helpers/tests/handleRankByValue.test.ts +55 -37
  229. package/src/helpers/tests/seriesColumnSettings.test.ts +23 -0
  230. package/src/helpers/tests/smallMultiplesHelpers.test.ts +37 -1
  231. package/src/helpers/tests/tooltipHelpers.test.ts +80 -0
  232. package/src/helpers/tooltipHelpers.ts +16 -0
  233. package/src/hooks/tests/useTooltip.test.tsx +353 -1
  234. package/src/hooks/usePrefersReducedMotion.ts +22 -0
  235. package/src/hooks/useReduceData.ts +4 -36
  236. package/src/hooks/useScales.ts +15 -0
  237. package/src/hooks/useTooltip.tsx +95 -64
  238. package/src/scss/main.scss +74 -2
  239. package/src/store/chart.reducer.ts +4 -3
  240. package/src/test/CdcChart.dataTable.test.tsx +490 -16
  241. package/src/test/CdcChart.dendrogram.test.tsx +277 -0
  242. package/src/test/CdcChart.finalization.test.tsx +143 -0
  243. package/src/test/CdcChart.network.test.tsx +271 -0
  244. package/src/types/ChartConfig.ts +91 -54
  245. package/src/types/ChartContext.ts +5 -0
  246. package/src/types/Horizon.ts +1 -1
  247. package/LICENSE +0 -201
  248. package/dist/cdcchart-BWVIUcAF.es.js +0 -4802
  249. package/src/_stories/Chart.Sankey.smoke.stories.tsx +0 -62
  250. package/src/components/Sankey/components/ColumnList.tsx +0 -19
  251. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +0 -34
  252. package/src/components/Sankey/useSankeyAlert.tsx +0 -60
  253. package/src/data/legacy-defaults.ts +0 -18
  254. package/src/helpers/ensureSpecialChartAxisTypes.ts +0 -11
  255. package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +0 -30
@@ -1,4 +1,15 @@
1
- import React, { useState, useEffect, useCallback, useRef, useId, useContext, useReducer, useMemo } from '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 Confirm from '@cdc/core/components/elements/Confirm'
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 { backfillDefaults } from '@cdc/core/helpers/backfillDefaults'
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 { missingRequiredSections } from '@cdc/core/helpers/missingRequiredSections'
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
- dispatch({ type: 'SET_CONFIG', payload: newConfig })
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(newConfig)
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 chartDataTableIsRendered =
252
- Boolean(config.table?.show) &&
287
+ const chartSupportsDataTable =
253
288
  config.visualizationType !== 'Spark Line' &&
254
- ((Boolean(config.xAxis?.dataKey) && config.visualizationType !== 'Sankey') || config.visualizationType === 'Sankey')
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
- // Create defaults without version to avoid overriding legacy configs
295
- const defaultsWithoutPalette = { ...defaults }
296
-
297
- // Only remove palette defaults for legacy (v1) configs
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 ?? (targetConfig.yAxis as any)?.yAxis?.label ?? targetConfig.yAxis?.label
385
+ ? processedYAxis ?? targetConfig.yAxis?.label
407
386
  : processedXAxis ?? targetConfig.xAxis?.label
408
387
 
409
388
  const runtimeYAxisLabel = isHorizontalVariant
410
- ? processedXAxis ?? (targetConfig.xAxis as any)?.xAxis?.label ?? targetConfig.xAxis?.label
389
+ ? processedXAxis ?? targetConfig.xAxis?.label
411
390
  : processedYAxis ?? targetConfig.yAxis?.label
391
+ const runtimeRightYAxisLabel = processedRightYAxis ?? targetConfig.yAxis?.rightLabel
412
392
 
413
- return { processedXAxis, processedYAxis, runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant }
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 { processedXAxis, processedYAxis, runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant } =
429
- getProcessedAxisLabels(newConfig, data || [])
430
-
431
- // Backfill missing properties from defaults, respecting legacy values
432
- backfillDefaults(newConfig, defaults, LEGACY_CHART_DEFAULTS)
433
- if (shouldUseHeatMapSideTitlePlacement) {
434
- newConfig.yAxis.titlePlacement = 'side'
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, dataOverride || stateData)
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
- if (newConfig.filters) {
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
- newConfig.runtime.seriesLabels[series.dataKey] = series.name || series.label || series.dataKey
516
- newConfig.runtime.seriesLabelsAll.push(series.name || series.dataKey)
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, stateData)
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 heatMapSeriesKeys = newConfig.series.map(series => series.dataKey)
582
- const heatMapSeriesLabels = newConfig.series.reduce<Record<string, string>>((acc, series) => {
583
- const heatMapColumnConfig = findColumnConfigByName(newConfig.columns || {}, series.dataKey)?.columnConfig
584
- const configuredColumnLabel = heatMapColumnConfig?.label
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((newConfig.yAxis as any)?.yAxis || newConfig.yAxis)
609
- const horizontalYAxisSource = cloneDeep((newConfig.xAxis as any)?.xAxis || newConfig.xAxis)
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.orientation = 'horizontal'
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 = { ...newConfig.yAxis, label: runtimeYAxisLabel ?? newConfig.yAxis.label }
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 = { ...newConfig.yAxis, label: runtimeYAxisLabel ?? newConfig.yAxis.label }
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 } = getProcessedAxisLabels(config, dataSource)
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 (isNaN(num) || !num) return num
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
- if (config.dataCutoff) {
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
- // TODO: should be part of the DataTransform class.
1254
- const clean = data => {
1255
- // cleaning is deleting data we need in forecasting charts.
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
- // specify keys that needs to be cleaned to render chart and skip rest
1259
- const CIkeys: string[] = Object.values(get(config, 'confidenceKeys', {})) as string[]
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[] = [...(seriesKeys ?? []), ...(CIkeys ?? [])]
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
- // key that does not need to be cleaned
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?.[0]?.tableData
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 clean(getExcludedData(config, configYAxisDomainData))
1321
+ return cleanChartData(getExcludedData(config, configYAxisDomainData))
1309
1322
  }
1310
1323
 
1311
- return clean(excludedData)
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
- const renderTopYAxisTitle = () =>
1405
- showTopYAxisTitle ? (
1406
- <div className='y-axis-top-title' style={{ fontSize: `${topYAxisTitleFontSize}px` }}>
1407
- {config.runtime.yAxis.label}
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
- {renderTopYAxisTitle()}
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
- {config.newViz && <Confirm updateConfig={updateConfig} config={config} />}
1434
- {!missingRequiredSections(config) && !config.newViz && (
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) && !externalFilters && config.visualizationType !== 'Spark Line' ? (
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
- {chartDataTableIsRendered
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?.[0]?.tableData
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 || 'No Data Available'}
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(config.visualizationType) &&
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 aria-hidden='true'>
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={!isLoading ? <EditorPanel datasets={datasets} /> : null}
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>