@cdc/chart 4.26.1 → 4.26.3

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 (173) hide show
  1. package/CLAUDE.local.md +79 -0
  2. package/LICENSE +201 -0
  3. package/dist/{cdcchart-dgT_1dIT.es.js → cdcchart-DQ00cQCm.es.js} +1 -20
  4. package/dist/cdcchart.js +54742 -49796
  5. package/examples/data/data-with-metadata.json +10 -0
  6. package/examples/default.json +378 -0
  7. package/examples/feature/__data__/horizon-chart-data.json +373 -0
  8. package/examples/feature/annotations/index.json +3 -6
  9. package/examples/feature/horizon/horizon-chart.json +395 -0
  10. package/examples/feature/pie/planet-pie-example-config.json +2 -1
  11. package/examples/line-chart-states.json +1085 -0
  12. package/examples/metadata-variables.json +58 -0
  13. package/examples/private/123.json +694 -0
  14. package/examples/private/anchor-issue.json +4094 -0
  15. package/examples/private/backwards-slider.json +10430 -0
  16. package/examples/private/georgia.csv +160 -0
  17. package/examples/private/timeline-data.json +1 -0
  18. package/examples/private/timeline.json +389 -0
  19. package/examples/radar-chart-simple.json +133 -0
  20. package/examples/radar-chart.json +148 -0
  21. package/index.html +1 -31
  22. package/package.json +57 -59
  23. package/src/CdcChart.tsx +8 -4
  24. package/src/CdcChartComponent.tsx +398 -284
  25. package/src/_stories/Chart.Anchors.stories.tsx +10 -0
  26. package/src/_stories/Chart.BoxPlot.stories.tsx +7 -0
  27. package/src/_stories/Chart.CI.stories.tsx +13 -0
  28. package/src/_stories/Chart.Combo.stories.tsx +17 -0
  29. package/src/_stories/Chart.CustomColors.stories.tsx +78 -0
  30. package/src/_stories/Chart.Defaults.stories.tsx +95 -0
  31. package/src/_stories/Chart.DynamicSeries.stories.tsx +19 -0
  32. package/src/_stories/Chart.Filters.stories.tsx +4 -0
  33. package/src/_stories/Chart.Forecast.stories.tsx +4 -0
  34. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +22 -0
  35. package/src/_stories/Chart.Legend.Gradient.stories.tsx +28 -0
  36. package/src/_stories/Chart.Patterns.stories.tsx +4 -0
  37. package/src/_stories/Chart.PreserveDecimals.stories.tsx +25 -0
  38. package/src/_stories/Chart.Regions.Categorical.stories.tsx +13 -0
  39. package/src/_stories/Chart.Regions.DateScale.stories.tsx +19 -0
  40. package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +25 -10
  41. package/src/_stories/Chart.ScatterPlot.stories.tsx +4 -0
  42. package/src/_stories/Chart.SmallMultiples.stories.tsx +16 -0
  43. package/src/_stories/Chart.SmallestLeftAxisMax.stories.tsx +64 -0
  44. package/src/_stories/Chart.stories.tsx +72 -1
  45. package/src/_stories/Chart.tooltip.stories.tsx +7 -0
  46. package/src/_stories/ChartAnnotation.stories.tsx +10 -0
  47. package/src/_stories/ChartAxisLabels.stories.tsx +4 -0
  48. package/src/_stories/ChartAxisTitles.stories.tsx +10 -0
  49. package/src/_stories/ChartBar.Editor.stories.tsx +97 -38
  50. package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
  51. package/src/_stories/ChartBrush.Matrix.Continuous.stories.tsx +41 -0
  52. package/src/_stories/ChartBrush.Matrix.Date.stories.tsx +114 -0
  53. package/src/_stories/ChartBrush.Matrix.DateTime.stories.tsx +78 -0
  54. package/src/_stories/ChartBrush.stories.tsx +7 -0
  55. package/src/_stories/ChartEditor.Editor.stories.tsx +1 -1
  56. package/src/_stories/ChartEditor.stories.tsx +7 -0
  57. package/src/_stories/ChartLine.QuadrantAngles.stories.tsx +89 -0
  58. package/src/_stories/ChartLine.Suppression.stories.tsx +7 -0
  59. package/src/_stories/ChartLine.Symbols.stories.tsx +4 -0
  60. package/src/_stories/ChartPrefixSuffix.stories.tsx +46 -1
  61. package/src/_stories/TechAdoptionWithLinks.stories.tsx +7 -0
  62. package/src/_stories/_mock/brush_continuous.json +86 -0
  63. package/src/_stories/_mock/brush_date_large.json +176 -0
  64. package/src/_stories/_mock/line_chart_angle_near_zero_fall.json +195 -0
  65. package/src/_stories/_mock/line_chart_angle_near_zero_rise.json +195 -0
  66. package/src/_stories/_mock/line_chart_angle_q1_steep_upward.json +195 -0
  67. package/src/_stories/_mock/line_chart_angle_q2_gentle_downward.json +195 -0
  68. package/src/_stories/_mock/line_chart_angle_q3_steep_downward.json +195 -0
  69. package/src/_stories/_mock/line_chart_angle_q4_gentle_upward.json +195 -0
  70. package/src/_stories/_mock/line_chart_quadrant_angles.json +264 -0
  71. package/src/_stories/_mock/paired-bar-abbr.json +421 -0
  72. package/src/_stories/_mock/pie_custom_colors.json +268 -0
  73. package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
  74. package/src/components/Annotations/components/AnnotationDraggable.styles.css +14 -20
  75. package/src/components/Annotations/components/AnnotationDraggable.tsx +240 -116
  76. package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -2
  77. package/src/components/Annotations/components/AnnotationDropdown.tsx +8 -12
  78. package/src/components/Annotations/components/AnnotationList.styles.css +12 -18
  79. package/src/components/Annotations/components/AnnotationList.tsx +5 -4
  80. package/src/components/Annotations/components/findNearestDatum.ts +75 -85
  81. package/src/components/Annotations/helpers/getVisibleAnnotations.ts +38 -0
  82. package/src/components/Axis/BottomAxis.tsx +277 -0
  83. package/src/components/Axis/LeftAxis.tsx +404 -0
  84. package/src/components/Axis/LeftAxisGridlines.tsx +77 -0
  85. package/src/components/Axis/PairedBarAxis.tsx +192 -0
  86. package/src/components/Axis/README.md +94 -0
  87. package/src/components/Axis/RightAxis.tsx +108 -0
  88. package/src/components/Axis/axis.constants.ts +21 -0
  89. package/src/components/Axis/index.ts +7 -0
  90. package/src/components/BarChart/components/BarChart.Horizontal.tsx +12 -28
  91. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -30
  92. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +12 -31
  93. package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -28
  94. package/src/components/BarChart/components/BarChart.tsx +7 -1
  95. package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
  96. package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
  97. package/src/components/BarChart/helpers/useBarChart.ts +3 -0
  98. package/src/components/Brush/BrushSelector.tsx +155 -22
  99. package/src/components/Brush/MiniChartPreview.tsx +133 -21
  100. package/src/components/EditorPanel/EditorPanel.tsx +81 -54
  101. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +67 -29
  102. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
  103. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +120 -2
  104. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +25 -43
  105. package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +353 -0
  106. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +83 -3
  107. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +66 -43
  108. package/src/components/EditorPanel/components/Panels/index.tsx +2 -0
  109. package/src/components/EditorPanel/editor-panel.scss +1 -1
  110. package/src/components/EditorPanel/useEditorPermissions.ts +55 -26
  111. package/src/components/ForestPlot/ForestPlot.tsx +26 -22
  112. package/src/components/HorizonChart/HorizonChart.tsx +131 -0
  113. package/src/components/HorizonChart/components/HorizonBand.tsx +160 -0
  114. package/src/components/HorizonChart/helpers/calculateHorizonBands.ts +27 -0
  115. package/src/components/HorizonChart/helpers/getHorizonLayerColors.ts +40 -0
  116. package/src/components/HorizonChart/index.tsx +3 -0
  117. package/src/components/Legend/Legend.Component.tsx +52 -4
  118. package/src/components/Legend/Legend.tsx +1 -1
  119. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +4 -4
  120. package/src/components/Legend/LegendValueRange.tsx +77 -0
  121. package/src/components/Legend/helpers/createFormatLabels.tsx +16 -2
  122. package/src/components/Legend/helpers/generateValueRanges.ts +92 -0
  123. package/src/components/LineChart/helpers/README.md +292 -0
  124. package/src/components/LineChart/helpers/labelPositioning.test.ts +245 -0
  125. package/src/components/LineChart/helpers/labelPositioning.ts +304 -0
  126. package/src/components/LineChart/index.tsx +44 -8
  127. package/src/components/LinearChart/README.md +109 -0
  128. package/src/components/LinearChart/VisualizationRenderer.tsx +267 -0
  129. package/src/components/LinearChart/linearChart.constants.ts +84 -0
  130. package/src/components/LinearChart/tests/LinearChart.test.tsx +278 -0
  131. package/src/components/LinearChart/tests/mockConfigContext.ts +131 -0
  132. package/src/components/LinearChart/utils/tickFormatting.ts +146 -0
  133. package/src/components/LinearChart.tsx +268 -1057
  134. package/src/components/PieChart/PieChart.tsx +20 -5
  135. package/src/components/RadarChart/RadarAxis.tsx +78 -0
  136. package/src/components/RadarChart/RadarChart.tsx +298 -0
  137. package/src/components/RadarChart/RadarGrid.tsx +64 -0
  138. package/src/components/RadarChart/RadarPolygon.tsx +91 -0
  139. package/src/components/RadarChart/helpers.ts +83 -0
  140. package/src/components/RadarChart/index.tsx +3 -0
  141. package/src/components/Regions/components/Regions.tsx +6 -6
  142. package/src/components/Sankey/components/Sankey.tsx +3 -3
  143. package/src/components/Sankey/sankey.scss +1 -1
  144. package/src/components/SmallMultiples/SmallMultiples.css +5 -5
  145. package/src/components/Sparkline/index.scss +4 -2
  146. package/src/components/WarmingStripes/WarmingStripes.tsx +95 -25
  147. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
  148. package/src/data/initial-state.js +37 -15
  149. package/src/data/legacy-defaults.ts +18 -0
  150. package/src/helpers/abbreviateNumber.ts +24 -17
  151. package/src/helpers/getChartPatternId.ts +17 -0
  152. package/src/helpers/getExcludedData.ts +4 -0
  153. package/src/helpers/getMinMax.ts +16 -2
  154. package/src/helpers/handleChartAriaLabels.ts +19 -19
  155. package/src/helpers/handleLineType.ts +22 -18
  156. package/src/helpers/seriesColumnSettings.ts +114 -0
  157. package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
  158. package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
  159. package/src/hooks/useProgrammaticTooltip.ts +23 -2
  160. package/src/hooks/useRightAxis.ts +14 -0
  161. package/src/hooks/useScales.ts +99 -56
  162. package/src/hooks/useTooltip.tsx +23 -3
  163. package/src/scss/main.scss +157 -79
  164. package/src/selectors/README.md +68 -0
  165. package/src/store/chart.reducer.ts +2 -0
  166. package/src/test/CdcChart.test.jsx +2 -2
  167. package/src/types/ChartConfig.ts +22 -0
  168. package/src/types/ChartContext.ts +1 -0
  169. package/src/types/Horizon.ts +64 -0
  170. package/tests/fixtures/chart-config-with-metadata.json +29 -0
  171. package/tests/fixtures/data-with-metadata.json +10 -0
  172. package/preview.html +0 -1616
  173. package/src/components/Annotations/components/helpers/index.tsx +0 -46
@@ -5,7 +5,7 @@ import ResizeObserver from 'resize-observer-polyfill'
5
5
  import 'whatwg-fetch'
6
6
  // Core components
7
7
  import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
8
- import Layout from '@cdc/core/components/Layout'
8
+ import { VisualizationContainer, VisualizationContent } from '@cdc/core/components/Layout'
9
9
  import Confirm from '@cdc/core/components/elements/Confirm'
10
10
  import Error from '@cdc/core/components/elements/Error'
11
11
  import SkipTo from '@cdc/core/components/elements/SkipTo'
@@ -22,15 +22,30 @@ 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
- import _ from 'lodash'
27
+ import cloneDeep from 'lodash/cloneDeep'
28
+ import defaultsDeep from 'lodash/defaultsDeep'
29
+ import lodashDefaults from 'lodash/defaults'
30
+ import findKey from 'lodash/findKey'
31
+ import forEach from 'lodash/forEach'
32
+ import get from 'lodash/get'
33
+ import isEmpty from 'lodash/isEmpty'
34
+ import isEqual from 'lodash/isEqual'
35
+ import isString from 'lodash/isString'
36
+ import kebabCase from 'lodash/kebabCase'
37
+ import pick from 'lodash/pick'
38
+ import remove from 'lodash/remove'
39
+ import set from 'lodash/set'
40
+ import uniq from 'lodash/uniq'
41
+ import xor from 'lodash/xor'
28
42
  // Primary Components
29
43
  import ConfigContext, { ChartDispatchContext } from './ConfigContext'
30
44
  import PieChart from './components/PieChart'
45
+ import RadarChart from './components/RadarChart'
31
46
  import SankeyChart from './components/Sankey'
32
47
  import LinearChart from './components/LinearChart'
33
- import { isDateScale } from '@cdc/core/helpers/cove/date'
48
+ import { isDateScale, formatDate as coreFormatDate } from '@cdc/core/helpers/cove/date'
34
49
 
35
50
  import { twoColorPalette } from '@cdc/core/data/colorPalettes'
36
51
  import { filterChartColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
@@ -39,6 +54,7 @@ import SparkLine from './components/Sparkline'
39
54
  import Legend from './components/Legend'
40
55
  import WarmingStripesGradientLegend from './components/WarmingStripes/WarmingStripesGradientLegend'
41
56
  import defaults from './data/initial-state'
57
+ import { LEGACY_CHART_DEFAULTS } from './data/legacy-defaults'
42
58
  import EditorPanel from './components/EditorPanel'
43
59
  import { abbreviateNumber } from './helpers/abbreviateNumber'
44
60
  import { handleChartTabbing } from './helpers/handleChartTabbing'
@@ -53,8 +69,10 @@ import Loading from '@cdc/core/components/Loading'
53
69
  import Filters from '@cdc/core/components/Filters'
54
70
  import MediaControls from '@cdc/core/components/MediaControls'
55
71
  import Annotation from './components/Annotations'
72
+ import { getVisibleAnnotations } from './components/Annotations/helpers/getVisibleAnnotations'
56
73
  // Core Helpers
57
74
  import { DataTransform } from '@cdc/core/helpers/DataTransform'
75
+ import { backfillDefaults } from '@cdc/core/helpers/backfillDefaults'
58
76
  import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
59
77
  import { missingRequiredSections } from '@cdc/core/helpers/missingRequiredSections'
60
78
  import { filterVizData } from '@cdc/core/helpers/filterVizData'
@@ -88,6 +106,8 @@ import { Datasets } from '@cdc/core/types/DataSet'
88
106
  import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
89
107
  import cloneConfig from '@cdc/core/helpers/cloneConfig'
90
108
  import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
109
+ import { ENABLE_CHART_MAP_TP5_TREATMENT, ENABLE_CHART_VISUAL_SETTINGS } from '@cdc/core/helpers/constants'
110
+ import CalloutFlag from '@cdc/core/assets/callout-flag.svg?url'
91
111
 
92
112
  interface CdcChartProps {
93
113
  config?: ChartConfig
@@ -133,7 +153,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
133
153
  coveLoadedEventRan,
134
154
  imageId,
135
155
  seriesHighlight,
136
- colorScale
156
+ colorScale,
157
+ brushData
137
158
  } = state
138
159
  const { description, visualizationType } = config
139
160
  const svgRef = useRef(null)
@@ -171,36 +192,32 @@ const CdcChart: React.FC<CdcChartProps> = ({
171
192
  }
172
193
  }
173
194
 
195
+ const markupOptions = {
196
+ isEditor,
197
+ filters: config.filters || [],
198
+ locale: config.locale,
199
+ dataMetadata: config.dataMetadata
200
+ }
201
+
174
202
  return {
175
203
  title: title
176
- ? processMarkupVariables(title, config.data || [], config.markupVariables, {
177
- isEditor,
178
- filters: config.filters || []
179
- }).processedContent
204
+ ? processMarkupVariables(title, config.data || [], config.markupVariables, markupOptions).processedContent
180
205
  : title,
181
206
  superTitle: config.superTitle
182
- ? processMarkupVariables(config.superTitle, config.data || [], config.markupVariables, {
183
- isEditor,
184
- filters: config.filters || []
185
- }).processedContent
207
+ ? processMarkupVariables(config.superTitle, config.data || [], config.markupVariables, markupOptions)
208
+ .processedContent
186
209
  : config.superTitle,
187
210
  introText: config.introText
188
- ? processMarkupVariables(config.introText, config.data || [], config.markupVariables, {
189
- isEditor,
190
- filters: config.filters || []
191
- }).processedContent
211
+ ? processMarkupVariables(config.introText, config.data || [], config.markupVariables, markupOptions)
212
+ .processedContent
192
213
  : config.introText,
193
214
  legacyFootnotes: config.legacyFootnotes
194
- ? processMarkupVariables(config.legacyFootnotes, config.data || [], config.markupVariables, {
195
- isEditor,
196
- filters: config.filters || []
197
- }).processedContent
215
+ ? processMarkupVariables(config.legacyFootnotes, config.data || [], config.markupVariables, markupOptions)
216
+ .processedContent
198
217
  : config.legacyFootnotes,
199
218
  description: config.description
200
- ? processMarkupVariables(config.description, config.data || [], config.markupVariables, {
201
- isEditor,
202
- filters: config.filters || []
203
- }).processedContent
219
+ ? processMarkupVariables(config.description, config.data || [], config.markupVariables, markupOptions)
220
+ .processedContent
204
221
  : config.description
205
222
  }
206
223
  }, [
@@ -234,10 +251,6 @@ const CdcChart: React.FC<CdcChartProps> = ({
234
251
  const { lineDatapointClass, contentClasses, sparkLineStyles } = useDataVizClasses(config)
235
252
  const legendId = useId()
236
253
 
237
- const hasDateAxis =
238
- (config.xAxis || config.yAxis) && ['date-time', 'date'].includes((config.xAxis || config.yAxis).type)
239
- const dataTableDefaultSortBy = hasDateAxis && config.xAxis.dataKey
240
-
241
254
  const convertLineToBarGraph = isConvertLineToBarGraph(config, filteredData)
242
255
 
243
256
  // Declaratively calculate series keys for pie charts based on filtered data
@@ -245,7 +258,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
245
258
  if (config.visualizationType !== 'Pie' || !config.xAxis?.dataKey) return null
246
259
  const data = filteredData?.length > 0 ? filteredData : excludedData
247
260
  if (!data) return null
248
- return _.uniq(data.map(d => d[config.xAxis.dataKey]))
261
+ return uniq(data.map(d => d[config.xAxis.dataKey]))
249
262
  }, [config.visualizationType, config.xAxis?.dataKey, filteredData, excludedData])
250
263
 
251
264
  const prepareConfig = (loadedConfig: ChartConfig) => {
@@ -263,16 +276,47 @@ const CdcChart: React.FC<CdcChartProps> = ({
263
276
  delete defaultsWithoutPalette.general?.palette
264
277
  }
265
278
 
279
+ // Override palette defaults for Line charts specifically
280
+ if (loadedConfig?.visualizationType === 'Line' && !loadedConfig?.general?.palette) {
281
+ if (!defaultsWithoutPalette.general) {
282
+ defaultsWithoutPalette.general = {}
283
+ }
284
+ defaultsWithoutPalette.general.palette = {
285
+ isReversed: false,
286
+ version: '2.0',
287
+ name: 'divergent_blue_cyan'
288
+ }
289
+ }
290
+
291
+ // Override palette defaults for Horizon Chart specifically
292
+ if (loadedConfig?.visualizationType === 'Horizon Chart' && !loadedConfig?.general?.palette) {
293
+ if (!defaultsWithoutPalette.general) {
294
+ defaultsWithoutPalette.general = {}
295
+ }
296
+ defaultsWithoutPalette.general.palette = {
297
+ isReversed: false,
298
+ version: '2.0',
299
+ name: 'sequential_blue'
300
+ }
301
+ }
302
+
266
303
  let newConfig = { ...defaultsWithoutPalette, ...loadedConfig }
267
304
 
268
- _.defaultsDeep(newConfig, {
305
+ // Ensure Horizon Chart has enough palette colors for all layers
306
+ if (newConfig.visualizationType === 'Horizon Chart') {
307
+ const numLayers = newConfig.horizon?.numLayers ?? 4
308
+ const currentCount = get(newConfig, 'general.paletteColorCount', 4)
309
+ set(newConfig, 'general.paletteColorCount', Math.max(currentCount, numLayers))
310
+ }
311
+
312
+ defaultsDeep(newConfig, {
269
313
  table: { showVertical: false }
270
314
  })
271
315
 
272
- _.set(newConfig, 'table.show', _.get(newConfig, 'table.show', !isDashboard))
316
+ set(newConfig, 'table.show', get(newConfig, 'table.show', !isDashboard))
273
317
 
274
- _.forEach(newConfig.series, series => {
275
- _.defaults(series, {
318
+ forEach(newConfig.series, series => {
319
+ lodashDefaults(series, {
276
320
  tooltip: true,
277
321
  axis: 'Left'
278
322
  })
@@ -291,15 +335,18 @@ const CdcChart: React.FC<CdcChartProps> = ({
291
335
  let processedYAxis = targetConfig.yAxis?.label
292
336
 
293
337
  if (targetConfig.enableMarkupVariables && targetConfig.markupVariables?.length) {
338
+ const axisMarkupOptions = {
339
+ isEditor,
340
+ filters: targetConfig.filters || [],
341
+ locale: targetConfig.locale,
342
+ dataMetadata: targetConfig.dataMetadata
343
+ }
294
344
  if (targetConfig.xAxis?.label) {
295
345
  processedXAxis = processMarkupVariables(
296
346
  targetConfig.xAxis.label,
297
347
  dataSource || [],
298
348
  targetConfig.markupVariables,
299
- {
300
- isEditor,
301
- filters: targetConfig.filters || []
302
- }
349
+ axisMarkupOptions
303
350
  ).processedContent
304
351
  }
305
352
  if (targetConfig.yAxis?.label) {
@@ -307,10 +354,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
307
354
  targetConfig.yAxis.label,
308
355
  dataSource || [],
309
356
  targetConfig.markupVariables,
310
- {
311
- isEditor,
312
- filters: targetConfig.filters || []
313
- }
357
+ axisMarkupOptions
314
358
  ).processedContent
315
359
  }
316
360
  }
@@ -342,12 +386,17 @@ const CdcChart: React.FC<CdcChartProps> = ({
342
386
  const { processedXAxis, processedYAxis, runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant } =
343
387
  getProcessedAxisLabels(newConfig, data || [])
344
388
 
345
- // Deeper copy
346
- Object.keys(defaults).forEach(key => {
347
- if (newConfig[key] && 'object' === typeof newConfig[key] && !Array.isArray(newConfig[key])) {
348
- newConfig[key] = { ...defaults[key], ...newConfig[key] }
389
+ // Backfill missing properties from defaults, respecting legacy values
390
+ backfillDefaults(newConfig, defaults, LEGACY_CHART_DEFAULTS)
391
+
392
+ // Auto-populate table.defaultSort for date-axis charts if not already set by user
393
+ const hasDateAxisType = ['date-time', 'date'].includes(newConfig.xAxis?.type)
394
+ if (hasDateAxisType && newConfig.xAxis?.dataKey && !newConfig.table?.defaultSort?.column) {
395
+ newConfig.table = {
396
+ ...newConfig.table,
397
+ defaultSort: { column: newConfig.xAxis.dataKey, sortDirection: 'desc' }
349
398
  }
350
- })
399
+ }
351
400
 
352
401
  const newExcludedData: any[] = getExcludedData(newConfig, dataOverride || stateData)
353
402
  dispatch({ type: 'SET_EXCLUDED_DATA', payload: newExcludedData })
@@ -374,7 +423,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
374
423
  const shouldPreserveError = existingErrorMessage && !isPieChartValidationError
375
424
 
376
425
  newConfig.runtime = {} as Runtime
377
- newConfig.runtime.series = _.cloneDeep(newConfig.series)
426
+ newConfig.runtime.series = cloneDeep(newConfig.series)
378
427
  newConfig.runtime.seriesLabels = {}
379
428
  newConfig.runtime.seriesLabelsAll = []
380
429
  newConfig.runtime.originalXAxis = newConfig.xAxis
@@ -382,17 +431,22 @@ const CdcChart: React.FC<CdcChartProps> = ({
382
431
  if (newConfig.visualizationType === 'Pie') {
383
432
  // Use the same data that will be passed to PieChart (after exclusions and filters)
384
433
  const pieData = currentData.length > 0 ? currentData : newExcludedData
385
- newConfig.runtime.seriesKeys = _.uniq(pieData.map(d => d[newConfig.xAxis.dataKey]))
434
+ newConfig.runtime.seriesKeys = uniq(pieData.map(d => d[newConfig.xAxis.dataKey]))
386
435
  newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
387
436
  newConfig.runtime.isPieChart = true // Flag to know when to use derived keys
437
+ } else if (newConfig.visualizationType === 'Radar') {
438
+ // Radar chart: seriesKeys are the entity names from xAxis.dataKey
439
+ const radarData = currentData.length > 0 ? currentData : newExcludedData
440
+ newConfig.runtime.seriesKeys = uniq(radarData.map(d => d[newConfig.xAxis.dataKey]))
441
+ newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
388
442
  } else {
389
443
  const finalData = dataOverride || newConfig.formattedData || newConfig.data
390
444
  newConfig.runtime.seriesKeys = (newConfig.runtime.series || []).flatMap(series => {
391
445
  if (series.dynamicCategory) {
392
- _.remove(newConfig.runtime.seriesLabelsAll, label => label === series.dataKey)
393
- _.remove(newConfig.runtime.series, s => s.dataKey === series.dataKey)
446
+ remove(newConfig.runtime.seriesLabelsAll, label => label === series.dataKey)
447
+ remove(newConfig.runtime.series, s => s.dataKey === series.dataKey)
394
448
  // grab the dynamic series keys from the data
395
- const seriesKeys: string[] = _.uniq(finalData.map(d => d[series.dynamicCategory]))
449
+ const seriesKeys: string[] = uniq(finalData.map(d => d[series.dynamicCategory]))
396
450
  // for each of those keys perform side effects
397
451
  seriesKeys.forEach(dataKey => {
398
452
  newConfig.runtime.seriesLabels[dataKey] = dataKey
@@ -458,10 +512,26 @@ const CdcChart: React.FC<CdcChartProps> = ({
458
512
  newConfig.visualizationSubType = 'stacked'
459
513
  }
460
514
 
515
+ if (newConfig.visualizationType === 'Horizon Chart' && newConfig.series) {
516
+ // Apply horizon defaults if not set
517
+ newConfig.horizon = {
518
+ numLayers: 4,
519
+ mode: 'offset', // Always offset for now, mirror hidden from UI
520
+ bandGap: 15,
521
+ bottomPadding: 15,
522
+ ...newConfig.horizon
523
+ }
524
+
525
+ // Set categorical as default xAxis type for horizon charts if not already set
526
+ if (!newConfig.xAxis.type) {
527
+ newConfig.xAxis.type = 'categorical'
528
+ }
529
+ }
530
+
461
531
  if (isHorizontalVariant) {
462
532
  // For horizontal charts, axes are swapped, so processedYAxis goes to runtime.xAxis and vice versa
463
- const horizontalXAxisSource = _.cloneDeep((newConfig.yAxis as any)?.yAxis || newConfig.yAxis)
464
- const horizontalYAxisSource = _.cloneDeep((newConfig.xAxis as any)?.xAxis || newConfig.xAxis)
533
+ const horizontalXAxisSource = cloneDeep((newConfig.yAxis as any)?.yAxis || newConfig.yAxis)
534
+ const horizontalYAxisSource = cloneDeep((newConfig.xAxis as any)?.xAxis || newConfig.xAxis)
465
535
  newConfig.runtime.xAxis = {
466
536
  ...horizontalXAxisSource,
467
537
  label: runtimeXAxisLabel ?? horizontalXAxisSource?.label
@@ -604,7 +674,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
604
674
  if (newConfig.dataUrl && !urlFilters) {
605
675
  // handle urls with spaces in the name.
606
676
  if (newConfig.dataUrl) newConfig.dataUrl = `${newConfig.dataUrl}`
607
- let newData = await fetchRemoteData(newConfig.dataUrl)
677
+ let { data: newData, dataMetadata } = await fetchRemoteData(newConfig.dataUrl)
678
+ newConfig.dataMetadata = dataMetadata
608
679
 
609
680
  if (newConfig.vegaConfig) {
610
681
  newData = extractCoveData(updateVegaData(newConfig.vegaConfig, newData))
@@ -618,7 +689,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
618
689
  if (newData) {
619
690
  newConfig.data = newData
620
691
  }
621
- } else if (newConfig.formattedData) {
692
+ } else if (newConfig.formattedData && Array.isArray(newConfig.formattedData)) {
622
693
  newConfig.data = newConfig.formattedData
623
694
  } else if (newConfig.dataDescription) {
624
695
  // For dashboard contexts, get data from datasets if config.data is undefined
@@ -654,7 +725,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
654
725
  updateConfig(preparedConfig, preppedData.data)
655
726
  }
656
727
  } catch (err) {
657
- console.error('Could not Load!')
728
+ console.error('Could not Load!', err)
658
729
  }
659
730
  }
660
731
 
@@ -665,7 +736,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
665
736
  * When cove has a config and container ref publish the cove_loaded event.
666
737
  */
667
738
  useEffect(() => {
668
- if (container && !isLoading && !_.isEmpty(config) && !coveLoadedEventRan) {
739
+ if (container && !isLoading && !isEmpty(config) && !coveLoadedEventRan) {
669
740
  publish('cove_loaded', { config: config })
670
741
  dispatch({ type: 'SET_LOADED_EVENT', payload: true })
671
742
  }
@@ -720,7 +791,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
720
791
  }, [externalFilters]) // eslint-disable-line
721
792
 
722
793
  // Declaratively update runtime series keys for pie charts when derived value changes
723
- if (config.runtime?.isPieChart && pieSeriesKeys && !_.isEqual(pieSeriesKeys, config.runtime?.seriesKeys)) {
794
+ if (config.runtime?.isPieChart && pieSeriesKeys && !isEqual(pieSeriesKeys, config.runtime?.seriesKeys)) {
724
795
  const newConfig = {
725
796
  ...config,
726
797
  runtime: {
@@ -746,11 +817,21 @@ const CdcChart: React.FC<CdcChartProps> = ({
746
817
  }
747
818
  }, [config, stateData]) // eslint-disable-line
748
819
 
820
+ // Clear brush selection when brush slider is disabled
821
+ useEffect(() => {
822
+ const isBrushDisabled = !config?.xAxis?.brushActive
823
+ const hasBrushData = Array.isArray(brushData) && brushData.length > 0
824
+
825
+ if (isBrushDisabled && hasBrushData) {
826
+ dispatch({ type: 'SET_BRUSH_DATA', payload: [] })
827
+ }
828
+ }, [config?.xAxis?.brushActive, brushData])
829
+
749
830
  // Updates runtime axis labels when config or data changes when using markup variables
750
831
  useEffect(() => {
751
832
  if (
752
833
  !config?.runtime ||
753
- _.isEmpty(config.runtime) ||
834
+ isEmpty(config.runtime) ||
754
835
  (!config.runtime.xAxis && !config.runtime.yAxis) ||
755
836
  !config.markupVariables?.length
756
837
  ) {
@@ -760,7 +841,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
760
841
  const dataSource = (stateData && stateData.length ? stateData : config.data) || []
761
842
  const { runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant } = getProcessedAxisLabels(config, dataSource)
762
843
 
763
- const runtimeClone = _.cloneDeep(config.runtime)
844
+ const runtimeClone = cloneDeep(config.runtime)
764
845
 
765
846
  if (!runtimeClone?.xAxis || !runtimeClone?.yAxis) {
766
847
  return
@@ -799,9 +880,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
799
880
  return handleShowAll()
800
881
  }
801
882
 
802
- const newHighlight = _.findKey(config.runtime.seriesLabels, v => v === label.datum) || label.datum
883
+ const newHighlight = findKey(config.runtime.seriesLabels, v => v === label.datum) || label.datum
803
884
 
804
- const newSeriesHighlight = _.xor(seriesHighlight, [newHighlight])
885
+ const newSeriesHighlight = xor(seriesHighlight, [newHighlight])
805
886
  dispatch({ type: 'SET_SERIES_HIGHLIGHT', payload: newSeriesHighlight })
806
887
  }
807
888
  // Called on reset button click, unhighlights all data series
@@ -844,15 +925,11 @@ const CdcChart: React.FC<CdcChartProps> = ({
844
925
  const formatDate = (date, i, ticks) => {
845
926
  const displayFormat =
846
927
  config.runtime[section].dateDisplayFormat || config.runtime[section].dateParseFormat || '%Y-%m-%d'
847
- let formattedDate = timeFormat(displayFormat)(date)
848
- // Handle the case where all months work with '%b.' except for May
849
- if (displayFormat?.includes('%b.') && formattedDate.includes('May.')) {
850
- formattedDate = formattedDate.replace(/May\./g, 'May')
851
- }
928
+ let formattedDate = coreFormatDate(displayFormat, date, config.locale)
852
929
  // Show years only once
853
930
  if (config.xAxis.showYearsOnce && displayFormat?.includes('%Y') && ticks) {
854
931
  const prevDate = ticks[i - 1] ? ticks[i - 1].value : null
855
- const prevFormattedDate = timeFormat(displayFormat)(prevDate)
932
+ const prevFormattedDate = coreFormatDate(displayFormat, prevDate, config.locale)
856
933
  const year = formattedDate.match(/\d{4}/)
857
934
  const prevYear = prevFormattedDate.match(/\d{4}/)
858
935
  if (year && prevYear && year[0] === prevYear[0]) {
@@ -863,7 +940,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
863
940
  }
864
941
 
865
942
  const formatTooltipsDate = date => {
866
- return timeFormat(config.tooltips.dateDisplayFormat)(date)
943
+ return coreFormatDate(config.tooltips.dateDisplayFormat, date, config.locale)
867
944
  }
868
945
 
869
946
  // Format numeric data based on settings in config OR from passed in settings for Additional Columns
@@ -1005,16 +1082,16 @@ const CdcChart: React.FC<CdcChartProps> = ({
1005
1082
  ) {
1006
1083
  num = num // eslint-disable-line
1007
1084
  } else {
1008
- num = num.toLocaleString('en-US', stringFormattingOptions)
1085
+ num = num.toLocaleString(config.locale, stringFormattingOptions)
1009
1086
  }
1010
1087
  let result = ''
1011
1088
 
1012
1089
  if (abbreviated && axis === 'left' && shouldAbbreviate) {
1013
- num = abbreviateNumber(parseFloat(num))
1090
+ num = abbreviateNumber(parseFloat(num), config.locale)
1014
1091
  }
1015
1092
 
1016
1093
  if (bottomAbbreviated && axis === 'bottom' && shouldAbbreviate) {
1017
- num = abbreviateNumber(parseFloat(num))
1094
+ num = abbreviateNumber(parseFloat(num), config.locale)
1018
1095
  }
1019
1096
 
1020
1097
  if (addColPrefix && axis === 'left') {
@@ -1082,8 +1159,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
1082
1159
  if (!Array.isArray(data)) return []
1083
1160
  if (config.visualizationType === 'Forecasting') return data
1084
1161
  // specify keys that needs to be cleaned to render chart and skip rest
1085
- const CIkeys: string[] = Object.values(_.get(config, 'confidenceKeys', {})) as string[]
1086
- const seriesKeys: string[] = _.get(config, 'series', []).map((s: any) => s.dataKey)
1162
+ const CIkeys: string[] = Object.values(get(config, 'confidenceKeys', {})) as string[]
1163
+ const seriesKeys: string[] = get(config, 'series', []).map((s: any) => s.dataKey)
1087
1164
  const keysToClean: string[] = [...(seriesKeys ?? []), ...(CIkeys ?? [])]
1088
1165
 
1089
1166
  // key that does not need to be cleaned
@@ -1105,7 +1182,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1105
1182
  .map(col => col.name)
1106
1183
  .concat([dynamicSeries.dynamicCategory, dynamicSeries.dataKey])
1107
1184
  if (config.xAxis?.dataKey) usedColumns.push(config.xAxis.dataKey)
1108
- return data.map(d => _.pick(d, usedColumns))
1185
+ return data.map(d => pick(d, usedColumns))
1109
1186
  }
1110
1187
 
1111
1188
  const pivotDynamicSeries = (config: ChartConfig): TableConfig => {
@@ -1118,17 +1195,56 @@ const CdcChart: React.FC<CdcChartProps> = ({
1118
1195
  return tableConfig
1119
1196
  }
1120
1197
 
1198
+ // Transform and clean data for chart rendering
1199
+ const transformedData = getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean })
1200
+
1201
+ // Filter annotations to only those visible in current data view
1202
+ const visibleAnnotations = getVisibleAnnotations(config.annotations, transformedData, config.xAxis?.dataKey)
1203
+ const isTp5Treatment = ENABLE_CHART_MAP_TP5_TREATMENT && config.visual?.tp5Treatment
1204
+ const visualSettingClasses = new Set([
1205
+ 'component--has-border-color-theme',
1206
+ 'component--has-accent',
1207
+ 'component--has-background',
1208
+ 'component--hide-background-color'
1209
+ ])
1210
+ const tp5Classes = new Set(['component--tp5-treatment', 'component--tp5-treatment-background'])
1211
+ const bodyClasses = contentClasses.filter(className => {
1212
+ if (!ENABLE_CHART_VISUAL_SETTINGS && visualSettingClasses.has(className)) return false
1213
+ if (!ENABLE_CHART_MAP_TP5_TREATMENT && tp5Classes.has(className)) return false
1214
+ return true
1215
+ })
1216
+ if (config.visualizationType === 'Spark Line' && config.visual?.background) {
1217
+ bodyClasses.push('component--has-background')
1218
+ }
1219
+ if (config.visualizationType === 'Spark Line' && config.visual?.hideBackgroundColor) {
1220
+ bodyClasses.push('component--hide-background-color')
1221
+ }
1222
+ if (isTp5Treatment && !bodyClasses.includes('no-borders')) bodyClasses.push('no-borders')
1223
+ const chartTitle = (
1224
+ <Title
1225
+ showTitle={config.showTitle}
1226
+ isDashboard={isDashboard}
1227
+ title={title}
1228
+ superTitle={processedSuperTitle}
1229
+ titleStyle={isTp5Treatment ? 'small' : config.titleStyle}
1230
+ classes={['chart-title', `${config.theme}`, 'cove-visualization__title', isTp5Treatment ? '' : 'mb-3']}
1231
+ style={undefined}
1232
+ config={config}
1233
+ />
1234
+ )
1235
+
1121
1236
  // Prevent render if loading
1122
1237
  let body = <Loading />
1123
1238
 
1124
1239
  const makeClassName = string => {
1125
- if (!_.isString(string)) return undefined
1240
+ if (!isString(string)) return undefined
1126
1241
 
1127
- return _.kebabCase(string)
1242
+ return kebabCase(string)
1128
1243
  }
1129
1244
  const getChartWrapperClasses = () => {
1130
1245
  const isLegendOnBottom = legend?.position === 'bottom' || isLegendWrapViewport(currentViewport)
1131
- const classes = ['chart-container', 'p-relative']
1246
+ const classes = ['chart-container', 'visualization-container', 'p-relative']
1247
+ const visualSettingClasses = ['component--has-border-color-theme', 'component--has-accent']
1132
1248
  if (legend?.position) {
1133
1249
  if (isLegendWrapViewport(currentViewport) && legend?.position !== 'top') {
1134
1250
  classes.push('legend-bottom')
@@ -1137,16 +1253,23 @@ const CdcChart: React.FC<CdcChartProps> = ({
1137
1253
  }
1138
1254
  }
1139
1255
  if (legend?.hide) classes.push('legend-hidden')
1256
+ if (contentClasses.includes('sparkline')) classes.push('sparkline')
1140
1257
  if (lineDatapointClass) classes.push(lineDatapointClass)
1141
1258
  if (!config.barHasBorder) classes.push('chart-bar--no-border')
1142
1259
  if (config.xAxis.brushActive && dashboardConfig?.type === 'dashboard' && (!isLegendOnBottom || legend.hide))
1143
1260
  classes.push('dashboard-brush')
1144
- classes.push(...contentClasses)
1261
+
1262
+ if (!ENABLE_CHART_VISUAL_SETTINGS) {
1263
+ const filtered = classes.filter(className => !visualSettingClasses.includes(className))
1264
+ if (!filtered.includes('no-borders')) filtered.push('no-borders')
1265
+ return filtered
1266
+ }
1267
+
1145
1268
  return classes
1146
1269
  }
1147
1270
 
1148
1271
  const getChartSubTextClasses = () => {
1149
- const classes = ['subtext mt-4']
1272
+ const classes = ['subtext']
1150
1273
  const isLegendOnBottom = legend?.position === 'bottom' || isLegendWrapViewport(currentViewport)
1151
1274
 
1152
1275
  if (config.isResponsiveTicks) classes.push('subtext--responsive-ticks ')
@@ -1161,198 +1284,34 @@ const CdcChart: React.FC<CdcChartProps> = ({
1161
1284
  )
1162
1285
  body = (
1163
1286
  <>
1164
- {isEditor && <EditorPanel datasets={datasets} />}
1165
- <Layout.Responsive isEditor={isEditor}>
1166
- {config.newViz && <Confirm updateConfig={updateConfig} config={config} />}
1167
- {!missingRequiredSections(config) && !config.newViz && (
1168
- <div
1169
- className={`cdc-chart-inner-container cove-component__content type-${makeClassName(
1170
- config.visualizationType
1171
- )}`}
1172
- aria-label={handleChartAriaLabels(config)}
1173
- tabIndex={0}
1174
- >
1175
- <Title
1176
- showTitle={config.showTitle}
1177
- isDashboard={isDashboard}
1178
- title={title}
1179
- superTitle={processedSuperTitle}
1180
- titleStyle={config.titleStyle}
1181
- classes={['chart-title', `${config.theme}`, 'cove-component__header', 'mb-3']}
1182
- style={undefined}
1183
- config={config}
1184
- />
1185
-
1186
- {/* Error Message Display - Show at top before visualization wrapper */}
1187
- {/* {(() => {
1188
- const errorMessage = config.runtime?.editorErrorMessage
1189
- const hasError = errorMessage && typeof errorMessage === 'string' && errorMessage.trim() !== ''
1190
- const shouldShow = undefined === config.newViz && isEditor && config.runtime && hasError
1191
- return shouldShow ? <Error errorMessage={errorMessage} /> : null
1192
- })()} */}
1193
-
1194
- {/* Visualization Wrapper */}
1195
- <div className={getChartWrapperClasses().join(' ')}>
1196
- {/* Intro Text/Message */}
1197
- {processedIntroText && config.visualizationType !== 'Spark Line' && (
1198
- <section className={`introText mb-4`}>{parse(processedIntroText)}</section>
1199
- )}
1200
-
1201
- {/* Filters */}
1202
- {config.filters && !externalFilters && config.visualizationType !== 'Spark Line' && (
1203
- <Filters
1204
- config={config}
1205
- setFilters={setFilters}
1206
- excludedData={excludedData}
1207
- dimensions={dimensions}
1208
- interactionLabel={interactionLabel}
1209
- />
1210
- )}
1211
- <SkipTo skipId={handleChartTabbing(config, legendId)} skipMessage='Skip Over Chart Container' />
1212
- {config.annotations?.length > 0 && (
1213
- <SkipTo
1214
- skipId={handleChartTabbing(config, legendId)}
1215
- skipMessage={`Skip over annotations`}
1216
- key={`skip-annotations`}
1217
- />
1218
- )}
1219
- <LegendWrapper>
1220
- <div
1221
- className={
1222
- legend.hide || isLegendWrapViewport(currentViewport)
1223
- ? 'w-100'
1224
- : legend.position === 'bottom' ||
1225
- legend.position === 'top' ||
1226
- visualizationType === 'Sankey' ||
1227
- visualizationType === 'Spark Line'
1228
- ? 'w-100'
1229
- : 'w-75'
1230
- }
1231
- >
1232
- {/* Check if there is data to display */}
1233
- {(!filteredData || filteredData.length === 0) && (
1234
- <div className='no-data-message' style={{ padding: '2rem', textAlign: 'center', color: '#666' }}>
1235
- {config.chartMessage?.noData || 'No Data Available'}
1236
- </div>
1237
- )}
1238
-
1239
- {/* All charts with LinearChart */}
1240
- {filteredData &&
1241
- filteredData.length > 0 &&
1242
- !['Spark Line', 'Line', 'Sankey', 'Pie', 'Sankey'].includes(config.visualizationType) && (
1243
- <div ref={parentRef} style={{ width: `100%` }}>
1244
- <ParentSize>
1245
- {parent => (
1246
- <LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
1247
- )}
1248
- </ParentSize>
1249
- </div>
1250
- )}
1251
-
1252
- {filteredData && filteredData.length > 0 && config.visualizationType === 'Pie' && (
1253
- <ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
1254
- {parent => (
1255
- <PieChart
1256
- ref={svgRef}
1257
- parentWidth={parent.width}
1258
- parentHeight={parent.height}
1259
- interactionLabel={interactionLabel}
1260
- />
1261
- )}
1262
- </ParentSize>
1263
- )}
1264
- {/* Line Chart */}
1265
- {filteredData &&
1266
- filteredData.length > 0 &&
1267
- config.visualizationType === 'Line' &&
1268
- (convertLineToBarGraph ? (
1269
- <div ref={parentRef} style={{ width: `100%` }}>
1270
- <ParentSize>
1271
- {parent => (
1272
- <LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
1273
- )}
1274
- </ParentSize>
1275
- </div>
1276
- ) : (
1277
- <div ref={parentRef} style={{ width: '100%' }}>
1278
- <ParentSize>
1279
- {parent => {
1280
- const labelMargin = 120
1281
- const widthReduction =
1282
- config.showLineSeriesLabels &&
1283
- (config.legend.position !== 'right' || config.legend.hide)
1284
- ? labelMargin
1285
- : 0
1286
- return (
1287
- <LinearChart
1288
- ref={svgRef}
1289
- parentWidth={parent.width - widthReduction}
1290
- parentHeight={parent.height}
1291
- />
1292
- )
1293
- }}
1294
- </ParentSize>
1295
- </div>
1296
- ))}
1297
- {/* Sparkline */}
1298
- {config.visualizationType === 'Spark Line' && (
1299
- <>
1300
- <Filters
1301
- config={config}
1302
- setFilters={setFilters}
1303
- excludedData={excludedData}
1304
- dimensions={dimensions}
1305
- interactionLabel={interactionLabel}
1306
- />
1307
- {processedIntroText && (
1308
- <section className='introText mb-4' style={{ padding: '0px 0 35px' }}>
1309
- {parse(processedIntroText)}
1310
- </section>
1311
- )}
1312
- <div style={{ height: `100px`, width: `100%`, ...sparkLineStyles }}>
1313
- <ParentSize>{parent => <SparkLine width={parent.width} height={parent.height} />}</ParentSize>
1314
- </div>
1315
- {description && (
1316
- <div className='subtext' style={{ padding: '35px 0 15px' }}>
1317
- {parse(description)}
1318
- </div>
1319
- )}
1320
- </>
1321
- )}
1322
- {/* Sankey */}
1323
- {config.visualizationType === 'Sankey' && (
1324
- <ParentSize aria-hidden='true'>
1325
- {parent => <SankeyChart runtime={config.runtime} width={parent.width} height={parent.height} />}
1326
- </ParentSize>
1327
- )}
1328
- </div>
1329
- {/* Legend */}
1330
- {!config.legend.hide &&
1331
- config.visualizationType !== 'Spark Line' &&
1332
- config.visualizationType !== 'Sankey' &&
1333
- !(config.visualizationType === 'Warming Stripes' && config.legend?.style === 'gradient') &&
1334
- !(config.visualizationType === 'Warming Stripes' && config.smallMultiples?.mode) && (
1335
- <Legend
1336
- ref={legendRef}
1337
- skipId={handleChartTabbing(config, legendId)}
1338
- interactionLabel={interactionLabel}
1339
- />
1340
- )}
1341
- {config.visualizationType === 'Warming Stripes' &&
1342
- config.legend?.style === 'gradient' &&
1343
- !config.smallMultiples?.mode && <WarmingStripesGradientLegend />}
1344
- </LegendWrapper>
1345
- {/* Link */}
1287
+ {config.newViz && <Confirm updateConfig={updateConfig} config={config} />}
1288
+ {!missingRequiredSections(config) && !config.newViz && (
1289
+ <VisualizationContent
1290
+ innerClassName={`type-${makeClassName(config.visualizationType)}`}
1291
+ innerProps={{ 'aria-label': handleChartAriaLabels(config), tabIndex: 0 }}
1292
+ bodyClassName={bodyClasses.join(' ')}
1293
+ bodyWrapClassName={isTp5Treatment ? 'cdc-callout d-flex flex-column tp5-chart-callout' : ''}
1294
+ filters={
1295
+ config.filters?.length > 0 && !externalFilters && config.visualizationType !== 'Spark Line' ? (
1296
+ <Filters
1297
+ config={config}
1298
+ setFilters={setFilters}
1299
+ excludedData={excludedData}
1300
+ dimensions={dimensions}
1301
+ interactionLabel={interactionLabel}
1302
+ />
1303
+ ) : undefined
1304
+ }
1305
+ bodySubtext={
1306
+ processedDescription && config.visualizationType !== 'Spark Line' ? (
1307
+ <div className={getChartSubTextClasses().join(' ')}>{parse(processedDescription)}</div>
1308
+ ) : null
1309
+ }
1310
+ bodyFooter={
1311
+ <>
1346
1312
  {isDashboard && config.table && config.table.show && config.table.showDataTableLink
1347
1313
  ? tableLink
1348
1314
  : link && link}
1349
- {/* Description */}
1350
-
1351
- {processedDescription && config.visualizationType !== 'Spark Line' && (
1352
- <div className={getChartSubTextClasses().join(' ')}>{parse(processedDescription)}</div>
1353
- )}
1354
-
1355
- {/* Data Table */}
1356
1315
  {(config.xAxis.dataKey &&
1357
1316
  config.table.show &&
1358
1317
  config.visualizationType !== 'Spark Line' &&
@@ -1381,15 +1340,12 @@ const CdcChart: React.FC<CdcChartProps> = ({
1381
1340
 
1382
1341
  return (
1383
1342
  <DataTable
1384
- /* changing the "key" will force the table to re-render
1385
- when the default sort changes while editing */
1386
- key={dataTableDefaultSortBy}
1343
+ key={config.table?.defaultSort?.column || ''}
1387
1344
  config={dataTableConfig}
1388
1345
  rawData={dataTableRawData}
1389
1346
  runtimeData={dataTableRuntimeData}
1390
1347
  expandDataTable={config.table.expanded}
1391
1348
  columns={dataTableColumns}
1392
- defaultSortBy={dataTableDefaultSortBy}
1393
1349
  displayGeoName={name => name}
1394
1350
  applyLegendToRow={applyLegendToRow}
1395
1351
  tableTitle={config.table.label}
@@ -1402,6 +1358,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1402
1358
  showDownloadImgButton={config.table.showDownloadImgButton}
1403
1359
  showDownloadPdfButton={config.table.showDownloadPdfButton}
1404
1360
  includeContextInDownload={config.table?.includeContextInDownload}
1361
+ hasSubtextAbove={Boolean(processedDescription && config.visualizationType !== 'Spark Line')}
1405
1362
  interactionLabel={interactionLabel}
1406
1363
  />
1407
1364
  )
@@ -1432,22 +1389,178 @@ const CdcChart: React.FC<CdcChartProps> = ({
1432
1389
  </MediaControls.Section>
1433
1390
  </div>
1434
1391
  )}
1435
- {config?.annotations?.length > 0 && <Annotation.Dropdown />}
1436
- {/* show pdf or image button */}
1392
+ {visibleAnnotations.length > 0 && <Annotation.Dropdown />}
1437
1393
  {processedLegacyFootnotes && (
1438
1394
  <section className='footnotes pt-2 mt-4'>{parse(processedLegacyFootnotes)}</section>
1439
1395
  )}
1440
- </div>
1396
+ </>
1397
+ }
1398
+ header={isTp5Treatment ? null : chartTitle}
1399
+ messageIsIntroText={config.visualizationType !== 'Spark Line' && !!processedIntroText}
1400
+ message={config.visualizationType !== 'Spark Line' && processedIntroText ? parse(processedIntroText) : null}
1401
+ footer={
1441
1402
  <FootnotesStandAlone
1442
1403
  config={configObj.footnotes}
1443
1404
  filters={config.filters?.filter(f => f.filterFootnotes)}
1444
1405
  markupVariables={config.markupVariables}
1445
1406
  enableMarkupVariables={config.enableMarkupVariables}
1446
1407
  data={config.data}
1408
+ dataMetadata={config.dataMetadata}
1447
1409
  />
1410
+ }
1411
+ >
1412
+ {isTp5Treatment && <img src={CalloutFlag} alt='' className='cdc-callout__flag' aria-hidden='true' />}
1413
+ {isTp5Treatment && chartTitle}
1414
+ <div className={getChartWrapperClasses().join(' ')}>
1415
+ <SkipTo skipId={handleChartTabbing(config, legendId)} skipMessage='Skip Over Chart Container' />
1416
+ {visibleAnnotations.length > 0 && (
1417
+ <SkipTo
1418
+ skipId={handleChartTabbing(config, legendId)}
1419
+ skipMessage={`Skip over annotations`}
1420
+ key={`skip-annotations`}
1421
+ />
1422
+ )}
1423
+ <LegendWrapper>
1424
+ <div
1425
+ className={
1426
+ legend.hide || isLegendWrapViewport(currentViewport)
1427
+ ? 'w-100'
1428
+ : legend.position === 'bottom' ||
1429
+ legend.position === 'top' ||
1430
+ visualizationType === 'Sankey' ||
1431
+ visualizationType === 'Spark Line'
1432
+ ? 'w-100'
1433
+ : 'w-75'
1434
+ }
1435
+ >
1436
+ {/* Check if there is data to display */}
1437
+ {(!filteredData || filteredData.length === 0) && (
1438
+ <div className='no-data-message' style={{ padding: '2rem', textAlign: 'center', color: '#666' }}>
1439
+ {config.chartMessage?.noData || 'No Data Available'}
1440
+ </div>
1441
+ )}
1442
+
1443
+ {/* All charts with LinearChart */}
1444
+ {filteredData &&
1445
+ filteredData.length > 0 &&
1446
+ !['Spark Line', 'Line', 'Sankey', 'Pie', 'Radar'].includes(config.visualizationType) && (
1447
+ <div ref={parentRef} style={{ width: `100%` }}>
1448
+ <ParentSize>
1449
+ {parent => (
1450
+ <LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
1451
+ )}
1452
+ </ParentSize>
1453
+ </div>
1454
+ )}
1455
+
1456
+ {filteredData && filteredData.length > 0 && config.visualizationType === 'Pie' && (
1457
+ <ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
1458
+ {parent => (
1459
+ <PieChart
1460
+ ref={svgRef}
1461
+ parentWidth={parent.width}
1462
+ parentHeight={parent.height}
1463
+ interactionLabel={interactionLabel}
1464
+ />
1465
+ )}
1466
+ </ParentSize>
1467
+ )}
1468
+ {/* Radar Chart */}
1469
+ {filteredData && filteredData.length > 0 && config.visualizationType === 'Radar' && (
1470
+ <ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
1471
+ {parent => (
1472
+ <RadarChart
1473
+ ref={svgRef}
1474
+ parentWidth={parent.width}
1475
+ parentHeight={parent.height}
1476
+ interactionLabel={interactionLabel}
1477
+ />
1478
+ )}
1479
+ </ParentSize>
1480
+ )}
1481
+ {/* Line Chart */}
1482
+ {filteredData &&
1483
+ filteredData.length > 0 &&
1484
+ config.visualizationType === 'Line' &&
1485
+ (convertLineToBarGraph ? (
1486
+ <div ref={parentRef} style={{ width: `100%` }}>
1487
+ <ParentSize>
1488
+ {parent => (
1489
+ <LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
1490
+ )}
1491
+ </ParentSize>
1492
+ </div>
1493
+ ) : (
1494
+ <div ref={parentRef} style={{ width: '100%' }}>
1495
+ <ParentSize>
1496
+ {parent => {
1497
+ const labelMargin = 120
1498
+ const widthReduction =
1499
+ config.showLineSeriesLabels && (config.legend.position !== 'right' || config.legend.hide)
1500
+ ? labelMargin
1501
+ : 0
1502
+ return (
1503
+ <LinearChart
1504
+ ref={svgRef}
1505
+ parentWidth={parent.width - widthReduction}
1506
+ parentHeight={parent.height}
1507
+ />
1508
+ )
1509
+ }}
1510
+ </ParentSize>
1511
+ </div>
1512
+ ))}
1513
+ {/* Sparkline */}
1514
+ {config.visualizationType === 'Spark Line' && (
1515
+ <>
1516
+ <Filters
1517
+ config={config}
1518
+ setFilters={setFilters}
1519
+ excludedData={excludedData}
1520
+ dimensions={dimensions}
1521
+ interactionLabel={interactionLabel}
1522
+ />
1523
+ {processedIntroText && (
1524
+ <section className='introText' style={{ padding: '0px 0 35px' }}>
1525
+ {parse(processedIntroText)}
1526
+ </section>
1527
+ )}
1528
+ <div style={{ height: `100px`, width: `100%`, ...sparkLineStyles }}>
1529
+ <ParentSize>{parent => <SparkLine width={parent.width} height={parent.height} />}</ParentSize>
1530
+ </div>
1531
+ {processedDescription && (
1532
+ <div className='subtext' style={{ padding: '35px 0 1.5rem' }}>
1533
+ {parse(processedDescription)}
1534
+ </div>
1535
+ )}
1536
+ </>
1537
+ )}
1538
+ {/* Sankey */}
1539
+ {config.visualizationType === 'Sankey' && (
1540
+ <ParentSize aria-hidden='true'>
1541
+ {parent => <SankeyChart runtime={config.runtime} width={parent.width} height={parent.height} />}
1542
+ </ParentSize>
1543
+ )}
1544
+ </div>
1545
+ {/* Legend */}
1546
+ {!config.legend.hide &&
1547
+ config.visualizationType !== 'Spark Line' &&
1548
+ config.visualizationType !== 'Sankey' &&
1549
+ !(config.visualizationType === 'Warming Stripes' && config.legend?.style === 'gradient') &&
1550
+ !(config.visualizationType === 'Warming Stripes' && config.smallMultiples?.mode) && (
1551
+ <Legend
1552
+ ref={legendRef}
1553
+ skipId={handleChartTabbing(config, legendId)}
1554
+ interactionLabel={interactionLabel}
1555
+ />
1556
+ )}
1557
+ {config.visualizationType === 'Warming Stripes' &&
1558
+ config.legend?.style === 'gradient' &&
1559
+ !config.smallMultiples?.mode && <WarmingStripesGradientLegend />}
1560
+ </LegendWrapper>
1448
1561
  </div>
1449
- )}
1450
- </Layout.Responsive>
1562
+ </VisualizationContent>
1563
+ )}
1451
1564
  </>
1452
1565
  )
1453
1566
  }
@@ -1505,25 +1618,26 @@ const CdcChart: React.FC<CdcChartProps> = ({
1505
1618
  setSharedFilterValue,
1506
1619
  svgRef,
1507
1620
  tableData: filteredData || excludedData,
1508
- transformedData: getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean }),
1621
+ transformedData,
1509
1622
  twoColorPalette,
1510
1623
  unfilteredData: stateData,
1511
- updateConfig
1624
+ updateConfig,
1625
+ visibleAnnotations
1512
1626
  }
1513
1627
 
1514
1628
  return (
1515
1629
  <ConfigContext.Provider value={contextValues}>
1516
1630
  <ChartDispatchContext.Provider value={dispatch}>
1517
- <Layout.VisualizationWrapper
1631
+ <VisualizationContainer
1518
1632
  config={config}
1519
1633
  isEditor={isEditor}
1520
1634
  currentViewport={currentViewport}
1521
1635
  ref={outerContainerRef}
1522
1636
  imageId={imageId}
1523
- showEditorPanel={config?.showEditorPanel}
1637
+ editorPanel={!isLoading ? <EditorPanel datasets={datasets} /> : null}
1524
1638
  >
1525
1639
  {body}
1526
- </Layout.VisualizationWrapper>
1640
+ </VisualizationContainer>
1527
1641
  </ChartDispatchContext.Provider>
1528
1642
  </ConfigContext.Provider>
1529
1643
  )