@cdc/chart 4.24.5 → 4.24.9-1

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 (86) hide show
  1. package/dist/cdcchart.js +50526 -42181
  2. package/examples/cases-year.json +13379 -0
  3. package/examples/feature/annotations/index.json +542 -0
  4. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
  5. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
  6. package/examples/xaxis.json +493 -0
  7. package/index.html +20 -10
  8. package/package.json +5 -4
  9. package/src/CdcChart.tsx +461 -172
  10. package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
  11. package/src/_stories/Chart.stories.tsx +18 -171
  12. package/src/_stories/ChartAnnotation.stories.tsx +32 -0
  13. package/src/_stories/_mock/annotation_category_mock.json +473 -0
  14. package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
  15. package/{examples/feature/line/line-chart.json → src/_stories/_mock/annotation_date-time_mock.json} +150 -69
  16. package/src/_stories/_mock/legend.gradient_mock.json +236 -0
  17. package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
  18. package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
  19. package/src/_stories/_mock/lollipop.json +171 -0
  20. package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
  21. package/src/components/Annotations/components/AnnotationDraggable.tsx +207 -0
  22. package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
  23. package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
  24. package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
  25. package/src/components/Annotations/components/AnnotationList.tsx +42 -0
  26. package/src/components/Annotations/components/findNearestDatum.ts +138 -0
  27. package/src/components/Annotations/components/helpers/index.tsx +46 -0
  28. package/src/components/Annotations/index.tsx +13 -0
  29. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
  30. package/src/components/AreaChart/components/AreaChart.jsx +1 -1
  31. package/src/components/Axis/Categorical.Axis.tsx +145 -0
  32. package/src/components/BarChart/components/BarChart.Horizontal.tsx +47 -44
  33. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +0 -1
  34. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -14
  35. package/src/components/BarChart/components/BarChart.Vertical.tsx +67 -30
  36. package/src/components/BarChart/helpers/index.ts +91 -0
  37. package/src/components/BrushChart.tsx +205 -0
  38. package/src/components/EditorPanel/EditorPanel.tsx +1794 -403
  39. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +320 -0
  40. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +282 -18
  41. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -8
  42. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
  43. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +4 -13
  44. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  45. package/src/components/EditorPanel/components/panels.scss +4 -0
  46. package/src/components/EditorPanel/editor-panel.scss +35 -3
  47. package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +105 -17
  48. package/src/components/Legend/Legend.Component.tsx +185 -194
  49. package/src/components/Legend/Legend.Suppression.tsx +146 -0
  50. package/src/components/Legend/Legend.tsx +21 -5
  51. package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
  52. package/src/components/Legend/helpers/index.ts +35 -0
  53. package/src/components/LegendWrapper.tsx +26 -0
  54. package/src/components/LineChart/LineChartProps.ts +1 -15
  55. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
  56. package/src/components/LineChart/components/LineChart.Circle.tsx +57 -8
  57. package/src/components/LineChart/helpers.ts +72 -14
  58. package/src/components/LineChart/index.tsx +117 -42
  59. package/src/components/LinearChart.tsx +1366 -0
  60. package/src/components/PairedBarChart.jsx +9 -9
  61. package/src/components/PieChart/PieChart.tsx +75 -18
  62. package/src/components/Sankey/index.tsx +89 -30
  63. package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
  64. package/src/components/Sparkline/components/SparkLine.tsx +2 -2
  65. package/src/components/ZoomBrush.tsx +90 -44
  66. package/src/data/initial-state.js +25 -7
  67. package/src/helpers/handleChartTabbing.ts +8 -0
  68. package/src/helpers/isConvertLineToBarGraph.ts +4 -0
  69. package/src/hooks/{useBarChart.js → useBarChart.ts} +2 -40
  70. package/src/hooks/useColorScale.ts +1 -1
  71. package/src/hooks/useLegendClasses.ts +68 -0
  72. package/src/hooks/useMinMax.ts +12 -7
  73. package/src/hooks/useScales.ts +58 -26
  74. package/src/hooks/useTooltip.tsx +135 -25
  75. package/src/scss/DataTable.scss +2 -1
  76. package/src/scss/main.scss +128 -28
  77. package/src/types/ChartConfig.ts +83 -10
  78. package/src/types/ChartContext.ts +14 -4
  79. package/tests-examples/helpers/testZeroValue.test.ts +30 -0
  80. package/src/components/BrushHandle.jsx +0 -17
  81. package/src/components/LineChart/index.scss +0 -1
  82. package/src/components/LinearChart.jsx +0 -774
  83. package/src/helpers/filterData.ts +0 -18
  84. package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
  85. package/src/hooks/useLegendClasses.js +0 -31
  86. /package/src/hooks/{useReduceData.js → useReduceData.ts} +0 -0
package/src/CdcChart.tsx CHANGED
@@ -7,6 +7,9 @@ import * as d3 from 'd3-array'
7
7
  import Layout from '@cdc/core/components/Layout'
8
8
  import Button from '@cdc/core/components/elements/Button'
9
9
 
10
+ //types
11
+ import { DimensionsType } from '@cdc/core/types/Dimensions'
12
+
10
13
  // External Libraries
11
14
  import { scaleOrdinal } from '@visx/scale'
12
15
  import ParentSize from '@visx/responsive/lib/components/ParentSize'
@@ -29,9 +32,9 @@ import Legend from './components/Legend'
29
32
  import defaults from './data/initial-state'
30
33
  import EditorPanel from './components/EditorPanel'
31
34
  import { abbreviateNumber } from './helpers/abbreviateNumber'
35
+ import { handleChartTabbing } from './helpers/handleChartTabbing'
32
36
  import { getQuartiles } from './helpers/getQuartiles'
33
37
  import { sortAsc, sortDesc } from './helpers/sort'
34
- import { filterData } from './helpers/filterData'
35
38
  import { handleChartAriaLabels } from './helpers/handleChartAriaLabels'
36
39
  import { lineOptions } from './helpers/lineOptions'
37
40
  import { handleLineType } from './helpers/handleLineType'
@@ -39,8 +42,10 @@ import { generateColorsArray } from './helpers/generateColorsArray'
39
42
  import Loading from '@cdc/core/components/Loading'
40
43
  import Filters from '@cdc/core/components/Filters'
41
44
  import MediaControls from '@cdc/core/components/MediaControls'
45
+ import Annotation from './components/Annotations'
42
46
 
43
47
  // Helpers
48
+ import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
44
49
  import { publish, subscribe, unsubscribe } from '@cdc/core/helpers/events'
45
50
  import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
46
51
  import numberFromString from '@cdc/core/helpers/numberFromString'
@@ -50,6 +55,7 @@ import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
50
55
  import isNumber from '@cdc/core/helpers/isNumber'
51
56
  import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
52
57
  import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
58
+ import { isConvertLineToBarGraph } from './helpers/isConvertLineToBarGraph'
53
59
 
54
60
  import './scss/main.scss'
55
61
  // load both then config below determines which to use
@@ -58,10 +64,26 @@ import { getFileExtension } from '@cdc/core/helpers/getFileExtension'
58
64
  import Title from '@cdc/core/components/ui/Title'
59
65
  import { ChartConfig } from './types/ChartConfig'
60
66
  import { Label } from './types/Label'
67
+ import { type ViewportSize } from './types/ChartConfig'
61
68
  import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
62
69
  import SkipTo from '@cdc/core/components/elements/SkipTo'
63
-
64
- export default function CdcChart({ configUrl, config: configObj, isEditor = false, isDebug = false, isDashboard = false, setConfig: setParentConfig, setEditing, hostname, link, setSharedFilter, setSharedFilterValue, dashboardConfig }) {
70
+ import { filterVizData } from '@cdc/core/helpers/filterVizData'
71
+ import LegendWrapper from './components/LegendWrapper'
72
+
73
+ export default function CdcChart({
74
+ configUrl,
75
+ config: configObj,
76
+ isEditor = false,
77
+ isDebug = false,
78
+ isDashboard = false,
79
+ setConfig: setParentConfig,
80
+ setEditing,
81
+ hostname,
82
+ link,
83
+ setSharedFilter,
84
+ setSharedFilterValue,
85
+ dashboardConfig
86
+ }) {
65
87
  const transform = new DataTransform()
66
88
  const [loading, setLoading] = useState(true)
67
89
  const [colorScale, setColorScale] = useState(null)
@@ -69,12 +91,15 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
69
91
  const [stateData, setStateData] = useState(config.data || [])
70
92
  const [excludedData, setExcludedData] = useState<Record<string, number>[] | undefined>(undefined)
71
93
  const [filteredData, setFilteredData] = useState<Record<string, any>[] | undefined>(undefined)
72
- const [seriesHighlight, setSeriesHighlight] = useState<string[]>(configObj && configObj?.legend?.seriesHighlight?.length ? [...configObj?.legend?.seriesHighlight] : [])
73
- const [currentViewport, setCurrentViewport] = useState('lg')
74
- const [dimensions, setDimensions] = useState<[number?, number?]>([])
94
+ const [seriesHighlight, setSeriesHighlight] = useState<string[]>(
95
+ configObj && configObj?.legend?.seriesHighlight?.length ? [...configObj?.legend?.seriesHighlight] : []
96
+ )
97
+ const [currentViewport, setCurrentViewport] = useState<ViewportSize>('lg')
98
+ const [dimensions, setDimensions] = useState<DimensionsType>([0, 0])
75
99
  const [externalFilters, setExternalFilters] = useState<any[]>()
76
100
  const [container, setContainer] = useState()
77
101
  const [coveLoadedEventRan, setCoveLoadedEventRan] = useState(false)
102
+ const [isDraggingAnnotation, setIsDraggingAnnotation] = useState(false)
78
103
  const [dynamicLegendItems, setDynamicLegendItems] = useState<any[]>([])
79
104
  const [imageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`)
80
105
  const [brushConfig, setBrushConfig] = useState({
@@ -82,11 +107,25 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
82
107
  isActive: false,
83
108
  isBrushing: false
84
109
  })
110
+
111
+ let [width] = dimensions
112
+ const useVertical = config.orientation === 'vertical'
113
+ const useMobileVertical = config.heights?.mobileVertical && ['xs', 'xxs'].includes(currentViewport)
114
+ const responsiveVertical = useMobileVertical ? 'mobileVertical' : 'vertical'
115
+ const renderedOrientation = useVertical ? responsiveVertical : 'horizontal'
116
+ let height = config.aspectRatio ? width * config.aspectRatio : config?.heights?.[renderedOrientation]
117
+ if (config.visualizationType === 'Pie') height = config?.heights?.[renderedOrientation]
118
+ height = height + Number(config.orientation === 'horizontal' ? config.yAxis.size : config?.xAxis?.size) + 45
119
+
85
120
  type Config = typeof config
86
121
  let legendMemo = useRef(new Map()) // map collection
87
122
  let innerContainerRef = useRef()
88
123
  const legendRef = useRef(null)
89
124
 
125
+ const handleDragStateChange = isDragging => {
126
+ setIsDraggingAnnotation(isDragging)
127
+ }
128
+
90
129
  if (isDebug) console.log('Chart config, isEditor', config, isEditor)
91
130
 
92
131
  // Destructure items from config for more readable JSX
@@ -101,7 +140,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
101
140
 
102
141
  const { barBorderClass, lineDatapointClass, contentClasses, sparkLineStyles } = useDataVizClasses(config)
103
142
  const legendId = useId()
104
- const handleChartTabbing = !config.legend?.hide ? legendId : config?.title ? `dataTableSection__${config.title.replace(/\s/g, '')}` : `dataTableSection`
143
+
144
+ const checkLineToBarGraph = () => {
145
+ return isConvertLineToBarGraph(config.visualizationType, filteredData, config.allowLineToBarGraph)
146
+ }
105
147
 
106
148
  const reloadURLData = async () => {
107
149
  if (config.dataUrl) {
@@ -164,7 +206,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
164
206
  if (data) {
165
207
  setStateData(data)
166
208
  setExcludedData(data)
167
- setFilteredData(filterData(config.filters, data))
209
+ setFilteredData(filterVizData(config.filters, data))
168
210
  }
169
211
  }
170
212
  }
@@ -175,7 +217,11 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
175
217
  // If data is included through a URL, fetch that and store
176
218
  let data: any[] = response.data || []
177
219
 
178
- const urlFilters = response.filters ? (response.filters.filter(filter => filter.type === 'url').length > 0 ? true : false) : false
220
+ const urlFilters = response.filters
221
+ ? response.filters.filter(filter => filter.type === 'url').length > 0
222
+ ? true
223
+ : false
224
+ : false
179
225
 
180
226
  if (response.dataUrl && !urlFilters) {
181
227
  try {
@@ -251,11 +297,11 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
251
297
  if (!series.axis) series.axis = 'Left'
252
298
  })
253
299
 
254
- if (!newConfig.data && data) {
300
+ if (data) {
255
301
  newConfig.data = data
256
302
  }
257
303
 
258
- const processedConfig = { ...(await coveUpdateWorker(newConfig)) }
304
+ const processedConfig = { ...coveUpdateWorker(newConfig) }
259
305
 
260
306
  updateConfig(processedConfig, data)
261
307
  }
@@ -275,7 +321,11 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
275
321
  if (newConfig.exclusions && newConfig.exclusions.active) {
276
322
  if (newConfig.xAxis.type === 'categorical' && newConfig.exclusions.keys?.length > 0) {
277
323
  newExcludedData = data.filter(e => !newConfig.exclusions.keys.includes(e[newConfig.xAxis.dataKey]))
278
- } else if (isDateScale(newConfig.xAxis) && (newConfig.exclusions.dateStart || newConfig.exclusions.dateEnd) && newConfig.xAxis.dateParseFormat) {
324
+ } else if (
325
+ isDateScale(newConfig.xAxis) &&
326
+ (newConfig.exclusions.dateStart || newConfig.exclusions.dateEnd) &&
327
+ newConfig.xAxis.dateParseFormat
328
+ ) {
279
329
  // Filter dates
280
330
  const timestamp = e => new Date(e).getTime()
281
331
 
@@ -286,7 +336,9 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
286
336
  let endDateValid = undefined !== typeof endDate && false === isNaN(endDate)
287
337
 
288
338
  if (startDateValid && endDateValid) {
289
- newExcludedData = data.filter(e => timestamp(e[newConfig.xAxis.dataKey]) >= startDate && timestamp(e[newConfig.xAxis.dataKey]) <= endDate)
339
+ newExcludedData = data.filter(
340
+ e => timestamp(e[newConfig.xAxis.dataKey]) >= startDate && timestamp(e[newConfig.xAxis.dataKey]) <= endDate
341
+ )
290
342
  } else if (startDateValid) {
291
343
  newExcludedData = data.filter(e => timestamp(e[newConfig.xAxis.dataKey]) >= startDate)
292
344
  } else if (endDateValid) {
@@ -305,38 +357,67 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
305
357
  let currentData: any[] = []
306
358
  if (newConfig.filters) {
307
359
  newConfig.filters.forEach((filter, index) => {
308
- let filterValues = []
309
-
310
- filterValues = filter.orderedValues || generateValuesForFilter(filter.columnName, newExcludedData).sort(filter.order === 'desc' ? sortDesc : sortAsc)
360
+ const filterValues =
361
+ filter.filterStyle === 'nested-dropdown'
362
+ ? filter.values
363
+ : filter.orderedValues ||
364
+ generateValuesForFilter(filter.columnName, newExcludedData).sort(
365
+ filter.order === 'desc' ? sortDesc : sortAsc
366
+ )
311
367
 
312
368
  newConfig.filters[index].values = filterValues
313
369
  // Initial filter should be active
314
370
 
315
- newConfig.filters[index].active = newConfig.filters[index].active || filterValues[0]
316
- newConfig.filters[index].filterStyle = newConfig.filters[index].filterStyle ? newConfig.filters[index].filterStyle : 'dropdown'
371
+ const includes = (arr: any[], val: any): boolean => (arr || []).map(val => String(val)).includes(String(val))
372
+ newConfig.filters[index].active =
373
+ !newConfig.filters[index].active || !includes(filterValues, newConfig.filters[index].active)
374
+ ? filterValues[0]
375
+ : newConfig.filters[index].active
376
+ newConfig.filters[index].filterStyle = newConfig.filters[index].filterStyle
377
+ ? newConfig.filters[index].filterStyle
378
+ : 'dropdown'
317
379
  })
318
- currentData = filterData(newConfig.filters, newExcludedData)
380
+ currentData = filterVizData(newConfig.filters, newExcludedData)
319
381
  setFilteredData(currentData)
320
382
  }
321
383
 
322
- if (newConfig.xAxis.type === 'date-time' && newConfig.barThickness > 0.1) {
323
- newConfig.barThickness = 0.035
324
- } else if (newConfig.xAxis.type !== 'date-time' && newConfig.barThickness < 0.1) {
325
- newConfig.barThickness = 0.35
384
+ if (newConfig.xAxis.type === 'date-time' && config.orientation === 'horizontal') {
385
+ newConfig.xAxis.type = 'date'
326
386
  }
327
387
 
328
388
  //Enforce default values that need to be calculated at runtime
329
389
  newConfig.runtime = {}
390
+ newConfig.runtime.series = newConfig.dynamicSeries ? [] : newConfig.series
330
391
  newConfig.runtime.seriesLabels = {}
331
392
  newConfig.runtime.seriesLabelsAll = []
332
393
  newConfig.runtime.originalXAxis = newConfig.xAxis
333
394
 
395
+ if (newConfig.dynamicSeries) {
396
+ let finalData = dataOverride || newConfig.formattedData || newConfig.data
397
+ if (finalData && finalData.length && finalData.length > 0) {
398
+ Object.keys(finalData[0]).forEach(seriesKey => {
399
+ if (
400
+ seriesKey !== newConfig.xAxis.dataKey &&
401
+ (!newConfig.filters || newConfig.filters.filter(filter => filter.columnName === seriesKey).length === 0) &&
402
+ (!newConfig.columns || Object.keys(newConfig.columns).indexOf(seriesKey) === -1)
403
+ ) {
404
+ newConfig.runtime.series.push({
405
+ dataKey: seriesKey,
406
+ type: newConfig.dynamicSeriesType,
407
+ lineType: newConfig.dynamicSeriesLineType,
408
+ tooltip: true
409
+ })
410
+ }
411
+ })
412
+ }
413
+ }
414
+
334
415
  if (newConfig.visualizationType === 'Pie') {
335
416
  newConfig.runtime.seriesKeys = (dataOverride || data).map(d => d[newConfig.xAxis.dataKey])
336
417
  newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
337
418
  } else {
338
- newConfig.runtime.seriesKeys = newConfig.series
339
- ? newConfig.series.map(series => {
419
+ newConfig.runtime.seriesKeys = newConfig.runtime.series
420
+ ? newConfig.runtime.series.map(series => {
340
421
  newConfig.runtime.seriesLabels[series.dataKey] = series.name || series.label || series.dataKey
341
422
  newConfig.runtime.seriesLabelsAll.push(series.name || series.dataKey)
342
423
  return series.dataKey
@@ -345,8 +426,12 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
345
426
  }
346
427
 
347
428
  if (newConfig.visualizationType === 'Box Plot' && newConfig.series) {
348
- let allKeys = newExcludedData ? newExcludedData.map(d => d[newConfig.xAxis.dataKey]) : data.map(d => d[newConfig.xAxis.dataKey])
349
- let allValues = newExcludedData ? newExcludedData.map(d => Number(d[newConfig?.series[0]?.dataKey])) : data.map(d => Number(d[newConfig?.series[0]?.dataKey]))
429
+ let allKeys = newExcludedData
430
+ ? newExcludedData.map(d => d[newConfig.xAxis.dataKey])
431
+ : data.map(d => d[newConfig.xAxis.dataKey])
432
+ let allValues = newExcludedData
433
+ ? newExcludedData.map(d => Number(d[newConfig?.series[0]?.dataKey]))
434
+ : data.map(d => Number(d[newConfig?.series[0]?.dataKey]))
350
435
 
351
436
  const uniqueArray = function (arrArg) {
352
437
  return arrArg.filter(function (elem, pos, arr) {
@@ -366,7 +451,9 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
366
451
  if (!g) throw new Error('No groups resolved in box plots')
367
452
 
368
453
  // filter data by group
369
- let filteredData = newExcludedData ? newExcludedData.filter(item => item[newConfig.xAxis.dataKey] === g) : data.filter(item => item[newConfig.xAxis.dataKey] === g)
454
+ let filteredData = newExcludedData
455
+ ? newExcludedData.filter(item => item[newConfig.xAxis.dataKey] === g)
456
+ : data.filter(item => item[newConfig.xAxis.dataKey] === g)
370
457
  let filteredDataValues: number[] = filteredData.map(item => Number(item[newConfig?.series[0]?.dataKey]))
371
458
 
372
459
  // Sort the data for upcoming functions.
@@ -454,7 +541,12 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
454
541
  if (series.type === 'Bar' || series.type === 'Combo') {
455
542
  newConfig.runtime.barSeriesKeys.push(series.dataKey)
456
543
  }
457
- if (series.type === 'Line' || series.type === 'dashed-sm' || series.type === 'dashed-md' || series.type === 'dashed-lg') {
544
+ if (
545
+ series.type === 'Line' ||
546
+ series.type === 'dashed-sm' ||
547
+ series.type === 'dashed-md' ||
548
+ series.type === 'dashed-lg'
549
+ ) {
458
550
  newConfig.runtime.lineSeriesKeys.push(series.dataKey)
459
551
  }
460
552
  if (series.type === 'Combo') {
@@ -481,13 +573,21 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
481
573
  })
482
574
  }
483
575
 
484
- if ((newConfig.visualizationType === 'Bar' && newConfig.orientation === 'horizontal') || ['Deviation Bar', 'Paired Bar', 'Forest Plot'].includes(newConfig.visualizationType)) {
576
+ if (
577
+ (newConfig.visualizationType === 'Bar' && newConfig.orientation === 'horizontal') ||
578
+ ['Deviation Bar', 'Paired Bar', 'Forest Plot'].includes(newConfig.visualizationType)
579
+ ) {
485
580
  newConfig.runtime.xAxis = newConfig.yAxis['yAxis'] ? newConfig.yAxis['yAxis'] : newConfig.yAxis
486
581
  newConfig.runtime.yAxis = newConfig.xAxis['xAxis'] ? newConfig.xAxis['xAxis'] : newConfig.xAxis
487
582
 
488
583
  newConfig.runtime.horizontal = false
489
584
  newConfig.orientation = 'horizontal'
490
- } else if (['Box Plot', 'Scatter Plot', 'Area Chart', 'Line', 'Forecasting'].includes(newConfig.visualizationType)) {
585
+ // remove after COVE supports categorical axis on horizonatal bars
586
+ newConfig.yAxis.type = newConfig.yAxis.type === 'categorical' ? 'linear' : newConfig.yAxis.type
587
+ } else if (
588
+ ['Box Plot', 'Scatter Plot', 'Area Chart', 'Line', 'Forecasting'].includes(newConfig.visualizationType) &&
589
+ !checkLineToBarGraph()
590
+ ) {
491
591
  newConfig.runtime.xAxis = newConfig.xAxis
492
592
  newConfig.runtime.yAxis = newConfig.yAxis
493
593
  newConfig.runtime.horizontal = false
@@ -499,10 +599,16 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
499
599
  }
500
600
 
501
601
  newConfig.runtime.uniqueId = Date.now()
502
- newConfig.runtime.editorErrorMessage = newConfig.visualizationType === 'Pie' && !newConfig.yAxis.dataKey ? 'Data Key property in Y Axis section must be set for pie charts.' : ''
602
+ newConfig.runtime.editorErrorMessage =
603
+ newConfig.visualizationType === 'Pie' && !newConfig.yAxis.dataKey
604
+ ? 'Data Key property in Y Axis section must be set for pie charts.'
605
+ : ''
503
606
 
504
607
  // Sankey Description box error message
505
- newConfig.runtime.editorErrorMessage = newConfig.visualizationType === 'Sankey' && !newConfig.description ? 'SUBTEXT/CITATION field is empty: A description of the Sankey Diagram data must be inputted.' : ''
608
+ newConfig.runtime.editorErrorMessage =
609
+ newConfig.visualizationType === 'Sankey' && !newConfig.description
610
+ ? 'SUBTEXT/CITATION field is empty: A description of the Sankey Diagram data must be inputted.'
611
+ : ''
506
612
 
507
613
  if (newConfig.legend.seriesHighlight?.length) {
508
614
  setSeriesHighlight(newConfig.legend?.seriesHighlight)
@@ -528,7 +634,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
528
634
 
529
635
  // Sorts data series for horizontal bar charts
530
636
  const sortData = (a, b) => {
531
- let sortKey = config.visualizationType === 'Bar' && config.visualizationSubType === 'horizontal' ? config.xAxis.dataKey : config.yAxis.sortKey
637
+ let sortKey =
638
+ config.visualizationType === 'Bar' && config.visualizationSubType === 'horizontal'
639
+ ? config.xAxis.dataKey
640
+ : config.yAxis.sortKey
532
641
  let aData = parseFloat(a[sortKey])
533
642
  let bData = parseFloat(b[sortKey])
534
643
 
@@ -545,15 +654,14 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
545
654
  const resizeObserver = new ResizeObserver(entries => {
546
655
  for (let entry of entries) {
547
656
  let { width, height } = entry.contentRect
548
- let newViewport = getViewport(width)
549
657
  let svgMarginWidth = 32
550
658
  let editorWidth = 350
551
659
 
552
- setCurrentViewport(newViewport)
660
+ width = isEditor ? width - editorWidth : width
553
661
 
554
- if (isEditor) {
555
- width = width - editorWidth
556
- }
662
+ let newViewport = getViewport(width)
663
+
664
+ setCurrentViewport(newViewport)
557
665
 
558
666
  if (entry.target.dataset.lollipop === 'true') {
559
667
  width = width - 2.5
@@ -628,14 +736,19 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
628
736
  let configCopy = { ...config }
629
737
  delete configCopy['filters']
630
738
  setConfig(configCopy)
631
- setFilteredData(filterData(externalFilters, excludedData))
739
+ setFilteredData(filterVizData(externalFilters, excludedData))
632
740
  }
633
741
  }
634
742
 
635
- if (externalFilters && externalFilters.length > 0 && externalFilters.length > 0 && externalFilters[0].hasOwnProperty('active')) {
743
+ if (
744
+ externalFilters &&
745
+ externalFilters.length > 0 &&
746
+ externalFilters.length > 0 &&
747
+ externalFilters[0].hasOwnProperty('active')
748
+ ) {
636
749
  let newConfigHere = { ...config, filters: externalFilters }
637
750
  setConfig(newConfigHere)
638
- setFilteredData(filterData(externalFilters, excludedData))
751
+ setFilteredData(filterVizData(externalFilters, excludedData))
639
752
  }
640
753
  }, [externalFilters]) // eslint-disable-line
641
754
 
@@ -647,10 +760,25 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
647
760
  }, [configObj.data]) // eslint-disable-line
648
761
  }
649
762
 
763
+ // This will set the bump chart's default scaling type to date-time
764
+ useEffect(() => {
765
+ if (['Bump Chart'].includes(config.visualizationType)) {
766
+ setConfig({
767
+ ...config,
768
+ xAxis: {
769
+ ...config.xAxis,
770
+ type: 'date-time'
771
+ }
772
+ })
773
+ }
774
+ }, [config.visualizationType])
775
+
650
776
  // Generates color palette to pass to child chart component
651
777
  useEffect(() => {
652
778
  if (stateData && config.xAxis && config.runtime?.seriesKeys) {
653
- const configPalette = ['Paired Bar', 'Deviation Bar'].includes(config.visualizationType) ? config.twoColor.palette : config.palette
779
+ const configPalette = ['Paired Bar', 'Deviation Bar'].includes(config.visualizationType)
780
+ ? config.twoColor.palette
781
+ : config.palette
654
782
  const allPalettes: Record<string, string[]> = { ...colorPalettes, ...twoColorPalette }
655
783
  let palette = config.customColors || allPalettes[configPalette]
656
784
  let numberOfKeys = config.runtime.seriesKeys.length
@@ -754,19 +882,6 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
754
882
  return timeFormat(config.tooltips.dateDisplayFormat)(date)
755
883
  }
756
884
 
757
- // function calculates the width of given text and its font-size
758
- function getTextWidth(text: string, font: string): number | undefined {
759
- const canvas = document.createElement('canvas')
760
- const context = canvas.getContext('2d')
761
- if (!context) {
762
- console.error('2d context not found')
763
- return
764
- }
765
- context.font = font || getComputedStyle(document.body).font
766
-
767
- return Math.ceil(context.measureText(text).width)
768
- }
769
-
770
885
  // Format numeric data based on settings in config OR from passed in settings for Additional Columns
771
886
  // - use only for old horizontal data - newer formatNumber is in helper/formatNumber
772
887
  // TODO: we should combine various formatNumber functions across this project.
@@ -785,7 +900,20 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
785
900
 
786
901
  // destructure dataFormat values
787
902
  let {
788
- dataFormat: { commas, abbreviated, roundTo, prefix, suffix, rightRoundTo, bottomRoundTo, rightPrefix, rightSuffix, bottomPrefix, bottomSuffix, bottomAbbreviated }
903
+ dataFormat: {
904
+ commas,
905
+ abbreviated,
906
+ roundTo,
907
+ prefix,
908
+ suffix,
909
+ rightRoundTo,
910
+ bottomRoundTo,
911
+ rightPrefix,
912
+ rightSuffix,
913
+ bottomPrefix,
914
+ bottomSuffix,
915
+ bottomAbbreviated
916
+ }
789
917
  } = config
790
918
 
791
919
  // check if value contains comma and remove it. later will add comma below.
@@ -856,7 +984,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
856
984
  // Edge case for small numbers with decimals
857
985
  // - if roundTo undefined which means it is blank, then do not round
858
986
 
859
- if ((axis === 'left' && commas && abbreviated && shouldAbbreviate) || (axis === 'bottom' && commas && abbreviated && shouldAbbreviate)) {
987
+ if (
988
+ (axis === 'left' && commas && abbreviated && shouldAbbreviate) ||
989
+ (axis === 'bottom' && commas && abbreviated && shouldAbbreviate)
990
+ ) {
860
991
  num = num // eslint-disable-line
861
992
  } else {
862
993
  num = num.toLocaleString('en-US', stringFormattingOptions)
@@ -912,21 +1043,6 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
912
1043
  return String(result)
913
1044
  }
914
1045
 
915
- // Select appropriate chart type
916
- const chartComponents = {
917
- 'Paired Bar': <LinearChart />,
918
- Forecasting: <LinearChart />,
919
- Bar: <LinearChart />,
920
- Line: <LinearChart />,
921
- Combo: <LinearChart />,
922
- Pie: <PieChart />,
923
- 'Box Plot': <LinearChart />,
924
- 'Area Chart': <LinearChart />,
925
- 'Scatter Plot': <LinearChart />,
926
- 'Deviation Bar': <LinearChart />,
927
- 'Forest Plot': <LinearChart />
928
- }
929
-
930
1046
  const missingRequiredSections = () => {
931
1047
  if (config.visualizationType === 'Sankey') return false // skip checks for now
932
1048
  if (config.visualizationType === 'Forecasting') return false // skip required checks for now.
@@ -936,7 +1052,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
936
1052
  return true
937
1053
  }
938
1054
  } else {
939
- if (undefined === config?.series || false === config?.series.length > 0) {
1055
+ if ((undefined === config?.series || false === config?.series.length > 0) && !config?.dynamicSeries) {
940
1056
  return true
941
1057
  }
942
1058
  }
@@ -1043,7 +1159,12 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
1043
1159
  <section className='waiting-container'>
1044
1160
  <h3>Finish Configuring</h3>
1045
1161
  <p>Set all required options to the left and confirm below to display a preview of the chart.</p>
1046
- <Button className='btn' style={{ margin: '1em auto' }} disabled={missingRequiredSections()} onClick={e => confirmDone(e)}>
1162
+ <Button
1163
+ className='btn'
1164
+ style={{ margin: '1em auto' }}
1165
+ disabled={missingRequiredSections()}
1166
+ onClick={e => confirmDone(e)}
1167
+ >
1047
1168
  I'm Done
1048
1169
  </Button>
1049
1170
  </section>
@@ -1107,17 +1228,40 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
1107
1228
  // Prevent render if loading
1108
1229
  let body = <Loading />
1109
1230
 
1231
+ const makeClassName = string => {
1232
+ if (!string || !string.toLowerCase) return
1233
+ return string.toLowerCase().replaceAll(/ /g, '-')
1234
+ }
1235
+
1110
1236
  const getChartWrapperClasses = () => {
1237
+ const isLegendOnBottom = legend?.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
1111
1238
  const classes = ['chart-container', 'p-relative']
1112
- if (config.legend.position === 'bottom') classes.push('bottom')
1113
- if (config.legend.hide) classes.push('legend-hidden')
1239
+ if (legend?.position) {
1240
+ if (['sm', 'xs', 'xxs'].includes(currentViewport) && legend?.position !== 'top') {
1241
+ classes.push('legend-bottom')
1242
+ } else {
1243
+ classes.push(`legend-${legend.position}`)
1244
+ }
1245
+ }
1246
+ if (legend?.hide) classes.push('legend-hidden')
1114
1247
  if (lineDatapointClass) classes.push(lineDatapointClass)
1115
1248
  if (!config.barHasBorder) classes.push('chart-bar--no-border')
1116
- if (isDebug) classes.push('debug')
1249
+ if (config.brush?.active && dashboardConfig?.type === 'dashboard' && (!isLegendOnBottom || legend.hide))
1250
+ classes.push('dashboard-brush')
1117
1251
  classes.push(...contentClasses)
1118
1252
  return classes
1119
1253
  }
1120
1254
 
1255
+ const getChartSubTextClasses = () => {
1256
+ const classes = ['subtext ']
1257
+ const isLegendOnBottom = legend?.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
1258
+
1259
+ if (config.isResponsiveTicks) classes.push('subtext--responsive-ticks ')
1260
+ if (config.brush?.active && !isLegendOnBottom) classes.push('subtext--brush-active ')
1261
+ if (config.brush?.active && config.legend.hide) classes.push('subtext--brush-active ')
1262
+ return classes
1263
+ }
1264
+
1121
1265
  if (!loading) {
1122
1266
  const tableLink = (
1123
1267
  <a href={`#data-table-${config.dataKey}`} className='margin-left-href'>
@@ -1131,70 +1275,202 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
1131
1275
  {config.newViz && <Confirm />}
1132
1276
  {undefined === config.newViz && isEditor && config.runtime && config.runtime?.editorErrorMessage && <Error />}
1133
1277
  {!missingRequiredSections() && !config.newViz && (
1134
- <div className='cdc-chart-inner-container cove-component__content' aria-label={handleChartAriaLabels(config)} tabIndex={0}>
1135
- <Title showTitle={config.showTitle} isDashboard={isDashboard} title={title} superTitle={config.superTitle} classes={['chart-title', `${config.theme}`, 'cove-component__header']} style={undefined} />
1278
+ <div
1279
+ className={`cdc-chart-inner-container cove-component__content type-${makeClassName(
1280
+ config.visualizationType
1281
+ )}`}
1282
+ aria-label={handleChartAriaLabels(config)}
1283
+ tabIndex={0}
1284
+ >
1285
+ <Title
1286
+ showTitle={config.showTitle}
1287
+ isDashboard={isDashboard}
1288
+ title={title}
1289
+ superTitle={config.superTitle}
1290
+ classes={['chart-title', `${config.theme}`, 'cove-component__header']}
1291
+ style={undefined}
1292
+ />
1293
+ {/* Intro Text/Message */}
1294
+ {config?.introText && config.visualizationType !== 'Spark Line' && (
1295
+ <section
1296
+ className={`introText legend_${config.legend.hide ? 'hidden' : 'visible'}_${config.legend.position} `}
1297
+ >
1298
+ {parse(config.introText)}
1299
+ </section>
1300
+ )}
1136
1301
 
1137
1302
  {/* Filters */}
1138
- {config.filters && !externalFilters && config.visualizationType !== 'Spark Line' && <Filters config={config} setConfig={setConfig} setFilteredData={setFilteredData} filteredData={filteredData} excludedData={excludedData} filterData={filterData} dimensions={dimensions} />}
1139
- <SkipTo skipId={handleChartTabbing} skipMessage='Skip Over Chart Container' />
1140
- {/* Visualization */}
1141
- {config?.introText && config.visualizationType !== 'Spark Line' && <section className='introText'>{parse(config.introText)}</section>}
1303
+ {config.filters && !externalFilters && config.visualizationType !== 'Spark Line' && (
1304
+ <Filters
1305
+ config={config}
1306
+ setConfig={setConfig}
1307
+ setFilteredData={setFilteredData}
1308
+ filteredData={filteredData}
1309
+ excludedData={excludedData}
1310
+ filterData={filterVizData}
1311
+ dimensions={dimensions}
1312
+ />
1313
+ )}
1314
+ <SkipTo skipId={handleChartTabbing(config, legendId)} skipMessage='Skip Over Chart Container' />
1315
+ {config.annotations?.length > 0 && (
1316
+ <SkipTo
1317
+ skipId={handleChartTabbing(config, legendId)}
1318
+ skipMessage={`Skip over annotations`}
1319
+ key={`skip-annotations`}
1320
+ />
1321
+ )}
1322
+
1323
+ {/* Visualization Wrapper */}
1142
1324
  <div className={getChartWrapperClasses().join(' ')}>
1143
- {/* All charts except sparkline */}
1144
- {config.visualizationType !== 'Spark Line' && chartComponents[config.visualizationType]}
1145
- {/* Sparkline */}
1146
- {config.visualizationType === 'Spark Line' && (
1147
- <>
1148
- <Filters config={config} setConfig={setConfig} setFilteredData={setFilteredData} filteredData={filteredData} excludedData={excludedData} filterData={filterData} dimensions={dimensions} />
1149
- {config?.introText && (
1150
- <section className='introText' style={{ padding: '0px 0 35px' }}>
1151
- {parse(config.introText)}
1152
- </section>
1153
- )}
1154
- <div style={{ height: `100px`, width: `100%`, ...sparkLineStyles }}>
1155
- <ParentSize>{parent => <SparkLine width={parent.width} height={parent.height} />}</ParentSize>
1156
- </div>
1157
- {description && (
1158
- <div className='subtext' style={{ padding: '35px 0 15px' }}>
1159
- {parse(description)}
1325
+ <LegendWrapper>
1326
+ <div
1327
+ className={
1328
+ legend.hide || ['xxs', 'xs', 'sm'].includes(currentViewport)
1329
+ ? 'w-100'
1330
+ : legend.position === 'bottom' || legend.position === 'top' || visualizationType === 'Sankey'
1331
+ ? 'w-100'
1332
+ : 'w-75'
1333
+ }
1334
+ >
1335
+ {/* All charts with LinearChart */}
1336
+ {!['Spark Line', 'Line', 'Sankey', 'Pie', 'Sankey'].includes(config.visualizationType) && (
1337
+ <div style={{ height, width: `100%` }}>
1338
+ <ParentSize>
1339
+ {parent => <LinearChart parentWidth={parent.width} parentHeight={parent.height} />}
1340
+ </ParentSize>
1160
1341
  </div>
1161
1342
  )}
1162
- </>
1343
+
1344
+ {config.visualizationType === 'Pie' && (
1345
+ <ParentSize className='justify-content-center d-flex' style={{ height, width: `100%` }}>
1346
+ {parent => <PieChart parentWidth={parent.width} parentHeight={parent.height} />}
1347
+ </ParentSize>
1348
+ )}
1349
+ {/* Line Chart */}
1350
+ {config.visualizationType === 'Line' &&
1351
+ (checkLineToBarGraph() ? (
1352
+ <div style={{ height: config?.heights?.vertical, width: `100%` }}>
1353
+ <ParentSize>
1354
+ {parent => <LinearChart parentWidth={parent.width} parentHeight={parent.height} />}
1355
+ </ParentSize>
1356
+ </div>
1357
+ ) : (
1358
+ <div style={{ height, width: `100%` }}>
1359
+ <ParentSize>
1360
+ {parent => <LinearChart parentWidth={parent.width} parentHeight={parent.height} />}
1361
+ </ParentSize>
1362
+ </div>
1363
+ ))}
1364
+ {/* Sparkline */}
1365
+ {config.visualizationType === 'Spark Line' && (
1366
+ <>
1367
+ <Filters
1368
+ config={config}
1369
+ setConfig={setConfig}
1370
+ setFilteredData={setFilteredData}
1371
+ filteredData={filteredData}
1372
+ excludedData={excludedData}
1373
+ filterData={filterVizData}
1374
+ dimensions={dimensions}
1375
+ />
1376
+ {config?.introText && (
1377
+ <section className='introText' style={{ padding: '0px 0 35px' }}>
1378
+ {parse(config.introText)}
1379
+ </section>
1380
+ )}
1381
+ <div style={{ height: `100px`, width: `100%`, ...sparkLineStyles }}>
1382
+ <ParentSize>{parent => <SparkLine width={parent.width} height={parent.height} />}</ParentSize>
1383
+ </div>
1384
+ {description && (
1385
+ <div className='subtext' style={{ padding: '35px 0 15px' }}>
1386
+ {parse(description)}
1387
+ </div>
1388
+ )}
1389
+ </>
1390
+ )}
1391
+ {/* Sankey */}
1392
+ {config.visualizationType === 'Sankey' && (
1393
+ <ParentSize aria-hidden='true'>
1394
+ {parent => <SankeyChart runtime={config.runtime} width={parent.width} height={parent.height} />}
1395
+ </ParentSize>
1396
+ )}
1397
+ </div>
1398
+ {/* Legend */}
1399
+ {!config.legend.hide &&
1400
+ config.visualizationType !== 'Spark Line' &&
1401
+ config.visualizationType !== 'Sankey' && (
1402
+ <Legend ref={legendRef} skipId={handleChartTabbing(config, legendId)} />
1403
+ )}
1404
+ </LegendWrapper>
1405
+ {/* Link */}
1406
+ {isDashboard && config.table && config.table.show && config.table.showDataTableLink
1407
+ ? tableLink
1408
+ : link && link}
1409
+ {/* Description */}
1410
+
1411
+ {description && config.visualizationType !== 'Spark Line' && (
1412
+ <div className={getChartSubTextClasses().join('')}>{parse(description)}</div>
1163
1413
  )}
1164
- {/* Sankey */}
1165
- {config.visualizationType === 'Sankey' && <ParentSize aria-hidden='true'>{parent => <SankeyChart runtime={config.runtime} width={parent.width} height={parent.height} />}</ParentSize>}
1166
- {!config.legend.hide && config.visualizationType !== 'Spark Line' && config.visualizationType !== 'Sankey' && <Legend ref={legendRef} />}
1414
+ {false && <Annotation.List />}
1415
+
1416
+ {/* buttons */}
1417
+ <MediaControls.Section classes={['download-buttons']}>
1418
+ {config.table.showDownloadImgButton && (
1419
+ <MediaControls.Button
1420
+ text='Download Image'
1421
+ title='Download Chart as Image'
1422
+ type='image'
1423
+ state={config}
1424
+ elementToCapture={imageId}
1425
+ />
1426
+ )}
1427
+ {config.table.showDownloadPdfButton && (
1428
+ <MediaControls.Button
1429
+ text='Download PDF'
1430
+ title='Download Chart as PDF'
1431
+ type='pdf'
1432
+ state={config}
1433
+ elementToCapture={imageId}
1434
+ />
1435
+ )}
1436
+ </MediaControls.Section>
1437
+ {/* Data Table */}
1438
+ {((config.xAxis.dataKey &&
1439
+ config.table.show &&
1440
+ config.visualizationType !== 'Spark Line' &&
1441
+ config.visualizationType !== 'Sankey') ||
1442
+ (config.visualizationType === 'Sankey' && config.table.show)) && (
1443
+ <DataTable
1444
+ config={config}
1445
+ rawData={
1446
+ config.visualizationType === 'Sankey'
1447
+ ? config?.data?.[0]?.tableData
1448
+ : config.table.customTableConfig
1449
+ ? filterVizData(config.filters, config.data)
1450
+ : config.data
1451
+ }
1452
+ runtimeData={
1453
+ config.visualizationType === 'Sankey'
1454
+ ? config?.data?.[0]?.tableData
1455
+ : filteredData || excludedData
1456
+ }
1457
+ expandDataTable={config.table.expanded}
1458
+ columns={config.columns}
1459
+ displayDataAsText={displayDataAsText}
1460
+ displayGeoName={displayGeoName}
1461
+ applyLegendToRow={applyLegendToRow}
1462
+ tableTitle={config.table.label}
1463
+ indexTitle={config.table.indexLabel}
1464
+ vizTitle={title}
1465
+ viewport={currentViewport}
1466
+ tabbingId={handleChartTabbing(config, legendId)}
1467
+ colorScale={colorScale}
1468
+ />
1469
+ )}
1470
+ {config?.annotations?.length > 0 && <Annotation.Dropdown />}
1471
+ {/* show pdf or image button */}
1167
1472
  </div>
1168
- {/* Link */}
1169
- {isDashboard && config.table && config.table.show && config.table.showDataTableLink ? tableLink : link && link}
1170
- {/* Description */}
1171
- {description && config.visualizationType !== 'Spark Line' && <div className={'column ' + config.isResponsiveTicks ? 'subtext--responsive-ticks' : 'subtext'}>{parse(description)}</div>}
1172
- {/* buttons */}
1173
- <MediaControls.Section classes={['download-buttons']}>
1174
- {config.table.showDownloadImgButton && <MediaControls.Button text='Download Image' title='Download Chart as Image' type='image' state={config} elementToCapture={imageId} />}
1175
- {config.table.showDownloadPdfButton && <MediaControls.Button text='Download PDF' title='Download Chart as PDF' type='pdf' state={config} elementToCapture={imageId} />}
1176
- </MediaControls.Section>
1177
- {/* Data Table */}
1178
- {((config.xAxis.dataKey && config.table.show && config.visualizationType !== 'Spark Line' && config.visualizationType !== 'Sankey') || (config.visualizationType === 'Sankey' && config.table.show)) && (
1179
- <DataTable
1180
- config={config}
1181
- rawData={config.visualizationType === 'Sankey' ? config?.data?.[0]?.tableData : config.table.customTableConfig ? filterData(config.filters, config.data) : config.data}
1182
- runtimeData={config.visualizationType === 'Sankey' ? config?.data?.[0]?.tableData : filteredData || excludedData}
1183
- expandDataTable={config.table.expanded}
1184
- columns={config.columns}
1185
- displayDataAsText={displayDataAsText}
1186
- displayGeoName={displayGeoName}
1187
- applyLegendToRow={applyLegendToRow}
1188
- tableTitle={config.table.label}
1189
- indexTitle={config.table.indexLabel}
1190
- vizTitle={title}
1191
- viewport={currentViewport}
1192
- tabbingId={handleChartTabbing}
1193
- colorScale={colorScale}
1194
- />
1195
- )}
1196
1473
  {config?.footnotes && <section className='footnotes'>{parse(config.footnotes)}</section>}
1197
- {/* show pdf or image button */}
1198
1474
  </div>
1199
1475
  )}
1200
1476
  </Layout.Responsive>
@@ -1202,7 +1478,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
1202
1478
  )
1203
1479
  }
1204
1480
 
1205
- const getXAxisData = d => (isDateScale(config.runtime.xAxis) ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
1481
+ const getXAxisData = d =>
1482
+ isDateScale(config.runtime.xAxis)
1483
+ ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
1484
+ : d[config.runtime.originalXAxis.dataKey]
1206
1485
  const getYAxisData = (d, seriesKey) => d[seriesKey]
1207
1486
 
1208
1487
  const capitalize = str => {
@@ -1211,59 +1490,69 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
1211
1490
 
1212
1491
  const contextValues = {
1213
1492
  brushConfig,
1214
- setBrushConfig,
1215
1493
  capitalize,
1216
- getXAxisData,
1217
- getYAxisData,
1218
- config,
1219
- setConfig,
1220
- rawData: stateData ?? {},
1221
- excludedData: excludedData,
1222
- transformedData: clean(filteredData || excludedData), // do this right before passing to components
1223
- tableData: filteredData || excludedData, // do not clean table data
1224
- unfilteredData: stateData,
1225
- seriesHighlight,
1494
+ clean,
1495
+ colorPalettes,
1226
1496
  colorScale,
1227
- dimensions,
1497
+ config,
1228
1498
  currentViewport,
1229
- parseDate,
1499
+ dashboardConfig,
1500
+ debugSvg: isDebug,
1501
+ dimensions,
1502
+ dynamicLegendItems,
1503
+ excludedData: excludedData,
1230
1504
  formatDate,
1231
- formatTooltipsDate,
1232
1505
  formatNumber,
1233
- loading,
1234
- updateConfig,
1235
- colorPalettes,
1236
- isDashboard,
1237
- setParentConfig,
1238
- missingRequiredSections,
1239
- setEditing,
1240
- setFilteredData,
1506
+ formatTooltipsDate,
1507
+ getTextWidth,
1508
+ getXAxisData,
1509
+ getYAxisData,
1241
1510
  handleChartAriaLabels,
1511
+ handleLineType,
1242
1512
  highlight,
1243
1513
  highlightReset,
1244
- legend,
1245
- setSeriesHighlight,
1246
- dynamicLegendItems,
1247
- setDynamicLegendItems,
1248
- filterData,
1249
1514
  imageId,
1250
- handleLineType,
1251
- lineOptions,
1252
- isNumber,
1253
- getTextWidth,
1254
- twoColorPalette,
1255
- isEditor,
1515
+ isDashboard,
1516
+ isLegendBottom: legend?.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport),
1256
1517
  isDebug,
1518
+ isDraggingAnnotation,
1519
+ handleDragStateChange,
1520
+ isEditor,
1521
+ isNumber,
1522
+ legend,
1523
+ lineOptions,
1524
+ loading,
1525
+ missingRequiredSections,
1526
+ outerContainerRef,
1527
+ parseDate,
1528
+ rawData: stateData ?? {},
1529
+ seriesHighlight,
1530
+ setBrushConfig,
1531
+ setConfig,
1532
+ setDynamicLegendItems,
1533
+ setEditing,
1534
+ setFilteredData,
1535
+ setParentConfig,
1536
+ setSeriesHighlight,
1257
1537
  setSharedFilter,
1258
1538
  setSharedFilterValue,
1259
- dashboardConfig,
1260
- debugSvg: isDebug,
1261
- clean
1539
+ tableData: filteredData || excludedData, // do not clean table data
1540
+ transformedData: clean(filteredData || excludedData), // do this right before passing to components
1541
+ twoColorPalette,
1542
+ unfilteredData: stateData,
1543
+ updateConfig
1262
1544
  }
1263
1545
 
1264
1546
  return (
1265
1547
  <ConfigContext.Provider value={contextValues}>
1266
- <Layout.VisualizationWrapper config={config} isEditor={isEditor} currentViewport={currentViewport} ref={outerContainerRef} imageId={imageId} showEditorPanel={config?.showEditorPanel}>
1548
+ <Layout.VisualizationWrapper
1549
+ config={config}
1550
+ isEditor={isEditor}
1551
+ currentViewport={currentViewport}
1552
+ ref={outerContainerRef}
1553
+ imageId={imageId}
1554
+ showEditorPanel={config?.showEditorPanel}
1555
+ >
1267
1556
  {body}
1268
1557
  </Layout.VisualizationWrapper>
1269
1558
  </ConfigContext.Provider>