@cdc/core 4.23.10 → 4.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/assets/icon-deviation-bar.svg +1 -0
- package/components/DataTable/DataTable.tsx +223 -0
- package/components/DataTable/components/BoxplotHeader.tsx +16 -0
- package/components/DataTable/components/CellAnchor.tsx +44 -0
- package/components/DataTable/components/ChartHeader.tsx +103 -0
- package/components/DataTable/components/ExpandCollapse.tsx +21 -0
- package/components/DataTable/components/Icons.tsx +10 -0
- package/components/DataTable/components/MapHeader.tsx +56 -0
- package/components/DataTable/components/SkipNav.tsx +7 -0
- package/components/DataTable/helpers/boxplotCellMatrix.tsx +64 -0
- package/components/DataTable/helpers/chartCellMatrix.tsx +92 -0
- package/components/DataTable/helpers/customColumns.ts +25 -0
- package/components/DataTable/helpers/customSort.ts +55 -0
- package/components/DataTable/helpers/getChartCellValue.ts +56 -0
- package/components/DataTable/helpers/getDataSeriesColumns.ts +29 -0
- package/components/DataTable/helpers/getSeriesName.ts +26 -0
- package/components/DataTable/helpers/mapCellMatrix.tsx +56 -0
- package/components/DataTable/helpers/regionCellMatrix.tsx +13 -0
- package/components/DataTable/helpers/standardizeState.js +76 -0
- package/components/DataTable/index.ts +1 -0
- package/components/DataTable/types/TableConfig.ts +52 -0
- package/components/DownloadButton.tsx +29 -0
- package/components/EditorPanel/DataTableEditor.tsx +133 -0
- package/components/EditorPanel/Inputs.tsx +150 -0
- package/components/Filters.jsx +3 -3
- package/components/LegendCircle.jsx +2 -2
- package/components/MediaControls.jsx +1 -1
- package/components/MultiSelect/MultiSelect.tsx +95 -0
- package/components/MultiSelect/index.ts +1 -0
- package/components/MultiSelect/multiselect.styles.css +50 -0
- package/components/Table/Table.tsx +69 -0
- package/components/Table/components/Cell.tsx +9 -0
- package/components/Table/components/GroupRow.tsx +20 -0
- package/components/Table/components/Row.tsx +26 -0
- package/components/Table/index.ts +1 -0
- package/components/Table/types/CellMatrix.ts +4 -0
- package/components/Table/types/RowType.ts +5 -0
- package/components/_stories/DataTable.stories.tsx +103 -0
- package/components/_stories/EditorPanel.stories.tsx +53 -0
- package/components/_stories/Inputs.stories.tsx +37 -0
- package/components/_stories/MultiSelect.stories.tsx +24 -0
- package/components/_stories/Table.stories.tsx +53 -0
- package/components/_stories/_mocks/dashboard_no_filter.json +121 -0
- package/components/_stories/_mocks/example-city-state.json +808 -0
- package/components/_stories/_mocks/row_type.json +42 -0
- package/components/_stories/styles.scss +9 -0
- package/components/inputs/{InputSelect.jsx → InputSelect.tsx} +15 -5
- package/components/managers/{DataDesigner.jsx → DataDesigner.tsx} +103 -94
- package/components/ui/{Icon.jsx → Icon.tsx} +3 -3
- package/components/ui/Title/Title.scss +95 -0
- package/components/ui/Title/index.tsx +34 -0
- package/components/ui/_stories/Title.stories.tsx +21 -0
- package/helpers/DataTransform.ts +75 -20
- package/helpers/cove/string.ts +11 -0
- package/helpers/fetchRemoteData.js +1 -1
- package/helpers/getFileExtension.ts +28 -5
- package/package.json +2 -2
- package/styles/_data-table.scss +3 -0
- package/styles/heading-colors.scss +0 -3
- package/styles/v2/layout/_component.scss +0 -11
- package/types/Axis.ts +41 -0
- package/types/Color.ts +5 -0
- package/types/Column.ts +15 -0
- package/types/ComponentStyles.ts +7 -0
- package/types/ComponentThemes.ts +13 -0
- package/types/EditorColumnProperties.ts +8 -0
- package/types/FilterBehavior.ts +1 -0
- package/types/Runtime.ts +29 -0
- package/types/Series.ts +1 -0
- package/types/Table.ts +18 -0
- package/types/UpdateFieldFunc.ts +1 -0
- package/types/Visualization.ts +21 -0
- package/components/DataTable.jsx +0 -754
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { DataTableProps } from '../DataTable'
|
|
2
|
+
import { DownIcon, UpIcon } from './Icons'
|
|
3
|
+
|
|
4
|
+
type MapHeaderProps = DataTableProps & {
|
|
5
|
+
sortBy: { column; asc }
|
|
6
|
+
setSortBy: Function
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy }: MapHeaderProps) => {
|
|
10
|
+
return (
|
|
11
|
+
<tr>
|
|
12
|
+
{Object.keys(columns)
|
|
13
|
+
.filter(column => columns[column].dataTable === true && columns[column].name)
|
|
14
|
+
.map((column, index) => {
|
|
15
|
+
let text
|
|
16
|
+
if (column !== 'geo') {
|
|
17
|
+
text = columns[column].label ? columns[column].label : columns[column].name
|
|
18
|
+
} else {
|
|
19
|
+
text = config.type === 'map' ? indexTitle : config.xAxis?.dataKey
|
|
20
|
+
}
|
|
21
|
+
if (config.type === 'map' && (text === undefined || text === '')) {
|
|
22
|
+
text = 'Location'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<th
|
|
27
|
+
key={`col-header-${column}__${index}`}
|
|
28
|
+
id={column}
|
|
29
|
+
tabIndex={0}
|
|
30
|
+
title={text}
|
|
31
|
+
role='columnheader'
|
|
32
|
+
scope='col'
|
|
33
|
+
onClick={() => {
|
|
34
|
+
setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false })
|
|
35
|
+
}}
|
|
36
|
+
onKeyDown={e => {
|
|
37
|
+
if (e.keyCode === 13) {
|
|
38
|
+
setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false })
|
|
39
|
+
}
|
|
40
|
+
}}
|
|
41
|
+
className={sortBy.column === column ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'}
|
|
42
|
+
{...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
|
|
43
|
+
>
|
|
44
|
+
{text}
|
|
45
|
+
{sortBy.column === column && <span className={'sort-icon'}>{!sortBy.asc ? <UpIcon /> : <DownIcon />}</span>}
|
|
46
|
+
<button>
|
|
47
|
+
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
|
|
48
|
+
</button>
|
|
49
|
+
</th>
|
|
50
|
+
)
|
|
51
|
+
})}
|
|
52
|
+
</tr>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default MapHeader
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CellMatrix } from '../../Table/types/CellMatrix'
|
|
2
|
+
|
|
3
|
+
const boxplotCellMatrix = ({ rows, config }): CellMatrix => {
|
|
4
|
+
const resolveName = key => {
|
|
5
|
+
let {
|
|
6
|
+
boxplot: { labels }
|
|
7
|
+
} = config
|
|
8
|
+
const columnLookup = {
|
|
9
|
+
columnMean: labels.mean,
|
|
10
|
+
columnMax: labels.maximum,
|
|
11
|
+
columnMin: labels.minimum,
|
|
12
|
+
columnIqr: labels.iqr,
|
|
13
|
+
columnCategory: 'Category',
|
|
14
|
+
columnMedian: labels.median,
|
|
15
|
+
columnFirstQuartile: labels.q1,
|
|
16
|
+
columnThirdQuartile: labels.q3,
|
|
17
|
+
columnOutliers: labels.outliers,
|
|
18
|
+
values: labels.values,
|
|
19
|
+
columnTotal: labels.total,
|
|
20
|
+
columnSd: 'Standard Deviation',
|
|
21
|
+
nonOutlierValues: 'Non Outliers',
|
|
22
|
+
columnLowerBounds: labels.lowerBounds,
|
|
23
|
+
columnUpperBounds: labels.upperBounds
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let resolvedName = columnLookup[key]
|
|
27
|
+
|
|
28
|
+
return resolvedName
|
|
29
|
+
}
|
|
30
|
+
let resolveCell = (rowid, plot) => {
|
|
31
|
+
if (Number(rowid) === 0) return true
|
|
32
|
+
if (Number(rowid) === 1) return plot.columnMax
|
|
33
|
+
if (Number(rowid) === 2) return plot.columnThirdQuartile
|
|
34
|
+
if (Number(rowid) === 3) return plot.columnMedian
|
|
35
|
+
if (Number(rowid) === 4) return plot.columnFirstQuartile
|
|
36
|
+
if (Number(rowid) === 5) return plot.columnMin
|
|
37
|
+
if (Number(rowid) === 6) return plot.columnTotal
|
|
38
|
+
if (Number(rowid) === 7) return plot.columnSd
|
|
39
|
+
if (Number(rowid) === 8) return plot.columnMean
|
|
40
|
+
if (Number(rowid) === 9) return plot.columnOutliers.length > 0 ? plot.columnOutliers.toString() : '-'
|
|
41
|
+
if (Number(rowid) === 10) return plot.values.length > 0 ? plot.values.toString() : '-'
|
|
42
|
+
return <p>-</p>
|
|
43
|
+
}
|
|
44
|
+
// get list of data keys for each row
|
|
45
|
+
let dataKeys = rows.map(row => {
|
|
46
|
+
return row[0]
|
|
47
|
+
})
|
|
48
|
+
let columns = ['Measures', ...config.boxplot.categories]
|
|
49
|
+
dataKeys.shift() // remove index 0 // we did header column separately
|
|
50
|
+
return dataKeys.map((rowkey, index) => {
|
|
51
|
+
return columns.map((column, colnum) => {
|
|
52
|
+
let cellValue
|
|
53
|
+
if (column === 'Measures') {
|
|
54
|
+
let labelValue = index > 0 ? resolveName(rowkey) : ''
|
|
55
|
+
cellValue = <>{labelValue}</>
|
|
56
|
+
} else {
|
|
57
|
+
cellValue = resolveCell(index, config.boxplot.plots[colnum - 1])
|
|
58
|
+
}
|
|
59
|
+
return cellValue
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default boxplotCellMatrix
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
2
|
+
import { customSort } from './customSort'
|
|
3
|
+
import { getSeriesName } from './getSeriesName'
|
|
4
|
+
import { DataTableProps } from '../DataTable'
|
|
5
|
+
import { getChartCellValue } from './getChartCellValue'
|
|
6
|
+
import { getDataSeriesColumns } from './getDataSeriesColumns'
|
|
7
|
+
import { ReactNode } from 'react'
|
|
8
|
+
import { CellMatrix, GroupCellMatrix } from '../../Table/types/CellMatrix'
|
|
9
|
+
|
|
10
|
+
type ChartRowsProps = DataTableProps & {
|
|
11
|
+
rows: string[]
|
|
12
|
+
isVertical: boolean
|
|
13
|
+
sortBy: { colIndex; column }
|
|
14
|
+
groupBy?: string
|
|
15
|
+
hasRowType?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorScale, groupBy, hasRowType }: ChartRowsProps): CellMatrix | GroupCellMatrix => {
|
|
19
|
+
const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
|
|
20
|
+
|
|
21
|
+
const dataSeriesColumnsSorted = () => {
|
|
22
|
+
if (!sortBy && sortBy.colIndex === null) return dataSeriesColumns
|
|
23
|
+
return dataSeriesColumns.sort((a, b) => {
|
|
24
|
+
if (sortBy.column === '__series__') return customSort(a, b, sortBy, config)
|
|
25
|
+
let row = runtimeData.find(d => d[config.xAxis?.dataKey] === sortBy.column)
|
|
26
|
+
|
|
27
|
+
const rowIndex = runtimeData[sortBy.colIndex - 1]
|
|
28
|
+
if (row) {
|
|
29
|
+
return customSort(row[a], row[b], sortBy, config)
|
|
30
|
+
}
|
|
31
|
+
if (row === undefined && rowIndex) {
|
|
32
|
+
return customSort(rowIndex[a], rowIndex[b], sortBy, config)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (isVertical) {
|
|
38
|
+
if (groupBy) {
|
|
39
|
+
const cellMatrix: GroupCellMatrix = {}
|
|
40
|
+
rows.forEach(row => {
|
|
41
|
+
let groupKey: string
|
|
42
|
+
let groupValues = []
|
|
43
|
+
dataSeriesColumns.forEach((column, j) => {
|
|
44
|
+
if (groupBy === column) {
|
|
45
|
+
groupKey = getChartCellValue(row, column, config, runtimeData)
|
|
46
|
+
} else {
|
|
47
|
+
groupValues.push(getChartCellValue(row, column, config, runtimeData))
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
if (!cellMatrix[groupKey]) {
|
|
51
|
+
cellMatrix[groupKey] = [groupValues]
|
|
52
|
+
} else {
|
|
53
|
+
cellMatrix[groupKey].push(groupValues)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
return cellMatrix
|
|
57
|
+
} else {
|
|
58
|
+
return rows.map(row => {
|
|
59
|
+
if (hasRowType) {
|
|
60
|
+
let rowType
|
|
61
|
+
let rowValues = []
|
|
62
|
+
dataSeriesColumns.forEach((column, j) => {
|
|
63
|
+
if (column.match(/row[_-]?type/i)) {
|
|
64
|
+
rowType = getChartCellValue(row, column, config, runtimeData)
|
|
65
|
+
} else {
|
|
66
|
+
rowValues.push(getChartCellValue(row, column, config, runtimeData))
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
return [rowType, ...rowValues]
|
|
70
|
+
} else {
|
|
71
|
+
return dataSeriesColumns.map((column, j) => getChartCellValue(row, column, config, runtimeData))
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
return dataSeriesColumnsSorted().map(column => {
|
|
77
|
+
const seriesName = getSeriesName(column, config)
|
|
78
|
+
let nodes: ReactNode[] =
|
|
79
|
+
config.visualizationType !== 'Pie'
|
|
80
|
+
? [
|
|
81
|
+
<>
|
|
82
|
+
{colorScale && colorScale(seriesName) && <LegendCircle fill={colorScale(seriesName)} />}
|
|
83
|
+
{seriesName}
|
|
84
|
+
</>
|
|
85
|
+
]
|
|
86
|
+
: []
|
|
87
|
+
return nodes.concat(rows.map((row, i) => getChartCellValue(row, column, config, runtimeData)))
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export default chartCellArray
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// removes null and excluded columns
|
|
2
|
+
const customColumns = (runtimeData: Object[] | Record<string, Object>, excludeColumns: string[] = []): Object[] | Record<string, Object> => {
|
|
3
|
+
if (!Array.isArray(runtimeData)) {
|
|
4
|
+
// currently we don't support Record types
|
|
5
|
+
return runtimeData
|
|
6
|
+
} else {
|
|
7
|
+
const runtimeDataMemo = {}
|
|
8
|
+
runtimeData.forEach(row => {
|
|
9
|
+
Object.keys(row).forEach(key => {
|
|
10
|
+
if (runtimeDataMemo[key] === undefined) runtimeDataMemo[key] = null
|
|
11
|
+
if (row[key] !== null && !excludeColumns.includes(key)) runtimeDataMemo[key] = true
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
return runtimeData.map(d => {
|
|
15
|
+
const row = {}
|
|
16
|
+
Object.keys(d).forEach(key => {
|
|
17
|
+
if (key.match(/row[_-]?type/i)) row['row_type'] = d[key]
|
|
18
|
+
if (runtimeDataMemo[key] === true) row[key] = d[key]
|
|
19
|
+
})
|
|
20
|
+
return row
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default customColumns
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { parseDate } from '@cdc/core/helpers/cove/date'
|
|
2
|
+
import { standardizeStateName } from './standardizeState'
|
|
3
|
+
|
|
4
|
+
export const customSort = (a, b, sortBy, config) => {
|
|
5
|
+
let valueA = a
|
|
6
|
+
let valueB = b
|
|
7
|
+
|
|
8
|
+
if (config.type === 'map') {
|
|
9
|
+
valueA = standardizeStateName(a)
|
|
10
|
+
valueB = standardizeStateName(b)
|
|
11
|
+
}
|
|
12
|
+
// Treat booleans and nulls as an empty string
|
|
13
|
+
valueA = valueA === false || valueA === true || valueA === null ? '' : valueA
|
|
14
|
+
valueB = valueB === false || valueB === true || valueB === null ? '' : valueB
|
|
15
|
+
|
|
16
|
+
const trimmedA = String(valueA).trim()
|
|
17
|
+
const trimmedB = String(valueB).trim()
|
|
18
|
+
|
|
19
|
+
if (config.xAxis?.dataKey === sortBy.column && config.xAxis.type === 'date') {
|
|
20
|
+
let dateA = parseDate(config.xAxis.dateParseFormat, trimmedA)
|
|
21
|
+
|
|
22
|
+
let dateB = parseDate(config.xAxis.dateParseFormat, trimmedB)
|
|
23
|
+
|
|
24
|
+
if (dateA && dateA.getTime) dateA = dateA.getTime()
|
|
25
|
+
|
|
26
|
+
if (dateB && dateB.getTime) dateB = dateB.getTime()
|
|
27
|
+
|
|
28
|
+
return !sortBy.asc ? dateA - dateB : dateB - dateA
|
|
29
|
+
}
|
|
30
|
+
// Check if values are numbers
|
|
31
|
+
const isNumA = !isNaN(Number(valueA)) && valueA !== undefined && valueA !== null && trimmedA !== ''
|
|
32
|
+
const isNumB = !isNaN(Number(valueB)) && valueB !== undefined && valueB !== null && trimmedB !== ''
|
|
33
|
+
|
|
34
|
+
// Handle empty strings or spaces
|
|
35
|
+
if (trimmedA === '' && trimmedB !== '') return !sortBy.asc ? -1 : 1
|
|
36
|
+
if (trimmedA !== '' && trimmedB === '') return !sortBy.asc ? 1 : -1
|
|
37
|
+
|
|
38
|
+
// Both are numbers: Compare numerically
|
|
39
|
+
if (isNumA && isNumB) {
|
|
40
|
+
return !sortBy.asc ? Number(valueA) - Number(valueB) : Number(valueB) - Number(valueA)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Only A is a number
|
|
44
|
+
if (isNumA) {
|
|
45
|
+
return !sortBy.asc ? -1 : 1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Only B is a number
|
|
49
|
+
if (isNumB) {
|
|
50
|
+
return !sortBy.asc ? 1 : -1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Neither are numbers: Compare as strings
|
|
54
|
+
return !sortBy.asc ? trimmedA.localeCompare(trimmedB) : trimmedB.localeCompare(trimmedA)
|
|
55
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { parseDate, formatDate } from '@cdc/core/helpers/cove/date'
|
|
2
|
+
import { formatNumber } from '@cdc/core/helpers/cove/number'
|
|
3
|
+
|
|
4
|
+
// if its additional column, return formatting params
|
|
5
|
+
const isAdditionalColumn = (column, config) => {
|
|
6
|
+
let inthere = false
|
|
7
|
+
let formattingParams = {}
|
|
8
|
+
const { columns } = config
|
|
9
|
+
if (columns) {
|
|
10
|
+
Object.keys(columns).forEach(keycol => {
|
|
11
|
+
const col = columns[keycol]
|
|
12
|
+
if (col.name === column) {
|
|
13
|
+
inthere = true
|
|
14
|
+
formattingParams = {
|
|
15
|
+
addColPrefix: col.prefix,
|
|
16
|
+
addColSuffix: col.suffix,
|
|
17
|
+
addColRoundTo: col.roundToPlace ? col.roundToPlace : '',
|
|
18
|
+
addColCommas: col.commas
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
return formattingParams
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const getChartCellValue = (row, column, config, runtimeData) => {
|
|
27
|
+
if (config.table.customTableConfig) return runtimeData[row][column]
|
|
28
|
+
const rowObj = runtimeData[row]
|
|
29
|
+
let cellValue // placeholder for formatting below
|
|
30
|
+
let labelValue = rowObj[column] // just raw X axis string
|
|
31
|
+
if (column === config.xAxis?.dataKey) {
|
|
32
|
+
// not the prettiest, but helper functions work nicely here.
|
|
33
|
+
cellValue = config.xAxis?.type === 'date' ? formatDate(config.xAxis?.dateDisplayFormat, parseDate(config.xAxis?.dateParseFormat, labelValue)) : labelValue
|
|
34
|
+
} else {
|
|
35
|
+
let resolvedAxis = 'left'
|
|
36
|
+
let leftAxisItems = config.series ? config.series.filter(item => item?.axis === 'Left') : []
|
|
37
|
+
let rightAxisItems = config.series ? config.series.filter(item => item?.axis === 'Right') : []
|
|
38
|
+
|
|
39
|
+
leftAxisItems.map(leftSeriesItem => {
|
|
40
|
+
if (leftSeriesItem.dataKey === column) resolvedAxis = 'left'
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
rightAxisItems.map(rightSeriesItem => {
|
|
44
|
+
if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
let addColParams = isAdditionalColumn(column, config)
|
|
48
|
+
if (addColParams) {
|
|
49
|
+
cellValue = config.dataFormat ? formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams) : runtimeData[row][column]
|
|
50
|
+
} else {
|
|
51
|
+
cellValue = config.dataFormat ? formatNumber(runtimeData[row][column], resolvedAxis, false, config) : runtimeData[row][column]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return cellValue
|
|
56
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const getDataSeriesColumns = (config, isVertical, runtimeData): string[] => {
|
|
2
|
+
if (config.table.customTableConfig) return runtimeData[0] ? Object.keys(runtimeData[0]) : []
|
|
3
|
+
let tmpSeriesColumns
|
|
4
|
+
if (config.visualizationType !== 'Pie') {
|
|
5
|
+
tmpSeriesColumns = isVertical ? [config.xAxis?.dataKey] : [] //, ...config.runtime.seriesLabelsAll
|
|
6
|
+
if (config.series) {
|
|
7
|
+
config.series.forEach(element => {
|
|
8
|
+
tmpSeriesColumns.push(element.dataKey)
|
|
9
|
+
})
|
|
10
|
+
} else if (runtimeData && runtimeData.length > 0) {
|
|
11
|
+
tmpSeriesColumns = Object.keys(runtimeData[0])
|
|
12
|
+
}
|
|
13
|
+
} else {
|
|
14
|
+
tmpSeriesColumns = [config.xAxis?.dataKey, config.yAxis?.dataKey] //Object.keys(runtimeData[0])
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// then add the additional Columns
|
|
18
|
+
if (config.columns && Object.keys(config.columns).length > 0) {
|
|
19
|
+
Object.keys(config.columns).forEach(function (key) {
|
|
20
|
+
var value = config.columns[key]
|
|
21
|
+
// add if not the index AND it is enabled to be added to data table
|
|
22
|
+
if (value.name !== config.xAxis?.dataKey && value.dataTable === true) {
|
|
23
|
+
tmpSeriesColumns.push(value.name)
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return tmpSeriesColumns
|
|
29
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const getLabel = (name, config) => {
|
|
2
|
+
let custLabel = ''
|
|
3
|
+
if (config.columns && Object.keys(config.columns).length > 0) {
|
|
4
|
+
Object.keys(config.columns).forEach(function (key) {
|
|
5
|
+
var tmpColumn = config.columns[key]
|
|
6
|
+
// add if not the index AND it is enabled to be added to data table
|
|
7
|
+
if (tmpColumn.name === name) {
|
|
8
|
+
custLabel = tmpColumn.label
|
|
9
|
+
}
|
|
10
|
+
})
|
|
11
|
+
return custLabel
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const getSeriesName = (column, config) => {
|
|
16
|
+
// If a user sets the name on a series use that.
|
|
17
|
+
let userUpdatedSeriesName = config.series ? config.series.filter(series => series.dataKey === column)?.[0]?.name : ''
|
|
18
|
+
if (userUpdatedSeriesName) return userUpdatedSeriesName
|
|
19
|
+
|
|
20
|
+
if (config.runtimeSeriesLabels && config.runtimeSeriesLabels[column]) return config.runtimeSeriesLabels[column]
|
|
21
|
+
|
|
22
|
+
let custLabel = getLabel(column, config) ? getLabel(column, config) : column
|
|
23
|
+
let text = column === config.xAxis?.dataKey ? config.table.indexLabel : custLabel
|
|
24
|
+
|
|
25
|
+
return text
|
|
26
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
2
|
+
import CellAnchor from '../components/CellAnchor'
|
|
3
|
+
import { DataTableProps } from '../DataTable'
|
|
4
|
+
import { ReactNode } from 'react'
|
|
5
|
+
|
|
6
|
+
type MapRowsProps = DataTableProps & {
|
|
7
|
+
rows: string[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const mapCellArray = ({ rows, columns, runtimeData, config, applyLegendToRow, displayGeoName, formatLegendLocation, displayDataAsText, navigationHandler, setFilteredCountryCode }: MapRowsProps): ReactNode[][] => {
|
|
11
|
+
return rows.map(row =>
|
|
12
|
+
Object.keys(columns)
|
|
13
|
+
.filter(column => columns[column].dataTable === true && columns[column].name)
|
|
14
|
+
.map(column => {
|
|
15
|
+
let cellValue
|
|
16
|
+
|
|
17
|
+
if (column === 'geo') {
|
|
18
|
+
const rowObj = runtimeData[row]
|
|
19
|
+
const legendColor = applyLegendToRow(rowObj)
|
|
20
|
+
|
|
21
|
+
let labelValue
|
|
22
|
+
const mapZoomHandler = config.general.type === 'bubble' && config.general.allowMapZoom && config.general.geoType === 'world' ? () => setFilteredCountryCode(row) : undefined
|
|
23
|
+
if ((config.general.geoType !== 'single-state' && config.general.geoType !== 'us-county') || config.general.type === 'us-geocode') {
|
|
24
|
+
labelValue = displayGeoName(row)
|
|
25
|
+
} else {
|
|
26
|
+
labelValue = formatLegendLocation(row)
|
|
27
|
+
}
|
|
28
|
+
cellValue = (
|
|
29
|
+
<div className='col-12'>
|
|
30
|
+
<LegendCircle fill={legendColor[0]} />
|
|
31
|
+
<CellAnchor markup={labelValue} row={rowObj} columns={columns} navigationHandler={navigationHandler} mapZoomHandler={mapZoomHandler} />
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
} else {
|
|
35
|
+
// check for special classes
|
|
36
|
+
let specialValFound = ''
|
|
37
|
+
let columnName = config.columns[column].name
|
|
38
|
+
const { specialClasses } = config.legend
|
|
39
|
+
if (specialClasses && specialClasses.length && typeof specialClasses[0] === 'object') {
|
|
40
|
+
specialClasses.forEach(specialClass => {
|
|
41
|
+
if (specialClass.key === columnName) {
|
|
42
|
+
if (String(runtimeData[row][specialClass.key]) === specialClass.value) {
|
|
43
|
+
specialValFound = specialClass.label
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
cellValue = displayDataAsText(specialValFound || runtimeData[row][columnName], column)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return cellValue
|
|
52
|
+
})
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default mapCellArray
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
const regionCellMatrix = ({ config }): ReactNode[][] => {
|
|
4
|
+
return config.regions.map(region => {
|
|
5
|
+
if (config.visualizationType === 'Box Plot') return []
|
|
6
|
+
if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return []
|
|
7
|
+
// region.from and region.to had formatDate(parseDate()) on it
|
|
8
|
+
// but they returned undefined - removed both for now (TT)
|
|
9
|
+
return [region.label, region.from, region.to]
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default regionCellMatrix
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const states = {
|
|
2
|
+
AL: 'Alabama',
|
|
3
|
+
AK: 'Alaska',
|
|
4
|
+
AS: 'American Samoa',
|
|
5
|
+
AZ: 'Arizona',
|
|
6
|
+
AR: 'Arkansas',
|
|
7
|
+
CA: 'California',
|
|
8
|
+
CO: 'Colorado',
|
|
9
|
+
CT: 'Connecticut',
|
|
10
|
+
DE: 'Delaware',
|
|
11
|
+
DC: 'District Of Columbia',
|
|
12
|
+
FM: 'Federated States Of Micronesia',
|
|
13
|
+
FL: 'Florida',
|
|
14
|
+
GA: 'Georgia',
|
|
15
|
+
GU: 'Guam',
|
|
16
|
+
HI: 'Hawaii',
|
|
17
|
+
ID: 'Idaho',
|
|
18
|
+
IL: 'Illinois',
|
|
19
|
+
IN: 'Indiana',
|
|
20
|
+
IA: 'Iowa',
|
|
21
|
+
KS: 'Kansas',
|
|
22
|
+
KY: 'Kentucky',
|
|
23
|
+
LA: 'Louisiana',
|
|
24
|
+
ME: 'Maine',
|
|
25
|
+
MH: 'Marshall Islands',
|
|
26
|
+
MD: 'Maryland',
|
|
27
|
+
MA: 'Massachusetts',
|
|
28
|
+
MI: 'Michigan',
|
|
29
|
+
MN: 'Minnesota',
|
|
30
|
+
MS: 'Mississippi',
|
|
31
|
+
MO: 'Missouri',
|
|
32
|
+
MT: 'Montana',
|
|
33
|
+
NE: 'Nebraska',
|
|
34
|
+
NV: 'Nevada',
|
|
35
|
+
NH: 'New Hampshire',
|
|
36
|
+
NJ: 'New Jersey',
|
|
37
|
+
NM: 'New Mexico',
|
|
38
|
+
NY: 'New York',
|
|
39
|
+
NC: 'North Carolina',
|
|
40
|
+
ND: 'North Dakota',
|
|
41
|
+
MP: 'Northern Mariana Islands',
|
|
42
|
+
OH: 'Ohio',
|
|
43
|
+
OK: 'Oklahoma',
|
|
44
|
+
OR: 'Oregon',
|
|
45
|
+
PW: 'Palau',
|
|
46
|
+
PA: 'Pennsylvania',
|
|
47
|
+
PR: 'Puerto Rico',
|
|
48
|
+
RI: 'Rhode Island',
|
|
49
|
+
SC: 'South Carolina',
|
|
50
|
+
SD: 'South Dakota',
|
|
51
|
+
TN: 'Tennessee',
|
|
52
|
+
TX: 'Texas',
|
|
53
|
+
UT: 'Utah',
|
|
54
|
+
VT: 'Vermont',
|
|
55
|
+
VI: 'Virgin Islands',
|
|
56
|
+
VA: 'Virginia',
|
|
57
|
+
WA: 'Washington',
|
|
58
|
+
WV: 'West Virginia',
|
|
59
|
+
WI: 'Wisconsin',
|
|
60
|
+
WY: 'Wyoming'
|
|
61
|
+
}
|
|
62
|
+
export const standardizeStateName = value => {
|
|
63
|
+
if (typeof value !== 'string' || !isNaN(Number(value))) {
|
|
64
|
+
return value
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const upperValue = value.toUpperCase()
|
|
68
|
+
|
|
69
|
+
// Handle U.S. Virgin Islands variations
|
|
70
|
+
if (['U.S. VIRGIN ISLANDS', 'VI', 'US VIRGIN ISLANDS', 'VIRGIN ISLANDS'].includes(upperValue)) {
|
|
71
|
+
return 'U.S. VIRGIN ISLANDS'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Return full name for state abbreviation or the original name
|
|
75
|
+
return states[upperValue] || value
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './DataTable'
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Axis } from '@cdc/core/types/Axis'
|
|
2
|
+
import { Series } from '@cdc/core/types/Series'
|
|
3
|
+
import { Runtime } from '@cdc/core/types/Runtime'
|
|
4
|
+
import { Table } from '@cdc/core/types/Table'
|
|
5
|
+
|
|
6
|
+
export type TableConfig = {
|
|
7
|
+
type?: string
|
|
8
|
+
table: Table
|
|
9
|
+
xAxis?: Axis
|
|
10
|
+
yAxis?: Axis
|
|
11
|
+
boxplot?: {
|
|
12
|
+
tableData: Object[]
|
|
13
|
+
labels: {
|
|
14
|
+
mean: string
|
|
15
|
+
maximum: string
|
|
16
|
+
minimum: string
|
|
17
|
+
iqr: string
|
|
18
|
+
median: string
|
|
19
|
+
q1: string
|
|
20
|
+
q3: string
|
|
21
|
+
outliers: string
|
|
22
|
+
values: string
|
|
23
|
+
total: string
|
|
24
|
+
lowerBounds: string
|
|
25
|
+
upperBounds: string
|
|
26
|
+
}
|
|
27
|
+
plots: []
|
|
28
|
+
categories: string[]
|
|
29
|
+
}
|
|
30
|
+
visualizationType?: string
|
|
31
|
+
general?: {
|
|
32
|
+
geoType: string
|
|
33
|
+
type: string
|
|
34
|
+
showDownloadButton: boolean
|
|
35
|
+
allowMapZoom?: boolean
|
|
36
|
+
}
|
|
37
|
+
columns?: {
|
|
38
|
+
geo: {
|
|
39
|
+
name: string
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
legend?: {
|
|
43
|
+
specialClasses?: { key: string; label: string; value: string }[]
|
|
44
|
+
hide?: boolean
|
|
45
|
+
}
|
|
46
|
+
series?: Series
|
|
47
|
+
regions?: { label: string; from: string; to: string; fromType: 'Fixed' | 'Previous Days'; toType: 'Fixed' | 'Last Date' }[]
|
|
48
|
+
runtimeSeriesLabels?: Object
|
|
49
|
+
dataFormat?: Object
|
|
50
|
+
runtime: Runtime
|
|
51
|
+
data: Object[]
|
|
52
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Papa from 'papaparse'
|
|
2
|
+
|
|
3
|
+
type DownloadButtonProps = {
|
|
4
|
+
rawData: Object
|
|
5
|
+
fileName: string
|
|
6
|
+
headerColor: string
|
|
7
|
+
skipId: string | number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const DownloadButton = ({ rawData, fileName, headerColor, skipId }: DownloadButtonProps) => {
|
|
11
|
+
const csvData = Papa.unparse(rawData)
|
|
12
|
+
const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' })
|
|
13
|
+
|
|
14
|
+
const saveBlob = () => {
|
|
15
|
+
//@ts-ignore
|
|
16
|
+
if (typeof window.navigator.msSaveBlob === 'function') {
|
|
17
|
+
//@ts-ignore
|
|
18
|
+
navigator.msSaveBlob(blob, fileName)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<a download={fileName} type='button' onClick={saveBlob} href={URL.createObjectURL(blob)} aria-label='Download this data in a CSV file format.' className={`${headerColor} no-border`} id={`${skipId}`} data-html2canvas-ignore role='button'>
|
|
24
|
+
Download Data (CSV)
|
|
25
|
+
</a>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default DownloadButton
|