@cdc/chart 4.26.6 → 4.26.10

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