@cdc/chart 4.26.2 → 4.26.4

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 (165) hide show
  1. package/CONFIG.md +169 -0
  2. package/README.md +42 -17
  3. package/dist/cdcchart.js +39419 -35180
  4. package/examples/__data__/data-with-metadata.json +10 -0
  5. package/examples/feature/bar/additional-column-tooltip.json +3 -3
  6. package/examples/feature/deviation/planet-deviation-config.json +2 -2
  7. package/examples/feature/paired-bar/paired-bar-example.json +2 -2
  8. package/examples/feature/pie/planet-pie-example-config.json +2 -1
  9. package/examples/feature/tests-case-rate/case-rate-example-config.json +2 -2
  10. package/examples/feature/tests-covid/covid-confidence-example-config.json +2 -2
  11. package/examples/feature/tests-covid/covid-example-config.json +2 -2
  12. package/examples/feature/tests-cutoff/cutoff-example-config.json +2 -2
  13. package/examples/feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json +2 -2
  14. package/examples/metadata-variables.json +58 -0
  15. package/examples/minimal-example.json +24 -0
  16. package/examples/private/test2.json +35012 -0
  17. package/package.json +4 -4
  18. package/src/CdcChart.tsx +8 -4
  19. package/src/CdcChartComponent.tsx +326 -290
  20. package/src/_stories/Chart.CustomColors.smoke.stories.tsx +97 -0
  21. package/src/_stories/Chart.Defaults.smoke.stories.tsx +95 -0
  22. package/src/_stories/{Chart.HTMLInDataTable.stories.tsx → Chart.HTMLInDataTable.smoke.stories.tsx} +10 -0
  23. package/src/_stories/Chart.Minimal.smoke.stories.tsx +25 -0
  24. package/src/_stories/{Chart.SmallMultiples.stories.tsx → Chart.SmallMultiples.smoke.stories.tsx} +17 -1
  25. package/src/_stories/Chart.SmallestLeftAxisMax.smoke.stories.tsx +64 -0
  26. package/src/_stories/{Chart.stories.tsx → Chart.smoke.stories.tsx} +48 -2
  27. package/src/_stories/{Chart.tooltip.stories.tsx → Chart.tooltip.smoke.stories.tsx} +68 -0
  28. package/src/_stories/ChartBar.Editor.stories.tsx +177 -115
  29. package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
  30. package/src/_stories/ChartEditor.Editor.stories.tsx +24 -1
  31. package/src/_stories/{Chart.CustomColors.stories.tsx → ChartLine.DashedDates.smoke.stories.tsx} +4 -4
  32. package/src/_stories/_mock/line_chart_dashed_dates.json +276 -0
  33. package/src/_stories/_mock/paired-bar-abbr.json +421 -0
  34. package/src/_stories/_mock/paired-bar.json +1 -1
  35. package/src/_stories/_mock/pie_custom_colors.json +268 -0
  36. package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
  37. package/src/components/Annotations/components/AnnotationDraggable.styles.css +10 -10
  38. package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -1
  39. package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -1
  40. package/src/components/Annotations/components/AnnotationList.styles.css +11 -11
  41. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -2
  42. package/src/components/Axis/BottomAxis.tsx +10 -3
  43. package/src/components/Axis/LeftAxis.tsx +2 -4
  44. package/src/components/Axis/LeftAxisGridlines.tsx +1 -3
  45. package/src/components/Axis/PairedBarAxis.tsx +10 -4
  46. package/src/components/BarChart/components/BarChart.Horizontal.tsx +21 -33
  47. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +21 -35
  48. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +21 -36
  49. package/src/components/BarChart/components/BarChart.Vertical.tsx +21 -34
  50. package/src/components/BarChart/components/BarChart.tsx +2 -6
  51. package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
  52. package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
  53. package/src/components/BarChart/helpers/useBarChart.ts +4 -1
  54. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +3 -3
  55. package/src/components/Brush/BrushSelector.tsx +2 -1
  56. package/src/components/Brush/MiniChartPreview.tsx +21 -26
  57. package/src/components/EditorPanel/EditorPanel.tsx +692 -954
  58. package/src/components/EditorPanel/EditorPanelContext.ts +1 -1
  59. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +218 -167
  60. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
  61. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +39 -1
  62. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +169 -135
  63. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +154 -109
  64. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  65. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +95 -28
  66. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +45 -42
  67. package/src/components/EditorPanel/editor-panel.scss +1 -1
  68. package/src/components/EditorPanel/useEditorPermissions.ts +1 -10
  69. package/src/components/Forecasting/Forecasting.tsx +2 -2
  70. package/src/components/ForestPlot/ForestPlot.tsx +26 -22
  71. package/src/components/HorizonChart/HorizonChart.tsx +10 -2
  72. package/src/components/HoverLine/HoverLine.tsx +6 -8
  73. package/src/components/Legend/Legend.Component.tsx +4 -4
  74. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +9 -4
  75. package/src/components/Legend/LegendGroup/LegendGroup.tsx +3 -1
  76. package/src/components/Legend/helpers/createFormatLabels.tsx +3 -2
  77. package/src/components/Legend/helpers/index.ts +0 -2
  78. package/src/components/LineChart/LineChartProps.ts +1 -0
  79. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +3 -2
  80. package/src/components/LineChart/index.tsx +11 -4
  81. package/src/components/LinearChart/VisualizationRenderer.tsx +11 -1
  82. package/src/components/LinearChart/tests/LinearChart.test.tsx +224 -0
  83. package/src/components/LinearChart/tests/mockConfigContext.ts +2 -0
  84. package/src/components/LinearChart.tsx +124 -47
  85. package/src/components/PairedBarChart.jsx +2 -2
  86. package/src/components/PieChart/PieChart.tsx +30 -13
  87. package/src/components/RadarChart/RadarChart.tsx +1 -1
  88. package/src/components/Regions/components/Regions.tsx +36 -26
  89. package/src/components/Sankey/components/Sankey.tsx +3 -3
  90. package/src/components/Sankey/sankey.scss +1 -1
  91. package/src/components/ScatterPlot/ScatterPlot.jsx +19 -17
  92. package/src/components/SmallMultiples/SmallMultipleTile.tsx +1 -0
  93. package/src/components/SmallMultiples/SmallMultiples.css +5 -5
  94. package/src/components/Sparkline/components/SparkLine.tsx +1 -1
  95. package/src/components/Sparkline/index.scss +4 -2
  96. package/src/components/WarmingStripes/WarmingStripes.tsx +5 -3
  97. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
  98. package/src/data/initial-state.js +24 -14
  99. package/src/data/legacy-defaults.ts +18 -0
  100. package/src/helpers/abbreviateNumber.ts +24 -17
  101. package/src/helpers/calculateLeftYAxisWidth.ts +193 -0
  102. package/src/helpers/getChartPatternId.ts +17 -0
  103. package/src/helpers/getMinMax.ts +16 -2
  104. package/src/helpers/seriesColumnSettings.ts +114 -0
  105. package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
  106. package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
  107. package/src/helpers/tests/tooltipHelpers.test.ts +151 -0
  108. package/src/helpers/tooltipHelpers.ts +176 -0
  109. package/src/hooks/useHighlightedBars.ts +9 -16
  110. package/src/hooks/useProgrammaticTooltip.ts +16 -2
  111. package/src/hooks/useRightAxis.ts +14 -0
  112. package/src/hooks/useScales.ts +92 -56
  113. package/src/hooks/useSmallMultipleSynchronization.ts +3 -4
  114. package/src/hooks/useTooltip.tsx +186 -40
  115. package/src/scss/main.scss +152 -79
  116. package/src/test/CdcChart.test.jsx +26 -2
  117. package/src/types/ChartConfig.ts +5 -0
  118. package/tests/fixtures/chart-config-with-metadata.json +29 -0
  119. package/tests/fixtures/data-with-metadata.json +10 -0
  120. package/examples/chart-regression-2.json +0 -2360
  121. package/examples/feature/__data__/age-adjusted-rates.json +0 -1486
  122. package/examples/feature/__data__/city-temperature.json +0 -2198
  123. package/examples/feature/bar/Barchart_with_negative.json +0 -34
  124. package/examples/feature/boxplot/box-plot-data.json +0 -71
  125. package/examples/feature/boxplot/boxplot-data.json +0 -156
  126. package/examples/feature/combo/combochart-categories_are_numbers .json +0 -18
  127. package/examples/feature/forest-plot/log.json +0 -26
  128. package/examples/feature/tests-date-exclusions/date-exclusions-data.json +0 -162
  129. package/examples/region-issue.json +0 -2065
  130. package/examples/xaxis.json +0 -493
  131. /package/examples/feature/bar/{tall-data.json → __data__/tall-data.json} +0 -0
  132. /package/examples/feature/deviation/{planet-deviation-data.json → __data__/planet-deviation-data.json} +0 -0
  133. /package/examples/feature/paired-bar/{paired-bar-data.json → __data__/paired-bar-data.json} +0 -0
  134. /package/examples/feature/paired-bar/{paired-bar-formatted.json → __data__/paired-bar-formatted.json} +0 -0
  135. /package/examples/feature/tests-case-rate/{case-rate-example-data.json → __data__/case-rate-example-data.json} +0 -0
  136. /package/examples/feature/tests-covid/{covid-example-data-confidence.json → __data__/covid-example-data-confidence.json} +0 -0
  137. /package/examples/feature/tests-covid/{covid-example-data.json → __data__/covid-example-data.json} +0 -0
  138. /package/examples/feature/tests-cutoff/{cutoff-example-data.json → __data__/cutoff-example-data.json} +0 -0
  139. /package/examples/feature/tests-non-numerics/{planet-example-data-nonnumeric.json → __data__/planet-example-data-nonnumeric.json} +0 -0
  140. /package/src/_stories/{Chart.Anchors.stories.tsx → Chart.Anchors.smoke.stories.tsx} +0 -0
  141. /package/src/_stories/{Chart.BoxPlot.stories.tsx → Chart.BoxPlot.smoke.stories.tsx} +0 -0
  142. /package/src/_stories/{Chart.CI.stories.tsx → Chart.CI.smoke.stories.tsx} +0 -0
  143. /package/src/_stories/{Chart.Combo.stories.tsx → Chart.Combo.smoke.stories.tsx} +0 -0
  144. /package/src/_stories/{Chart.DynamicSeries.stories.tsx → Chart.DynamicSeries.smoke.stories.tsx} +0 -0
  145. /package/src/_stories/{Chart.Filters.stories.tsx → Chart.Filters.smoke.stories.tsx} +0 -0
  146. /package/src/_stories/{Chart.Forecast.stories.tsx → Chart.Forecast.smoke.stories.tsx} +0 -0
  147. /package/src/_stories/{Chart.Legend.Gradient.stories.tsx → Chart.Legend.Gradient.smoke.stories.tsx} +0 -0
  148. /package/src/_stories/{Chart.Patterns.stories.tsx → Chart.Patterns.smoke.stories.tsx} +0 -0
  149. /package/src/_stories/{Chart.PreserveDecimals.stories.tsx → Chart.PreserveDecimals.smoke.stories.tsx} +0 -0
  150. /package/src/_stories/{Chart.Regions.Categorical.stories.tsx → Chart.Regions.Categorical.smoke.stories.tsx} +0 -0
  151. /package/src/_stories/{Chart.Regions.DateScale.stories.tsx → Chart.Regions.DateScale.smoke.stories.tsx} +0 -0
  152. /package/src/_stories/{Chart.Regions.DateTimeScale.stories.tsx → Chart.Regions.DateTimeScale.smoke.stories.tsx} +0 -0
  153. /package/src/_stories/{Chart.ScatterPlot.stories.tsx → Chart.ScatterPlot.smoke.stories.tsx} +0 -0
  154. /package/src/_stories/{ChartAnnotation.stories.tsx → ChartAnnotation.smoke.stories.tsx} +0 -0
  155. /package/src/_stories/{ChartAxisLabels.stories.tsx → ChartAxisLabels.smoke.stories.tsx} +0 -0
  156. /package/src/_stories/{ChartAxisTitles.stories.tsx → ChartAxisTitles.smoke.stories.tsx} +0 -0
  157. /package/src/_stories/{ChartBrush.Matrix.Continuous.stories.tsx → ChartBrush.Matrix.Continuous.smoke.stories.tsx} +0 -0
  158. /package/src/_stories/{ChartBrush.Matrix.Date.stories.tsx → ChartBrush.Matrix.Date.smoke.stories.tsx} +0 -0
  159. /package/src/_stories/{ChartBrush.Matrix.DateTime.stories.tsx → ChartBrush.Matrix.DateTime.smoke.stories.tsx} +0 -0
  160. /package/src/_stories/{ChartBrush.stories.tsx → ChartBrush.smoke.stories.tsx} +0 -0
  161. /package/src/_stories/{ChartLine.QuadrantAngles.stories.tsx → ChartLine.QuadrantAngles.smoke.stories.tsx} +0 -0
  162. /package/src/_stories/{ChartLine.Suppression.stories.tsx → ChartLine.Suppression.smoke.stories.tsx} +0 -0
  163. /package/src/_stories/{ChartLine.Symbols.stories.tsx → ChartLine.Symbols.smoke.stories.tsx} +0 -0
  164. /package/src/_stories/{ChartPrefixSuffix.stories.tsx → ChartPrefixSuffix.smoke.stories.tsx} +0 -0
  165. /package/src/_stories/{TechAdoptionWithLinks.stories.tsx → TechAdoptionWithLinks.smoke.stories.tsx} +0 -0
@@ -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'
@@ -24,7 +24,21 @@ import { Label } from './types/Label'
24
24
  import ParentSize from '@visx/responsive/lib/components/ParentSize'
25
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'
@@ -40,6 +54,7 @@ import SparkLine from './components/Sparkline'
40
54
  import Legend from './components/Legend'
41
55
  import WarmingStripesGradientLegend from './components/WarmingStripes/WarmingStripesGradientLegend'
42
56
  import defaults from './data/initial-state'
57
+ import { LEGACY_CHART_DEFAULTS } from './data/legacy-defaults'
43
58
  import EditorPanel from './components/EditorPanel'
44
59
  import { abbreviateNumber } from './helpers/abbreviateNumber'
45
60
  import { handleChartTabbing } from './helpers/handleChartTabbing'
@@ -57,6 +72,7 @@ import Annotation from './components/Annotations'
57
72
  import { getVisibleAnnotations } from './components/Annotations/helpers/getVisibleAnnotations'
58
73
  // Core Helpers
59
74
  import { DataTransform } from '@cdc/core/helpers/DataTransform'
75
+ import { backfillDefaults } from '@cdc/core/helpers/backfillDefaults'
60
76
  import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
61
77
  import { missingRequiredSections } from '@cdc/core/helpers/missingRequiredSections'
62
78
  import { filterVizData } from '@cdc/core/helpers/filterVizData'
@@ -90,6 +106,8 @@ import { Datasets } from '@cdc/core/types/DataSet'
90
106
  import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
91
107
  import cloneConfig from '@cdc/core/helpers/cloneConfig'
92
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'
93
111
 
94
112
  interface CdcChartProps {
95
113
  config?: ChartConfig
@@ -174,36 +192,32 @@ const CdcChart: React.FC<CdcChartProps> = ({
174
192
  }
175
193
  }
176
194
 
195
+ const markupOptions = {
196
+ isEditor,
197
+ filters: config.filters || [],
198
+ locale: config.locale,
199
+ dataMetadata: config.dataMetadata
200
+ }
201
+
177
202
  return {
178
203
  title: title
179
- ? processMarkupVariables(title, config.data || [], config.markupVariables, {
180
- isEditor,
181
- filters: config.filters || []
182
- }).processedContent
204
+ ? processMarkupVariables(title, config.data || [], config.markupVariables, markupOptions).processedContent
183
205
  : title,
184
206
  superTitle: config.superTitle
185
- ? processMarkupVariables(config.superTitle, config.data || [], config.markupVariables, {
186
- isEditor,
187
- filters: config.filters || []
188
- }).processedContent
207
+ ? processMarkupVariables(config.superTitle, config.data || [], config.markupVariables, markupOptions)
208
+ .processedContent
189
209
  : config.superTitle,
190
210
  introText: config.introText
191
- ? processMarkupVariables(config.introText, config.data || [], config.markupVariables, {
192
- isEditor,
193
- filters: config.filters || []
194
- }).processedContent
211
+ ? processMarkupVariables(config.introText, config.data || [], config.markupVariables, markupOptions)
212
+ .processedContent
195
213
  : config.introText,
196
214
  legacyFootnotes: config.legacyFootnotes
197
- ? processMarkupVariables(config.legacyFootnotes, config.data || [], config.markupVariables, {
198
- isEditor,
199
- filters: config.filters || []
200
- }).processedContent
215
+ ? processMarkupVariables(config.legacyFootnotes, config.data || [], config.markupVariables, markupOptions)
216
+ .processedContent
201
217
  : config.legacyFootnotes,
202
218
  description: config.description
203
- ? processMarkupVariables(config.description, config.data || [], config.markupVariables, {
204
- isEditor,
205
- filters: config.filters || []
206
- }).processedContent
219
+ ? processMarkupVariables(config.description, config.data || [], config.markupVariables, markupOptions)
220
+ .processedContent
207
221
  : config.description
208
222
  }
209
223
  }, [
@@ -237,10 +251,6 @@ const CdcChart: React.FC<CdcChartProps> = ({
237
251
  const { lineDatapointClass, contentClasses, sparkLineStyles } = useDataVizClasses(config)
238
252
  const legendId = useId()
239
253
 
240
- const hasDateAxis =
241
- (config.xAxis || config.yAxis) && ['date-time', 'date'].includes((config.xAxis || config.yAxis).type)
242
- const dataTableDefaultSortBy = hasDateAxis && config.xAxis.dataKey
243
-
244
254
  const convertLineToBarGraph = isConvertLineToBarGraph(config, filteredData)
245
255
 
246
256
  // Declaratively calculate series keys for pie charts based on filtered data
@@ -248,7 +258,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
248
258
  if (config.visualizationType !== 'Pie' || !config.xAxis?.dataKey) return null
249
259
  const data = filteredData?.length > 0 ? filteredData : excludedData
250
260
  if (!data) return null
251
- return _.uniq(data.map(d => d[config.xAxis.dataKey]))
261
+ return uniq(data.map(d => d[config.xAxis.dataKey]))
252
262
  }, [config.visualizationType, config.xAxis?.dataKey, filteredData, excludedData])
253
263
 
254
264
  const prepareConfig = (loadedConfig: ChartConfig) => {
@@ -295,18 +305,18 @@ const CdcChart: React.FC<CdcChartProps> = ({
295
305
  // Ensure Horizon Chart has enough palette colors for all layers
296
306
  if (newConfig.visualizationType === 'Horizon Chart') {
297
307
  const numLayers = newConfig.horizon?.numLayers ?? 4
298
- const currentCount = _.get(newConfig, 'general.paletteColorCount', 4)
299
- _.set(newConfig, 'general.paletteColorCount', Math.max(currentCount, numLayers))
308
+ const currentCount = get(newConfig, 'general.paletteColorCount', 4)
309
+ set(newConfig, 'general.paletteColorCount', Math.max(currentCount, numLayers))
300
310
  }
301
311
 
302
- _.defaultsDeep(newConfig, {
312
+ defaultsDeep(newConfig, {
303
313
  table: { showVertical: false }
304
314
  })
305
315
 
306
- _.set(newConfig, 'table.show', _.get(newConfig, 'table.show', !isDashboard))
316
+ set(newConfig, 'table.show', get(newConfig, 'table.show', !isDashboard))
307
317
 
308
- _.forEach(newConfig.series, series => {
309
- _.defaults(series, {
318
+ forEach(newConfig.series, series => {
319
+ lodashDefaults(series, {
310
320
  tooltip: true,
311
321
  axis: 'Left'
312
322
  })
@@ -325,15 +335,18 @@ const CdcChart: React.FC<CdcChartProps> = ({
325
335
  let processedYAxis = targetConfig.yAxis?.label
326
336
 
327
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
+ }
328
344
  if (targetConfig.xAxis?.label) {
329
345
  processedXAxis = processMarkupVariables(
330
346
  targetConfig.xAxis.label,
331
347
  dataSource || [],
332
348
  targetConfig.markupVariables,
333
- {
334
- isEditor,
335
- filters: targetConfig.filters || []
336
- }
349
+ axisMarkupOptions
337
350
  ).processedContent
338
351
  }
339
352
  if (targetConfig.yAxis?.label) {
@@ -341,10 +354,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
341
354
  targetConfig.yAxis.label,
342
355
  dataSource || [],
343
356
  targetConfig.markupVariables,
344
- {
345
- isEditor,
346
- filters: targetConfig.filters || []
347
- }
357
+ axisMarkupOptions
348
358
  ).processedContent
349
359
  }
350
360
  }
@@ -376,12 +386,17 @@ const CdcChart: React.FC<CdcChartProps> = ({
376
386
  const { processedXAxis, processedYAxis, runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant } =
377
387
  getProcessedAxisLabels(newConfig, data || [])
378
388
 
379
- // Deeper copy
380
- Object.keys(defaults).forEach(key => {
381
- if (newConfig[key] && 'object' === typeof newConfig[key] && !Array.isArray(newConfig[key])) {
382
- 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' }
383
398
  }
384
- })
399
+ }
385
400
 
386
401
  const newExcludedData: any[] = getExcludedData(newConfig, dataOverride || stateData)
387
402
  dispatch({ type: 'SET_EXCLUDED_DATA', payload: newExcludedData })
@@ -408,7 +423,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
408
423
  const shouldPreserveError = existingErrorMessage && !isPieChartValidationError
409
424
 
410
425
  newConfig.runtime = {} as Runtime
411
- newConfig.runtime.series = _.cloneDeep(newConfig.series)
426
+ newConfig.runtime.series = cloneDeep(newConfig.series)
412
427
  newConfig.runtime.seriesLabels = {}
413
428
  newConfig.runtime.seriesLabelsAll = []
414
429
  newConfig.runtime.originalXAxis = newConfig.xAxis
@@ -416,22 +431,22 @@ const CdcChart: React.FC<CdcChartProps> = ({
416
431
  if (newConfig.visualizationType === 'Pie') {
417
432
  // Use the same data that will be passed to PieChart (after exclusions and filters)
418
433
  const pieData = currentData.length > 0 ? currentData : newExcludedData
419
- newConfig.runtime.seriesKeys = _.uniq(pieData.map(d => d[newConfig.xAxis.dataKey]))
434
+ newConfig.runtime.seriesKeys = uniq(pieData.map(d => d[newConfig.xAxis.dataKey]))
420
435
  newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
421
436
  newConfig.runtime.isPieChart = true // Flag to know when to use derived keys
422
437
  } else if (newConfig.visualizationType === 'Radar') {
423
438
  // Radar chart: seriesKeys are the entity names from xAxis.dataKey
424
439
  const radarData = currentData.length > 0 ? currentData : newExcludedData
425
- newConfig.runtime.seriesKeys = _.uniq(radarData.map(d => d[newConfig.xAxis.dataKey]))
440
+ newConfig.runtime.seriesKeys = uniq(radarData.map(d => d[newConfig.xAxis.dataKey]))
426
441
  newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
427
442
  } else {
428
443
  const finalData = dataOverride || newConfig.formattedData || newConfig.data
429
444
  newConfig.runtime.seriesKeys = (newConfig.runtime.series || []).flatMap(series => {
430
445
  if (series.dynamicCategory) {
431
- _.remove(newConfig.runtime.seriesLabelsAll, label => label === series.dataKey)
432
- _.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)
433
448
  // grab the dynamic series keys from the data
434
- const seriesKeys: string[] = _.uniq(finalData.map(d => d[series.dynamicCategory]))
449
+ const seriesKeys: string[] = uniq(finalData.map(d => d[series.dynamicCategory]))
435
450
  // for each of those keys perform side effects
436
451
  seriesKeys.forEach(dataKey => {
437
452
  newConfig.runtime.seriesLabels[dataKey] = dataKey
@@ -515,8 +530,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
515
530
 
516
531
  if (isHorizontalVariant) {
517
532
  // For horizontal charts, axes are swapped, so processedYAxis goes to runtime.xAxis and vice versa
518
- const horizontalXAxisSource = _.cloneDeep((newConfig.yAxis as any)?.yAxis || newConfig.yAxis)
519
- 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)
520
535
  newConfig.runtime.xAxis = {
521
536
  ...horizontalXAxisSource,
522
537
  label: runtimeXAxisLabel ?? horizontalXAxisSource?.label
@@ -525,7 +540,6 @@ const CdcChart: React.FC<CdcChartProps> = ({
525
540
  ...horizontalYAxisSource,
526
541
  label: runtimeYAxisLabel ?? horizontalYAxisSource?.label
527
542
  }
528
- newConfig.runtime.yAxis.labelOffset *= -1
529
543
 
530
544
  newConfig.runtime.horizontal = false
531
545
  newConfig.orientation = 'horizontal'
@@ -659,7 +673,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
659
673
  if (newConfig.dataUrl && !urlFilters) {
660
674
  // handle urls with spaces in the name.
661
675
  if (newConfig.dataUrl) newConfig.dataUrl = `${newConfig.dataUrl}`
662
- let newData = await fetchRemoteData(newConfig.dataUrl)
676
+ let { data: newData, dataMetadata } = await fetchRemoteData(newConfig.dataUrl)
677
+ newConfig.dataMetadata = dataMetadata
663
678
 
664
679
  if (newConfig.vegaConfig) {
665
680
  newData = extractCoveData(updateVegaData(newConfig.vegaConfig, newData))
@@ -720,7 +735,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
720
735
  * When cove has a config and container ref publish the cove_loaded event.
721
736
  */
722
737
  useEffect(() => {
723
- if (container && !isLoading && !_.isEmpty(config) && !coveLoadedEventRan) {
738
+ if (container && !isLoading && !isEmpty(config) && !coveLoadedEventRan) {
724
739
  publish('cove_loaded', { config: config })
725
740
  dispatch({ type: 'SET_LOADED_EVENT', payload: true })
726
741
  }
@@ -775,7 +790,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
775
790
  }, [externalFilters]) // eslint-disable-line
776
791
 
777
792
  // Declaratively update runtime series keys for pie charts when derived value changes
778
- if (config.runtime?.isPieChart && pieSeriesKeys && !_.isEqual(pieSeriesKeys, config.runtime?.seriesKeys)) {
793
+ if (config.runtime?.isPieChart && pieSeriesKeys && !isEqual(pieSeriesKeys, config.runtime?.seriesKeys)) {
779
794
  const newConfig = {
780
795
  ...config,
781
796
  runtime: {
@@ -815,7 +830,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
815
830
  useEffect(() => {
816
831
  if (
817
832
  !config?.runtime ||
818
- _.isEmpty(config.runtime) ||
833
+ isEmpty(config.runtime) ||
819
834
  (!config.runtime.xAxis && !config.runtime.yAxis) ||
820
835
  !config.markupVariables?.length
821
836
  ) {
@@ -825,7 +840,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
825
840
  const dataSource = (stateData && stateData.length ? stateData : config.data) || []
826
841
  const { runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant } = getProcessedAxisLabels(config, dataSource)
827
842
 
828
- const runtimeClone = _.cloneDeep(config.runtime)
843
+ const runtimeClone = cloneDeep(config.runtime)
829
844
 
830
845
  if (!runtimeClone?.xAxis || !runtimeClone?.yAxis) {
831
846
  return
@@ -864,9 +879,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
864
879
  return handleShowAll()
865
880
  }
866
881
 
867
- const newHighlight = _.findKey(config.runtime.seriesLabels, v => v === label.datum) || label.datum
882
+ const newHighlight = findKey(config.runtime.seriesLabels, v => v === label.datum) || label.datum
868
883
 
869
- const newSeriesHighlight = _.xor(seriesHighlight, [newHighlight])
884
+ const newSeriesHighlight = xor(seriesHighlight, [newHighlight])
870
885
  dispatch({ type: 'SET_SERIES_HIGHLIGHT', payload: newSeriesHighlight })
871
886
  }
872
887
  // Called on reset button click, unhighlights all data series
@@ -909,11 +924,11 @@ const CdcChart: React.FC<CdcChartProps> = ({
909
924
  const formatDate = (date, i, ticks) => {
910
925
  const displayFormat =
911
926
  config.runtime[section].dateDisplayFormat || config.runtime[section].dateParseFormat || '%Y-%m-%d'
912
- let formattedDate = coreFormatDate(displayFormat, date)
927
+ let formattedDate = coreFormatDate(displayFormat, date, config.locale)
913
928
  // Show years only once
914
929
  if (config.xAxis.showYearsOnce && displayFormat?.includes('%Y') && ticks) {
915
930
  const prevDate = ticks[i - 1] ? ticks[i - 1].value : null
916
- const prevFormattedDate = coreFormatDate(displayFormat, prevDate)
931
+ const prevFormattedDate = coreFormatDate(displayFormat, prevDate, config.locale)
917
932
  const year = formattedDate.match(/\d{4}/)
918
933
  const prevYear = prevFormattedDate.match(/\d{4}/)
919
934
  if (year && prevYear && year[0] === prevYear[0]) {
@@ -924,7 +939,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
924
939
  }
925
940
 
926
941
  const formatTooltipsDate = date => {
927
- return coreFormatDate(config.tooltips.dateDisplayFormat, date)
942
+ return coreFormatDate(config.tooltips.dateDisplayFormat, date, config.locale)
928
943
  }
929
944
 
930
945
  // Format numeric data based on settings in config OR from passed in settings for Additional Columns
@@ -1066,16 +1081,16 @@ const CdcChart: React.FC<CdcChartProps> = ({
1066
1081
  ) {
1067
1082
  num = num // eslint-disable-line
1068
1083
  } else {
1069
- num = num.toLocaleString('en-US', stringFormattingOptions)
1084
+ num = num.toLocaleString(config.locale, stringFormattingOptions)
1070
1085
  }
1071
1086
  let result = ''
1072
1087
 
1073
1088
  if (abbreviated && axis === 'left' && shouldAbbreviate) {
1074
- num = abbreviateNumber(parseFloat(num))
1089
+ num = abbreviateNumber(parseFloat(num), config.locale)
1075
1090
  }
1076
1091
 
1077
1092
  if (bottomAbbreviated && axis === 'bottom' && shouldAbbreviate) {
1078
- num = abbreviateNumber(parseFloat(num))
1093
+ num = abbreviateNumber(parseFloat(num), config.locale)
1079
1094
  }
1080
1095
 
1081
1096
  if (addColPrefix && axis === 'left') {
@@ -1143,8 +1158,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
1143
1158
  if (!Array.isArray(data)) return []
1144
1159
  if (config.visualizationType === 'Forecasting') return data
1145
1160
  // specify keys that needs to be cleaned to render chart and skip rest
1146
- const CIkeys: string[] = Object.values(_.get(config, 'confidenceKeys', {})) as string[]
1147
- const seriesKeys: string[] = _.get(config, 'series', []).map((s: any) => s.dataKey)
1161
+ const CIkeys: string[] = Object.values(get(config, 'confidenceKeys', {})) as string[]
1162
+ const seriesKeys: string[] = get(config, 'series', []).map((s: any) => s.dataKey)
1148
1163
  const keysToClean: string[] = [...(seriesKeys ?? []), ...(CIkeys ?? [])]
1149
1164
 
1150
1165
  // key that does not need to be cleaned
@@ -1166,7 +1181,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1166
1181
  .map(col => col.name)
1167
1182
  .concat([dynamicSeries.dynamicCategory, dynamicSeries.dataKey])
1168
1183
  if (config.xAxis?.dataKey) usedColumns.push(config.xAxis.dataKey)
1169
- return data.map(d => _.pick(d, usedColumns))
1184
+ return data.map(d => pick(d, usedColumns))
1170
1185
  }
1171
1186
 
1172
1187
  const pivotDynamicSeries = (config: ChartConfig): TableConfig => {
@@ -1184,18 +1199,51 @@ const CdcChart: React.FC<CdcChartProps> = ({
1184
1199
 
1185
1200
  // Filter annotations to only those visible in current data view
1186
1201
  const visibleAnnotations = getVisibleAnnotations(config.annotations, transformedData, config.xAxis?.dataKey)
1202
+ const isTp5Treatment = ENABLE_CHART_MAP_TP5_TREATMENT && config.visual?.tp5Treatment
1203
+ const visualSettingClasses = new Set([
1204
+ 'component--has-border-color-theme',
1205
+ 'component--has-accent',
1206
+ 'component--has-background',
1207
+ 'component--hide-background-color'
1208
+ ])
1209
+ const tp5Classes = new Set(['component--tp5-treatment', 'component--tp5-treatment-background'])
1210
+ const bodyClasses = contentClasses.filter(className => {
1211
+ if (!ENABLE_CHART_VISUAL_SETTINGS && visualSettingClasses.has(className)) return false
1212
+ if (!ENABLE_CHART_MAP_TP5_TREATMENT && tp5Classes.has(className)) return false
1213
+ return true
1214
+ })
1215
+ if (config.visualizationType === 'Spark Line' && config.visual?.background) {
1216
+ bodyClasses.push('component--has-background')
1217
+ }
1218
+ if (config.visualizationType === 'Spark Line' && config.visual?.hideBackgroundColor) {
1219
+ bodyClasses.push('component--hide-background-color')
1220
+ }
1221
+ if (isTp5Treatment && !bodyClasses.includes('no-borders')) bodyClasses.push('no-borders')
1222
+ const chartTitle = (
1223
+ <Title
1224
+ showTitle={config.showTitle}
1225
+ isDashboard={isDashboard}
1226
+ title={title}
1227
+ superTitle={processedSuperTitle}
1228
+ titleStyle={isTp5Treatment ? 'small' : config.titleStyle}
1229
+ classes={['chart-title', `${config.theme}`, 'cove-visualization__title', isTp5Treatment ? '' : 'mb-3']}
1230
+ style={undefined}
1231
+ config={config}
1232
+ />
1233
+ )
1187
1234
 
1188
1235
  // Prevent render if loading
1189
1236
  let body = <Loading />
1190
1237
 
1191
1238
  const makeClassName = string => {
1192
- if (!_.isString(string)) return undefined
1239
+ if (!isString(string)) return undefined
1193
1240
 
1194
- return _.kebabCase(string)
1241
+ return kebabCase(string)
1195
1242
  }
1196
1243
  const getChartWrapperClasses = () => {
1197
1244
  const isLegendOnBottom = legend?.position === 'bottom' || isLegendWrapViewport(currentViewport)
1198
- const classes = ['chart-container', 'p-relative']
1245
+ const classes = ['chart-container', 'visualization-container', 'p-relative']
1246
+ const visualSettingClasses = ['component--has-border-color-theme', 'component--has-accent']
1199
1247
  if (legend?.position) {
1200
1248
  if (isLegendWrapViewport(currentViewport) && legend?.position !== 'top') {
1201
1249
  classes.push('legend-bottom')
@@ -1204,16 +1252,23 @@ const CdcChart: React.FC<CdcChartProps> = ({
1204
1252
  }
1205
1253
  }
1206
1254
  if (legend?.hide) classes.push('legend-hidden')
1255
+ if (contentClasses.includes('sparkline')) classes.push('sparkline')
1207
1256
  if (lineDatapointClass) classes.push(lineDatapointClass)
1208
1257
  if (!config.barHasBorder) classes.push('chart-bar--no-border')
1209
1258
  if (config.xAxis.brushActive && dashboardConfig?.type === 'dashboard' && (!isLegendOnBottom || legend.hide))
1210
1259
  classes.push('dashboard-brush')
1211
- classes.push(...contentClasses)
1260
+
1261
+ if (!ENABLE_CHART_VISUAL_SETTINGS) {
1262
+ const filtered = classes.filter(className => !visualSettingClasses.includes(className))
1263
+ if (!filtered.includes('no-borders')) filtered.push('no-borders')
1264
+ return filtered
1265
+ }
1266
+
1212
1267
  return classes
1213
1268
  }
1214
1269
 
1215
1270
  const getChartSubTextClasses = () => {
1216
- const classes = ['subtext mt-4']
1271
+ const classes = ['subtext']
1217
1272
  const isLegendOnBottom = legend?.position === 'bottom' || isLegendWrapViewport(currentViewport)
1218
1273
 
1219
1274
  if (config.isResponsiveTicks) classes.push('subtext--responsive-ticks ')
@@ -1228,211 +1283,34 @@ const CdcChart: React.FC<CdcChartProps> = ({
1228
1283
  )
1229
1284
  body = (
1230
1285
  <>
1231
- {isEditor && <EditorPanel datasets={datasets} />}
1232
- <Layout.Responsive isEditor={isEditor}>
1233
- {config.newViz && <Confirm updateConfig={updateConfig} config={config} />}
1234
- {!missingRequiredSections(config) && !config.newViz && (
1235
- <div
1236
- className={`cdc-chart-inner-container cove-component__content type-${makeClassName(
1237
- config.visualizationType
1238
- )}`}
1239
- aria-label={handleChartAriaLabels(config)}
1240
- tabIndex={0}
1241
- >
1242
- <Title
1243
- showTitle={config.showTitle}
1244
- isDashboard={isDashboard}
1245
- title={title}
1246
- superTitle={processedSuperTitle}
1247
- titleStyle={config.titleStyle}
1248
- classes={['chart-title', `${config.theme}`, 'cove-component__header', 'mb-3']}
1249
- style={undefined}
1250
- config={config}
1251
- />
1252
-
1253
- {/* Error Message Display - Show at top before visualization wrapper */}
1254
- {/* {(() => {
1255
- const errorMessage = config.runtime?.editorErrorMessage
1256
- const hasError = errorMessage && typeof errorMessage === 'string' && errorMessage.trim() !== ''
1257
- const shouldShow = undefined === config.newViz && isEditor && config.runtime && hasError
1258
- return shouldShow ? <Error errorMessage={errorMessage} /> : null
1259
- })()} */}
1260
-
1261
- {/* Visualization Wrapper */}
1262
- <div className={getChartWrapperClasses().join(' ')}>
1263
- {/* Intro Text/Message */}
1264
- {processedIntroText && config.visualizationType !== 'Spark Line' && (
1265
- <section className={`introText mb-4`}>{parse(processedIntroText)}</section>
1266
- )}
1267
-
1268
- {/* Filters */}
1269
- {config.filters && !externalFilters && config.visualizationType !== 'Spark Line' && (
1270
- <Filters
1271
- config={config}
1272
- setFilters={setFilters}
1273
- excludedData={excludedData}
1274
- dimensions={dimensions}
1275
- interactionLabel={interactionLabel}
1276
- />
1277
- )}
1278
- <SkipTo skipId={handleChartTabbing(config, legendId)} skipMessage='Skip Over Chart Container' />
1279
- {visibleAnnotations.length > 0 && (
1280
- <SkipTo
1281
- skipId={handleChartTabbing(config, legendId)}
1282
- skipMessage={`Skip over annotations`}
1283
- key={`skip-annotations`}
1284
- />
1285
- )}
1286
- <LegendWrapper>
1287
- <div
1288
- className={
1289
- legend.hide || isLegendWrapViewport(currentViewport)
1290
- ? 'w-100'
1291
- : legend.position === 'bottom' ||
1292
- legend.position === 'top' ||
1293
- visualizationType === 'Sankey' ||
1294
- visualizationType === 'Spark Line'
1295
- ? 'w-100'
1296
- : 'w-75'
1297
- }
1298
- >
1299
- {/* Check if there is data to display */}
1300
- {(!filteredData || filteredData.length === 0) && (
1301
- <div className='no-data-message' style={{ padding: '2rem', textAlign: 'center', color: '#666' }}>
1302
- {config.chartMessage?.noData || 'No Data Available'}
1303
- </div>
1304
- )}
1305
-
1306
- {/* All charts with LinearChart */}
1307
- {filteredData &&
1308
- filteredData.length > 0 &&
1309
- !['Spark Line', 'Line', 'Sankey', 'Pie', 'Radar'].includes(config.visualizationType) && (
1310
- <div ref={parentRef} style={{ width: `100%` }}>
1311
- <ParentSize>
1312
- {parent => (
1313
- <LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
1314
- )}
1315
- </ParentSize>
1316
- </div>
1317
- )}
1318
-
1319
- {filteredData && filteredData.length > 0 && config.visualizationType === 'Pie' && (
1320
- <ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
1321
- {parent => (
1322
- <PieChart
1323
- ref={svgRef}
1324
- parentWidth={parent.width}
1325
- parentHeight={parent.height}
1326
- interactionLabel={interactionLabel}
1327
- />
1328
- )}
1329
- </ParentSize>
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
- )}
1344
- {/* Line Chart */}
1345
- {filteredData &&
1346
- filteredData.length > 0 &&
1347
- config.visualizationType === 'Line' &&
1348
- (convertLineToBarGraph ? (
1349
- <div ref={parentRef} style={{ width: `100%` }}>
1350
- <ParentSize>
1351
- {parent => (
1352
- <LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
1353
- )}
1354
- </ParentSize>
1355
- </div>
1356
- ) : (
1357
- <div ref={parentRef} style={{ width: '100%' }}>
1358
- <ParentSize>
1359
- {parent => {
1360
- const labelMargin = 120
1361
- const widthReduction =
1362
- config.showLineSeriesLabels &&
1363
- (config.legend.position !== 'right' || config.legend.hide)
1364
- ? labelMargin
1365
- : 0
1366
- return (
1367
- <LinearChart
1368
- ref={svgRef}
1369
- parentWidth={parent.width - widthReduction}
1370
- parentHeight={parent.height}
1371
- />
1372
- )
1373
- }}
1374
- </ParentSize>
1375
- </div>
1376
- ))}
1377
- {/* Sparkline */}
1378
- {config.visualizationType === 'Spark Line' && (
1379
- <>
1380
- <Filters
1381
- config={config}
1382
- setFilters={setFilters}
1383
- excludedData={excludedData}
1384
- dimensions={dimensions}
1385
- interactionLabel={interactionLabel}
1386
- />
1387
- {processedIntroText && (
1388
- <section className='introText mb-4' style={{ padding: '0px 0 35px' }}>
1389
- {parse(processedIntroText)}
1390
- </section>
1391
- )}
1392
- <div style={{ height: `100px`, width: `100%`, ...sparkLineStyles }}>
1393
- <ParentSize>{parent => <SparkLine width={parent.width} height={parent.height} />}</ParentSize>
1394
- </div>
1395
- {description && (
1396
- <div className='subtext' style={{ padding: '35px 0 15px' }}>
1397
- {parse(description)}
1398
- </div>
1399
- )}
1400
- </>
1401
- )}
1402
- {/* Sankey */}
1403
- {config.visualizationType === 'Sankey' && (
1404
- <ParentSize aria-hidden='true'>
1405
- {parent => <SankeyChart runtime={config.runtime} width={parent.width} height={parent.height} />}
1406
- </ParentSize>
1407
- )}
1408
- </div>
1409
- {/* Legend */}
1410
- {!config.legend.hide &&
1411
- config.visualizationType !== 'Spark Line' &&
1412
- config.visualizationType !== 'Sankey' &&
1413
- !(config.visualizationType === 'Warming Stripes' && config.legend?.style === 'gradient') &&
1414
- !(config.visualizationType === 'Warming Stripes' && config.smallMultiples?.mode) && (
1415
- <Legend
1416
- ref={legendRef}
1417
- skipId={handleChartTabbing(config, legendId)}
1418
- interactionLabel={interactionLabel}
1419
- />
1420
- )}
1421
- {config.visualizationType === 'Warming Stripes' &&
1422
- config.legend?.style === 'gradient' &&
1423
- !config.smallMultiples?.mode && <WarmingStripesGradientLegend />}
1424
- </LegendWrapper>
1425
- {/* Link */}
1286
+ {config.newViz && <Confirm updateConfig={updateConfig} config={config} />}
1287
+ {!missingRequiredSections(config) && !config.newViz && (
1288
+ <VisualizationContent
1289
+ innerClassName={`type-${makeClassName(config.visualizationType)}`}
1290
+ innerProps={{ 'aria-label': handleChartAriaLabels(config), tabIndex: 0 }}
1291
+ bodyClassName={bodyClasses.join(' ')}
1292
+ bodyWrapClassName={isTp5Treatment ? 'cdc-callout d-flex flex-column tp5-chart-callout' : ''}
1293
+ filters={
1294
+ config.filters?.length > 0 && !externalFilters && config.visualizationType !== 'Spark Line' ? (
1295
+ <Filters
1296
+ config={config}
1297
+ setFilters={setFilters}
1298
+ excludedData={excludedData}
1299
+ dimensions={dimensions}
1300
+ interactionLabel={interactionLabel}
1301
+ />
1302
+ ) : undefined
1303
+ }
1304
+ bodySubtext={
1305
+ processedDescription && config.visualizationType !== 'Spark Line' ? (
1306
+ <div className={`cove-prose ${getChartSubTextClasses().join(' ')}`}>{parse(processedDescription)}</div>
1307
+ ) : null
1308
+ }
1309
+ bodyFooter={
1310
+ <>
1426
1311
  {isDashboard && config.table && config.table.show && config.table.showDataTableLink
1427
1312
  ? tableLink
1428
1313
  : link && link}
1429
- {/* Description */}
1430
-
1431
- {processedDescription && config.visualizationType !== 'Spark Line' && (
1432
- <div className={getChartSubTextClasses().join(' ')}>{parse(processedDescription)}</div>
1433
- )}
1434
-
1435
- {/* Data Table */}
1436
1314
  {(config.xAxis.dataKey &&
1437
1315
  config.table.show &&
1438
1316
  config.visualizationType !== 'Spark Line' &&
@@ -1461,15 +1339,12 @@ const CdcChart: React.FC<CdcChartProps> = ({
1461
1339
 
1462
1340
  return (
1463
1341
  <DataTable
1464
- /* changing the "key" will force the table to re-render
1465
- when the default sort changes while editing */
1466
- key={dataTableDefaultSortBy}
1342
+ key={config.table?.defaultSort?.column || ''}
1467
1343
  config={dataTableConfig}
1468
1344
  rawData={dataTableRawData}
1469
1345
  runtimeData={dataTableRuntimeData}
1470
1346
  expandDataTable={config.table.expanded}
1471
1347
  columns={dataTableColumns}
1472
- defaultSortBy={dataTableDefaultSortBy}
1473
1348
  displayGeoName={name => name}
1474
1349
  applyLegendToRow={applyLegendToRow}
1475
1350
  tableTitle={config.table.label}
@@ -1482,6 +1357,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1482
1357
  showDownloadImgButton={config.table.showDownloadImgButton}
1483
1358
  showDownloadPdfButton={config.table.showDownloadPdfButton}
1484
1359
  includeContextInDownload={config.table?.includeContextInDownload}
1360
+ hasSubtextAbove={Boolean(processedDescription && config.visualizationType !== 'Spark Line')}
1485
1361
  interactionLabel={interactionLabel}
1486
1362
  />
1487
1363
  )
@@ -1513,21 +1389,181 @@ const CdcChart: React.FC<CdcChartProps> = ({
1513
1389
  </div>
1514
1390
  )}
1515
1391
  {visibleAnnotations.length > 0 && <Annotation.Dropdown />}
1516
- {/* show pdf or image button */}
1517
1392
  {processedLegacyFootnotes && (
1518
- <section className='footnotes pt-2 mt-4'>{parse(processedLegacyFootnotes)}</section>
1393
+ <section className='footnotes cove-prose pt-2 mt-4'>{parse(processedLegacyFootnotes)}</section>
1519
1394
  )}
1520
- </div>
1395
+ </>
1396
+ }
1397
+ header={isTp5Treatment ? null : chartTitle}
1398
+ messageIsIntroText={config.visualizationType !== 'Spark Line' && !!processedIntroText}
1399
+ message={
1400
+ config.visualizationType !== 'Spark Line' && processedIntroText ? (
1401
+ <div className='cove-prose'>{parse(processedIntroText)}</div>
1402
+ ) : null
1403
+ }
1404
+ footer={
1521
1405
  <FootnotesStandAlone
1522
1406
  config={configObj.footnotes}
1523
1407
  filters={config.filters?.filter(f => f.filterFootnotes)}
1524
1408
  markupVariables={config.markupVariables}
1525
1409
  enableMarkupVariables={config.enableMarkupVariables}
1526
1410
  data={config.data}
1411
+ dataMetadata={config.dataMetadata}
1527
1412
  />
1413
+ }
1414
+ >
1415
+ {isTp5Treatment && <img src={CalloutFlag} alt='' className='cdc-callout__flag' aria-hidden='true' />}
1416
+ {isTp5Treatment && chartTitle}
1417
+ <div className={getChartWrapperClasses().join(' ')}>
1418
+ <SkipTo skipId={handleChartTabbing(config, legendId)} skipMessage='Skip Over Chart Container' />
1419
+ {visibleAnnotations.length > 0 && (
1420
+ <SkipTo
1421
+ skipId={handleChartTabbing(config, legendId)}
1422
+ skipMessage={`Skip over annotations`}
1423
+ key={`skip-annotations`}
1424
+ />
1425
+ )}
1426
+ <LegendWrapper>
1427
+ <div
1428
+ className={
1429
+ legend.hide || isLegendWrapViewport(currentViewport)
1430
+ ? 'w-100'
1431
+ : legend.position === 'bottom' ||
1432
+ legend.position === 'top' ||
1433
+ visualizationType === 'Sankey' ||
1434
+ visualizationType === 'Spark Line'
1435
+ ? 'w-100'
1436
+ : 'w-75'
1437
+ }
1438
+ >
1439
+ {/* Check if there is data to display */}
1440
+ {(!filteredData || filteredData.length === 0) && (
1441
+ <div className='no-data-message' style={{ padding: '2rem', textAlign: 'center', color: '#666' }}>
1442
+ {config.chartMessage?.noData || 'No Data Available'}
1443
+ </div>
1444
+ )}
1445
+
1446
+ {/* All charts with LinearChart */}
1447
+ {filteredData &&
1448
+ filteredData.length > 0 &&
1449
+ !['Spark Line', 'Line', 'Sankey', 'Pie', 'Radar'].includes(config.visualizationType) && (
1450
+ <div ref={parentRef} style={{ width: `100%` }}>
1451
+ <ParentSize>
1452
+ {parent => (
1453
+ <LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
1454
+ )}
1455
+ </ParentSize>
1456
+ </div>
1457
+ )}
1458
+
1459
+ {filteredData && filteredData.length > 0 && config.visualizationType === 'Pie' && (
1460
+ <ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
1461
+ {parent => (
1462
+ <PieChart
1463
+ ref={svgRef}
1464
+ parentWidth={parent.width}
1465
+ parentHeight={parent.height}
1466
+ interactionLabel={interactionLabel}
1467
+ />
1468
+ )}
1469
+ </ParentSize>
1470
+ )}
1471
+ {/* Radar Chart */}
1472
+ {filteredData && filteredData.length > 0 && config.visualizationType === 'Radar' && (
1473
+ <ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
1474
+ {parent => (
1475
+ <RadarChart
1476
+ ref={svgRef}
1477
+ parentWidth={parent.width}
1478
+ parentHeight={parent.height}
1479
+ interactionLabel={interactionLabel}
1480
+ />
1481
+ )}
1482
+ </ParentSize>
1483
+ )}
1484
+ {/* Line Chart */}
1485
+ {filteredData &&
1486
+ filteredData.length > 0 &&
1487
+ config.visualizationType === 'Line' &&
1488
+ (convertLineToBarGraph ? (
1489
+ <div ref={parentRef} style={{ width: `100%` }}>
1490
+ <ParentSize>
1491
+ {parent => (
1492
+ <LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
1493
+ )}
1494
+ </ParentSize>
1495
+ </div>
1496
+ ) : (
1497
+ <div ref={parentRef} style={{ width: '100%' }}>
1498
+ <ParentSize>
1499
+ {parent => {
1500
+ const labelMargin = 120
1501
+ const widthReduction =
1502
+ config.showLineSeriesLabels && (config.legend.position !== 'right' || config.legend.hide)
1503
+ ? labelMargin
1504
+ : 0
1505
+ return (
1506
+ <LinearChart
1507
+ ref={svgRef}
1508
+ parentWidth={parent.width - widthReduction}
1509
+ parentHeight={parent.height}
1510
+ />
1511
+ )
1512
+ }}
1513
+ </ParentSize>
1514
+ </div>
1515
+ ))}
1516
+ {/* Sparkline */}
1517
+ {config.visualizationType === 'Spark Line' && (
1518
+ <>
1519
+ <Filters
1520
+ config={config}
1521
+ setFilters={setFilters}
1522
+ excludedData={excludedData}
1523
+ dimensions={dimensions}
1524
+ interactionLabel={interactionLabel}
1525
+ />
1526
+ {processedIntroText && (
1527
+ <section className='introText cove-prose' style={{ padding: '0px 0 35px' }}>
1528
+ {parse(processedIntroText)}
1529
+ </section>
1530
+ )}
1531
+ <div style={{ height: `100px`, width: `100%`, ...sparkLineStyles }}>
1532
+ <ParentSize>{parent => <SparkLine width={parent.width} height={parent.height} />}</ParentSize>
1533
+ </div>
1534
+ {processedDescription && (
1535
+ <div className='subtext cove-prose' style={{ padding: '35px 0 1.5rem' }}>
1536
+ {parse(processedDescription)}
1537
+ </div>
1538
+ )}
1539
+ </>
1540
+ )}
1541
+ {/* Sankey */}
1542
+ {config.visualizationType === 'Sankey' && (
1543
+ <ParentSize aria-hidden='true'>
1544
+ {parent => <SankeyChart runtime={config.runtime} width={parent.width} height={parent.height} />}
1545
+ </ParentSize>
1546
+ )}
1547
+ </div>
1548
+ {/* Legend */}
1549
+ {!config.legend.hide &&
1550
+ config.visualizationType !== 'Spark Line' &&
1551
+ config.visualizationType !== 'Sankey' &&
1552
+ !(config.visualizationType === 'Warming Stripes' && config.legend?.style === 'gradient') &&
1553
+ !(config.visualizationType === 'Warming Stripes' && config.smallMultiples?.mode) && (
1554
+ <Legend
1555
+ ref={legendRef}
1556
+ skipId={handleChartTabbing(config, legendId)}
1557
+ interactionLabel={interactionLabel}
1558
+ />
1559
+ )}
1560
+ {config.visualizationType === 'Warming Stripes' &&
1561
+ config.legend?.style === 'gradient' &&
1562
+ !config.smallMultiples?.mode && <WarmingStripesGradientLegend />}
1563
+ </LegendWrapper>
1528
1564
  </div>
1529
- )}
1530
- </Layout.Responsive>
1565
+ </VisualizationContent>
1566
+ )}
1531
1567
  </>
1532
1568
  )
1533
1569
  }
@@ -1595,16 +1631,16 @@ const CdcChart: React.FC<CdcChartProps> = ({
1595
1631
  return (
1596
1632
  <ConfigContext.Provider value={contextValues}>
1597
1633
  <ChartDispatchContext.Provider value={dispatch}>
1598
- <Layout.VisualizationWrapper
1634
+ <VisualizationContainer
1599
1635
  config={config}
1600
1636
  isEditor={isEditor}
1601
1637
  currentViewport={currentViewport}
1602
1638
  ref={outerContainerRef}
1603
1639
  imageId={imageId}
1604
- showEditorPanel={config?.showEditorPanel}
1640
+ editorPanel={!isLoading ? <EditorPanel datasets={datasets} /> : null}
1605
1641
  >
1606
1642
  {body}
1607
- </Layout.VisualizationWrapper>
1643
+ </VisualizationContainer>
1608
1644
  </ChartDispatchContext.Provider>
1609
1645
  </ConfigContext.Provider>
1610
1646
  )