@cdc/core 4.23.8 → 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 -176
- package/components/Filters.jsx +7 -5
- package/components/{GlobalContext.jsx → GlobalContext.tsx} +16 -5
- package/components/Legend/index.tsx +39 -0
- 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} +28 -23
- 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 +38 -5
- 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
- package/LICENSE +0 -201
|
@@ -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,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
|
-
|
|
38
|
-
|
|
37
|
+
const isVertical = !(config.type === 'chart' && !config.table?.showVertical)
|
|
38
|
+
|
|
39
39
|
const customSort = (a, b) => {
|
|
40
|
-
|
|
40
|
+
let valueA = a
|
|
41
|
+
let valueB = b
|
|
41
42
|
|
|
42
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
b = b === null || b === undefined ? '' : b
|
|
47
|
+
const trimmedA = String(valueA).trim()
|
|
48
|
+
const trimmedB = String(valueB).trim()
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
if (config.xAxis?.dataKey === sortBy.column && config.xAxis.type === 'date') {
|
|
51
|
+
let dateA = parseDate(config.xAxis.dateParseFormat, trimmedA)
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
a = aNum
|
|
53
|
-
}
|
|
53
|
+
let dateB = parseDate(config.xAxis.dateParseFormat, trimmedB)
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
if (dateA && dateA.getTime) dateA = dateA.getTime()
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
b = bNum
|
|
59
|
-
}
|
|
57
|
+
if (dateB && dateB.getTime) dateB = dateB.getTime()
|
|
60
58
|
|
|
61
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
//
|
|
90
|
-
if (
|
|
91
|
-
return 1
|
|
79
|
+
// Only B is a number
|
|
80
|
+
if (isNumB) {
|
|
81
|
+
return !sortBy.asc ? 1 : -1
|
|
92
82
|
}
|
|
93
83
|
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
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
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
276
|
-
config.series
|
|
277
|
-
|
|
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
|
|
262
|
+
tmpSeriesColumns = [config.xAxis?.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
|
|
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,180 @@ 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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
+
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
|
+
}
|
|
351
390
|
}
|
|
352
391
|
|
|
353
392
|
// if its additional column, return formatting params
|
|
354
393
|
const isAdditionalColumn = column => {
|
|
355
394
|
let inthere = false
|
|
356
395
|
let formattingParams = {}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
+
}
|
|
365
406
|
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
407
|
+
})
|
|
408
|
+
}
|
|
368
409
|
return formattingParams
|
|
369
410
|
}
|
|
370
411
|
|
|
371
|
-
const
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
}
|
|
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') : []
|
|
402
423
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
+
}
|
|
413
479
|
}
|
|
414
480
|
|
|
415
481
|
const upIcon = (
|
|
@@ -442,8 +508,8 @@ const DataTable = props => {
|
|
|
442
508
|
? [config.yAxis.dataKey]
|
|
443
509
|
: config.visualizationType === 'Box Plot'
|
|
444
510
|
? Object.entries(config.boxplot.tableData[0])
|
|
445
|
-
: config.runtime
|
|
446
|
-
[config.runtime
|
|
511
|
+
: config.runtime?.seriesKeys),
|
|
512
|
+
[config.runtime?.seriesKeys]) // eslint-disable-line
|
|
447
513
|
|
|
448
514
|
if (config.visualizationType !== 'Box Plot') {
|
|
449
515
|
const genMapHeader = columns => {
|
|
@@ -451,12 +517,12 @@ const DataTable = props => {
|
|
|
451
517
|
<tr>
|
|
452
518
|
{Object.keys(columns)
|
|
453
519
|
.filter(column => columns[column].dataTable === true && columns[column].name)
|
|
454
|
-
.map(column => {
|
|
520
|
+
.map((column, index) => {
|
|
455
521
|
let text
|
|
456
522
|
if (column !== 'geo') {
|
|
457
523
|
text = columns[column].label ? columns[column].label : columns[column].name
|
|
458
524
|
} else {
|
|
459
|
-
text = config.type === 'map' ? indexTitle : config.xAxis
|
|
525
|
+
text = config.type === 'map' ? indexTitle : config.xAxis?.dataKey
|
|
460
526
|
}
|
|
461
527
|
if (config.type === 'map' && (text === undefined || text === '')) {
|
|
462
528
|
text = 'Location'
|
|
@@ -464,7 +530,7 @@ const DataTable = props => {
|
|
|
464
530
|
|
|
465
531
|
return (
|
|
466
532
|
<th
|
|
467
|
-
key={`col-header-${column}`}
|
|
533
|
+
key={`col-header-${column}__${index}`}
|
|
468
534
|
id={column}
|
|
469
535
|
tabIndex='0'
|
|
470
536
|
title={text}
|
|
@@ -496,8 +562,8 @@ const DataTable = props => {
|
|
|
496
562
|
return (
|
|
497
563
|
<ErrorBoundary component='DataTable'>
|
|
498
564
|
<MediaControls.Section classes={['download-links']}>
|
|
499
|
-
<MediaControls.Link config={config} />
|
|
500
|
-
{(config.table.download || config.general
|
|
565
|
+
<MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
|
|
566
|
+
{(config.table.download || config.general?.showDownloadButton) && <DownloadButton />}
|
|
501
567
|
</MediaControls.Section>
|
|
502
568
|
<section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
|
|
503
569
|
<a id='skip-nav' className='cdcdataviz-sr-only-focusable' href={`#${skipId}`}>
|
|
@@ -519,7 +585,7 @@ const DataTable = props => {
|
|
|
519
585
|
{tableTitle}
|
|
520
586
|
</div>
|
|
521
587
|
<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'}>
|
|
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'}>
|
|
523
589
|
<caption className='cdcdataviz-sr-only'>{caption}</caption>
|
|
524
590
|
<thead style={{ position: 'sticky', top: 0, zIndex: 999 }}>{config.type === 'map' ? genMapHeader(columns) : genChartHeader(columns, runtimeData)}</thead>
|
|
525
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
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import React, { createContext, useContext, useState } from 'react'
|
|
1
|
+
import React, { ReactElement, createContext, useContext, useState } from 'react'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type Global = {
|
|
4
|
+
overlay: {
|
|
5
|
+
object: ReactElement,
|
|
6
|
+
show: boolean,
|
|
7
|
+
disableBgClose: boolean,
|
|
8
|
+
actions: {
|
|
9
|
+
openOverlay: (any, boolean?) => void,
|
|
10
|
+
toggleOverlay: (boolean?) => void
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export const GlobalContext = createContext<Global>({} as Global)
|
|
4
15
|
export const useGlobalContext = () => useContext(GlobalContext)
|
|
5
16
|
|
|
6
17
|
export const GlobalContextProvider = ({ children }) => {
|
|
7
|
-
const [globalContextData, setGlobalContextData] = useState({})
|
|
18
|
+
const [globalContextData, setGlobalContextData] = useState<Global>({} as Global)
|
|
8
19
|
|
|
9
20
|
const openOverlay = (obj, disableBgClose = false) => {
|
|
10
21
|
let payload = { object: obj, show: true, disableBgClose: disableBgClose }
|
|
11
|
-
setGlobalContextData(context => ({ ...context, overlay: { ...payload } }))
|
|
22
|
+
setGlobalContextData(context => ({ ...context, overlay: { ...payload } } as Global))
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
const toggleOverlay = (display = false) => {
|
|
@@ -21,7 +32,7 @@ export const GlobalContextProvider = ({ children }) => {
|
|
|
21
32
|
}))
|
|
22
33
|
}
|
|
23
34
|
|
|
24
|
-
const globalSettings = {
|
|
35
|
+
const globalSettings: Global = {
|
|
25
36
|
overlay: {
|
|
26
37
|
object: globalContextData.overlay?.object || null,
|
|
27
38
|
show: globalContextData.overlay?.show || false,
|