@cdc/chart 4.26.2 → 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 (70) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcchart.js +35674 -32430
  3. package/examples/data/data-with-metadata.json +10 -0
  4. package/examples/feature/pie/planet-pie-example-config.json +2 -1
  5. package/examples/metadata-variables.json +58 -0
  6. package/package.json +3 -3
  7. package/src/CdcChart.tsx +8 -4
  8. package/src/CdcChartComponent.tsx +321 -288
  9. package/src/_stories/Chart.CustomColors.stories.tsx +74 -0
  10. package/src/_stories/Chart.Defaults.stories.tsx +95 -0
  11. package/src/_stories/Chart.SmallestLeftAxisMax.stories.tsx +64 -0
  12. package/src/_stories/Chart.stories.tsx +36 -2
  13. package/src/_stories/ChartBar.Editor.stories.tsx +97 -38
  14. package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
  15. package/src/_stories/ChartEditor.Editor.stories.tsx +1 -1
  16. package/src/_stories/_mock/paired-bar-abbr.json +421 -0
  17. package/src/_stories/_mock/pie_custom_colors.json +268 -0
  18. package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
  19. package/src/components/Annotations/components/AnnotationDraggable.styles.css +10 -10
  20. package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -1
  21. package/src/components/Annotations/components/AnnotationList.styles.css +11 -11
  22. package/src/components/Axis/BottomAxis.tsx +10 -3
  23. package/src/components/Axis/PairedBarAxis.tsx +10 -4
  24. package/src/components/BarChart/components/BarChart.Horizontal.tsx +12 -28
  25. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -30
  26. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +12 -31
  27. package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -28
  28. package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
  29. package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
  30. package/src/components/BarChart/helpers/useBarChart.ts +3 -0
  31. package/src/components/Brush/BrushSelector.tsx +2 -1
  32. package/src/components/Brush/MiniChartPreview.tsx +21 -26
  33. package/src/components/EditorPanel/EditorPanel.tsx +56 -43
  34. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +9 -9
  35. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
  36. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +39 -1
  37. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +24 -42
  38. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +83 -2
  39. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +45 -42
  40. package/src/components/EditorPanel/editor-panel.scss +1 -1
  41. package/src/components/ForestPlot/ForestPlot.tsx +26 -22
  42. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +4 -4
  43. package/src/components/Legend/helpers/createFormatLabels.tsx +3 -2
  44. package/src/components/LinearChart/tests/LinearChart.test.tsx +77 -0
  45. package/src/components/LinearChart/tests/mockConfigContext.ts +2 -0
  46. package/src/components/LinearChart.tsx +26 -6
  47. package/src/components/PieChart/PieChart.tsx +19 -4
  48. package/src/components/RadarChart/RadarChart.tsx +1 -1
  49. package/src/components/Regions/components/Regions.tsx +6 -6
  50. package/src/components/Sankey/components/Sankey.tsx +3 -3
  51. package/src/components/Sankey/sankey.scss +1 -1
  52. package/src/components/SmallMultiples/SmallMultiples.css +5 -5
  53. package/src/components/Sparkline/index.scss +4 -2
  54. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
  55. package/src/data/initial-state.js +23 -14
  56. package/src/data/legacy-defaults.ts +18 -0
  57. package/src/helpers/abbreviateNumber.ts +24 -17
  58. package/src/helpers/getChartPatternId.ts +17 -0
  59. package/src/helpers/getMinMax.ts +16 -2
  60. package/src/helpers/seriesColumnSettings.ts +114 -0
  61. package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
  62. package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
  63. package/src/hooks/useRightAxis.ts +14 -0
  64. package/src/hooks/useScales.ts +92 -56
  65. package/src/hooks/useTooltip.tsx +20 -3
  66. package/src/scss/main.scss +152 -79
  67. package/src/test/CdcChart.test.jsx +2 -2
  68. package/src/types/ChartConfig.ts +4 -0
  69. package/tests/fixtures/chart-config-with-metadata.json +29 -0
  70. package/tests/fixtures/data-with-metadata.json +10 -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
@@ -659,7 +674,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
659
674
  if (newConfig.dataUrl && !urlFilters) {
660
675
  // handle urls with spaces in the name.
661
676
  if (newConfig.dataUrl) newConfig.dataUrl = `${newConfig.dataUrl}`
662
- let newData = await fetchRemoteData(newConfig.dataUrl)
677
+ let { data: newData, dataMetadata } = await fetchRemoteData(newConfig.dataUrl)
678
+ newConfig.dataMetadata = dataMetadata
663
679
 
664
680
  if (newConfig.vegaConfig) {
665
681
  newData = extractCoveData(updateVegaData(newConfig.vegaConfig, newData))
@@ -720,7 +736,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
720
736
  * When cove has a config and container ref publish the cove_loaded event.
721
737
  */
722
738
  useEffect(() => {
723
- if (container && !isLoading && !_.isEmpty(config) && !coveLoadedEventRan) {
739
+ if (container && !isLoading && !isEmpty(config) && !coveLoadedEventRan) {
724
740
  publish('cove_loaded', { config: config })
725
741
  dispatch({ type: 'SET_LOADED_EVENT', payload: true })
726
742
  }
@@ -775,7 +791,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
775
791
  }, [externalFilters]) // eslint-disable-line
776
792
 
777
793
  // Declaratively update runtime series keys for pie charts when derived value changes
778
- if (config.runtime?.isPieChart && pieSeriesKeys && !_.isEqual(pieSeriesKeys, config.runtime?.seriesKeys)) {
794
+ if (config.runtime?.isPieChart && pieSeriesKeys && !isEqual(pieSeriesKeys, config.runtime?.seriesKeys)) {
779
795
  const newConfig = {
780
796
  ...config,
781
797
  runtime: {
@@ -815,7 +831,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
815
831
  useEffect(() => {
816
832
  if (
817
833
  !config?.runtime ||
818
- _.isEmpty(config.runtime) ||
834
+ isEmpty(config.runtime) ||
819
835
  (!config.runtime.xAxis && !config.runtime.yAxis) ||
820
836
  !config.markupVariables?.length
821
837
  ) {
@@ -825,7 +841,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
825
841
  const dataSource = (stateData && stateData.length ? stateData : config.data) || []
826
842
  const { runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant } = getProcessedAxisLabels(config, dataSource)
827
843
 
828
- const runtimeClone = _.cloneDeep(config.runtime)
844
+ const runtimeClone = cloneDeep(config.runtime)
829
845
 
830
846
  if (!runtimeClone?.xAxis || !runtimeClone?.yAxis) {
831
847
  return
@@ -864,9 +880,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
864
880
  return handleShowAll()
865
881
  }
866
882
 
867
- 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
868
884
 
869
- const newSeriesHighlight = _.xor(seriesHighlight, [newHighlight])
885
+ const newSeriesHighlight = xor(seriesHighlight, [newHighlight])
870
886
  dispatch({ type: 'SET_SERIES_HIGHLIGHT', payload: newSeriesHighlight })
871
887
  }
872
888
  // Called on reset button click, unhighlights all data series
@@ -909,11 +925,11 @@ const CdcChart: React.FC<CdcChartProps> = ({
909
925
  const formatDate = (date, i, ticks) => {
910
926
  const displayFormat =
911
927
  config.runtime[section].dateDisplayFormat || config.runtime[section].dateParseFormat || '%Y-%m-%d'
912
- let formattedDate = coreFormatDate(displayFormat, date)
928
+ let formattedDate = coreFormatDate(displayFormat, date, config.locale)
913
929
  // Show years only once
914
930
  if (config.xAxis.showYearsOnce && displayFormat?.includes('%Y') && ticks) {
915
931
  const prevDate = ticks[i - 1] ? ticks[i - 1].value : null
916
- const prevFormattedDate = coreFormatDate(displayFormat, prevDate)
932
+ const prevFormattedDate = coreFormatDate(displayFormat, prevDate, config.locale)
917
933
  const year = formattedDate.match(/\d{4}/)
918
934
  const prevYear = prevFormattedDate.match(/\d{4}/)
919
935
  if (year && prevYear && year[0] === prevYear[0]) {
@@ -924,7 +940,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
924
940
  }
925
941
 
926
942
  const formatTooltipsDate = date => {
927
- return coreFormatDate(config.tooltips.dateDisplayFormat, date)
943
+ return coreFormatDate(config.tooltips.dateDisplayFormat, date, config.locale)
928
944
  }
929
945
 
930
946
  // Format numeric data based on settings in config OR from passed in settings for Additional Columns
@@ -1066,16 +1082,16 @@ const CdcChart: React.FC<CdcChartProps> = ({
1066
1082
  ) {
1067
1083
  num = num // eslint-disable-line
1068
1084
  } else {
1069
- num = num.toLocaleString('en-US', stringFormattingOptions)
1085
+ num = num.toLocaleString(config.locale, stringFormattingOptions)
1070
1086
  }
1071
1087
  let result = ''
1072
1088
 
1073
1089
  if (abbreviated && axis === 'left' && shouldAbbreviate) {
1074
- num = abbreviateNumber(parseFloat(num))
1090
+ num = abbreviateNumber(parseFloat(num), config.locale)
1075
1091
  }
1076
1092
 
1077
1093
  if (bottomAbbreviated && axis === 'bottom' && shouldAbbreviate) {
1078
- num = abbreviateNumber(parseFloat(num))
1094
+ num = abbreviateNumber(parseFloat(num), config.locale)
1079
1095
  }
1080
1096
 
1081
1097
  if (addColPrefix && axis === 'left') {
@@ -1143,8 +1159,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
1143
1159
  if (!Array.isArray(data)) return []
1144
1160
  if (config.visualizationType === 'Forecasting') return data
1145
1161
  // 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)
1162
+ const CIkeys: string[] = Object.values(get(config, 'confidenceKeys', {})) as string[]
1163
+ const seriesKeys: string[] = get(config, 'series', []).map((s: any) => s.dataKey)
1148
1164
  const keysToClean: string[] = [...(seriesKeys ?? []), ...(CIkeys ?? [])]
1149
1165
 
1150
1166
  // key that does not need to be cleaned
@@ -1166,7 +1182,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1166
1182
  .map(col => col.name)
1167
1183
  .concat([dynamicSeries.dynamicCategory, dynamicSeries.dataKey])
1168
1184
  if (config.xAxis?.dataKey) usedColumns.push(config.xAxis.dataKey)
1169
- return data.map(d => _.pick(d, usedColumns))
1185
+ return data.map(d => pick(d, usedColumns))
1170
1186
  }
1171
1187
 
1172
1188
  const pivotDynamicSeries = (config: ChartConfig): TableConfig => {
@@ -1184,18 +1200,51 @@ const CdcChart: React.FC<CdcChartProps> = ({
1184
1200
 
1185
1201
  // Filter annotations to only those visible in current data view
1186
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
+ )
1187
1235
 
1188
1236
  // Prevent render if loading
1189
1237
  let body = <Loading />
1190
1238
 
1191
1239
  const makeClassName = string => {
1192
- if (!_.isString(string)) return undefined
1240
+ if (!isString(string)) return undefined
1193
1241
 
1194
- return _.kebabCase(string)
1242
+ return kebabCase(string)
1195
1243
  }
1196
1244
  const getChartWrapperClasses = () => {
1197
1245
  const isLegendOnBottom = legend?.position === 'bottom' || isLegendWrapViewport(currentViewport)
1198
- 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']
1199
1248
  if (legend?.position) {
1200
1249
  if (isLegendWrapViewport(currentViewport) && legend?.position !== 'top') {
1201
1250
  classes.push('legend-bottom')
@@ -1204,16 +1253,23 @@ const CdcChart: React.FC<CdcChartProps> = ({
1204
1253
  }
1205
1254
  }
1206
1255
  if (legend?.hide) classes.push('legend-hidden')
1256
+ if (contentClasses.includes('sparkline')) classes.push('sparkline')
1207
1257
  if (lineDatapointClass) classes.push(lineDatapointClass)
1208
1258
  if (!config.barHasBorder) classes.push('chart-bar--no-border')
1209
1259
  if (config.xAxis.brushActive && dashboardConfig?.type === 'dashboard' && (!isLegendOnBottom || legend.hide))
1210
1260
  classes.push('dashboard-brush')
1211
- 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
+
1212
1268
  return classes
1213
1269
  }
1214
1270
 
1215
1271
  const getChartSubTextClasses = () => {
1216
- const classes = ['subtext mt-4']
1272
+ const classes = ['subtext']
1217
1273
  const isLegendOnBottom = legend?.position === 'bottom' || isLegendWrapViewport(currentViewport)
1218
1274
 
1219
1275
  if (config.isResponsiveTicks) classes.push('subtext--responsive-ticks ')
@@ -1228,211 +1284,34 @@ const CdcChart: React.FC<CdcChartProps> = ({
1228
1284
  )
1229
1285
  body = (
1230
1286
  <>
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 */}
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
+ <>
1426
1312
  {isDashboard && config.table && config.table.show && config.table.showDataTableLink
1427
1313
  ? tableLink
1428
1314
  : 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
1315
  {(config.xAxis.dataKey &&
1437
1316
  config.table.show &&
1438
1317
  config.visualizationType !== 'Spark Line' &&
@@ -1461,15 +1340,12 @@ const CdcChart: React.FC<CdcChartProps> = ({
1461
1340
 
1462
1341
  return (
1463
1342
  <DataTable
1464
- /* changing the "key" will force the table to re-render
1465
- when the default sort changes while editing */
1466
- key={dataTableDefaultSortBy}
1343
+ key={config.table?.defaultSort?.column || ''}
1467
1344
  config={dataTableConfig}
1468
1345
  rawData={dataTableRawData}
1469
1346
  runtimeData={dataTableRuntimeData}
1470
1347
  expandDataTable={config.table.expanded}
1471
1348
  columns={dataTableColumns}
1472
- defaultSortBy={dataTableDefaultSortBy}
1473
1349
  displayGeoName={name => name}
1474
1350
  applyLegendToRow={applyLegendToRow}
1475
1351
  tableTitle={config.table.label}
@@ -1482,6 +1358,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1482
1358
  showDownloadImgButton={config.table.showDownloadImgButton}
1483
1359
  showDownloadPdfButton={config.table.showDownloadPdfButton}
1484
1360
  includeContextInDownload={config.table?.includeContextInDownload}
1361
+ hasSubtextAbove={Boolean(processedDescription && config.visualizationType !== 'Spark Line')}
1485
1362
  interactionLabel={interactionLabel}
1486
1363
  />
1487
1364
  )
@@ -1513,21 +1390,177 @@ const CdcChart: React.FC<CdcChartProps> = ({
1513
1390
  </div>
1514
1391
  )}
1515
1392
  {visibleAnnotations.length > 0 && <Annotation.Dropdown />}
1516
- {/* show pdf or image button */}
1517
1393
  {processedLegacyFootnotes && (
1518
1394
  <section className='footnotes pt-2 mt-4'>{parse(processedLegacyFootnotes)}</section>
1519
1395
  )}
1520
- </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={
1521
1402
  <FootnotesStandAlone
1522
1403
  config={configObj.footnotes}
1523
1404
  filters={config.filters?.filter(f => f.filterFootnotes)}
1524
1405
  markupVariables={config.markupVariables}
1525
1406
  enableMarkupVariables={config.enableMarkupVariables}
1526
1407
  data={config.data}
1408
+ dataMetadata={config.dataMetadata}
1527
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>
1528
1561
  </div>
1529
- )}
1530
- </Layout.Responsive>
1562
+ </VisualizationContent>
1563
+ )}
1531
1564
  </>
1532
1565
  )
1533
1566
  }
@@ -1595,16 +1628,16 @@ const CdcChart: React.FC<CdcChartProps> = ({
1595
1628
  return (
1596
1629
  <ConfigContext.Provider value={contextValues}>
1597
1630
  <ChartDispatchContext.Provider value={dispatch}>
1598
- <Layout.VisualizationWrapper
1631
+ <VisualizationContainer
1599
1632
  config={config}
1600
1633
  isEditor={isEditor}
1601
1634
  currentViewport={currentViewport}
1602
1635
  ref={outerContainerRef}
1603
1636
  imageId={imageId}
1604
- showEditorPanel={config?.showEditorPanel}
1637
+ editorPanel={!isLoading ? <EditorPanel datasets={datasets} /> : null}
1605
1638
  >
1606
1639
  {body}
1607
- </Layout.VisualizationWrapper>
1640
+ </VisualizationContainer>
1608
1641
  </ChartDispatchContext.Provider>
1609
1642
  </ConfigContext.Provider>
1610
1643
  )