@cdc/core 4.23.8 → 4.23.10-alpha

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.
@@ -26,7 +26,7 @@ export const AdvancedEditor = ({ loadConfig, state, convertStateToConfig }) => {
26
26
  'markup-include': ['Markup Include', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/Markup-Include.html', <MarkupIncludeIcon />]
27
27
  }
28
28
 
29
- if (!state.type) return
29
+ if (!state.type) return <></>
30
30
  return (
31
31
  <>
32
32
  <a href={typeLookup[state.type][1]} target='_blank' rel='noopener noreferrer' className='guidance-link'>
@@ -15,7 +15,7 @@ import Loading from '@cdc/core/components/Loading'
15
15
 
16
16
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
17
17
  const DataTable = props => {
18
- const { config, tableTitle, indexTitle, vizTitle, rawData, runtimeData, headerColor, expandDataTable, columns, displayDataAsText, applyLegendToRow, displayGeoName, navigationHandler, viewport, formatLegendLocation, tabbingId, isDebug } = props
18
+ const { config, dataConfig, tableTitle, indexTitle, vizTitle, rawData, runtimeData, headerColor, colorScale, expandDataTable, columns, displayDataAsText, applyLegendToRow, displayGeoName, navigationHandler, viewport, formatLegendLocation, tabbingId, isDebug } = props
19
19
 
20
20
  /* eslint-disable no-console */
21
21
  if (isDebug) {
@@ -28,83 +28,61 @@ const DataTable = props => {
28
28
 
29
29
  const [expanded, setExpanded] = useState(expandDataTable)
30
30
 
31
- const [sortBy, setSortBy] = useState({ column: config.type === 'map' ? 'geo' : 'date', asc: false })
31
+ const [sortBy, setSortBy] = useState({ column: config.type === 'map' ? 'geo' : 'date', asc: false, colIndex: null })
32
32
 
33
33
  const [accessibilityLabel, setAccessibilityLabel] = useState('')
34
34
 
35
35
  const fileName = `${vizTitle || 'data-table'}.csv`
36
36
 
37
- // Catch all sorting method used on load by default but also on user click
38
- // Having a custom method means we can add in any business logic we want going forward
37
+ const isVertical = !(config.type === 'chart' && !config.table?.showVertical)
38
+
39
39
  const customSort = (a, b) => {
40
- const digitRegex = /\d+/
40
+ let valueA = a
41
+ let valueB = b
41
42
 
42
- const hasNumber = value => digitRegex.test(value)
43
+ // Treat booleans and nulls as an empty string
44
+ valueA = valueA === false || valueA === true || valueA === null ? '' : valueA
45
+ valueB = valueB === false || valueB == true || valueB === null ? '' : valueB
43
46
 
44
- // force null and undefined to the bottom
45
- a = a === null || a === undefined ? '' : a
46
- b = b === null || b === undefined ? '' : b
47
+ const trimmedA = String(valueA).trim()
48
+ const trimmedB = String(valueB).trim()
47
49
 
48
- // convert any strings that are actually numbers to proper data type
49
- const aNum = Number(a)
50
+ if (config.xAxis?.dataKey === sortBy.column && config.xAxis.type === 'date') {
51
+ let dateA = parseDate(config.xAxis.dateParseFormat, trimmedA)
50
52
 
51
- if (!Number.isNaN(aNum)) {
52
- a = aNum
53
- }
53
+ let dateB = parseDate(config.xAxis.dateParseFormat, trimmedB)
54
54
 
55
- const bNum = Number(b)
55
+ if (dateA && dateA.getTime) dateA = dateA.getTime()
56
56
 
57
- if (!Number.isNaN(bNum)) {
58
- b = bNum
59
- }
57
+ if (dateB && dateB.getTime) dateB = dateB.getTime()
60
58
 
61
- // remove iso code prefixes
62
- if (typeof a === 'string') {
63
- a = a.replace('us-', '')
64
- a = displayGeoName(a)
59
+ return !sortBy.asc ? dateA - dateB : dateB - dateA
65
60
  }
61
+ // Check if values are numbers
62
+ const isNumA = !isNaN(Number(valueA)) && valueA !== undefined && valueA !== null && trimmedA !== ''
63
+ const isNumB = !isNaN(Number(valueB)) && valueB !== undefined && valueB !== null && trimmedB !== ''
66
64
 
67
- if (typeof b === 'string') {
68
- b = b.replace('us-', '')
69
- b = displayGeoName(b)
70
- }
65
+ // Handle empty strings or spaces
66
+ if (trimmedA === '' && trimmedB !== '') return !sortBy.asc ? -1 : 1
67
+ if (trimmedA !== '' && trimmedB === '') return !sortBy.asc ? 1 : -1
71
68
 
72
- // force any string values to lowercase
73
- a = typeof a === 'string' ? a.toLowerCase() : a
74
- b = typeof b === 'string' ? b.toLowerCase() : b
75
-
76
- // If the string contains a number, remove the text from the value and only sort by the number. Only uses the first number it finds.
77
- if (typeof a === 'string' && hasNumber(a) === true) {
78
- a = a.match(digitRegex)[0]
79
-
80
- a = Number(a)
69
+ // Both are numbers: Compare numerically
70
+ if (isNumA && isNumB) {
71
+ return !sortBy.asc ? Number(valueA) - Number(valueB) : Number(valueB) - Number(valueA)
81
72
  }
82
73
 
83
- if (typeof b === 'string' && hasNumber(b) === true) {
84
- b = b.match(digitRegex)[0]
85
-
86
- b = Number(b)
74
+ // Only A is a number
75
+ if (isNumA) {
76
+ return !sortBy.asc ? -1 : 1
87
77
  }
88
78
 
89
- // When comparing a number to a string, always send string to bottom
90
- if (typeof a === 'number' && typeof b === 'string') {
91
- return 1
79
+ // Only B is a number
80
+ if (isNumB) {
81
+ return !sortBy.asc ? 1 : -1
92
82
  }
93
83
 
94
- if (typeof b === 'number' && typeof a === 'string') {
95
- return -1
96
- }
97
-
98
- // Return either 1 or -1 to indicate a sort priority
99
- if (a > b) {
100
- return 1
101
- }
102
- if (a < b) {
103
- return -1
104
- }
105
- // returning 0, undefined or any falsey value will use subsequent sorts or
106
- // the index as a tiebreaker
107
- return 0
84
+ // Neither are numbers: Compare as strings
85
+ return !sortBy.asc ? trimmedA.localeCompare(trimmedB) : trimmedB.localeCompare(trimmedA)
108
86
  }
109
87
 
110
88
  // Optionally wrap cell with anchor if config defines a navigation url
@@ -146,7 +124,7 @@ const DataTable = props => {
146
124
  if (rawData !== undefined) {
147
125
  let csvData
148
126
  // only use fullGeoName on County maps and no other
149
- if (config.general.geoType === 'us-county') {
127
+ if (config.general?.geoType === 'us-county') {
150
128
  // Unparse + Add column for full Geo name along with State
151
129
  csvData = Papa.unparse(rawData.map(row => ({ FullGeoName: formatLegendLocation(row[config.columns.geo.name]), ...row })))
152
130
  } else {
@@ -201,19 +179,19 @@ const DataTable = props => {
201
179
  break
202
180
  }
203
181
 
204
- const rows = Object.keys(runtimeData).sort((a, b) => {
205
- let sortVal
206
- if (config.type === 'map' && config.columns) {
207
- sortVal = customSort(runtimeData[a][config.columns[sortBy.column].name], runtimeData[b][config.columns[sortBy.column].name])
208
- }
209
- if (config.type === 'chart') {
210
- sortVal = customSort(runtimeData[a][sortBy.column], runtimeData[b][sortBy.column])
211
- }
212
- if (!sortBy.asc) return sortVal
213
- if (sortVal === 0) return 0
214
- if (sortVal < 0) return 1
215
- return -1
216
- })
182
+ const rawRows = Object.keys(runtimeData)
183
+ const rows = isVertical
184
+ ? rawRows.sort((a, b) => {
185
+ let sortVal = 0
186
+ if (config.type === 'map' && config.columns) {
187
+ sortVal = customSort(runtimeData[a][config.columns[sortBy.column].name], runtimeData[b][config.columns[sortBy.column].name])
188
+ }
189
+ if (config.type === 'chart' || config.type === 'dashboard') {
190
+ sortVal = customSort(runtimeData[a][sortBy.column], runtimeData[b][sortBy.column])
191
+ }
192
+ return sortVal
193
+ })
194
+ : rawRows
217
195
 
218
196
  const genMapRows = rows => {
219
197
  const allrows = rows.map(row => {
@@ -272,20 +250,24 @@ const DataTable = props => {
272
250
  const dataSeriesColumns = () => {
273
251
  let tmpSeriesColumns
274
252
  if (config.visualizationType !== 'Pie') {
275
- tmpSeriesColumns = [config.xAxis.dataKey] //, ...config.runtime.seriesLabelsAll
276
- config.series.forEach(element => {
277
- tmpSeriesColumns.push(element.dataKey)
278
- })
253
+ tmpSeriesColumns = isVertical ? [config.xAxis?.dataKey] : [] //, ...config.runtime.seriesLabelsAll
254
+ if (config.series) {
255
+ config.series.forEach(element => {
256
+ tmpSeriesColumns.push(element.dataKey)
257
+ })
258
+ } else if (runtimeData && runtimeData.length > 0) {
259
+ tmpSeriesColumns = Object.keys(runtimeData[0])
260
+ }
279
261
  } else {
280
- tmpSeriesColumns = [config.xAxis.dataKey, config.yAxis.dataKey] //Object.keys(runtimeData[0])
262
+ tmpSeriesColumns = isVertical ? [config.xAxis?.dataKey, config.yAxis?.dataKey] : [config.yAxis?.dataKey] //Object.keys(runtimeData[0])
281
263
  }
282
264
 
283
265
  // then add the additional Columns
284
- if (Object.keys(config.columns).length > 0) {
266
+ if (config.columns && Object.keys(config.columns).length > 0) {
285
267
  Object.keys(config.columns).forEach(function (key) {
286
268
  var value = config.columns[key]
287
269
  // add if not the index AND it is enabled to be added to data table
288
- if (value.name !== config.xAxis.dataKey && value.dataTable === true) {
270
+ if (value.name !== config.xAxis?.dataKey && value.dataTable === true) {
289
271
  tmpSeriesColumns.push(value.name)
290
272
  }
291
273
  })
@@ -293,10 +275,22 @@ const DataTable = props => {
293
275
 
294
276
  return tmpSeriesColumns
295
277
  }
296
-
278
+ const dataSeriesColumnsSorted = () => {
279
+ return dataSeriesColumns().sort((a, b) => {
280
+ if (sortBy.column === '__series__') return customSort(a, b)
281
+ let row = runtimeData.find(d => d[config.xAxis?.dataKey] === sortBy.column)
282
+ const rowIndex = runtimeData[sortBy.colIndex - 1]
283
+ if (row) {
284
+ return customSort(row[a], row[b])
285
+ }
286
+ if (row === undefined && rowIndex) {
287
+ return customSort(rowIndex[a], rowIndex[b])
288
+ }
289
+ })
290
+ }
297
291
  const getLabel = name => {
298
292
  let custLabel = ''
299
- if (Object.keys(config.columns).length > 0) {
293
+ if (config.columns && Object.keys(config.columns).length > 0) {
300
294
  Object.keys(config.columns).forEach(function (key) {
301
295
  var tmpColumn = config.columns[key]
302
296
  // add if not the index AND it is enabled to be added to data table
@@ -308,108 +302,184 @@ const DataTable = props => {
308
302
  }
309
303
  }
310
304
 
305
+ const getSeriesName = column => {
306
+ // If a user sets the name on a series use that.
307
+ let userUpdatedSeriesName = config.series ? config.series.filter(series => series.dataKey === column)?.[0]?.name : ''
308
+ if (userUpdatedSeriesName) return userUpdatedSeriesName
309
+
310
+ if (config.runtimeSeriesLabels && config.runtimeSeriesLabels[column]) return config.runtimeSeriesLabels[column]
311
+
312
+ let custLabel = getLabel(column) ? getLabel(column) : column
313
+ let text = column === config.xAxis?.dataKey ? config.table.indexLabel : custLabel
314
+
315
+ return text
316
+ }
317
+
311
318
  const genChartHeader = (columns, data) => {
312
319
  if (!data) return
313
- return (
314
- <tr>
315
- {dataSeriesColumns().map(column => {
316
- let custLabel = getLabel(column) ? getLabel(column) : column
317
- let text = column === config.xAxis.dataKey ? config.table.indexLabel : custLabel
318
-
319
- // If a user sets the name on a series use that.
320
- let userUpdatedSeriesName = config.series.filter(series => series.dataKey === column)?.[0]?.name
321
- if (userUpdatedSeriesName) text = userUpdatedSeriesName
322
-
323
- return (
324
- <th
325
- key={`col-header-${column}`}
326
- tabIndex='0'
327
- title={text}
328
- role='columnheader'
329
- scope='col'
330
- onClick={() => {
331
- setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false })
332
- }}
333
- onKeyDown={e => {
334
- if (e.keyCode === 13) {
335
- setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false })
336
- }
337
- }}
338
- className={sortBy.column === column ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'}
339
- {...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
340
- >
341
- {text}
342
- {sortBy.column === column && <span className={'sort-icon'}>{!sortBy.asc ? upIcon : downIcon}</span>}
343
- <button>
344
- <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
345
- </button>
346
- </th>
347
- )
348
- })}
349
- </tr>
350
- )
320
+ if (isVertical) {
321
+ return (
322
+ <tr>
323
+ {dataSeriesColumns().map((column, index) => {
324
+ const text = getSeriesName(column)
325
+
326
+ return (
327
+ <th
328
+ key={`col-header-${column}__${index}`}
329
+ tabIndex='0'
330
+ title={text}
331
+ role='columnheader'
332
+ scope='col'
333
+ onClick={() => {
334
+ setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false, colIndex: index })
335
+ }}
336
+ onKeyDown={e => {
337
+ if (e.keyCode === 13) {
338
+ setColIndex(index)
339
+ setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false, colIndex: index })
340
+ }
341
+ }}
342
+ className={sortBy.column === column ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'}
343
+ {...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
344
+ >
345
+ {text}
346
+ {column === sortBy.column && <span className={'sort-icon'}>{!sortBy.asc ? upIcon : downIcon}</span>}
347
+ <button>
348
+ <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
349
+ </button>
350
+ </th>
351
+ )
352
+ })}
353
+ </tr>
354
+ )
355
+ } else {
356
+ const sliceVal = config.visualizationType === 'Pie' ? 1 : 0
357
+ return (
358
+ <tr>
359
+ {['__series__', ...Object.keys(runtimeData)].slice(sliceVal).map((row, index) => {
360
+ let column = config.xAxis?.dataKey
361
+ let text = row !== '__series__' ? getChartCellValue(row, column) : '__series__'
362
+ return (
363
+ <th
364
+ key={`col-header-${text}__${index}`}
365
+ tabIndex='0'
366
+ title={text}
367
+ role='columnheader'
368
+ scope='col'
369
+ onClick={() => {
370
+ setSortBy({ column: text, asc: sortBy.column === text ? !sortBy.asc : false, colIndex: index })
371
+ }}
372
+ onKeyDown={e => {
373
+ if (e.keyCode === 13) {
374
+ setSortBy({ column: text, asc: sortBy.column === text ? !sortBy.asc : false, colIndex: index })
375
+ }
376
+ }}
377
+ className={sortBy.column === text ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'}
378
+ {...(sortBy.column === text ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
379
+ >
380
+ {text === '__series__' ? '' : text}
381
+ {index === sortBy.colIndex && <span className={'sort-icon'}>{!sortBy.asc ? upIcon : downIcon}</span>}
382
+ <button>
383
+ <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === text ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
384
+ </button>
385
+ </th>
386
+ )
387
+ })}
388
+ </tr>
389
+ )
390
+ }
351
391
  }
352
392
 
353
393
  // if its additional column, return formatting params
354
394
  const isAdditionalColumn = column => {
355
395
  let inthere = false
356
396
  let formattingParams = {}
357
- Object.keys(config.columns).forEach(keycol => {
358
- if (config.columns[keycol].name === column) {
359
- inthere = true
360
- formattingParams = {
361
- addColPrefix: config.columns[keycol].prefix,
362
- addColSuffix: config.columns[keycol].suffix,
363
- addColRoundTo: config.columns[keycol].roundToPlace ? config.columns[keycol].roundToPlace : '',
364
- addColCommas: config.columns[keycol].commas
397
+ if (config.columns) {
398
+ Object.keys(config.columns).forEach(keycol => {
399
+ if (config.columns[keycol].name === column) {
400
+ inthere = true
401
+ formattingParams = {
402
+ addColPrefix: config.columns[keycol].prefix,
403
+ addColSuffix: config.columns[keycol].suffix,
404
+ addColRoundTo: config.columns[keycol].roundToPlace ? config.columns[keycol].roundToPlace : '',
405
+ addColCommas: config.columns[keycol].commas
406
+ }
365
407
  }
366
- }
367
- })
408
+ })
409
+ }
368
410
  return formattingParams
369
411
  }
370
412
 
371
- const genChartRows = rows => {
372
- const allRows = rows.map(row => {
373
- return (
374
- <tr role='row'>
375
- {dataSeriesColumns().map(column => {
376
- const rowObj = runtimeData[row]
377
- let cellValue // placeholder for formatting below
378
- let labelValue = rowObj[column] // just raw X axis string
379
- if (column === config.xAxis.dataKey) {
380
- // not the prettiest, but helper functions work nicely here.
381
- cellValue = <>{config.xAxis.type === 'date' ? formatDate(config.xAxis.dateDisplayFormat, parseDate(config.xAxis.dateParseFormat, labelValue)) : labelValue}</>
382
- } else {
383
- let resolvedAxis = 'left'
384
- let leftAxisItems = config.series.filter(item => item?.axis === 'Left')
385
- let rightAxisItems = config.series.filter(item => item?.axis === 'Right')
386
-
387
- leftAxisItems.map(leftSeriesItem => {
388
- if (leftSeriesItem.dataKey === column) resolvedAxis = 'left'
389
- })
390
-
391
- rightAxisItems.map(rightSeriesItem => {
392
- if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
393
- })
394
-
395
- let addColParams = isAdditionalColumn(column)
396
- if (addColParams) {
397
- cellValue = formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams)
398
- } else {
399
- cellValue = formatNumber(runtimeData[row][column], resolvedAxis, false, config)
400
- }
401
- }
413
+ const getChartCellValue = (row, column) => {
414
+ const rowObj = runtimeData[row]
415
+ let cellValue // placeholder for formatting below
416
+ let labelValue = rowObj[column] // just raw X axis string
417
+ if (column === config.xAxis?.dataKey) {
418
+ // not the prettiest, but helper functions work nicely here.
419
+ cellValue = config.xAxis?.type === 'date' ? formatDate(config.xAxis?.dateDisplayFormat, parseDate(config.xAxis?.dateParseFormat, labelValue)) : labelValue
420
+ } else {
421
+ let resolvedAxis = 'left'
422
+ let leftAxisItems = config.series ? config.series.filter(item => item?.axis === 'Left') : []
423
+ let rightAxisItems = config.series ? config.series.filter(item => item?.axis === 'Right') : []
402
424
 
403
- return (
404
- <td tabIndex='0' role='gridcell' id={`${runtimeData[config.runtime.originalXAxis.dataKey]}--${row}`}>
405
- {cellValue}
425
+ leftAxisItems.map(leftSeriesItem => {
426
+ if (leftSeriesItem.dataKey === column) resolvedAxis = 'left'
427
+ })
428
+
429
+ rightAxisItems.map(rightSeriesItem => {
430
+ if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
431
+ })
432
+
433
+ let addColParams = isAdditionalColumn(column)
434
+ if (addColParams) {
435
+ cellValue = config.dataFormat ? formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams) : runtimeData[row][column]
436
+ } else {
437
+ cellValue = config.dataFormat ? formatNumber(runtimeData[row][column], resolvedAxis, false, config) : runtimeData[row][column]
438
+ }
439
+ }
440
+
441
+ return cellValue
442
+ }
443
+
444
+ const getChartCell = (row, column) => {
445
+ return (
446
+ <td tabIndex='0' role='gridcell' id={`${runtimeData[config.runtime?.originalXAxis?.dataKey]}--${row}`}>
447
+ {getChartCellValue(row, column)}
448
+ </td>
449
+ )
450
+ }
451
+
452
+ const genChartRows = rows => {
453
+ if (isVertical) {
454
+ const allRows = rows.map((row, index) => {
455
+ return (
456
+ <tr key={`${row}__${index}`} role='row'>
457
+ {dataSeriesColumns().map(column => {
458
+ return getChartCell(row, column)
459
+ })}
460
+ </tr>
461
+ )
462
+ })
463
+ return allRows
464
+ } else {
465
+ const allRows = dataSeriesColumnsSorted().map(column => {
466
+ return (
467
+ <tr role='row'>
468
+ {config.visualizationType !== 'Pie' && (
469
+ <td>
470
+ {colorScale && colorScale(getSeriesName(column)) && <LegendCircle fill={colorScale(getSeriesName(column))} />}
471
+ {getSeriesName(column)}
406
472
  </td>
407
- )
408
- })}
409
- </tr>
410
- )
411
- })
412
- return allRows
473
+ )}
474
+
475
+ {rows.map(row => {
476
+ return getChartCell(row, column)
477
+ })}
478
+ </tr>
479
+ )
480
+ })
481
+ return allRows
482
+ }
413
483
  }
414
484
 
415
485
  const upIcon = (
@@ -425,7 +495,8 @@ const DataTable = props => {
425
495
 
426
496
  const limitHeight = {
427
497
  maxHeight: config.table.limitHeight && `${config.table.height}px`,
428
- overflowY: 'scroll'
498
+ overflowY: 'scroll',
499
+ marginBottom: '33px'
429
500
  }
430
501
 
431
502
  const caption = useMemo(() => {
@@ -442,8 +513,8 @@ const DataTable = props => {
442
513
  ? [config.yAxis.dataKey]
443
514
  : config.visualizationType === 'Box Plot'
444
515
  ? Object.entries(config.boxplot.tableData[0])
445
- : config.runtime.seriesKeys),
446
- [config.runtime.seriesKeys]) // eslint-disable-line
516
+ : config.runtime?.seriesKeys),
517
+ [config.runtime?.seriesKeys]) // eslint-disable-line
447
518
 
448
519
  if (config.visualizationType !== 'Box Plot') {
449
520
  const genMapHeader = columns => {
@@ -451,12 +522,12 @@ const DataTable = props => {
451
522
  <tr>
452
523
  {Object.keys(columns)
453
524
  .filter(column => columns[column].dataTable === true && columns[column].name)
454
- .map(column => {
525
+ .map((column, index) => {
455
526
  let text
456
527
  if (column !== 'geo') {
457
528
  text = columns[column].label ? columns[column].label : columns[column].name
458
529
  } else {
459
- text = config.type === 'map' ? indexTitle : config.xAxis.dataKey
530
+ text = config.type === 'map' ? indexTitle : config.xAxis?.dataKey
460
531
  }
461
532
  if (config.type === 'map' && (text === undefined || text === '')) {
462
533
  text = 'Location'
@@ -464,7 +535,7 @@ const DataTable = props => {
464
535
 
465
536
  return (
466
537
  <th
467
- key={`col-header-${column}`}
538
+ key={`col-header-${column}__${index}`}
468
539
  id={column}
469
540
  tabIndex='0'
470
541
  title={text}
@@ -496,8 +567,8 @@ const DataTable = props => {
496
567
  return (
497
568
  <ErrorBoundary component='DataTable'>
498
569
  <MediaControls.Section classes={['download-links']}>
499
- <MediaControls.Link config={config} />
500
- {(config.table.download || config.general.showDownloadButton) && <DownloadButton />}
570
+ <MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
571
+ {(config.table.download || config.general?.showDownloadButton) && <DownloadButton />}
501
572
  </MediaControls.Section>
502
573
  <section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
503
574
  <a id='skip-nav' className='cdcdataviz-sr-only-focusable' href={`#${skipId}`}>
@@ -519,7 +590,7 @@ const DataTable = props => {
519
590
  {tableTitle}
520
591
  </div>
521
592
  <div className='table-container' style={limitHeight}>
522
- <table height={expanded ? null : 0} role='table' aria-live='assertive' className={expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'} hidden={!expanded} aria-rowcount={config?.data?.length ? config.data.length : '-1'}>
593
+ <table height={expanded ? null : 0} role='table' aria-live='assertive' className={`${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}${isVertical ? '' : ' horizontal'}`} hidden={!expanded} aria-rowcount={config?.data?.length ? config.data.length : '-1'}>
523
594
  <caption className='cdcdataviz-sr-only'>{caption}</caption>
524
595
  <thead style={{ position: 'sticky', top: 0, zIndex: 999 }}>{config.type === 'map' ? genMapHeader(columns) : genChartHeader(columns, runtimeData)}</thead>
525
596
  <tbody>{config.type === 'map' ? genMapRows(rows) : genChartRows(rows)}</tbody>
@@ -289,6 +289,7 @@ const Filters = props => {
289
289
  <select
290
290
  id={`filter-${outerIndex}`}
291
291
  name={label}
292
+ aria-label={label}
292
293
  className='filter-select'
293
294
  data-index='0'
294
295
  value={active}
@@ -312,7 +313,7 @@ const Filters = props => {
312
313
  delete filtersToLoop.fromHash
313
314
 
314
315
  return filtersToLoop.map((singleFilter, outerIndex) => {
315
- if(singleFilter.showDropdown === false) return
316
+ if (singleFilter.showDropdown === false) return
316
317
 
317
318
  const values = []
318
319
  const pillValues = []
@@ -382,11 +383,11 @@ const Filters = props => {
382
383
  return (
383
384
  <div className={classList.join(' ')} key={outerIndex}>
384
385
  <>
385
- {label && <label htmlFor={label}>{label}</label>}
386
+ {label && <label htmlFor={`filter-${outerIndex}`}>{label}</label>}
386
387
  {filterStyle === 'tab' && !mobileFilterStyle && <Filters.Tabs tabs={tabValues} />}
387
388
  {filterStyle === 'pill' && !mobileFilterStyle && <Filters.Pills pills={pillValues} />}
388
389
  {filterStyle === 'tab bar' && !mobileFilterStyle && <Filters.TabBar filter={singleFilter} index={outerIndex} />}
389
- {(filterStyle === 'dropdown' || mobileFilterStyle) && <Filters.Dropdown index={outerIndex} label={label} active={active} filters={values} />}
390
+ {(filterStyle === 'dropdown' || mobileFilterStyle) && <Filters.Dropdown filter={singleFilter} index={outerIndex} label={label} active={active} filters={values} />}
390
391
  </>
391
392
  </div>
392
393
  )
@@ -394,7 +395,7 @@ const Filters = props => {
394
395
  }
395
396
  }
396
397
 
397
- if (visualizationConfig?.filters?.length === 0 || props?.filteredData?.length === 0) return
398
+ if (visualizationConfig?.filters?.length === 0) return
398
399
  return (
399
400
  <Filters>
400
401
  <Filters.Section>
@@ -417,7 +418,8 @@ Filters.propTypes = {
417
418
  // exclusions
418
419
  excludedData: PropTypes.array,
419
420
  // function for filtering the data
420
- filterData: PropTypes.func
421
+ filterData: PropTypes.func,
422
+ dimensions: PropTypes.array
421
423
  }
422
424
 
423
425
  export default Filters