@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.
Files changed (54) hide show
  1. package/LICENSE +201 -0
  2. package/assets/icon-deviation-bar.svg +1 -0
  3. package/components/DataTable/DataTable.tsx +205 -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 +91 -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 +78 -0
  13. package/components/DataTable/helpers/customSort.ts +55 -0
  14. package/components/DataTable/helpers/getChartCellValue.ts +55 -0
  15. package/components/DataTable/helpers/getDataSeriesColumns.ts +28 -0
  16. package/components/DataTable/helpers/getSeriesName.ts +26 -0
  17. package/components/DataTable/helpers/mapCellMatrix.tsx +56 -0
  18. package/components/DataTable/helpers/regionCellMatrix.tsx +13 -0
  19. package/components/DataTable/helpers/standardizeState.js +76 -0
  20. package/components/DataTable/index.ts +1 -0
  21. package/components/DataTable/types/TableConfig.ts +57 -0
  22. package/components/DownloadButton.tsx +29 -0
  23. package/components/LegendCircle.jsx +2 -2
  24. package/components/Table/Table.tsx +49 -0
  25. package/components/Table/components/Cell.tsx +9 -0
  26. package/components/Table/components/GroupRow.tsx +16 -0
  27. package/components/Table/components/Row.tsx +19 -0
  28. package/components/Table/index.ts +1 -0
  29. package/components/Table/types/CellMatrix.ts +4 -0
  30. package/components/_stories/DataTable.stories.tsx +62 -0
  31. package/components/_stories/Table.stories.tsx +53 -0
  32. package/components/_stories/_mocks/dashboard_no_filter.json +121 -0
  33. package/components/_stories/_mocks/example-city-state.json +808 -0
  34. package/components/_stories/styles.scss +9 -0
  35. package/components/managers/{DataDesigner.jsx → DataDesigner.tsx} +96 -87
  36. package/components/ui/Title/Title.scss +95 -0
  37. package/components/ui/Title/index.tsx +34 -0
  38. package/components/ui/_stories/Title.stories.tsx +21 -0
  39. package/helpers/DataTransform.ts +45 -18
  40. package/helpers/cove/string.ts +11 -0
  41. package/helpers/fetchRemoteData.js +1 -1
  42. package/package.json +2 -2
  43. package/styles/_data-table.scss +1 -0
  44. package/styles/heading-colors.scss +0 -3
  45. package/styles/v2/layout/_component.scss +0 -11
  46. package/types/Axis.ts +6 -0
  47. package/types/Color.ts +5 -0
  48. package/types/ComponentStyles.ts +7 -0
  49. package/types/ComponentThemes.ts +13 -0
  50. package/types/EditorColumnProperties.ts +8 -0
  51. package/types/Runtime.ts +9 -0
  52. package/types/Series.ts +1 -0
  53. package/types/Visualization.ts +21 -0
  54. 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: 'inline-block',
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,9 @@
1
+ const Cell = ({ children }) => {
2
+ return (
3
+ <td tabIndex={0} role='gridcell'>
4
+ {children}
5
+ </td>
6
+ )
7
+ }
8
+
9
+ export default Cell
@@ -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'
@@ -0,0 +1,4 @@
1
+ import { ReactNode } from 'react'
2
+
3
+ export type CellMatrix = ReactNode[][]
4
+ export type GroupCellMatrix = Record<string, CellMatrix>