@cdc/core 4.23.10 → 4.23.11
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 +205 -0
- package/components/DataTable/components/BoxplotHeader.tsx +16 -0
- package/components/DataTable/components/CellAnchor.tsx +44 -0
- package/components/DataTable/components/ChartHeader.tsx +91 -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 +78 -0
- package/components/DataTable/helpers/customSort.ts +55 -0
- package/components/DataTable/helpers/getChartCellValue.ts +55 -0
- package/components/DataTable/helpers/getDataSeriesColumns.ts +28 -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 +57 -0
- package/components/DownloadButton.tsx +29 -0
- package/components/LegendCircle.jsx +2 -2
- package/components/Table/Table.tsx +49 -0
- package/components/Table/components/Cell.tsx +9 -0
- package/components/Table/components/GroupRow.tsx +16 -0
- package/components/Table/components/Row.tsx +19 -0
- package/components/Table/index.ts +1 -0
- package/components/Table/types/CellMatrix.ts +4 -0
- package/components/_stories/DataTable.stories.tsx +62 -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/styles.scss +9 -0
- package/components/managers/{DataDesigner.jsx → DataDesigner.tsx} +96 -87
- 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 +45 -18
- package/helpers/cove/string.ts +11 -0
- package/helpers/fetchRemoteData.js +1 -1
- package/package.json +2 -2
- package/styles/_data-table.scss +1 -0
- package/styles/heading-colors.scss +0 -3
- package/styles/v2/layout/_component.scss +0 -11
- package/types/Axis.ts +6 -0
- package/types/Color.ts +5 -0
- package/types/ComponentStyles.ts +7 -0
- package/types/ComponentThemes.ts +13 -0
- package/types/EditorColumnProperties.ts +8 -0
- package/types/Runtime.ts +9 -0
- package/types/Series.ts +1 -0
- package/types/Visualization.ts +21 -0
- package/components/DataTable.jsx +0 -754
|
@@ -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,78 @@
|
|
|
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
|
+
}
|
|
16
|
+
|
|
17
|
+
const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorScale, groupBy }: ChartRowsProps): CellMatrix | GroupCellMatrix => {
|
|
18
|
+
const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
|
|
19
|
+
|
|
20
|
+
const dataSeriesColumnsSorted = () => {
|
|
21
|
+
if (!sortBy && sortBy.colIndex === null) return dataSeriesColumns
|
|
22
|
+
return dataSeriesColumns.sort((a, b) => {
|
|
23
|
+
if (sortBy.column === '__series__') return customSort(a, b, sortBy, config)
|
|
24
|
+
let row = runtimeData.find(d => d[config.xAxis?.dataKey] === sortBy.column)
|
|
25
|
+
|
|
26
|
+
const rowIndex = runtimeData[sortBy.colIndex - 1]
|
|
27
|
+
if (row) {
|
|
28
|
+
return customSort(row[a], row[b], sortBy, config)
|
|
29
|
+
}
|
|
30
|
+
if (row === undefined && rowIndex) {
|
|
31
|
+
return customSort(rowIndex[a], rowIndex[b], sortBy, config)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (isVertical) {
|
|
37
|
+
if (groupBy) {
|
|
38
|
+
const cellMatrix: GroupCellMatrix = {}
|
|
39
|
+
rows.forEach(row => {
|
|
40
|
+
let groupKey: string
|
|
41
|
+
let groupValues = []
|
|
42
|
+
dataSeriesColumns.forEach((column, j) => {
|
|
43
|
+
if (groupBy === column) {
|
|
44
|
+
groupKey = getChartCellValue(row, column, config, runtimeData)
|
|
45
|
+
} else {
|
|
46
|
+
groupValues.push(getChartCellValue(row, column, config, runtimeData))
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
if (!cellMatrix[groupKey]) {
|
|
50
|
+
cellMatrix[groupKey] = [groupValues]
|
|
51
|
+
} else {
|
|
52
|
+
cellMatrix[groupKey].push(groupValues)
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
return cellMatrix
|
|
56
|
+
} else {
|
|
57
|
+
return rows.map(row => {
|
|
58
|
+
return dataSeriesColumns.map((column, j) => getChartCellValue(row, column, config, runtimeData))
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
return dataSeriesColumnsSorted().map(column => {
|
|
63
|
+
const seriesName = getSeriesName(column, config)
|
|
64
|
+
let nodes: ReactNode[] =
|
|
65
|
+
config.visualizationType !== 'Pie'
|
|
66
|
+
? [
|
|
67
|
+
<>
|
|
68
|
+
{colorScale && colorScale(seriesName) && <LegendCircle fill={colorScale(seriesName)} />}
|
|
69
|
+
{seriesName}
|
|
70
|
+
</>
|
|
71
|
+
]
|
|
72
|
+
: []
|
|
73
|
+
return nodes.concat(rows.map((row, i) => getChartCellValue(row, column, config, runtimeData)))
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default chartCellArray
|
|
@@ -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,55 @@
|
|
|
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
|
+
const rowObj = runtimeData[row]
|
|
28
|
+
let cellValue // placeholder for formatting below
|
|
29
|
+
let labelValue = rowObj[column] // just raw X axis string
|
|
30
|
+
if (column === config.xAxis?.dataKey) {
|
|
31
|
+
// not the prettiest, but helper functions work nicely here.
|
|
32
|
+
cellValue = config.xAxis?.type === 'date' ? formatDate(config.xAxis?.dateDisplayFormat, parseDate(config.xAxis?.dateParseFormat, labelValue)) : labelValue
|
|
33
|
+
} else {
|
|
34
|
+
let resolvedAxis = 'left'
|
|
35
|
+
let leftAxisItems = config.series ? config.series.filter(item => item?.axis === 'Left') : []
|
|
36
|
+
let rightAxisItems = config.series ? config.series.filter(item => item?.axis === 'Right') : []
|
|
37
|
+
|
|
38
|
+
leftAxisItems.map(leftSeriesItem => {
|
|
39
|
+
if (leftSeriesItem.dataKey === column) resolvedAxis = 'left'
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
rightAxisItems.map(rightSeriesItem => {
|
|
43
|
+
if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
let addColParams = isAdditionalColumn(column, config)
|
|
47
|
+
if (addColParams) {
|
|
48
|
+
cellValue = config.dataFormat ? formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams) : runtimeData[row][column]
|
|
49
|
+
} else {
|
|
50
|
+
cellValue = config.dataFormat ? formatNumber(runtimeData[row][column], resolvedAxis, false, config) : runtimeData[row][column]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return cellValue
|
|
55
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const getDataSeriesColumns = (config, isVertical, runtimeData): string[] => {
|
|
2
|
+
let tmpSeriesColumns
|
|
3
|
+
if (config.visualizationType !== 'Pie') {
|
|
4
|
+
tmpSeriesColumns = isVertical ? [config.xAxis?.dataKey] : [] //, ...config.runtime.seriesLabelsAll
|
|
5
|
+
if (config.series) {
|
|
6
|
+
config.series.forEach(element => {
|
|
7
|
+
tmpSeriesColumns.push(element.dataKey)
|
|
8
|
+
})
|
|
9
|
+
} else if (runtimeData && runtimeData.length > 0) {
|
|
10
|
+
tmpSeriesColumns = Object.keys(runtimeData[0])
|
|
11
|
+
}
|
|
12
|
+
} else {
|
|
13
|
+
tmpSeriesColumns = [config.xAxis?.dataKey, config.yAxis?.dataKey] //Object.keys(runtimeData[0])
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// then add the additional Columns
|
|
17
|
+
if (config.columns && Object.keys(config.columns).length > 0) {
|
|
18
|
+
Object.keys(config.columns).forEach(function (key) {
|
|
19
|
+
var value = config.columns[key]
|
|
20
|
+
// add if not the index AND it is enabled to be added to data table
|
|
21
|
+
if (value.name !== config.xAxis?.dataKey && value.dataTable === true) {
|
|
22
|
+
tmpSeriesColumns.push(value.name)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return tmpSeriesColumns
|
|
28
|
+
}
|
|
@@ -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,57 @@
|
|
|
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
|
+
|
|
5
|
+
export type TableConfig = {
|
|
6
|
+
type?: string
|
|
7
|
+
table: {
|
|
8
|
+
showVertical?: boolean
|
|
9
|
+
indexLabel: string
|
|
10
|
+
limitHeight: boolean
|
|
11
|
+
height: string | number
|
|
12
|
+
caption: string
|
|
13
|
+
download: boolean
|
|
14
|
+
}
|
|
15
|
+
xAxis?: Axis
|
|
16
|
+
yAxis?: Axis
|
|
17
|
+
boxplot?: {
|
|
18
|
+
tableData: Object[]
|
|
19
|
+
labels: {
|
|
20
|
+
mean: string
|
|
21
|
+
maximum: string
|
|
22
|
+
minimum: string
|
|
23
|
+
iqr: string
|
|
24
|
+
median: string
|
|
25
|
+
q1: string
|
|
26
|
+
q3: string
|
|
27
|
+
outliers: string
|
|
28
|
+
values: string
|
|
29
|
+
total: string
|
|
30
|
+
lowerBounds: string
|
|
31
|
+
upperBounds: string
|
|
32
|
+
}
|
|
33
|
+
plots: []
|
|
34
|
+
categories: string[]
|
|
35
|
+
}
|
|
36
|
+
visualizationType?: string
|
|
37
|
+
general?: {
|
|
38
|
+
geoType: string
|
|
39
|
+
type: string
|
|
40
|
+
showDownloadButton: boolean
|
|
41
|
+
allowMapZoom?: boolean
|
|
42
|
+
}
|
|
43
|
+
columns?: {
|
|
44
|
+
geo: {
|
|
45
|
+
name: string
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
legend?: {
|
|
49
|
+
specialClasses: { key: string; label: string; value: string }[]
|
|
50
|
+
}
|
|
51
|
+
series?: Series
|
|
52
|
+
regions?: { label: string; from: string; to: string }[]
|
|
53
|
+
runtimeSeriesLabels?: Object
|
|
54
|
+
dataFormat?: Object
|
|
55
|
+
runtime: Runtime
|
|
56
|
+
data: Object[]
|
|
57
|
+
}
|
|
@@ -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
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
-
export default function LegendCircle({ fill, borderColor }) {
|
|
3
|
+
export default function LegendCircle({ fill, borderColor, display = 'inline-block' }) {
|
|
4
4
|
const styles = {
|
|
5
5
|
marginRight: '5px',
|
|
6
6
|
borderRadius: '300px',
|
|
7
7
|
verticalAlign: 'middle',
|
|
8
|
-
display:
|
|
8
|
+
display: display,
|
|
9
9
|
height: '1em',
|
|
10
10
|
width: '1em',
|
|
11
11
|
border: borderColor ? `${borderColor} 1px solid` : 'rgba(0,0,0,.3) 1px solid',
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
import Row from './components/Row'
|
|
3
|
+
import GroupRow from './components/GroupRow'
|
|
4
|
+
import { CellMatrix, GroupCellMatrix } from './types/CellMatrix'
|
|
5
|
+
|
|
6
|
+
type TableProps = {
|
|
7
|
+
childrenMatrix: CellMatrix | GroupCellMatrix
|
|
8
|
+
tableName: string
|
|
9
|
+
caption: string
|
|
10
|
+
stickyHeader?: boolean
|
|
11
|
+
headContent: ReactNode
|
|
12
|
+
tableOptions: {
|
|
13
|
+
className: string
|
|
14
|
+
'aria-live'?: 'off' | 'assertive' | 'polite'
|
|
15
|
+
hidden?: boolean
|
|
16
|
+
'aria-rowcount'?: number
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Position = 'sticky'
|
|
21
|
+
|
|
22
|
+
const Table = ({ childrenMatrix, tableName, caption, stickyHeader, headContent, tableOptions }: TableProps) => {
|
|
23
|
+
const headStyle = stickyHeader ? { position: 'sticky' as Position, top: 0, zIndex: 999 } : {}
|
|
24
|
+
const isGroupedMatrix = !Array.isArray(childrenMatrix)
|
|
25
|
+
return (
|
|
26
|
+
<table {...tableOptions}>
|
|
27
|
+
<caption className='visually-hidden'>{caption}</caption>
|
|
28
|
+
<thead style={headStyle}>{headContent}</thead>
|
|
29
|
+
<tbody>
|
|
30
|
+
{isGroupedMatrix
|
|
31
|
+
? Object.keys(childrenMatrix).flatMap(groupName => {
|
|
32
|
+
let colSpan = 0
|
|
33
|
+
const rows = childrenMatrix[groupName].map((row, i) => {
|
|
34
|
+
colSpan = row.length
|
|
35
|
+
const key = `${tableName}-${groupName}-row-${i}`
|
|
36
|
+
return <Row key={key} rowKey={key} childRow={row} />
|
|
37
|
+
})
|
|
38
|
+
return [<GroupRow label={groupName} colSpan={colSpan} key={`${tableName}-${groupName}`} />, ...rows]
|
|
39
|
+
})
|
|
40
|
+
: childrenMatrix.map((childRow, i) => {
|
|
41
|
+
const key = `${tableName}-row-${i}`
|
|
42
|
+
return <Row key={key} rowKey={key} childRow={childRow} />
|
|
43
|
+
})}
|
|
44
|
+
</tbody>
|
|
45
|
+
</table>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default Table
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type GroupRowProps = {
|
|
2
|
+
label: string
|
|
3
|
+
colSpan: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const GroupRow = ({ label, colSpan }: GroupRowProps) => {
|
|
7
|
+
return (
|
|
8
|
+
<tr>
|
|
9
|
+
<th scope='colgroup' colSpan={colSpan}>
|
|
10
|
+
{label}
|
|
11
|
+
</th>
|
|
12
|
+
</tr>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default GroupRow
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
import Cell from './Cell'
|
|
3
|
+
|
|
4
|
+
type RowProps = {
|
|
5
|
+
childRow: ReactNode[]
|
|
6
|
+
rowKey: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const Row = ({ childRow, rowKey }: RowProps) => {
|
|
10
|
+
return (
|
|
11
|
+
<tr>
|
|
12
|
+
{childRow.map((child, i) => (
|
|
13
|
+
<Cell key={rowKey + '__' + i}>{child}</Cell>
|
|
14
|
+
))}
|
|
15
|
+
</tr>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default Row
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Table'
|