@cdc/chart 4.24.12 → 4.25.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 (72) hide show
  1. package/dist/cdcchart.js +79611 -78971
  2. package/examples/feature/boxplot/boxplot.json +2 -157
  3. package/examples/feature/boxplot/testing.csv +23 -38
  4. package/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json +394 -30
  5. package/examples/private/ehdi.json +29939 -0
  6. package/examples/private/not-loading.json +360 -0
  7. package/index.html +7 -14
  8. package/package.json +2 -2
  9. package/src/CdcChart.tsx +92 -1512
  10. package/src/CdcChartComponent.tsx +1105 -0
  11. package/src/_stories/Chart.Anchors.stories.tsx +1 -1
  12. package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
  13. package/src/_stories/Chart.DynamicSeries.stories.tsx +1 -1
  14. package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
  15. package/src/_stories/Chart.ScatterPlot.stories.tsx +19 -0
  16. package/src/_stories/Chart.tooltip.stories.tsx +1 -2
  17. package/src/_stories/ChartAnnotation.stories.tsx +1 -1
  18. package/src/_stories/ChartAxisLabels.stories.tsx +1 -1
  19. package/src/_stories/ChartAxisTitles.stories.tsx +1 -1
  20. package/src/_stories/ChartEditor.stories.tsx +1 -1
  21. package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
  22. package/src/_stories/ChartLine.Symbols.stories.tsx +18 -0
  23. package/src/_stories/ChartPrefixSuffix.stories.tsx +1 -1
  24. package/src/_stories/_mock/line_chart_symbols.json +437 -0
  25. package/src/_stories/_mock/scatterplot-image-download.json +1244 -0
  26. package/src/components/Annotations/components/AnnotationDraggable.tsx +3 -11
  27. package/src/components/Annotations/components/AnnotationDropdown.tsx +3 -3
  28. package/src/components/Axis/Categorical.Axis.tsx +3 -4
  29. package/src/components/BarChart/components/BarChart.Horizontal.tsx +14 -5
  30. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +10 -4
  31. package/src/components/BarChart/components/BarChart.Vertical.tsx +2 -2
  32. package/src/components/BoxPlot/BoxPlot.tsx +34 -32
  33. package/src/components/BoxPlot/helpers/index.ts +108 -18
  34. package/src/components/DeviationBar.jsx +2 -6
  35. package/src/components/EditorPanel/EditorPanel.tsx +62 -6
  36. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +4 -0
  37. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +44 -7
  38. package/src/components/ForestPlot/ForestPlot.tsx +176 -26
  39. package/src/components/Legend/Legend.Component.tsx +29 -38
  40. package/src/components/Legend/Legend.Suppression.tsx +3 -5
  41. package/src/components/Legend/Legend.tsx +2 -2
  42. package/src/components/Legend/LegendLine.Shape.tsx +51 -0
  43. package/src/components/Legend/helpers/createFormatLabels.tsx +29 -26
  44. package/src/components/Legend/helpers/getLegendClasses.ts +20 -38
  45. package/src/components/Legend/helpers/index.ts +14 -7
  46. package/src/components/Legend/tests/getLegendClasses.test.ts +3 -20
  47. package/src/components/LineChart/components/LineChart.Circle.tsx +90 -88
  48. package/src/components/LineChart/index.tsx +4 -0
  49. package/src/components/LinearChart.tsx +65 -31
  50. package/src/components/PairedBarChart.jsx +2 -9
  51. package/src/components/ZoomBrush.tsx +5 -7
  52. package/src/data/initial-state.js +6 -3
  53. package/src/helpers/getBoxPlotConfig.ts +68 -0
  54. package/src/helpers/getColorScale.ts +28 -0
  55. package/src/helpers/getComboChartConfig.ts +42 -0
  56. package/src/helpers/getExcludedData.ts +37 -0
  57. package/src/helpers/getTopAxis.ts +7 -0
  58. package/src/hooks/useBarChart.ts +28 -9
  59. package/src/hooks/{useHighlightedBars.js → useHighlightedBars.ts} +2 -1
  60. package/src/hooks/useIntersectionObserver.ts +37 -0
  61. package/src/hooks/useMinMax.ts +4 -0
  62. package/src/hooks/useReduceData.ts +1 -1
  63. package/src/hooks/useTooltip.tsx +9 -1
  64. package/src/index.jsx +1 -0
  65. package/src/scss/DataTable.scss +0 -5
  66. package/src/scss/main.scss +30 -115
  67. package/src/types/ChartConfig.ts +6 -3
  68. package/src/types/ChartContext.ts +1 -3
  69. package/src/helpers/getQuartiles.ts +0 -27
  70. package/src/hooks/useColorScale.ts +0 -50
  71. package/src/hooks/useIntersectionObserver.jsx +0 -29
  72. package/src/hooks/useTopAxis.js +0 -6
@@ -0,0 +1,1105 @@
1
+ import React, { useState, useEffect, useCallback, useRef, useId, useContext } from 'react'
2
+
3
+ // IE11
4
+ import ResizeObserver from 'resize-observer-polyfill'
5
+ import 'whatwg-fetch'
6
+ // Core components
7
+ import Layout from '@cdc/core/components/Layout'
8
+ import Confirm from '@cdc/core/components/elements/Confirm'
9
+ import Error from '@cdc/core/components/elements/Error'
10
+ import SkipTo from '@cdc/core/components/elements/SkipTo'
11
+ import Title from '@cdc/core/components/ui/Title'
12
+ import DataTable from '@cdc/core/components/DataTable'
13
+ // Local Components
14
+ import LegendWrapper from './components/LegendWrapper'
15
+ //types
16
+ import { DimensionsType } from '@cdc/core/types/Dimensions'
17
+ import { type DashboardConfig } from '@cdc/dashboard/src/types/DashboardConfig'
18
+ import type { TableConfig } from '@cdc/core/components/DataTable/types/TableConfig'
19
+ import { AllChartsConfig, ChartConfig } from './types/ChartConfig'
20
+ import { type ViewportSize } from './types/ChartConfig'
21
+ import { Pivot } from '@cdc/core/types/Table'
22
+ import { Runtime } from '@cdc/core/types/Runtime'
23
+ import { Label } from './types/Label'
24
+ // External Libraries
25
+ import ParentSize from '@visx/responsive/lib/components/ParentSize'
26
+ import { timeParse, timeFormat } from 'd3-time-format'
27
+ import parse from 'html-react-parser'
28
+ import 'react-tooltip/dist/react-tooltip.css'
29
+ import _ from 'lodash'
30
+ // Primary Components
31
+ import ConfigContext from './ConfigContext'
32
+ import PieChart from './components/PieChart'
33
+ import SankeyChart from './components/Sankey'
34
+ import LinearChart from './components/LinearChart'
35
+ import { isDateScale } from '@cdc/core/helpers/cove/date'
36
+
37
+ import { colorPalettesChart as colorPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
38
+
39
+ import SparkLine from './components/Sparkline'
40
+ import Legend from './components/Legend'
41
+ import defaults from './data/initial-state'
42
+ import EditorPanel from './components/EditorPanel'
43
+ import { abbreviateNumber } from './helpers/abbreviateNumber'
44
+ import { handleChartTabbing } from './helpers/handleChartTabbing'
45
+
46
+ import { handleChartAriaLabels } from './helpers/handleChartAriaLabels'
47
+ import { lineOptions } from './helpers/lineOptions'
48
+ import { handleLineType } from './helpers/handleLineType'
49
+ import { handleRankByValue } from './helpers/handleRankByValue'
50
+ import { generateColorsArray } from './helpers/generateColorsArray'
51
+ import Loading from '@cdc/core/components/Loading'
52
+ import Filters from '@cdc/core/components/Filters'
53
+ import MediaControls from '@cdc/core/components/MediaControls'
54
+ import Annotation from './components/Annotations'
55
+ // Core Helpers
56
+ import { DataTransform } from '@cdc/core/helpers/DataTransform'
57
+ import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
58
+ import { missingRequiredSections } from '@cdc/core/helpers/missingRequiredSections'
59
+ import { filterVizData } from '@cdc/core/helpers/filterVizData'
60
+ import { addValuesToFilters } from '@cdc/core/helpers/addValuesToFilters'
61
+ import { publish, subscribe, unsubscribe } from '@cdc/core/helpers/events'
62
+ import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
63
+ import numberFromString from '@cdc/core/helpers/numberFromString'
64
+ import getViewport from '@cdc/core/helpers/getViewport'
65
+ import isNumber from '@cdc/core/helpers/isNumber'
66
+ import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
67
+ import EditorContext from '../../editor/src/ConfigContext'
68
+ // Local helpers
69
+ import { isConvertLineToBarGraph } from './helpers/isConvertLineToBarGraph'
70
+ import { getBoxPlotConfig } from './helpers/getBoxPlotConfig'
71
+ import { getComboChartConfig } from './helpers/getComboChartConfig'
72
+ import { getExcludedData } from './helpers/getExcludedData'
73
+ import { getColorScale } from './helpers/getColorScale'
74
+ // styles
75
+ import './scss/main.scss'
76
+
77
+ interface CdcChartProps {
78
+ config?: ChartConfig
79
+ isEditor?: boolean
80
+ isDebug?: boolean
81
+ isDashboard?: boolean
82
+ setConfig?: (config: ChartConfig) => void
83
+ setEditing?: (editing: boolean) => void
84
+ hostname?: string
85
+ link?: string
86
+ setSharedFilter?: (filter: any) => void
87
+ setSharedFilterValue?: (value: any) => void
88
+ dashboardConfig?: DashboardConfig
89
+ }
90
+ const CdcChart: React.FC<CdcChartProps> = ({
91
+ config: configObj,
92
+ isEditor = false,
93
+ isDebug = false,
94
+ isDashboard = false,
95
+ setConfig: setParentConfig,
96
+ setEditing,
97
+ link,
98
+ setSharedFilter,
99
+ setSharedFilterValue,
100
+ dashboardConfig
101
+ }) => {
102
+ const transform = new DataTransform()
103
+ const [loading, setLoading] = useState(true)
104
+ const svgRef = useRef(null)
105
+ const [colorScale, setColorScale] = useState(null)
106
+ const [config, _setConfig] = useState<ChartConfig>({} as ChartConfig)
107
+ const [stateData, setStateData] = useState(_.cloneDeep(configObj?.data) || [])
108
+ const [excludedData, setExcludedData] = useState<Record<string, number>[] | undefined>(undefined)
109
+ const [filteredData, setFilteredData] = useState<Record<string, any>[] | undefined>(undefined)
110
+ const [seriesHighlight, setSeriesHighlight] = useState<string[]>(
111
+ configObj && configObj?.legend?.seriesHighlight?.length ? [...configObj?.legend?.seriesHighlight] : []
112
+ )
113
+ const [currentViewport, setCurrentViewport] = useState<ViewportSize>('lg')
114
+ const [dimensions, setDimensions] = useState<DimensionsType>([0, 0])
115
+ const [externalFilters, setExternalFilters] = useState<any[]>()
116
+ const [container, setContainer] = useState()
117
+ const [coveLoadedEventRan, setCoveLoadedEventRan] = useState(false)
118
+ const [isDraggingAnnotation, setIsDraggingAnnotation] = useState(false)
119
+ const [dynamicLegendItems, setDynamicLegendItems] = useState<any[]>([])
120
+ const [imageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`)
121
+ const [brushConfig, setBrushConfig] = useState({
122
+ data: [],
123
+ isActive: false,
124
+ isBrushing: false
125
+ })
126
+ const editorContext = useContext(EditorContext)
127
+ const setConfig = newConfig => {
128
+ _setConfig(newConfig)
129
+ if (isEditor && !isDashboard) {
130
+ editorContext.setTempConfig(newConfig)
131
+ }
132
+ }
133
+
134
+ const { description, visualizationType } = config
135
+
136
+ const legendRef = useRef(null)
137
+ const parentRef = useRef(null)
138
+
139
+ const handleDragStateChange = isDragging => {
140
+ setIsDraggingAnnotation(isDragging)
141
+ }
142
+
143
+ if (isDebug) console.log('Chart config, isEditor', config, isEditor)
144
+
145
+ // Destructure items from config for more readable JSX
146
+ let { legend, title } = config
147
+
148
+ // set defaults on titles if blank AND only in editor
149
+ if (isEditor) {
150
+ if (!title || title === '') title = 'Chart Title'
151
+ }
152
+
153
+ if (config.table && (!config.table?.label || config.table?.label === '')) config.table.label = 'Data Table'
154
+
155
+ const { lineDatapointClass, contentClasses, sparkLineStyles } = useDataVizClasses(config)
156
+ const legendId = useId()
157
+
158
+ const hasDateAxis =
159
+ (config.xAxis || config.yAxis) && ['date-time', 'date'].includes((config.xAxis || config.yAxis).type)
160
+ const dataTableDefaultSortBy = hasDateAxis && config.xAxis.dataKey
161
+
162
+ const checkLineToBarGraph = () => {
163
+ return isConvertLineToBarGraph(config.visualizationType, filteredData, config.allowLineToBarGraph)
164
+ }
165
+
166
+ const prepareConfig = (loadedConfig: ChartConfig, data): ChartConfig => {
167
+ let newConfig = _.defaultsDeep(loadedConfig, defaults)
168
+ _.defaultsDeep(newConfig, {
169
+ table: { showVertical: false }
170
+ })
171
+
172
+ _.set(newConfig, 'table.show', _.get(newConfig, 'table.show', !isDashboard))
173
+
174
+ _.forEach(newConfig.series, series => {
175
+ _.defaults(series, {
176
+ tooltip: true,
177
+ axis: 'Left'
178
+ })
179
+ })
180
+
181
+ if (newConfig.visualizationType === 'Bump Chart') {
182
+ newConfig.xAxis.type === 'date-time'
183
+ }
184
+
185
+ return { ...coveUpdateWorker(newConfig) }
186
+ }
187
+
188
+ const updateConfig = (_config: AllChartsConfig, dataOverride?: any[]) => {
189
+ const newConfig = _.cloneDeep(_config)
190
+ let data = dataOverride || stateData
191
+
192
+ data = handleRankByValue(data, newConfig)
193
+
194
+ // Deeper copy
195
+ Object.keys(defaults).forEach(key => {
196
+ if (newConfig[key] && 'object' === typeof newConfig[key] && !Array.isArray(newConfig[key])) {
197
+ newConfig[key] = { ...defaults[key], ...newConfig[key] }
198
+ }
199
+ })
200
+
201
+ const newExcludedData: any[] = getExcludedData(newConfig, dataOverride || stateData)
202
+
203
+ setExcludedData(newExcludedData)
204
+
205
+ // After data is grabbed, loop through and generate filter column values if there are any
206
+ let currentData: any[] = []
207
+ if (newConfig.filters) {
208
+ const filtersWithValues = addValuesToFilters(newConfig.filters, newExcludedData)
209
+ currentData = filterVizData(filtersWithValues, newExcludedData)
210
+ setFilteredData(currentData)
211
+ }
212
+
213
+ if (newConfig.xAxis.type === 'date-time' && config.orientation === 'horizontal') {
214
+ newConfig.xAxis.type = 'date'
215
+ }
216
+
217
+ //Enforce default values that need to be calculated at runtime
218
+ newConfig.runtime = {} as Runtime
219
+ newConfig.runtime.series = _.cloneDeep(newConfig.series)
220
+ newConfig.runtime.seriesLabels = {}
221
+ newConfig.runtime.seriesLabelsAll = []
222
+ newConfig.runtime.originalXAxis = newConfig.xAxis
223
+
224
+ if (newConfig.visualizationType === 'Pie') {
225
+ newConfig.runtime.seriesKeys = (dataOverride || data).map(d => d[newConfig.xAxis.dataKey])
226
+ newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
227
+ } else {
228
+ const finalData = dataOverride || newConfig.formattedData || newConfig.data
229
+ newConfig.runtime.seriesKeys = (newConfig.runtime.series || []).flatMap(series => {
230
+ if (series.dynamicCategory) {
231
+ _.remove(newConfig.runtime.seriesLabelsAll, label => label === series.dataKey)
232
+ _.remove(newConfig.runtime.series, s => s.dataKey === series.dataKey)
233
+ // grab the dynamic series keys from the data
234
+ const seriesKeys: string[] = _.uniq(finalData.map(d => d[series.dynamicCategory]))
235
+ // for each of those keys perform side effects
236
+ seriesKeys.forEach(dataKey => {
237
+ newConfig.runtime.seriesLabels[dataKey] = dataKey
238
+ newConfig.runtime.seriesLabelsAll.push(dataKey)
239
+ newConfig.runtime.series.push({
240
+ dataKey,
241
+ type: series.type,
242
+ lineType: series.lineType,
243
+ originalDataKey: series.dataKey,
244
+ dynamicCategory: series.dynamicCategory,
245
+ tooltip: true
246
+ })
247
+ })
248
+ // return the series keys
249
+ return seriesKeys
250
+ } else {
251
+ newConfig.runtime.seriesLabels[series.dataKey] = series.name || series.label || series.dataKey
252
+ newConfig.runtime.seriesLabelsAll.push(series.name || series.dataKey)
253
+ // return the series keys
254
+ return [series.dataKey]
255
+ }
256
+ })
257
+ }
258
+
259
+ if (newConfig.visualizationType === 'Box Plot' && newConfig.series) {
260
+ const [plots, categories] = getBoxPlotConfig(newConfig, stateData)
261
+ newConfig.boxplot['categories'] = categories
262
+ newConfig.boxplot.plots = plots
263
+ }
264
+ if (newConfig.visualizationType === 'Combo' && newConfig.series) {
265
+ newConfig.runtime = getComboChartConfig(newConfig)
266
+ }
267
+
268
+ if (newConfig.visualizationType === 'Forecasting' && newConfig.series) {
269
+ newConfig.runtime.forecastingSeriesKeys = []
270
+
271
+ newConfig.series.forEach(series => {
272
+ if (series.type === 'Forecasting') {
273
+ newConfig.runtime.forecastingSeriesKeys.push(series)
274
+ }
275
+ })
276
+ }
277
+
278
+ if (newConfig.visualizationType === 'Area Chart' && newConfig.series) {
279
+ newConfig.runtime.areaSeriesKeys = []
280
+
281
+ newConfig.series.forEach(series => {
282
+ newConfig.runtime.areaSeriesKeys.push({ ...series, type: 'Area Chart' })
283
+ })
284
+ }
285
+
286
+ if (
287
+ (newConfig.visualizationType === 'Bar' && newConfig.orientation === 'horizontal') ||
288
+ ['Deviation Bar', 'Paired Bar', 'Forest Plot'].includes(newConfig.visualizationType)
289
+ ) {
290
+ newConfig.runtime.xAxis = newConfig.yAxis['yAxis'] ? newConfig.yAxis['yAxis'] : newConfig.yAxis
291
+ newConfig.runtime.yAxis = newConfig.xAxis['xAxis'] ? newConfig.xAxis['xAxis'] : newConfig.xAxis
292
+ newConfig.runtime.yAxis.labelOffset *= -1
293
+
294
+ newConfig.runtime.horizontal = false
295
+ newConfig.orientation = 'horizontal'
296
+ // remove after COVE supports categorical axis on horizonatal bars
297
+ newConfig.yAxis.type = newConfig.yAxis.type === 'categorical' ? 'linear' : newConfig.yAxis.type
298
+ } else if (
299
+ ['Box Plot', 'Scatter Plot', 'Area Chart', 'Line', 'Forecasting'].includes(newConfig.visualizationType) &&
300
+ !checkLineToBarGraph()
301
+ ) {
302
+ newConfig.runtime.xAxis = newConfig.xAxis
303
+ newConfig.runtime.yAxis = newConfig.yAxis
304
+ newConfig.runtime.horizontal = false
305
+ newConfig.orientation = 'vertical'
306
+ } else {
307
+ newConfig.runtime.xAxis = newConfig.xAxis
308
+ newConfig.runtime.yAxis = newConfig.yAxis
309
+ newConfig.runtime.horizontal = false
310
+ }
311
+
312
+ newConfig.runtime.uniqueId = Date.now()
313
+ newConfig.runtime.editorErrorMessage =
314
+ newConfig.visualizationType === 'Pie' && !newConfig.yAxis.dataKey
315
+ ? 'Data Key property in Y Axis section must be set for pie charts.'
316
+ : ''
317
+
318
+ // Sankey Description box error message
319
+ newConfig.runtime.editorErrorMessage = ''
320
+
321
+ if (newConfig.legend.seriesHighlight?.length) {
322
+ setSeriesHighlight(newConfig.legend?.seriesHighlight)
323
+ }
324
+
325
+ setConfig(newConfig)
326
+ }
327
+
328
+ // Sorts data series for horizontal bar charts
329
+ const sortData = (a, b) => {
330
+ let sortKey =
331
+ config.visualizationType === 'Bar' && config.visualizationSubType === 'horizontal'
332
+ ? config.xAxis.dataKey
333
+ : config.yAxis.sortKey
334
+ let aData = parseFloat(a[sortKey])
335
+ let bData = parseFloat(b[sortKey])
336
+
337
+ if (aData < bData) {
338
+ return config.sortData === 'ascending' ? 1 : -1
339
+ } else if (aData > bData) {
340
+ return config.sortData === 'ascending' ? -1 : 1
341
+ } else {
342
+ return 0
343
+ }
344
+ }
345
+
346
+ // Observes changes to outermost container and changes viewport size in state
347
+ const resizeObserver = new ResizeObserver(entries => {
348
+ for (let entry of entries) {
349
+ let { width, height } = entry.contentRect
350
+ const svgMarginWidth = 15
351
+ const editorWidth = 350
352
+
353
+ width = isEditor ? width - editorWidth : width
354
+
355
+ const newViewport = getViewport(width)
356
+
357
+ setCurrentViewport(newViewport)
358
+
359
+ if (entry.target.dataset.lollipop === 'true') {
360
+ width = width - 2.5
361
+ }
362
+
363
+ width = width - svgMarginWidth
364
+
365
+ setDimensions([width, height])
366
+ }
367
+ })
368
+
369
+ const outerContainerRef = useCallback(node => {
370
+ if (node !== null) {
371
+ resizeObserver.observe(node)
372
+ }
373
+
374
+ setContainer(node)
375
+ }, []) // eslint-disable-line
376
+
377
+ // Load data when component first mounts
378
+
379
+ const prepareData = (config, data) => {
380
+ if (config.dataDescription) {
381
+ data = transform.autoStandardize(data)
382
+ data = transform.developerStandardize(data, config.dataDescription)
383
+ }
384
+
385
+ data = handleRankByValue(data, config)
386
+
387
+ return data
388
+ }
389
+ useEffect(() => {
390
+ const load = async () => {
391
+ try {
392
+ const data = configObj.data
393
+ if (configObj.data && configObj) {
394
+ const preparedConfig = await prepareConfig(configObj, data)
395
+ const preparedData = prepareData(configObj, data)
396
+ setStateData(preparedData)
397
+ setExcludedData(preparedData)
398
+ updateConfig(preparedConfig, preparedData)
399
+ }
400
+ } catch (err) {
401
+ console.error('Could not Load!')
402
+ }
403
+ }
404
+
405
+ load()
406
+ }, [configObj?.data?.length ? configObj.data : null])
407
+
408
+ /**
409
+ * When cove has a config and container ref publish the cove_loaded event.
410
+ */
411
+ useEffect(() => {
412
+ if (container && !_.isEmpty(config) && !coveLoadedEventRan) {
413
+ publish('cove_loaded', { config: config })
414
+ setCoveLoadedEventRan(true)
415
+ }
416
+ }, [container, config]) // eslint-disable-line
417
+
418
+ /**
419
+ * Handles filter change events outside of COVE
420
+ * Updates externalFilters state
421
+ * Another useEffect listens to externalFilterChanges and updates the config.
422
+ */
423
+ useEffect(() => {
424
+ const handleFilterData = e => {
425
+ let tmp: any[] = []
426
+ tmp.push(e.detail)
427
+ setExternalFilters(tmp)
428
+ }
429
+
430
+ subscribe('cove_filterData', e => handleFilterData(e))
431
+
432
+ return () => {
433
+ unsubscribe('cove_filterData', handleFilterData)
434
+ }
435
+ }, [config])
436
+
437
+ /**
438
+ * Handles changes to externalFilters
439
+ * For some reason e.detail is returning [order: "asc"] even though
440
+ * we're not passing that in. The code here checks for an active prop instead of an empty array.
441
+ */
442
+ useEffect(() => {
443
+ if (externalFilters && externalFilters[0]) {
444
+ const hasActiveProperty = externalFilters[0].hasOwnProperty('active')
445
+
446
+ if (!hasActiveProperty) {
447
+ let configCopy = { ...config }
448
+ delete configCopy['filters']
449
+ setConfig(configCopy)
450
+ setFilteredData(filterVizData(externalFilters, excludedData))
451
+ }
452
+ }
453
+
454
+ if (
455
+ externalFilters &&
456
+ externalFilters.length > 0 &&
457
+ externalFilters.length > 0 &&
458
+ externalFilters[0].hasOwnProperty('active')
459
+ ) {
460
+ let newConfigHere = { ...config, filters: externalFilters }
461
+ setConfig(newConfigHere)
462
+ setFilteredData(filterVizData(externalFilters, excludedData))
463
+ }
464
+ }, [externalFilters]) // eslint-disable-line
465
+
466
+ // Generates color palette to pass to child chart component
467
+ useEffect(() => {
468
+ if (stateData && config.xAxis && config.runtime?.seriesKeys) {
469
+ const newColorScale = getColorScale(config)
470
+
471
+ setColorScale(newColorScale)
472
+ setLoading(false)
473
+ }
474
+
475
+ if (config && stateData && config.sortData) {
476
+ stateData.sort(sortData)
477
+ }
478
+ }, [config, stateData]) // eslint-disable-line
479
+
480
+ // Called on legend click, highlights/unhighlights the data series with the given label
481
+ const highlight = (label: Label): void => {
482
+ if (seriesHighlight.length + 1 === config.runtime.seriesKeys.length && config.visualizationType !== 'Forecasting') {
483
+ return handleShowAll()
484
+ }
485
+
486
+ const newHighlight = _.findKey(config.runtime.seriesLabels, v => v === label.datum) || label.datum
487
+
488
+ const newSeriesHighlight = _.xor(seriesHighlight, [newHighlight])
489
+ setSeriesHighlight(newSeriesHighlight)
490
+ }
491
+ // Called on reset button click, unhighlights all data series
492
+ const handleShowAll = () => {
493
+ try {
494
+ const legend = legendRef.current
495
+ if (!legend) throw new Error('No legend available to set previous focus on.')
496
+ legend.focus()
497
+ } catch (e) {
498
+ console.error('COVE:', e.message)
499
+ }
500
+ setSeriesHighlight([])
501
+ }
502
+
503
+ const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
504
+
505
+ const parseDate = (dateString, showError = true) => {
506
+ let date = timeParse(config.runtime[section].dateParseFormat)(dateString)
507
+ if (!date) {
508
+ if (showError) {
509
+ config.runtime.editorErrorMessage = `Error parsing date "${dateString}". Try reviewing your data and date parse settings in the X Axis section.`
510
+ }
511
+ return new Date()
512
+ } else {
513
+ return date
514
+ }
515
+ }
516
+
517
+ const formatDate = (date, i, ticks) => {
518
+ let formattedDate = timeFormat(config.runtime[section].dateDisplayFormat)(date)
519
+ // Handle the case where all months work with '%b.' except for May
520
+ if (config.runtime[section].dateDisplayFormat?.includes('%b.') && formattedDate.includes('May.')) {
521
+ formattedDate = formattedDate.replace(/May\./g, 'May')
522
+ }
523
+ // Show years only once
524
+ if (config.xAxis.showYearsOnce && config.runtime[section].dateDisplayFormat?.includes('%Y') && ticks) {
525
+ const prevDate = ticks[i - 1] ? ticks[i - 1].value : null
526
+ const prevFormattedDate = timeFormat(config.runtime[section].dateDisplayFormat)(prevDate)
527
+ const year = formattedDate.match(/\d{4}/)
528
+ const prevYear = prevFormattedDate.match(/\d{4}/)
529
+ if (year && prevYear && year[0] === prevYear[0]) {
530
+ formattedDate = formattedDate.replace(year, '')
531
+ }
532
+ }
533
+ return formattedDate
534
+ }
535
+
536
+ const formatTooltipsDate = date => {
537
+ return timeFormat(config.tooltips.dateDisplayFormat)(date)
538
+ }
539
+
540
+ // Format numeric data based on settings in config OR from passed in settings for Additional Columns
541
+ // - use only for old horizontal data - newer formatNumber is in helper/formatNumber
542
+ // TODO: we should combine various formatNumber functions across this project.
543
+ // TODO suggestion: pass all options as object key/values to allow for more flexibility
544
+ const formatNumber = (
545
+ num,
546
+ axis,
547
+ shouldAbbreviate = false,
548
+ addColPrefix,
549
+ addColSuffix,
550
+ addColRoundTo,
551
+ { index, length } = { index: null, length: null }
552
+ ) => {
553
+ // if num is NaN return num
554
+ if (isNaN(num) || !num) return num
555
+ // Check if the input number is negative
556
+ const isNegative = num < 0
557
+
558
+ if (axis === undefined || !axis) axis = 'left'
559
+
560
+ // If the input number is negative, take the absolute value
561
+ if (isNegative) {
562
+ num = Math.abs(num)
563
+ }
564
+
565
+ // destructure dataFormat values
566
+ let {
567
+ dataFormat: {
568
+ commas,
569
+ abbreviated,
570
+ roundTo,
571
+ prefix,
572
+ suffix,
573
+ rightRoundTo,
574
+ bottomRoundTo,
575
+ rightPrefix,
576
+ rightSuffix,
577
+ bottomPrefix,
578
+ bottomSuffix,
579
+ bottomAbbreviated,
580
+ onlyShowTopPrefixSuffix
581
+ }
582
+ } = config
583
+
584
+ // check if value contains comma and remove it. later will add comma below.
585
+ if (String(num).indexOf(',') !== -1) num = num.replaceAll(',', '')
586
+
587
+ let original = num
588
+ let stringFormattingOptions: any = {
589
+ useGrouping: commas ? true : false // for old chart data table to work right cant just leave this to undefined
590
+ }
591
+ if (axis === 'left' || axis === undefined) {
592
+ let roundToPlace
593
+ if (addColRoundTo !== undefined) {
594
+ // if its an Additional Column
595
+ roundToPlace = addColRoundTo ? Number(addColRoundTo) : 0
596
+ } else {
597
+ roundToPlace = roundTo ? Number(roundTo) : 0
598
+ }
599
+ stringFormattingOptions = {
600
+ useGrouping: addColRoundTo ? true : config.dataFormat.commas ? true : false,
601
+ minimumFractionDigits: roundToPlace,
602
+ maximumFractionDigits: roundToPlace
603
+ }
604
+ }
605
+
606
+ if (axis === 'right') {
607
+ stringFormattingOptions = {
608
+ useGrouping: config.dataFormat.rightCommas ? true : false,
609
+ minimumFractionDigits: rightRoundTo ? Number(rightRoundTo) : 0,
610
+ maximumFractionDigits: rightRoundTo ? Number(rightRoundTo) : 0
611
+ }
612
+ }
613
+
614
+ const resolveBottomTickRounding = () => {
615
+ if (config.forestPlot.type === 'Logarithmic' && !bottomRoundTo) return 2
616
+ if (Number(bottomRoundTo)) return Number(bottomRoundTo)
617
+ return 0
618
+ }
619
+
620
+ if (axis === 'bottom') {
621
+ stringFormattingOptions = {
622
+ useGrouping: config.dataFormat.bottomCommas ? true : false,
623
+ minimumFractionDigits: resolveBottomTickRounding(),
624
+ maximumFractionDigits: resolveBottomTickRounding()
625
+ }
626
+ }
627
+
628
+ num = numberFromString(num)
629
+
630
+ if (isNaN(num)) {
631
+ config.runtime.editorErrorMessage = `Unable to parse number from data ${original}. Try reviewing your data and selections in the Data Series section.`
632
+ return original
633
+ }
634
+
635
+ if (!config.dataFormat) return num
636
+ if (config.dataCutoff) {
637
+ let cutoff = numberFromString(config.dataCutoff)
638
+
639
+ if (num < cutoff) {
640
+ num = cutoff
641
+ }
642
+ }
643
+
644
+ // When we're formatting the left axis
645
+ // Use commas also updates bars and the data table
646
+ // We can't use commas when we're formatting the dataFormatted number
647
+ // Example: commas -> 12,000; abbreviated -> 12k (correct); abbreviated & commas -> 12 (incorrect)
648
+ //
649
+ // Edge case for small numbers with decimals
650
+ // - if roundTo undefined which means it is blank, then do not round
651
+
652
+ if (
653
+ (axis === 'left' && commas && abbreviated && shouldAbbreviate) ||
654
+ (axis === 'bottom' && commas && abbreviated && shouldAbbreviate)
655
+ ) {
656
+ num = num // eslint-disable-line
657
+ } else {
658
+ num = num.toLocaleString('en-US', stringFormattingOptions)
659
+ }
660
+ let result = ''
661
+
662
+ if (abbreviated && axis === 'left' && shouldAbbreviate) {
663
+ num = abbreviateNumber(parseFloat(num))
664
+ }
665
+
666
+ if (bottomAbbreviated && axis === 'bottom' && shouldAbbreviate) {
667
+ num = abbreviateNumber(parseFloat(num))
668
+ }
669
+
670
+ if (addColPrefix && axis === 'left') {
671
+ result = addColPrefix + result
672
+ } else {
673
+ // if onlyShowTopPrefixSuffix only show top prefix
674
+ const suppressAllButLast = onlyShowTopPrefixSuffix && length - 1 !== index
675
+ if (prefix && axis === 'left' && !suppressAllButLast) {
676
+ result += prefix
677
+ }
678
+ }
679
+
680
+ if (rightPrefix && axis === 'right') {
681
+ result += rightPrefix
682
+ }
683
+
684
+ if (bottomPrefix && axis === 'bottom') {
685
+ result += bottomPrefix
686
+ }
687
+
688
+ // combine prefix and num
689
+ result += num
690
+
691
+ if (addColSuffix && axis === 'left') {
692
+ result += addColSuffix
693
+ } else {
694
+ if (suffix && axis === 'left' && !onlyShowTopPrefixSuffix) {
695
+ result += suffix
696
+ }
697
+ }
698
+
699
+ if (rightSuffix && axis === 'right') {
700
+ result += rightSuffix
701
+ }
702
+
703
+ if (bottomSuffix && axis === 'bottom') {
704
+ result += bottomSuffix
705
+ }
706
+ if (isNegative) {
707
+ result = '-' + result
708
+ }
709
+
710
+ return String(result)
711
+ }
712
+
713
+ // this is passed DOWN into the various components
714
+ // then they do a lookup based on the bin number as index into here (TT)
715
+ const applyLegendToRow = rowObj => {
716
+ try {
717
+ if (!rowObj) throw new Error('COVE: No rowObj in applyLegendToRow')
718
+ // Navigation map
719
+ if ('navigation' === config.type) {
720
+ let mapColorPalette = colorPalettes[config.color] || colorPalettes['bluegreenreverse']
721
+ return generateColorsArray(mapColorPalette[3])
722
+ }
723
+
724
+ // Fail state
725
+ return generateColorsArray()
726
+ } catch (e) {
727
+ console.error('COVE: ', e) // eslint-disable-line
728
+ }
729
+ }
730
+
731
+ // TODO: should be part of the DataTransform class.
732
+ const clean = data => {
733
+ // cleaning is deleting data we need in forecasting charts.
734
+ if (!Array.isArray(data)) return []
735
+ if (config.visualizationType === 'Forecasting') return data
736
+ if (config.series?.some(series => !!series.dynamicCategory)) return data
737
+ return config?.xAxis?.dataKey ? transform.cleanData(data, config.xAxis.dataKey) : data
738
+ }
739
+
740
+ const getTableRuntimeData = () => {
741
+ if (visualizationType === 'Sankey') return config?.data?.[0]?.tableData
742
+ const data = filteredData || excludedData
743
+ const dynamicSeries = config.series.find(series => !!series.dynamicCategory)
744
+ if (!dynamicSeries) return data
745
+ const usedColumns = Object.values(config.columns)
746
+ .filter(col => col.dataTable)
747
+ .map(col => col.name)
748
+ .concat([dynamicSeries.dynamicCategory, dynamicSeries.dataKey])
749
+ if (config.xAxis?.dataKey) usedColumns.push(config.xAxis.dataKey)
750
+ return data.map(d => _.pick(d, usedColumns))
751
+ }
752
+
753
+ const pivotDynamicSeries = (config: ChartConfig): TableConfig => {
754
+ const tableConfig: TableConfig = _.cloneDeep(config)
755
+ const dynamicSeries = tableConfig.series.find(series => !!series.dynamicCategory)
756
+ if (dynamicSeries) {
757
+ const pivot: Pivot = { columnName: dynamicSeries.dynamicCategory, valueColumns: [dynamicSeries.dataKey] }
758
+ tableConfig.table.pivot = pivot
759
+ }
760
+ return tableConfig
761
+ }
762
+
763
+ // Prevent render if loading
764
+ let body = <Loading />
765
+
766
+ const makeClassName = string => {
767
+ if (!_.isString(string)) return undefined
768
+
769
+ return _.kebabCase(string)
770
+ }
771
+ const getChartWrapperClasses = () => {
772
+ const isLegendOnBottom = legend?.position === 'bottom' || isLegendWrapViewport(currentViewport)
773
+ const classes = ['chart-container', 'p-relative']
774
+ if (legend?.position) {
775
+ if (isLegendWrapViewport(currentViewport) && legend?.position !== 'top') {
776
+ classes.push('legend-bottom')
777
+ } else {
778
+ classes.push(`legend-${legend.position}`)
779
+ }
780
+ }
781
+ if (legend?.hide) classes.push('legend-hidden')
782
+ if (lineDatapointClass) classes.push(lineDatapointClass)
783
+ if (!config.barHasBorder) classes.push('chart-bar--no-border')
784
+ if (config.brush?.active && dashboardConfig?.type === 'dashboard' && (!isLegendOnBottom || legend.hide))
785
+ classes.push('dashboard-brush')
786
+ classes.push(...contentClasses)
787
+ return classes
788
+ }
789
+
790
+ const getChartSubTextClasses = () => {
791
+ const classes = ['subtext mt-4']
792
+ const isLegendOnBottom = legend?.position === 'bottom' || isLegendWrapViewport(currentViewport)
793
+
794
+ if (config.isResponsiveTicks) classes.push('subtext--responsive-ticks ')
795
+ if (config.brush?.active && !isLegendOnBottom) classes.push('subtext--brush-active ')
796
+ if (config.brush?.active && config.legend.hide) classes.push('subtext--brush-active ')
797
+ return classes
798
+ }
799
+
800
+ if (!loading) {
801
+ const tableLink = (
802
+ <a href={`#data-table-${config.dataKey}`} className='margin-left-href'>
803
+ {config.dataKey} (Go to Table)
804
+ </a>
805
+ )
806
+
807
+ body = (
808
+ <>
809
+ {isEditor && <EditorPanel />}
810
+ <Layout.Responsive isEditor={isEditor}>
811
+ {config.newViz && <Confirm updateConfig={updateConfig} config={config} />}
812
+ {undefined === config.newViz && isEditor && config.runtime && config.runtime?.editorErrorMessage && (
813
+ <Error errorMessage={config.runtime.editorErrorMessage} />
814
+ )}
815
+ {!missingRequiredSections(config) && !config.newViz && (
816
+ <div
817
+ className={`cdc-chart-inner-container cove-component__content type-${makeClassName(
818
+ config.visualizationType
819
+ )}`}
820
+ aria-label={handleChartAriaLabels(config)}
821
+ tabIndex={0}
822
+ >
823
+ <Title
824
+ showTitle={config.showTitle}
825
+ isDashboard={isDashboard}
826
+ title={title}
827
+ superTitle={config.superTitle}
828
+ classes={['chart-title', `${config.theme}`, 'cove-component__header', 'mb-3']}
829
+ style={undefined}
830
+ />
831
+
832
+ {/* Visualization Wrapper */}
833
+ <div className={getChartWrapperClasses().join(' ')}>
834
+ {/* Intro Text/Message */}
835
+ {config?.introText && config.visualizationType !== 'Spark Line' && (
836
+ <section className={`introText mb-4`}>{parse(config.introText)}</section>
837
+ )}
838
+
839
+ {/* Filters */}
840
+ {config.filters && !externalFilters && config.visualizationType !== 'Spark Line' && (
841
+ <Filters
842
+ config={config}
843
+ setConfig={setConfig}
844
+ setFilteredData={setFilteredData}
845
+ filteredData={filteredData}
846
+ excludedData={excludedData}
847
+ filterData={filterVizData}
848
+ dimensions={dimensions}
849
+ />
850
+ )}
851
+ <SkipTo skipId={handleChartTabbing(config, legendId)} skipMessage='Skip Over Chart Container' />
852
+ {config.annotations?.length > 0 && (
853
+ <SkipTo
854
+ skipId={handleChartTabbing(config, legendId)}
855
+ skipMessage={`Skip over annotations`}
856
+ key={`skip-annotations`}
857
+ />
858
+ )}
859
+ <LegendWrapper>
860
+ <div
861
+ className={
862
+ legend.hide || isLegendWrapViewport(currentViewport)
863
+ ? 'w-100'
864
+ : legend.position === 'bottom' || legend.position === 'top' || visualizationType === 'Sankey'
865
+ ? 'w-100'
866
+ : 'w-75'
867
+ }
868
+ >
869
+ {/* All charts with LinearChart */}
870
+ {!['Spark Line', 'Line', 'Sankey', 'Pie', 'Sankey'].includes(config.visualizationType) && (
871
+ <div ref={parentRef} style={{ width: `100%` }}>
872
+ <ParentSize>
873
+ {parent => (
874
+ <LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
875
+ )}
876
+ </ParentSize>
877
+ </div>
878
+ )}
879
+
880
+ {config.visualizationType === 'Pie' && (
881
+ <ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
882
+ {parent => <PieChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />}
883
+ </ParentSize>
884
+ )}
885
+ {/* Line Chart */}
886
+ {config.visualizationType === 'Line' &&
887
+ (checkLineToBarGraph() ? (
888
+ <div ref={parentRef} style={{ width: `100%` }}>
889
+ <ParentSize>
890
+ {parent => (
891
+ <LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
892
+ )}
893
+ </ParentSize>
894
+ </div>
895
+ ) : (
896
+ <div ref={parentRef} style={{ width: `100%` }}>
897
+ <ParentSize>
898
+ {parent => (
899
+ <LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
900
+ )}
901
+ </ParentSize>
902
+ </div>
903
+ ))}
904
+ {/* Sparkline */}
905
+ {config.visualizationType === 'Spark Line' && (
906
+ <>
907
+ <Filters
908
+ config={config}
909
+ setConfig={setConfig}
910
+ setFilteredData={setFilteredData}
911
+ filteredData={filteredData}
912
+ excludedData={excludedData}
913
+ filterData={filterVizData}
914
+ dimensions={dimensions}
915
+ />
916
+ {config?.introText && (
917
+ <section className='introText mb-4' style={{ padding: '0px 0 35px' }}>
918
+ {parse(config.introText)}
919
+ </section>
920
+ )}
921
+ <div style={{ height: `100px`, width: `100%`, ...sparkLineStyles }}>
922
+ <ParentSize>{parent => <SparkLine width={parent.width} height={parent.height} />}</ParentSize>
923
+ </div>
924
+ {description && (
925
+ <div className='subtext' style={{ padding: '35px 0 15px' }}>
926
+ {parse(description)}
927
+ </div>
928
+ )}
929
+ </>
930
+ )}
931
+ {/* Sankey */}
932
+ {config.visualizationType === 'Sankey' && (
933
+ <ParentSize aria-hidden='true'>
934
+ {parent => <SankeyChart runtime={config.runtime} width={parent.width} height={parent.height} />}
935
+ </ParentSize>
936
+ )}
937
+ </div>
938
+ {/* Legend */}
939
+ {!config.legend.hide &&
940
+ config.visualizationType !== 'Spark Line' &&
941
+ config.visualizationType !== 'Sankey' && (
942
+ <Legend ref={legendRef} skipId={handleChartTabbing(config, legendId)} />
943
+ )}
944
+ </LegendWrapper>
945
+ {/* Link */}
946
+ {isDashboard && config.table && config.table.show && config.table.showDataTableLink
947
+ ? tableLink
948
+ : link && link}
949
+ {/* Description */}
950
+
951
+ {config.description && config.visualizationType !== 'Spark Line' && (
952
+ <div className={getChartSubTextClasses(config, currentViewport, isLegendWrapViewport).join(' ')}>
953
+ {parse(config.description)}
954
+ </div>
955
+ )}
956
+
957
+ {/* buttons */}
958
+ <MediaControls.Section classes={['download-buttons']}>
959
+ {config.table.showDownloadImgButton && (
960
+ <MediaControls.Button
961
+ text='Download Image'
962
+ title='Download Chart as Image'
963
+ type='image'
964
+ state={config}
965
+ elementToCapture={imageId}
966
+ />
967
+ )}
968
+ {config.table.showDownloadPdfButton && (
969
+ <MediaControls.Button
970
+ text='Download PDF'
971
+ title='Download Chart as PDF'
972
+ type='pdf'
973
+ state={config}
974
+ elementToCapture={imageId}
975
+ />
976
+ )}
977
+ </MediaControls.Section>
978
+ {/* Data Table */}
979
+ {((config.xAxis.dataKey &&
980
+ config.table.show &&
981
+ config.visualizationType !== 'Spark Line' &&
982
+ config.visualizationType !== 'Sankey') ||
983
+ (config.visualizationType === 'Sankey' && config.table.show)) && (
984
+ <DataTable
985
+ /* changing the "key" will force the table to re-render
986
+ when the default sort changes while editing */
987
+ key={dataTableDefaultSortBy}
988
+ config={pivotDynamicSeries(config)}
989
+ rawData={
990
+ config.visualizationType === 'Sankey'
991
+ ? config?.data?.[0]?.tableData
992
+ : config.table.customTableConfig
993
+ ? filterVizData(config.filters, config.data)
994
+ : config.data
995
+ }
996
+ runtimeData={getTableRuntimeData()}
997
+ expandDataTable={config.table.expanded}
998
+ columns={config.columns}
999
+ defaultSortBy={dataTableDefaultSortBy}
1000
+ displayGeoName={name => name}
1001
+ applyLegendToRow={applyLegendToRow}
1002
+ tableTitle={config.table.label}
1003
+ indexTitle={config.table.indexLabel}
1004
+ vizTitle={title}
1005
+ viewport={currentViewport}
1006
+ tabbingId={handleChartTabbing(config, legendId)}
1007
+ colorScale={colorScale}
1008
+ />
1009
+ )}
1010
+ {config?.annotations?.length > 0 && <Annotation.Dropdown />}
1011
+ {/* show pdf or image button */}
1012
+ {config?.footnotes && <section className='footnotes pt-2 mt-4'>{parse(config.footnotes)}</section>}
1013
+ </div>
1014
+ </div>
1015
+ )}
1016
+ </Layout.Responsive>
1017
+ </>
1018
+ )
1019
+ }
1020
+
1021
+ const getXAxisData = d =>
1022
+ isDateScale(config.runtime.xAxis)
1023
+ ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
1024
+ : d[config.runtime.originalXAxis.dataKey]
1025
+ const getYAxisData = (d, seriesKey) => d[seriesKey]
1026
+
1027
+ const capitalize = str => {
1028
+ return str.charAt(0).toUpperCase() + str.slice(1)
1029
+ }
1030
+
1031
+ const contextValues = {
1032
+ brushConfig,
1033
+ capitalize,
1034
+ clean,
1035
+ colorPalettes,
1036
+ colorScale,
1037
+ config,
1038
+ currentViewport,
1039
+ dashboardConfig,
1040
+ debugSvg: isDebug,
1041
+ dimensions,
1042
+ dynamicLegendItems,
1043
+ excludedData,
1044
+ formatDate,
1045
+ formatNumber,
1046
+ formatTooltipsDate,
1047
+ getXAxisData,
1048
+ getYAxisData,
1049
+ handleChartAriaLabels,
1050
+ handleLineType,
1051
+ handleChartTabbing,
1052
+ highlight,
1053
+ handleShowAll,
1054
+ imageId,
1055
+ isDashboard,
1056
+ isDebug,
1057
+ isDraggingAnnotation,
1058
+ handleDragStateChange,
1059
+ isEditor,
1060
+ isNumber,
1061
+ legend,
1062
+ legendId,
1063
+ legendRef,
1064
+ lineOptions,
1065
+ loading,
1066
+ missingRequiredSections,
1067
+ outerContainerRef,
1068
+ parentRef,
1069
+ parseDate,
1070
+ rawData: _.cloneDeep(stateData) ?? {},
1071
+ seriesHighlight,
1072
+ setBrushConfig,
1073
+ setConfig,
1074
+ setDynamicLegendItems,
1075
+ setEditing,
1076
+ setFilteredData,
1077
+ setParentConfig,
1078
+ setSeriesHighlight,
1079
+ setSharedFilter,
1080
+ setSharedFilterValue,
1081
+ svgRef,
1082
+ tableData: filteredData || excludedData, // do not clean table data
1083
+ transformedData: clean(filteredData || excludedData), // do this right before passing to components
1084
+ twoColorPalette,
1085
+ unfilteredData: _.cloneDeep(stateData),
1086
+ updateConfig
1087
+ }
1088
+
1089
+ return (
1090
+ <ConfigContext.Provider value={contextValues}>
1091
+ <Layout.VisualizationWrapper
1092
+ config={config}
1093
+ isEditor={isEditor}
1094
+ currentViewport={currentViewport}
1095
+ ref={outerContainerRef}
1096
+ imageId={imageId}
1097
+ showEditorPanel={config?.showEditorPanel}
1098
+ >
1099
+ {body}
1100
+ </Layout.VisualizationWrapper>
1101
+ </ConfigContext.Provider>
1102
+ )
1103
+ }
1104
+
1105
+ export default CdcChart