@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.
Files changed (74) hide show
  1. package/LICENSE +201 -0
  2. package/assets/icon-deviation-bar.svg +1 -0
  3. package/components/DataTable/DataTable.tsx +223 -0
  4. package/components/DataTable/components/BoxplotHeader.tsx +16 -0
  5. package/components/DataTable/components/CellAnchor.tsx +44 -0
  6. package/components/DataTable/components/ChartHeader.tsx +103 -0
  7. package/components/DataTable/components/ExpandCollapse.tsx +21 -0
  8. package/components/DataTable/components/Icons.tsx +10 -0
  9. package/components/DataTable/components/MapHeader.tsx +56 -0
  10. package/components/DataTable/components/SkipNav.tsx +7 -0
  11. package/components/DataTable/helpers/boxplotCellMatrix.tsx +64 -0
  12. package/components/DataTable/helpers/chartCellMatrix.tsx +92 -0
  13. package/components/DataTable/helpers/customColumns.ts +25 -0
  14. package/components/DataTable/helpers/customSort.ts +55 -0
  15. package/components/DataTable/helpers/getChartCellValue.ts +56 -0
  16. package/components/DataTable/helpers/getDataSeriesColumns.ts +29 -0
  17. package/components/DataTable/helpers/getSeriesName.ts +26 -0
  18. package/components/DataTable/helpers/mapCellMatrix.tsx +56 -0
  19. package/components/DataTable/helpers/regionCellMatrix.tsx +13 -0
  20. package/components/DataTable/helpers/standardizeState.js +76 -0
  21. package/components/DataTable/index.ts +1 -0
  22. package/components/DataTable/types/TableConfig.ts +52 -0
  23. package/components/DownloadButton.tsx +29 -0
  24. package/components/EditorPanel/DataTableEditor.tsx +133 -0
  25. package/components/EditorPanel/Inputs.tsx +150 -0
  26. package/components/Filters.jsx +3 -3
  27. package/components/LegendCircle.jsx +2 -2
  28. package/components/MediaControls.jsx +1 -1
  29. package/components/MultiSelect/MultiSelect.tsx +95 -0
  30. package/components/MultiSelect/index.ts +1 -0
  31. package/components/MultiSelect/multiselect.styles.css +50 -0
  32. package/components/Table/Table.tsx +69 -0
  33. package/components/Table/components/Cell.tsx +9 -0
  34. package/components/Table/components/GroupRow.tsx +20 -0
  35. package/components/Table/components/Row.tsx +26 -0
  36. package/components/Table/index.ts +1 -0
  37. package/components/Table/types/CellMatrix.ts +4 -0
  38. package/components/Table/types/RowType.ts +5 -0
  39. package/components/_stories/DataTable.stories.tsx +103 -0
  40. package/components/_stories/EditorPanel.stories.tsx +53 -0
  41. package/components/_stories/Inputs.stories.tsx +37 -0
  42. package/components/_stories/MultiSelect.stories.tsx +24 -0
  43. package/components/_stories/Table.stories.tsx +53 -0
  44. package/components/_stories/_mocks/dashboard_no_filter.json +121 -0
  45. package/components/_stories/_mocks/example-city-state.json +808 -0
  46. package/components/_stories/_mocks/row_type.json +42 -0
  47. package/components/_stories/styles.scss +9 -0
  48. package/components/inputs/{InputSelect.jsx → InputSelect.tsx} +15 -5
  49. package/components/managers/{DataDesigner.jsx → DataDesigner.tsx} +103 -94
  50. package/components/ui/{Icon.jsx → Icon.tsx} +3 -3
  51. package/components/ui/Title/Title.scss +95 -0
  52. package/components/ui/Title/index.tsx +34 -0
  53. package/components/ui/_stories/Title.stories.tsx +21 -0
  54. package/helpers/DataTransform.ts +75 -20
  55. package/helpers/cove/string.ts +11 -0
  56. package/helpers/fetchRemoteData.js +1 -1
  57. package/helpers/getFileExtension.ts +28 -5
  58. package/package.json +2 -2
  59. package/styles/_data-table.scss +3 -0
  60. package/styles/heading-colors.scss +0 -3
  61. package/styles/v2/layout/_component.scss +0 -11
  62. package/types/Axis.ts +41 -0
  63. package/types/Color.ts +5 -0
  64. package/types/Column.ts +15 -0
  65. package/types/ComponentStyles.ts +7 -0
  66. package/types/ComponentThemes.ts +13 -0
  67. package/types/EditorColumnProperties.ts +8 -0
  68. package/types/FilterBehavior.ts +1 -0
  69. package/types/Runtime.ts +29 -0
  70. package/types/Series.ts +1 -0
  71. package/types/Table.ts +18 -0
  72. package/types/UpdateFieldFunc.ts +1 -0
  73. package/types/Visualization.ts +21 -0
  74. 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,7 @@
1
+ const SkipNav = skipId => (
2
+ <a id='skip-nav' className='cdcdataviz-sr-only-focusable' href={`#${skipId}`}>
3
+ Skip Navigation or Skip to Content
4
+ </a>
5
+ )
6
+
7
+ export default SkipNav
@@ -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