@cdc/chart 4.25.11 → 4.26.2

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 (181) hide show
  1. package/CLAUDE.local.md +79 -0
  2. package/dist/{cdcchart-dgT_1dIT.es.js → cdcchart-DQ00cQCm.es.js} +1 -20
  3. package/dist/cdcchart.js +51401 -50814
  4. package/examples/default.json +378 -0
  5. package/examples/feature/__data__/horizon-chart-data.json +373 -0
  6. package/examples/feature/annotations/index.json +3 -6
  7. package/examples/feature/horizon/horizon-chart.json +395 -0
  8. package/examples/feature/pie/planet-pie-example-config.json +48 -2
  9. package/examples/line-chart-states.json +1085 -0
  10. package/examples/private/123.json +694 -0
  11. package/examples/private/DEV-12100.json +1303 -0
  12. package/examples/private/anchor-issue.json +4094 -0
  13. package/examples/private/backwards-slider.json +10430 -0
  14. package/examples/private/cat-y.json +1235 -0
  15. package/examples/private/data-points.json +228 -0
  16. package/examples/private/georgia.csv +160 -0
  17. package/examples/private/height.json +3915 -0
  18. package/examples/private/links.json +569 -0
  19. package/examples/private/quadrant.txt +30 -0
  20. package/examples/private/test-forecast.json +5510 -0
  21. package/examples/private/timeline-data.json +1 -0
  22. package/examples/private/timeline.json +389 -0
  23. package/examples/private/warming-stripe-test.json +2578 -0
  24. package/examples/private/warming-stripes.json +4763 -0
  25. package/examples/radar-chart-simple.json +133 -0
  26. package/examples/radar-chart.json +148 -0
  27. package/examples/tech-adoption-with-links.json +560 -0
  28. package/index.html +1 -36
  29. package/package.json +59 -60
  30. package/src/CdcChartComponent.tsx +206 -89
  31. package/src/_stories/Chart.Anchors.stories.tsx +10 -0
  32. package/src/_stories/Chart.BoxPlot.stories.tsx +7 -0
  33. package/src/_stories/Chart.CI.stories.tsx +13 -0
  34. package/src/_stories/Chart.Combo.stories.tsx +17 -0
  35. package/src/_stories/Chart.CustomColors.stories.tsx +4 -0
  36. package/src/_stories/Chart.DynamicSeries.stories.tsx +19 -0
  37. package/src/_stories/Chart.Filters.stories.tsx +4 -0
  38. package/src/_stories/Chart.Forecast.stories.tsx +4 -0
  39. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +22 -0
  40. package/src/_stories/Chart.Legend.Gradient.stories.tsx +28 -0
  41. package/src/_stories/Chart.Patterns.stories.tsx +4 -0
  42. package/src/_stories/Chart.PreserveDecimals.stories.tsx +25 -0
  43. package/src/_stories/Chart.Regions.Categorical.stories.tsx +161 -0
  44. package/src/_stories/Chart.Regions.DateScale.stories.tsx +216 -0
  45. package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +312 -0
  46. package/src/_stories/Chart.ScatterPlot.stories.tsx +4 -0
  47. package/src/_stories/Chart.SmallMultiples.stories.tsx +16 -0
  48. package/src/_stories/Chart.stories.tsx +45 -0
  49. package/src/_stories/Chart.tooltip.stories.tsx +7 -0
  50. package/src/_stories/ChartAnnotation.stories.tsx +10 -0
  51. package/src/_stories/ChartAxisLabels.stories.tsx +4 -0
  52. package/src/_stories/ChartAxisTitles.stories.tsx +10 -0
  53. package/src/_stories/ChartBar.Editor.stories.tsx +11 -6
  54. package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
  55. package/src/_stories/ChartBrush.Matrix.Continuous.stories.tsx +41 -0
  56. package/src/_stories/ChartBrush.Matrix.Date.stories.tsx +114 -0
  57. package/src/_stories/ChartBrush.Matrix.DateTime.stories.tsx +78 -0
  58. package/src/_stories/ChartBrush.stories.tsx +57 -0
  59. package/src/_stories/ChartEditor.Editor.stories.tsx +3 -5
  60. package/src/_stories/ChartEditor.stories.tsx +7 -0
  61. package/src/_stories/ChartLine.QuadrantAngles.stories.tsx +89 -0
  62. package/src/_stories/ChartLine.Suppression.stories.tsx +7 -0
  63. package/src/_stories/ChartLine.Symbols.stories.tsx +4 -0
  64. package/src/_stories/ChartPrefixSuffix.stories.tsx +46 -1
  65. package/src/_stories/TechAdoptionWithLinks.stories.tsx +34 -0
  66. package/src/_stories/_mock/brush_continuous.json +86 -0
  67. package/src/_stories/_mock/brush_date_large.json +176 -0
  68. package/src/_stories/_mock/brush_enabled.json +326 -0
  69. package/src/_stories/_mock/brush_mock.json +2 -69
  70. package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
  71. package/src/_stories/_mock/line_chart_angle_near_zero_fall.json +195 -0
  72. package/src/_stories/_mock/line_chart_angle_near_zero_rise.json +195 -0
  73. package/src/_stories/_mock/line_chart_angle_q1_steep_upward.json +195 -0
  74. package/src/_stories/_mock/line_chart_angle_q2_gentle_downward.json +195 -0
  75. package/src/_stories/_mock/line_chart_angle_q3_steep_downward.json +195 -0
  76. package/src/_stories/_mock/line_chart_angle_q4_gentle_upward.json +195 -0
  77. package/src/_stories/_mock/line_chart_quadrant_angles.json +264 -0
  78. package/src/components/Annotations/components/AnnotationDraggable.styles.css +11 -17
  79. package/src/components/Annotations/components/AnnotationDraggable.tsx +240 -116
  80. package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -2
  81. package/src/components/Annotations/components/AnnotationDropdown.tsx +8 -12
  82. package/src/components/Annotations/components/AnnotationList.styles.css +4 -10
  83. package/src/components/Annotations/components/AnnotationList.tsx +5 -4
  84. package/src/components/Annotations/components/findNearestDatum.ts +75 -85
  85. package/src/components/Annotations/helpers/getVisibleAnnotations.ts +38 -0
  86. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -2
  87. package/src/components/Axis/BottomAxis.tsx +270 -0
  88. package/src/components/Axis/Categorical.Axis.tsx +6 -7
  89. package/src/components/Axis/LeftAxis.tsx +404 -0
  90. package/src/components/Axis/LeftAxisGridlines.tsx +77 -0
  91. package/src/components/Axis/PairedBarAxis.tsx +186 -0
  92. package/src/components/Axis/README.md +94 -0
  93. package/src/components/Axis/RightAxis.tsx +108 -0
  94. package/src/components/Axis/axis.constants.ts +21 -0
  95. package/src/components/Axis/index.ts +7 -0
  96. package/src/components/BarChart/components/BarChart.Horizontal.tsx +178 -24
  97. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
  98. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
  99. package/src/components/BarChart/components/BarChart.Vertical.tsx +6 -8
  100. package/src/components/BarChart/components/BarChart.tsx +7 -1
  101. package/src/components/BarChart/components/context.tsx +1 -0
  102. package/src/components/BarChart/helpers/useBarChart.ts +14 -2
  103. package/src/components/Brush/BrushSelector.tsx +1390 -0
  104. package/src/components/Brush/MiniChartPreview.tsx +400 -0
  105. package/src/components/DeviationBar.jsx +9 -7
  106. package/src/components/EditorPanel/EditorPanel.tsx +2734 -2595
  107. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +60 -22
  108. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
  109. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +137 -30
  110. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +2 -0
  111. package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +353 -0
  112. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +0 -1
  113. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +30 -25
  114. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +42 -28
  115. package/src/components/EditorPanel/components/Panels/index.tsx +2 -0
  116. package/src/components/EditorPanel/useEditorPermissions.ts +81 -39
  117. package/src/components/HorizonChart/HorizonChart.tsx +131 -0
  118. package/src/components/HorizonChart/components/HorizonBand.tsx +160 -0
  119. package/src/components/HorizonChart/helpers/calculateHorizonBands.ts +27 -0
  120. package/src/components/HorizonChart/helpers/getHorizonLayerColors.ts +40 -0
  121. package/src/components/HorizonChart/index.tsx +3 -0
  122. package/src/components/Legend/Legend.Component.tsx +52 -4
  123. package/src/components/Legend/Legend.tsx +4 -3
  124. package/src/components/Legend/LegendValueRange.tsx +77 -0
  125. package/src/components/Legend/helpers/createFormatLabels.tsx +164 -2
  126. package/src/components/Legend/helpers/generateValueRanges.ts +92 -0
  127. package/src/components/Legend/helpers/index.ts +10 -6
  128. package/src/components/LineChart/helpers/README.md +292 -0
  129. package/src/components/LineChart/helpers/labelPositioning.test.ts +245 -0
  130. package/src/components/LineChart/helpers/labelPositioning.ts +304 -0
  131. package/src/components/LineChart/index.tsx +44 -8
  132. package/src/components/LinearChart/README.md +109 -0
  133. package/src/components/LinearChart/VisualizationRenderer.tsx +267 -0
  134. package/src/components/LinearChart/linearChart.constants.ts +84 -0
  135. package/src/components/LinearChart/tests/LinearChart.test.tsx +201 -0
  136. package/src/components/LinearChart/tests/mockConfigContext.ts +129 -0
  137. package/src/components/LinearChart/utils/tickFormatting.ts +146 -0
  138. package/src/components/LinearChart.tsx +338 -1082
  139. package/src/components/PairedBarChart.jsx +20 -3
  140. package/src/components/PieChart/PieChart.tsx +1 -1
  141. package/src/components/RadarChart/RadarAxis.tsx +78 -0
  142. package/src/components/RadarChart/RadarChart.tsx +298 -0
  143. package/src/components/RadarChart/RadarGrid.tsx +64 -0
  144. package/src/components/RadarChart/RadarPolygon.tsx +91 -0
  145. package/src/components/RadarChart/helpers.ts +83 -0
  146. package/src/components/RadarChart/index.tsx +3 -0
  147. package/src/components/Regions/components/Regions.tsx +365 -122
  148. package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
  149. package/src/components/SmallMultiples/SmallMultipleTile.tsx +5 -1
  150. package/src/components/WarmingStripes/WarmingStripes.tsx +230 -0
  151. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
  152. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
  153. package/src/components/WarmingStripes/index.tsx +3 -0
  154. package/src/data/initial-state.js +17 -2
  155. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
  156. package/src/helpers/getExcludedData.ts +4 -0
  157. package/src/helpers/getMinMax.ts +12 -7
  158. package/src/helpers/handleChartAriaLabels.ts +19 -19
  159. package/src/helpers/handleLineType.ts +22 -18
  160. package/src/helpers/sizeHelpers.ts +0 -20
  161. package/src/helpers/smallMultiplesHelpers.ts +1 -1
  162. package/src/hooks/useChartHoverAnalytics.tsx +10 -9
  163. package/src/hooks/useProgrammaticTooltip.ts +23 -2
  164. package/src/hooks/useScales.ts +18 -1
  165. package/src/hooks/useTooltip.tsx +34 -10
  166. package/src/scss/DataTable.scss +0 -4
  167. package/src/scss/main.scss +22 -3
  168. package/src/selectors/README.md +68 -0
  169. package/src/store/chart.reducer.ts +2 -0
  170. package/src/test/CdcChart.test.jsx +1 -1
  171. package/src/types/ChartConfig.ts +21 -0
  172. package/src/types/ChartContext.ts +1 -0
  173. package/src/types/Horizon.ts +64 -0
  174. package/src/types/Label.ts +1 -0
  175. package/src/utils/analyticsTracking.ts +19 -0
  176. package/LICENSE +0 -201
  177. package/src/components/Annotations/components/helpers/index.tsx +0 -46
  178. package/src/components/Brush/BrushChart.tsx +0 -128
  179. package/src/components/Brush/BrushController.tsx +0 -71
  180. package/src/components/Brush/types.tsx +0 -8
  181. package/src/components/BrushChart.tsx +0 -223
package/package.json CHANGED
@@ -1,83 +1,82 @@
1
1
  {
2
2
  "name": "@cdc/chart",
3
- "version": "4.25.11",
3
+ "version": "4.26.2",
4
4
  "description": "React component for visualizing tabular data in various types of charts",
5
- "moduleName": "CdcChart",
6
- "main": "dist/cdcchart",
7
- "type": "module",
8
- "scripts": {
9
- "start": "vite --open",
10
- "build": "vite build",
11
- "preview": "vite preview",
12
- "graph": "nx graph",
13
- "prepublishOnly": "lerna run --scope @cdc/chart build",
14
- "test": "vitest run --reporter verbose",
15
- "test-watch": "vitest watch --reporter verbose",
16
- "test-watch:ui": "vitest --ui"
17
- },
18
- "repository": {
19
- "type": "git",
20
- "url": "git+https://github.com/CDCgov/cdc-open-viz",
21
- "directory": "packages/chart"
22
- },
23
- "author": "Matthew Pallansch <mpallansch@adittech.com>",
24
- "bugs": {
25
- "url": "https://github.com/CDCgov/cdc-open-viz/issues"
26
- },
27
5
  "license": "Apache-2.0",
6
+ "author": "Matthew Pallansch <mpallansch@adittech.com>",
7
+ "bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
28
8
  "dependencies": {
29
- "@cdc/core": "^4.25.11",
9
+ "@cdc/core": "^4.26.2",
30
10
  "@hello-pangea/dnd": "^16.2.0",
31
11
  "@react-spring/web": "^9.7.5",
32
12
  "@rollup/plugin-dsv": "^3.0.2",
33
- "@visx/annotation": "^3.3.0",
34
- "@visx/axis": "3.12.0",
13
+ "@visx/annotation": "^3.12.0",
14
+ "@visx/axis": "^3.12.0",
35
15
  "@visx/brush": "^3.12.0",
36
- "@visx/curve": "3.12.0",
16
+ "@visx/curve": "^3.12.0",
37
17
  "@visx/drag": "^3.12.0",
38
- "@visx/event": "3.12.0",
39
- "@visx/glyph": "3.12.0",
40
- "@visx/gradient": "3.12.0",
41
- "@visx/legend": "3.12.0",
42
- "@visx/marker": "3.12.0",
43
- "@visx/mock-data": "3.12.0",
44
- "@visx/pattern": "^3.0.0",
45
- "@visx/responsive": "^2.10.0",
46
- "@visx/scale": "3.12.0",
47
- "@visx/shape": "3.12.0",
48
- "@visx/stats": "3.12.0",
49
- "@visx/text": "3.12.0",
50
- "@visx/tooltip": "3.12.0",
51
- "@vitejs/plugin-react": "^4.3.4",
52
- "chroma-js": "3.1.2",
53
- "d3-array": "3.2.4",
54
- "d3-format": "^3.1.0",
18
+ "@visx/event": "^3.12.0",
19
+ "@visx/glyph": "^3.12.0",
20
+ "@visx/gradient": "^3.12.0",
21
+ "@visx/legend": "^3.12.0",
22
+ "@visx/marker": "^3.12.0",
23
+ "@visx/mock-data": "^3.12.0",
24
+ "@visx/pattern": "^3.12.0",
25
+ "@visx/responsive": "^3.12.0",
26
+ "@visx/scale": "^3.12.0",
27
+ "@visx/shape": "^3.12.0",
28
+ "@visx/stats": "^3.12.0",
29
+ "@visx/text": "^3.12.0",
30
+ "@visx/tooltip": "^3.12.0",
31
+ "@vitejs/plugin-react": "^5.1.2",
32
+ "chroma-js": "^3.1.2",
33
+ "d3-array": "^3.2.4",
34
+ "d3-format": "^3.1.2",
55
35
  "d3-sankey": "^0.12.3",
56
- "d3-time-format": "4.1.0",
57
- "dompurify": "^3.1.5",
58
- "html-react-parser": "5.2.3",
36
+ "d3-time-format": "^4.1.0",
37
+ "dompurify": "^3.3.1",
38
+ "html-react-parser": "^5.2.3",
59
39
  "js-base64": "^2.5.2",
60
- "lodash": "^4.17.21",
61
- "papaparse": "5.5.2",
40
+ "lodash": "^4.17.23",
41
+ "papaparse": "^5.5.2",
62
42
  "react-accessible-accordion": "^5.0.1",
63
- "react-icons": "5.5.0",
43
+ "react-icons": "^5.5.0",
64
44
  "react-tooltip": "5.8.2-beta.3",
65
45
  "resize-observer-polyfill": "^1.5.1",
66
- "use-debounce": "^6.0.1",
67
- "vite": "^5.4.21",
46
+ "sass": "^1.89.2",
47
+ "use-debounce": "^10.1.0",
48
+ "vite": "^7.3.1",
68
49
  "vite-plugin-css-injected-by-js": "^2.4.0",
69
- "vite-plugin-svgr": "^2.4.0",
70
- "whatwg-fetch": "3.6.20"
71
- },
72
- "peerDependencies": {
73
- "react": "^18.2.0",
74
- "react-dom": "^18.2.0"
50
+ "vite-plugin-svgr": "^4.2.0",
51
+ "whatwg-fetch": "^3.6.20"
75
52
  },
76
- "gitHead": "5f09a137c22f454111ab5f4cd7fdf1d2d58e31bd",
77
53
  "devDependencies": {
78
54
  "@types/d3-array": "^3.2.1",
79
55
  "@types/d3-format": "^3.0.4",
80
56
  "@types/d3-sankey": "^0.12.4",
81
57
  "@types/d3-time-format": "^4.0.3"
82
- }
58
+ },
59
+ "gitHead": "be3413e8e1149abf94225108f86a7910f56e0616",
60
+ "main": "dist/cdcchart",
61
+ "moduleName": "CdcChart",
62
+ "peerDependencies": {
63
+ "react": "^18.2.0",
64
+ "react-dom": "^18.2.0"
65
+ },
66
+ "repository": {
67
+ "type": "git",
68
+ "url": "git+https://github.com/CDCgov/cdc-open-viz",
69
+ "directory": "packages/chart"
70
+ },
71
+ "scripts": {
72
+ "build": "vite build",
73
+ "graph": "nx graph",
74
+ "prepublishOnly": "lerna run --scope @cdc/chart build",
75
+ "preview": "vite preview",
76
+ "start": "vite --open",
77
+ "test": "vitest run --reporter verbose",
78
+ "test-watch": "vitest watch --reporter verbose",
79
+ "test-watch:ui": "vitest --ui"
80
+ },
81
+ "type": "module"
83
82
  }
@@ -22,21 +22,23 @@ import { Runtime } from '@cdc/core/types/Runtime'
22
22
  import { Label } from './types/Label'
23
23
  // External Libraries
24
24
  import ParentSize from '@visx/responsive/lib/components/ParentSize'
25
- import { timeParse, timeFormat } from 'd3-time-format'
25
+ import { timeParse } from 'd3-time-format'
26
26
  import parse from 'html-react-parser'
27
27
  import _ from 'lodash'
28
28
  // Primary Components
29
29
  import ConfigContext, { ChartDispatchContext } from './ConfigContext'
30
30
  import PieChart from './components/PieChart'
31
+ import RadarChart from './components/RadarChart'
31
32
  import SankeyChart from './components/Sankey'
32
33
  import LinearChart from './components/LinearChart'
33
- import { isDateScale } from '@cdc/core/helpers/cove/date'
34
+ import { isDateScale, formatDate as coreFormatDate } from '@cdc/core/helpers/cove/date'
34
35
 
35
36
  import { twoColorPalette } from '@cdc/core/data/colorPalettes'
36
37
  import { filterChartColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
37
38
 
38
39
  import SparkLine from './components/Sparkline'
39
40
  import Legend from './components/Legend'
41
+ import WarmingStripesGradientLegend from './components/WarmingStripes/WarmingStripesGradientLegend'
40
42
  import defaults from './data/initial-state'
41
43
  import EditorPanel from './components/EditorPanel'
42
44
  import { abbreviateNumber } from './helpers/abbreviateNumber'
@@ -52,6 +54,7 @@ import Loading from '@cdc/core/components/Loading'
52
54
  import Filters from '@cdc/core/components/Filters'
53
55
  import MediaControls from '@cdc/core/components/MediaControls'
54
56
  import Annotation from './components/Annotations'
57
+ import { getVisibleAnnotations } from './components/Annotations/helpers/getVisibleAnnotations'
55
58
  // Core Helpers
56
59
  import { DataTransform } from '@cdc/core/helpers/DataTransform'
57
60
  import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
@@ -132,7 +135,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
132
135
  coveLoadedEventRan,
133
136
  imageId,
134
137
  seriesHighlight,
135
- colorScale
138
+ colorScale,
139
+ brushData
136
140
  } = state
137
141
  const { description, visualizationType } = config
138
142
  const svgRef = useRef(null)
@@ -239,6 +243,14 @@ const CdcChart: React.FC<CdcChartProps> = ({
239
243
 
240
244
  const convertLineToBarGraph = isConvertLineToBarGraph(config, filteredData)
241
245
 
246
+ // Declaratively calculate series keys for pie charts based on filtered data
247
+ const pieSeriesKeys = useMemo(() => {
248
+ if (config.visualizationType !== 'Pie' || !config.xAxis?.dataKey) return null
249
+ const data = filteredData?.length > 0 ? filteredData : excludedData
250
+ if (!data) return null
251
+ return _.uniq(data.map(d => d[config.xAxis.dataKey]))
252
+ }, [config.visualizationType, config.xAxis?.dataKey, filteredData, excludedData])
253
+
242
254
  const prepareConfig = (loadedConfig: ChartConfig) => {
243
255
  // Create defaults without version to avoid overriding legacy configs
244
256
  const defaultsWithoutPalette = { ...defaults }
@@ -254,8 +266,39 @@ const CdcChart: React.FC<CdcChartProps> = ({
254
266
  delete defaultsWithoutPalette.general?.palette
255
267
  }
256
268
 
269
+ // Override palette defaults for Line charts specifically
270
+ if (loadedConfig?.visualizationType === 'Line' && !loadedConfig?.general?.palette) {
271
+ if (!defaultsWithoutPalette.general) {
272
+ defaultsWithoutPalette.general = {}
273
+ }
274
+ defaultsWithoutPalette.general.palette = {
275
+ isReversed: false,
276
+ version: '2.0',
277
+ name: 'divergent_blue_cyan'
278
+ }
279
+ }
280
+
281
+ // Override palette defaults for Horizon Chart specifically
282
+ if (loadedConfig?.visualizationType === 'Horizon Chart' && !loadedConfig?.general?.palette) {
283
+ if (!defaultsWithoutPalette.general) {
284
+ defaultsWithoutPalette.general = {}
285
+ }
286
+ defaultsWithoutPalette.general.palette = {
287
+ isReversed: false,
288
+ version: '2.0',
289
+ name: 'sequential_blue'
290
+ }
291
+ }
292
+
257
293
  let newConfig = { ...defaultsWithoutPalette, ...loadedConfig }
258
294
 
295
+ // Ensure Horizon Chart has enough palette colors for all layers
296
+ if (newConfig.visualizationType === 'Horizon Chart') {
297
+ const numLayers = newConfig.horizon?.numLayers ?? 4
298
+ const currentCount = _.get(newConfig, 'general.paletteColorCount', 4)
299
+ _.set(newConfig, 'general.paletteColorCount', Math.max(currentCount, numLayers))
300
+ }
301
+
259
302
  _.defaultsDeep(newConfig, {
260
303
  table: { showVertical: false }
261
304
  })
@@ -375,6 +418,12 @@ const CdcChart: React.FC<CdcChartProps> = ({
375
418
  const pieData = currentData.length > 0 ? currentData : newExcludedData
376
419
  newConfig.runtime.seriesKeys = _.uniq(pieData.map(d => d[newConfig.xAxis.dataKey]))
377
420
  newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
421
+ newConfig.runtime.isPieChart = true // Flag to know when to use derived keys
422
+ } else if (newConfig.visualizationType === 'Radar') {
423
+ // Radar chart: seriesKeys are the entity names from xAxis.dataKey
424
+ const radarData = currentData.length > 0 ? currentData : newExcludedData
425
+ newConfig.runtime.seriesKeys = _.uniq(radarData.map(d => d[newConfig.xAxis.dataKey]))
426
+ newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
378
427
  } else {
379
428
  const finalData = dataOverride || newConfig.formattedData || newConfig.data
380
429
  newConfig.runtime.seriesKeys = (newConfig.runtime.series || []).flatMap(series => {
@@ -448,6 +497,22 @@ const CdcChart: React.FC<CdcChartProps> = ({
448
497
  newConfig.visualizationSubType = 'stacked'
449
498
  }
450
499
 
500
+ if (newConfig.visualizationType === 'Horizon Chart' && newConfig.series) {
501
+ // Apply horizon defaults if not set
502
+ newConfig.horizon = {
503
+ numLayers: 4,
504
+ mode: 'offset', // Always offset for now, mirror hidden from UI
505
+ bandGap: 15,
506
+ bottomPadding: 15,
507
+ ...newConfig.horizon
508
+ }
509
+
510
+ // Set categorical as default xAxis type for horizon charts if not already set
511
+ if (!newConfig.xAxis.type) {
512
+ newConfig.xAxis.type = 'categorical'
513
+ }
514
+ }
515
+
451
516
  if (isHorizontalVariant) {
452
517
  // For horizontal charts, axes are swapped, so processedYAxis goes to runtime.xAxis and vice versa
453
518
  const horizontalXAxisSource = _.cloneDeep((newConfig.yAxis as any)?.yAxis || newConfig.yAxis)
@@ -559,7 +624,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
559
624
  for (let entry of entries) {
560
625
  let { width, height } = entry.contentRect
561
626
 
562
- const editorIsOpen = isEditor && !!document.querySelector('.editor-panel:not(.hidden)')
627
+ const editorIsOpen = isEditor
563
628
  width = editorIsOpen ? width - EDITOR_WIDTH : width
564
629
 
565
630
  const newViewport = getViewport(width)
@@ -608,7 +673,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
608
673
  if (newData) {
609
674
  newConfig.data = newData
610
675
  }
611
- } else if (newConfig.formattedData) {
676
+ } else if (newConfig.formattedData && Array.isArray(newConfig.formattedData)) {
612
677
  newConfig.data = newConfig.formattedData
613
678
  } else if (newConfig.dataDescription) {
614
679
  // For dashboard contexts, get data from datasets if config.data is undefined
@@ -644,7 +709,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
644
709
  updateConfig(preparedConfig, preppedData.data)
645
710
  }
646
711
  } catch (err) {
647
- console.error('Could not Load!')
712
+ console.error('Could not Load!', err)
648
713
  }
649
714
  }
650
715
 
@@ -709,6 +774,19 @@ const CdcChart: React.FC<CdcChartProps> = ({
709
774
  }
710
775
  }, [externalFilters]) // eslint-disable-line
711
776
 
777
+ // Declaratively update runtime series keys for pie charts when derived value changes
778
+ if (config.runtime?.isPieChart && pieSeriesKeys && !_.isEqual(pieSeriesKeys, config.runtime?.seriesKeys)) {
779
+ const newConfig = {
780
+ ...config,
781
+ runtime: {
782
+ ...config.runtime,
783
+ seriesKeys: pieSeriesKeys,
784
+ seriesLabelsAll: pieSeriesKeys
785
+ }
786
+ }
787
+ setConfig(newConfig)
788
+ }
789
+
712
790
  // Generates color palette to pass to child chart component
713
791
  useEffect(() => {
714
792
  if (stateData && config.xAxis && config.runtime?.seriesKeys) {
@@ -723,6 +801,16 @@ const CdcChart: React.FC<CdcChartProps> = ({
723
801
  }
724
802
  }, [config, stateData]) // eslint-disable-line
725
803
 
804
+ // Clear brush selection when brush slider is disabled
805
+ useEffect(() => {
806
+ const isBrushDisabled = !config?.xAxis?.brushActive
807
+ const hasBrushData = Array.isArray(brushData) && brushData.length > 0
808
+
809
+ if (isBrushDisabled && hasBrushData) {
810
+ dispatch({ type: 'SET_BRUSH_DATA', payload: [] })
811
+ }
812
+ }, [config?.xAxis?.brushActive, brushData])
813
+
726
814
  // Updates runtime axis labels when config or data changes when using markup variables
727
815
  useEffect(() => {
728
816
  if (
@@ -819,15 +907,13 @@ const CdcChart: React.FC<CdcChartProps> = ({
819
907
  }
820
908
 
821
909
  const formatDate = (date, i, ticks) => {
822
- let formattedDate = timeFormat(config.runtime[section].dateDisplayFormat)(date)
823
- // Handle the case where all months work with '%b.' except for May
824
- if (config.runtime[section].dateDisplayFormat?.includes('%b.') && formattedDate.includes('May.')) {
825
- formattedDate = formattedDate.replace(/May\./g, 'May')
826
- }
910
+ const displayFormat =
911
+ config.runtime[section].dateDisplayFormat || config.runtime[section].dateParseFormat || '%Y-%m-%d'
912
+ let formattedDate = coreFormatDate(displayFormat, date)
827
913
  // Show years only once
828
- if (config.xAxis.showYearsOnce && config.runtime[section].dateDisplayFormat?.includes('%Y') && ticks) {
914
+ if (config.xAxis.showYearsOnce && displayFormat?.includes('%Y') && ticks) {
829
915
  const prevDate = ticks[i - 1] ? ticks[i - 1].value : null
830
- const prevFormattedDate = timeFormat(config.runtime[section].dateDisplayFormat)(prevDate)
916
+ const prevFormattedDate = coreFormatDate(displayFormat, prevDate)
831
917
  const year = formattedDate.match(/\d{4}/)
832
918
  const prevYear = prevFormattedDate.match(/\d{4}/)
833
919
  if (year && prevYear && year[0] === prevYear[0]) {
@@ -838,7 +924,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
838
924
  }
839
925
 
840
926
  const formatTooltipsDate = date => {
841
- return timeFormat(config.tooltips.dateDisplayFormat)(date)
927
+ return coreFormatDate(config.tooltips.dateDisplayFormat, date)
842
928
  }
843
929
 
844
930
  // Format numeric data based on settings in config OR from passed in settings for Additional Columns
@@ -1093,6 +1179,12 @@ const CdcChart: React.FC<CdcChartProps> = ({
1093
1179
  return tableConfig
1094
1180
  }
1095
1181
 
1182
+ // Transform and clean data for chart rendering
1183
+ const transformedData = getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean })
1184
+
1185
+ // Filter annotations to only those visible in current data view
1186
+ const visibleAnnotations = getVisibleAnnotations(config.annotations, transformedData, config.xAxis?.dataKey)
1187
+
1096
1188
  // Prevent render if loading
1097
1189
  let body = <Loading />
1098
1190
 
@@ -1125,8 +1217,6 @@ const CdcChart: React.FC<CdcChartProps> = ({
1125
1217
  const isLegendOnBottom = legend?.position === 'bottom' || isLegendWrapViewport(currentViewport)
1126
1218
 
1127
1219
  if (config.isResponsiveTicks) classes.push('subtext--responsive-ticks ')
1128
- if (config.xAxis.brushActive && !isLegendOnBottom) classes.push('subtext--brush-active ')
1129
- if (config.xAxis.brushActive && config.legend.hide) classes.push('subtext--brush-active ')
1130
1220
  return classes
1131
1221
  }
1132
1222
 
@@ -1154,6 +1244,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1154
1244
  isDashboard={isDashboard}
1155
1245
  title={title}
1156
1246
  superTitle={processedSuperTitle}
1247
+ titleStyle={config.titleStyle}
1157
1248
  classes={['chart-title', `${config.theme}`, 'cove-component__header', 'mb-3']}
1158
1249
  style={undefined}
1159
1250
  config={config}
@@ -1185,7 +1276,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1185
1276
  />
1186
1277
  )}
1187
1278
  <SkipTo skipId={handleChartTabbing(config, legendId)} skipMessage='Skip Over Chart Container' />
1188
- {config.annotations?.length > 0 && (
1279
+ {visibleAnnotations.length > 0 && (
1189
1280
  <SkipTo
1190
1281
  skipId={handleChartTabbing(config, legendId)}
1191
1282
  skipMessage={`Skip over annotations`}
@@ -1215,7 +1306,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1215
1306
  {/* All charts with LinearChart */}
1216
1307
  {filteredData &&
1217
1308
  filteredData.length > 0 &&
1218
- !['Spark Line', 'Line', 'Sankey', 'Pie', 'Sankey'].includes(config.visualizationType) && (
1309
+ !['Spark Line', 'Line', 'Sankey', 'Pie', 'Radar'].includes(config.visualizationType) && (
1219
1310
  <div ref={parentRef} style={{ width: `100%` }}>
1220
1311
  <ParentSize>
1221
1312
  {parent => (
@@ -1237,6 +1328,19 @@ const CdcChart: React.FC<CdcChartProps> = ({
1237
1328
  )}
1238
1329
  </ParentSize>
1239
1330
  )}
1331
+ {/* Radar Chart */}
1332
+ {filteredData && filteredData.length > 0 && config.visualizationType === 'Radar' && (
1333
+ <ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
1334
+ {parent => (
1335
+ <RadarChart
1336
+ ref={svgRef}
1337
+ parentWidth={parent.width}
1338
+ parentHeight={parent.height}
1339
+ interactionLabel={interactionLabel}
1340
+ />
1341
+ )}
1342
+ </ParentSize>
1343
+ )}
1240
1344
  {/* Line Chart */}
1241
1345
  {filteredData &&
1242
1346
  filteredData.length > 0 &&
@@ -1305,13 +1409,18 @@ const CdcChart: React.FC<CdcChartProps> = ({
1305
1409
  {/* Legend */}
1306
1410
  {!config.legend.hide &&
1307
1411
  config.visualizationType !== 'Spark Line' &&
1308
- config.visualizationType !== 'Sankey' && (
1412
+ config.visualizationType !== 'Sankey' &&
1413
+ !(config.visualizationType === 'Warming Stripes' && config.legend?.style === 'gradient') &&
1414
+ !(config.visualizationType === 'Warming Stripes' && config.smallMultiples?.mode) && (
1309
1415
  <Legend
1310
1416
  ref={legendRef}
1311
1417
  skipId={handleChartTabbing(config, legendId)}
1312
1418
  interactionLabel={interactionLabel}
1313
1419
  />
1314
1420
  )}
1421
+ {config.visualizationType === 'Warming Stripes' &&
1422
+ config.legend?.style === 'gradient' &&
1423
+ !config.smallMultiples?.mode && <WarmingStripesGradientLegend />}
1315
1424
  </LegendWrapper>
1316
1425
  {/* Link */}
1317
1426
  {isDashboard && config.table && config.table.show && config.table.showDataTableLink
@@ -1323,80 +1432,87 @@ const CdcChart: React.FC<CdcChartProps> = ({
1323
1432
  <div className={getChartSubTextClasses().join(' ')}>{parse(processedDescription)}</div>
1324
1433
  )}
1325
1434
 
1326
- {/* buttons */}
1327
- <MediaControls.Section classes={['download-buttons']}>
1328
- {config.table.showDownloadImgButton && (
1329
- <MediaControls.Button
1330
- text='Download Image'
1331
- title='Download Chart as Image'
1332
- type='image'
1333
- state={config}
1334
- elementToCapture={imageId}
1335
- interactionLabel={interactionLabel}
1336
- />
1337
- )}
1338
- {config.table.showDownloadPdfButton && (
1339
- <MediaControls.Button
1340
- text='Download PDF'
1341
- title='Download Chart as PDF'
1342
- type='pdf'
1343
- state={config}
1344
- elementToCapture={imageId}
1345
- interactionLabel={interactionLabel}
1346
- />
1347
- )}
1348
- </MediaControls.Section>
1349
1435
  {/* Data Table */}
1350
- {((config.xAxis.dataKey &&
1436
+ {(config.xAxis.dataKey &&
1351
1437
  config.table.show &&
1352
1438
  config.visualizationType !== 'Spark Line' &&
1353
1439
  config.visualizationType !== 'Sankey') ||
1354
- (config.visualizationType === 'Sankey' && config.table.show)) &&
1355
- (() => {
1356
- let dataTableConfig = pivotDynamicSeries(config)
1357
- let dataTableColumns = config.columns
1358
- let dataTableRuntimeData = getTableRuntimeData()
1359
- let dataTableRawData =
1360
- config.visualizationType === 'Sankey'
1361
- ? config?.data?.[0]?.tableData
1362
- : config.table.customTableConfig
1363
- ? filterVizData(config.filters, config.data)
1364
- : config.data
1365
-
1366
- if (config.smallMultiples?.mode) {
1367
- const prepared = prepareSmallMultiplesDataTable(config, config.columns, dataTableRuntimeData)
1368
- dataTableConfig = prepared.config
1369
- dataTableColumns = prepared.columns
1370
- dataTableRuntimeData = prepared.runtimeData
1371
- if (config.smallMultiples.mode === 'by-column') {
1372
- dataTableRawData = prepared.config.data
1440
+ (config.visualizationType === 'Sankey' && config.table.show)
1441
+ ? (() => {
1442
+ let dataTableConfig = pivotDynamicSeries(config)
1443
+ let dataTableColumns = config.columns
1444
+ let dataTableRuntimeData = getTableRuntimeData()
1445
+ let dataTableRawData =
1446
+ config.visualizationType === 'Sankey'
1447
+ ? config?.data?.[0]?.tableData
1448
+ : config.table.customTableConfig
1449
+ ? filterVizData(config.filters, config.data)
1450
+ : config.data
1451
+
1452
+ if (config.smallMultiples?.mode) {
1453
+ const prepared = prepareSmallMultiplesDataTable(config, config.columns, dataTableRuntimeData)
1454
+ dataTableConfig = prepared.config
1455
+ dataTableColumns = prepared.columns
1456
+ dataTableRuntimeData = prepared.runtimeData
1457
+ if (config.smallMultiples.mode === 'by-column') {
1458
+ dataTableRawData = prepared.config.data
1459
+ }
1373
1460
  }
1374
- }
1375
1461
 
1376
- return (
1377
- <DataTable
1378
- /* changing the "key" will force the table to re-render
1379
- when the default sort changes while editing */
1380
- key={dataTableDefaultSortBy}
1381
- config={dataTableConfig}
1382
- rawData={dataTableRawData}
1383
- runtimeData={dataTableRuntimeData}
1384
- expandDataTable={config.table.expanded}
1385
- columns={dataTableColumns}
1386
- defaultSortBy={dataTableDefaultSortBy}
1387
- displayGeoName={name => name}
1388
- applyLegendToRow={applyLegendToRow}
1389
- tableTitle={config.table.label}
1390
- indexTitle={config.table.indexLabel}
1391
- vizTitle={title}
1392
- viewport={currentViewport}
1393
- tabbingId={handleChartTabbing(config, legendId)}
1394
- colorScale={colorScale}
1395
- interactionLabel={interactionLabel}
1396
- />
1397
- )
1398
- })()}
1399
- {config?.annotations?.length > 0 && <Annotation.Dropdown />}
1462
+ return (
1463
+ <DataTable
1464
+ /* changing the "key" will force the table to re-render
1465
+ when the default sort changes while editing */
1466
+ key={dataTableDefaultSortBy}
1467
+ config={dataTableConfig}
1468
+ rawData={dataTableRawData}
1469
+ runtimeData={dataTableRuntimeData}
1470
+ expandDataTable={config.table.expanded}
1471
+ columns={dataTableColumns}
1472
+ defaultSortBy={dataTableDefaultSortBy}
1473
+ displayGeoName={name => name}
1474
+ applyLegendToRow={applyLegendToRow}
1475
+ tableTitle={config.table.label}
1476
+ indexTitle={config.table.indexLabel}
1477
+ vizTitle={title}
1478
+ viewport={currentViewport}
1479
+ tabbingId={handleChartTabbing(config, legendId)}
1480
+ colorScale={colorScale}
1481
+ imageRef={imageId}
1482
+ showDownloadImgButton={config.table.showDownloadImgButton}
1483
+ showDownloadPdfButton={config.table.showDownloadPdfButton}
1484
+ includeContextInDownload={config.table?.includeContextInDownload}
1485
+ interactionLabel={interactionLabel}
1486
+ />
1487
+ )
1488
+ })()
1489
+ : (config.table.showDownloadImgButton || config.table.showDownloadPdfButton) && (
1490
+ <div className='w-100 d-flex justify-content-end'>
1491
+ <MediaControls.Section classes={['download-links', 'mt-4', 'mb-2']}>
1492
+ {config.table.showDownloadImgButton && (
1493
+ <MediaControls.DownloadLink
1494
+ type='image'
1495
+ title='Download Chart as Image'
1496
+ state={config}
1497
+ elementToCapture={imageId}
1498
+ interactionLabel={interactionLabel}
1499
+ includeContextInDownload={config.table?.includeContextInDownload}
1500
+ />
1501
+ )}
1502
+ {config.table.showDownloadPdfButton && (
1503
+ <MediaControls.DownloadLink
1504
+ type='pdf'
1505
+ title='Download Chart as PDF'
1506
+ state={config}
1507
+ elementToCapture={imageId}
1508
+ interactionLabel={interactionLabel}
1509
+ includeContextInDownload={config.table?.includeContextInDownload}
1510
+ />
1511
+ )}
1512
+ </MediaControls.Section>
1513
+ </div>
1514
+ )}
1515
+ {visibleAnnotations.length > 0 && <Annotation.Dropdown />}
1400
1516
  {/* show pdf or image button */}
1401
1517
  {processedLegacyFootnotes && (
1402
1518
  <section className='footnotes pt-2 mt-4'>{parse(processedLegacyFootnotes)}</section>
@@ -1469,10 +1585,11 @@ const CdcChart: React.FC<CdcChartProps> = ({
1469
1585
  setSharedFilterValue,
1470
1586
  svgRef,
1471
1587
  tableData: filteredData || excludedData,
1472
- transformedData: getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean }),
1588
+ transformedData,
1473
1589
  twoColorPalette,
1474
1590
  unfilteredData: stateData,
1475
- updateConfig
1591
+ updateConfig,
1592
+ visibleAnnotations
1476
1593
  }
1477
1594
 
1478
1595
  return (
@@ -3,6 +3,7 @@ import { Meta, Story } from '@storybook/react-vite'
3
3
  import Chart from '../CdcChartComponent'
4
4
  import exampleComboBarNonNumeric from './../../examples/feature/tests-date-exclusions/date-exclusions-config.json'
5
5
  import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
6
+ import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
6
7
 
7
8
  export default {
8
9
  title: 'Components/Templates/Chart/Anchors',
@@ -15,17 +16,26 @@ export const Anchor_DateLinear: Story = {
15
16
  args: {
16
17
  config: exampleComboBarNonNumeric,
17
18
  isEditor: false
19
+ },
20
+ play: async ({ canvasElement }) => {
21
+ await assertVisualizationRendered(canvasElement)
18
22
  }
19
23
  }
20
24
 
21
25
  export const Anchor_Categorical: Story = {
22
26
  args: {
23
27
  config: editConfigKeys(exampleComboBarNonNumeric, [{ path: ['xAxis', 'type'], value: 'categorical' }])
28
+ },
29
+ play: async ({ canvasElement }) => {
30
+ await assertVisualizationRendered(canvasElement)
24
31
  }
25
32
  }
26
33
 
27
34
  export const Anchor_Date_Time: Story = {
28
35
  args: {
29
36
  config: editConfigKeys(exampleComboBarNonNumeric, [{ path: ['xAxis', 'type'], value: 'date-time' }])
37
+ },
38
+ play: async ({ canvasElement }) => {
39
+ await assertVisualizationRendered(canvasElement)
30
40
  }
31
41
  }