@cdc/chart 4.24.12 → 4.25.2-25

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