@cdc/core 4.23.9 → 4.23.10
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.
- package/components/AdvancedEditor.jsx +1 -1
- package/components/DataTable.jsx +242 -201
- package/components/Filters.jsx +7 -5
- package/components/{GlobalContext.jsx → GlobalContext.tsx} +16 -5
- package/components/MediaControls.jsx +7 -6
- package/components/elements/_stories/Button.stories.tsx +28 -0
- package/components/elements/_stories/Card.stories.tsx +21 -0
- package/components/managers/_stories/DataDesigner.stories.tsx +26 -0
- package/components/ui/Accordion.jsx +1 -1
- package/components/ui/Icon.jsx +3 -1
- package/components/ui/Modal.jsx +1 -1
- package/components/ui/Select.jsx +30 -0
- package/components/ui/Tooltip.jsx +3 -2
- package/components/ui/_stories/Accordion.stories.tsx +36 -0
- package/components/ui/_stories/Icon.stories.tsx +22 -0
- package/data/colorPalettes.js +11 -0
- package/helpers/{DataTransform.js → DataTransform.ts} +26 -18
- package/helpers/fetchRemoteData.js +3 -9
- package/helpers/gatherQueryParams.ts +7 -0
- package/helpers/getFileExtension.ts +5 -0
- package/helpers/lineChartHelpers.js +7 -0
- package/helpers/useDataVizClasses.js +9 -3
- package/package.json +2 -2
- package/styles/_data-table.scss +25 -9
- package/styles/_reset.scss +6 -0
- package/styles/_typography.scss +20 -0
- package/styles/base.scss +1 -0
- package/styles/v2/layout/_component.scss +3 -3
- package/styles/v2/themes/_color-definitions.scss +27 -45
|
@@ -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'>
|
package/components/DataTable.jsx
CHANGED
|
@@ -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,107 +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
|
-
|
|
38
|
-
// Having a custom method means we can add in any business logic we want going forward
|
|
39
|
-
const customSort = (a, b) => {
|
|
40
|
-
const isDateA = Date.parse(a)
|
|
41
|
-
const isDateB = Date.parse(b)
|
|
42
|
-
|
|
43
|
-
const isNumberA = !isNaN(a)
|
|
44
|
-
const isNumberB = !isNaN(b)
|
|
45
|
-
|
|
46
|
-
if (isDateA && isDateB) {
|
|
47
|
-
return sortBy.asc ? new Date(a) - new Date(b) : new Date(b) - new Date(a)
|
|
48
|
-
}
|
|
49
|
-
if (isNumberA && isNumberB) {
|
|
50
|
-
return sortBy.asc ? Number(a) - Number(b) : Number(b) - Number(a)
|
|
51
|
-
}
|
|
52
|
-
if (typeof a === 'string' && typeof b === 'string') {
|
|
53
|
-
return sortBy.asc ? a.localeCompare(b) : b.localeCompare(a)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return 0
|
|
57
|
-
}
|
|
58
|
-
const customSortX = (a, b) => {
|
|
59
|
-
const digitRegex = /\d+/
|
|
60
|
-
|
|
61
|
-
const hasNumber = value => digitRegex.test(value)
|
|
37
|
+
const isVertical = !(config.type === 'chart' && !config.table?.showVertical)
|
|
62
38
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// check for dates first
|
|
68
|
-
if (!isNaN(Date.parse(a)) && !isNaN(Date.parse(b))) {
|
|
69
|
-
return Date.parse(a) - Date.parse(b)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// convert any strings that are actually numbers to proper data type
|
|
73
|
-
const aNum = Number(a)
|
|
74
|
-
|
|
75
|
-
if (!Number.isNaN(aNum)) {
|
|
76
|
-
a = aNum
|
|
77
|
-
}
|
|
39
|
+
const customSort = (a, b) => {
|
|
40
|
+
let valueA = a
|
|
41
|
+
let valueB = b
|
|
78
42
|
|
|
79
|
-
|
|
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
|
|
80
46
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
47
|
+
const trimmedA = String(valueA).trim()
|
|
48
|
+
const trimmedB = String(valueB).trim()
|
|
84
49
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
a = a.replace('us-', '')
|
|
88
|
-
a = displayGeoName(a)
|
|
89
|
-
}
|
|
50
|
+
if (config.xAxis?.dataKey === sortBy.column && config.xAxis.type === 'date') {
|
|
51
|
+
let dateA = parseDate(config.xAxis.dateParseFormat, trimmedA)
|
|
90
52
|
|
|
91
|
-
|
|
92
|
-
b = b.replace('us-', '')
|
|
93
|
-
b = displayGeoName(b)
|
|
94
|
-
}
|
|
53
|
+
let dateB = parseDate(config.xAxis.dateParseFormat, trimmedB)
|
|
95
54
|
|
|
96
|
-
|
|
97
|
-
a = typeof a === 'string' ? a.toLowerCase() : a
|
|
98
|
-
b = typeof b === 'string' ? b.toLowerCase() : b
|
|
55
|
+
if (dateA && dateA.getTime) dateA = dateA.getTime()
|
|
99
56
|
|
|
100
|
-
|
|
101
|
-
if (typeof a === 'string' && hasNumber(a) === true) {
|
|
102
|
-
a = a.match(digitRegex)[0]
|
|
57
|
+
if (dateB && dateB.getTime) dateB = dateB.getTime()
|
|
103
58
|
|
|
104
|
-
|
|
59
|
+
return !sortBy.asc ? dateA - dateB : dateB - dateA
|
|
105
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 !== ''
|
|
106
64
|
|
|
107
|
-
|
|
108
|
-
|
|
65
|
+
// Handle empty strings or spaces
|
|
66
|
+
if (trimmedA === '' && trimmedB !== '') return !sortBy.asc ? -1 : 1
|
|
67
|
+
if (trimmedA !== '' && trimmedB === '') return !sortBy.asc ? 1 : -1
|
|
109
68
|
|
|
110
|
-
|
|
69
|
+
// Both are numbers: Compare numerically
|
|
70
|
+
if (isNumA && isNumB) {
|
|
71
|
+
return !sortBy.asc ? Number(valueA) - Number(valueB) : Number(valueB) - Number(valueA)
|
|
111
72
|
}
|
|
112
73
|
|
|
113
|
-
//
|
|
114
|
-
if (
|
|
115
|
-
return 1
|
|
74
|
+
// Only A is a number
|
|
75
|
+
if (isNumA) {
|
|
76
|
+
return !sortBy.asc ? -1 : 1
|
|
116
77
|
}
|
|
117
78
|
|
|
118
|
-
|
|
119
|
-
|
|
79
|
+
// Only B is a number
|
|
80
|
+
if (isNumB) {
|
|
81
|
+
return !sortBy.asc ? 1 : -1
|
|
120
82
|
}
|
|
121
83
|
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
return 1
|
|
125
|
-
}
|
|
126
|
-
if (a < b) {
|
|
127
|
-
return -1
|
|
128
|
-
}
|
|
129
|
-
// returning 0, undefined or any falsey value will use subsequent sorts or
|
|
130
|
-
// the index as a tiebreaker
|
|
131
|
-
return 0
|
|
84
|
+
// Neither are numbers: Compare as strings
|
|
85
|
+
return !sortBy.asc ? trimmedA.localeCompare(trimmedB) : trimmedB.localeCompare(trimmedA)
|
|
132
86
|
}
|
|
133
87
|
|
|
134
88
|
// Optionally wrap cell with anchor if config defines a navigation url
|
|
@@ -170,7 +124,7 @@ const DataTable = props => {
|
|
|
170
124
|
if (rawData !== undefined) {
|
|
171
125
|
let csvData
|
|
172
126
|
// only use fullGeoName on County maps and no other
|
|
173
|
-
if (config.general
|
|
127
|
+
if (config.general?.geoType === 'us-county') {
|
|
174
128
|
// Unparse + Add column for full Geo name along with State
|
|
175
129
|
csvData = Papa.unparse(rawData.map(row => ({ FullGeoName: formatLegendLocation(row[config.columns.geo.name]), ...row })))
|
|
176
130
|
} else {
|
|
@@ -225,20 +179,19 @@ const DataTable = props => {
|
|
|
225
179
|
break
|
|
226
180
|
}
|
|
227
181
|
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
})
|
|
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
|
|
242
195
|
|
|
243
196
|
const genMapRows = rows => {
|
|
244
197
|
const allrows = rows.map(row => {
|
|
@@ -297,20 +250,24 @@ const DataTable = props => {
|
|
|
297
250
|
const dataSeriesColumns = () => {
|
|
298
251
|
let tmpSeriesColumns
|
|
299
252
|
if (config.visualizationType !== 'Pie') {
|
|
300
|
-
tmpSeriesColumns = [config.xAxis
|
|
301
|
-
config.series
|
|
302
|
-
|
|
303
|
-
|
|
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
|
+
}
|
|
304
261
|
} else {
|
|
305
|
-
tmpSeriesColumns = [config.xAxis
|
|
262
|
+
tmpSeriesColumns = [config.xAxis?.dataKey, config.yAxis?.dataKey] //Object.keys(runtimeData[0])
|
|
306
263
|
}
|
|
307
264
|
|
|
308
265
|
// then add the additional Columns
|
|
309
|
-
if (Object.keys(config.columns).length > 0) {
|
|
266
|
+
if (config.columns && Object.keys(config.columns).length > 0) {
|
|
310
267
|
Object.keys(config.columns).forEach(function (key) {
|
|
311
268
|
var value = config.columns[key]
|
|
312
269
|
// add if not the index AND it is enabled to be added to data table
|
|
313
|
-
if (value.name !== config.xAxis
|
|
270
|
+
if (value.name !== config.xAxis?.dataKey && value.dataTable === true) {
|
|
314
271
|
tmpSeriesColumns.push(value.name)
|
|
315
272
|
}
|
|
316
273
|
})
|
|
@@ -318,10 +275,22 @@ const DataTable = props => {
|
|
|
318
275
|
|
|
319
276
|
return tmpSeriesColumns
|
|
320
277
|
}
|
|
321
|
-
|
|
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
|
+
}
|
|
322
291
|
const getLabel = name => {
|
|
323
292
|
let custLabel = ''
|
|
324
|
-
if (Object.keys(config.columns).length > 0) {
|
|
293
|
+
if (config.columns && Object.keys(config.columns).length > 0) {
|
|
325
294
|
Object.keys(config.columns).forEach(function (key) {
|
|
326
295
|
var tmpColumn = config.columns[key]
|
|
327
296
|
// add if not the index AND it is enabled to be added to data table
|
|
@@ -333,108 +302,180 @@ const DataTable = props => {
|
|
|
333
302
|
}
|
|
334
303
|
}
|
|
335
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
|
+
|
|
336
318
|
const genChartHeader = (columns, data) => {
|
|
337
319
|
if (!data) return
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
+
return (
|
|
357
|
+
<tr>
|
|
358
|
+
{['__series__', ...Object.keys(runtimeData)].map((row, index) => {
|
|
359
|
+
let column = config.xAxis?.dataKey
|
|
360
|
+
let text = row !== '__series__' ? getChartCellValue(row, column) : '__series__'
|
|
361
|
+
return (
|
|
362
|
+
<th
|
|
363
|
+
key={`col-header-${text}__${index}`}
|
|
364
|
+
tabIndex='0'
|
|
365
|
+
title={text}
|
|
366
|
+
role='columnheader'
|
|
367
|
+
scope='col'
|
|
368
|
+
onClick={() => {
|
|
369
|
+
setSortBy({ column: text, asc: sortBy.column === text ? !sortBy.asc : false, colIndex: index })
|
|
370
|
+
}}
|
|
371
|
+
onKeyDown={e => {
|
|
372
|
+
if (e.keyCode === 13) {
|
|
373
|
+
setSortBy({ column: text, asc: sortBy.column === text ? !sortBy.asc : false, colIndex: index })
|
|
374
|
+
}
|
|
375
|
+
}}
|
|
376
|
+
className={sortBy.column === text ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'}
|
|
377
|
+
{...(sortBy.column === text ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
|
|
378
|
+
>
|
|
379
|
+
{text === '__series__' ? '' : text}
|
|
380
|
+
{index === sortBy.colIndex && <span className={'sort-icon'}>{!sortBy.asc ? upIcon : downIcon}</span>}
|
|
381
|
+
<button>
|
|
382
|
+
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === text ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
|
|
383
|
+
</button>
|
|
384
|
+
</th>
|
|
385
|
+
)
|
|
386
|
+
})}
|
|
387
|
+
</tr>
|
|
388
|
+
)
|
|
389
|
+
}
|
|
376
390
|
}
|
|
377
391
|
|
|
378
392
|
// if its additional column, return formatting params
|
|
379
393
|
const isAdditionalColumn = column => {
|
|
380
394
|
let inthere = false
|
|
381
395
|
let formattingParams = {}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
396
|
+
if (config.columns) {
|
|
397
|
+
Object.keys(config.columns).forEach(keycol => {
|
|
398
|
+
if (config.columns[keycol].name === column) {
|
|
399
|
+
inthere = true
|
|
400
|
+
formattingParams = {
|
|
401
|
+
addColPrefix: config.columns[keycol].prefix,
|
|
402
|
+
addColSuffix: config.columns[keycol].suffix,
|
|
403
|
+
addColRoundTo: config.columns[keycol].roundToPlace ? config.columns[keycol].roundToPlace : '',
|
|
404
|
+
addColCommas: config.columns[keycol].commas
|
|
405
|
+
}
|
|
390
406
|
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
407
|
+
})
|
|
408
|
+
}
|
|
393
409
|
return formattingParams
|
|
394
410
|
}
|
|
395
411
|
|
|
396
|
-
const
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
} else {
|
|
408
|
-
let resolvedAxis = 'left'
|
|
409
|
-
let leftAxisItems = config.series.filter(item => item?.axis === 'Left')
|
|
410
|
-
let rightAxisItems = config.series.filter(item => item?.axis === 'Right')
|
|
411
|
-
|
|
412
|
-
leftAxisItems.map(leftSeriesItem => {
|
|
413
|
-
if (leftSeriesItem.dataKey === column) resolvedAxis = 'left'
|
|
414
|
-
})
|
|
415
|
-
|
|
416
|
-
rightAxisItems.map(rightSeriesItem => {
|
|
417
|
-
if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
|
|
418
|
-
})
|
|
419
|
-
|
|
420
|
-
let addColParams = isAdditionalColumn(column)
|
|
421
|
-
if (addColParams) {
|
|
422
|
-
cellValue = formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams)
|
|
423
|
-
} else {
|
|
424
|
-
cellValue = formatNumber(runtimeData[row][column], resolvedAxis, false, config)
|
|
425
|
-
}
|
|
426
|
-
}
|
|
412
|
+
const getChartCellValue = (row, column) => {
|
|
413
|
+
const rowObj = runtimeData[row]
|
|
414
|
+
let cellValue // placeholder for formatting below
|
|
415
|
+
let labelValue = rowObj[column] // just raw X axis string
|
|
416
|
+
if (column === config.xAxis?.dataKey) {
|
|
417
|
+
// not the prettiest, but helper functions work nicely here.
|
|
418
|
+
cellValue = config.xAxis?.type === 'date' ? formatDate(config.xAxis?.dateDisplayFormat, parseDate(config.xAxis?.dateParseFormat, labelValue)) : labelValue
|
|
419
|
+
} else {
|
|
420
|
+
let resolvedAxis = 'left'
|
|
421
|
+
let leftAxisItems = config.series ? config.series.filter(item => item?.axis === 'Left') : []
|
|
422
|
+
let rightAxisItems = config.series ? config.series.filter(item => item?.axis === 'Right') : []
|
|
427
423
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
424
|
+
leftAxisItems.map(leftSeriesItem => {
|
|
425
|
+
if (leftSeriesItem.dataKey === column) resolvedAxis = 'left'
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
rightAxisItems.map(rightSeriesItem => {
|
|
429
|
+
if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
let addColParams = isAdditionalColumn(column)
|
|
433
|
+
if (addColParams) {
|
|
434
|
+
cellValue = config.dataFormat ? formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams) : runtimeData[row][column]
|
|
435
|
+
} else {
|
|
436
|
+
cellValue = config.dataFormat ? formatNumber(runtimeData[row][column], resolvedAxis, false, config) : runtimeData[row][column]
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return cellValue
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const getChartCell = (row, column) => {
|
|
444
|
+
return (
|
|
445
|
+
<td tabIndex='0' role='gridcell' id={`${runtimeData[config.runtime?.originalXAxis?.dataKey]}--${row}`}>
|
|
446
|
+
{getChartCellValue(row, column)}
|
|
447
|
+
</td>
|
|
448
|
+
)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const genChartRows = rows => {
|
|
452
|
+
if (isVertical) {
|
|
453
|
+
const allRows = rows.map((row, index) => {
|
|
454
|
+
return (
|
|
455
|
+
<tr key={`${row}__${index}`} role='row'>
|
|
456
|
+
{dataSeriesColumns().map(column => {
|
|
457
|
+
return getChartCell(row, column)
|
|
458
|
+
})}
|
|
459
|
+
</tr>
|
|
460
|
+
)
|
|
461
|
+
})
|
|
462
|
+
return allRows
|
|
463
|
+
} else {
|
|
464
|
+
const allRows = dataSeriesColumnsSorted().map(column => {
|
|
465
|
+
return (
|
|
466
|
+
<tr role='row'>
|
|
467
|
+
<td>
|
|
468
|
+
{colorScale && colorScale(getSeriesName(column)) && <LegendCircle fill={colorScale(getSeriesName(column))} />}
|
|
469
|
+
{getSeriesName(column)}
|
|
470
|
+
</td>
|
|
471
|
+
{rows.map(row => {
|
|
472
|
+
return getChartCell(row, column)
|
|
473
|
+
})}
|
|
474
|
+
</tr>
|
|
475
|
+
)
|
|
476
|
+
})
|
|
477
|
+
return allRows
|
|
478
|
+
}
|
|
438
479
|
}
|
|
439
480
|
|
|
440
481
|
const upIcon = (
|
|
@@ -467,8 +508,8 @@ const DataTable = props => {
|
|
|
467
508
|
? [config.yAxis.dataKey]
|
|
468
509
|
: config.visualizationType === 'Box Plot'
|
|
469
510
|
? Object.entries(config.boxplot.tableData[0])
|
|
470
|
-
: config.runtime
|
|
471
|
-
[config.runtime
|
|
511
|
+
: config.runtime?.seriesKeys),
|
|
512
|
+
[config.runtime?.seriesKeys]) // eslint-disable-line
|
|
472
513
|
|
|
473
514
|
if (config.visualizationType !== 'Box Plot') {
|
|
474
515
|
const genMapHeader = columns => {
|
|
@@ -476,12 +517,12 @@ const DataTable = props => {
|
|
|
476
517
|
<tr>
|
|
477
518
|
{Object.keys(columns)
|
|
478
519
|
.filter(column => columns[column].dataTable === true && columns[column].name)
|
|
479
|
-
.map(column => {
|
|
520
|
+
.map((column, index) => {
|
|
480
521
|
let text
|
|
481
522
|
if (column !== 'geo') {
|
|
482
523
|
text = columns[column].label ? columns[column].label : columns[column].name
|
|
483
524
|
} else {
|
|
484
|
-
text = config.type === 'map' ? indexTitle : config.xAxis
|
|
525
|
+
text = config.type === 'map' ? indexTitle : config.xAxis?.dataKey
|
|
485
526
|
}
|
|
486
527
|
if (config.type === 'map' && (text === undefined || text === '')) {
|
|
487
528
|
text = 'Location'
|
|
@@ -489,7 +530,7 @@ const DataTable = props => {
|
|
|
489
530
|
|
|
490
531
|
return (
|
|
491
532
|
<th
|
|
492
|
-
key={`col-header-${column}`}
|
|
533
|
+
key={`col-header-${column}__${index}`}
|
|
493
534
|
id={column}
|
|
494
535
|
tabIndex='0'
|
|
495
536
|
title={text}
|
|
@@ -521,8 +562,8 @@ const DataTable = props => {
|
|
|
521
562
|
return (
|
|
522
563
|
<ErrorBoundary component='DataTable'>
|
|
523
564
|
<MediaControls.Section classes={['download-links']}>
|
|
524
|
-
<MediaControls.Link config={config} />
|
|
525
|
-
{(config.table.download || config.general
|
|
565
|
+
<MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
|
|
566
|
+
{(config.table.download || config.general?.showDownloadButton) && <DownloadButton />}
|
|
526
567
|
</MediaControls.Section>
|
|
527
568
|
<section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
|
|
528
569
|
<a id='skip-nav' className='cdcdataviz-sr-only-focusable' href={`#${skipId}`}>
|
|
@@ -544,7 +585,7 @@ const DataTable = props => {
|
|
|
544
585
|
{tableTitle}
|
|
545
586
|
</div>
|
|
546
587
|
<div className='table-container' style={limitHeight}>
|
|
547
|
-
<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'}>
|
|
588
|
+
<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'}>
|
|
548
589
|
<caption className='cdcdataviz-sr-only'>{caption}</caption>
|
|
549
590
|
<thead style={{ position: 'sticky', top: 0, zIndex: 999 }}>{config.type === 'map' ? genMapHeader(columns) : genChartHeader(columns, runtimeData)}</thead>
|
|
550
591
|
<tbody>{config.type === 'map' ? genMapRows(rows) : genChartRows(rows)}</tbody>
|
package/components/Filters.jsx
CHANGED
|
@@ -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={
|
|
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
|
|
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
|