@cdc/core 4.23.11 → 4.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/DataTable/DataTable.tsx +39 -10
- package/components/DataTable/components/ChartHeader.tsx +17 -5
- package/components/DataTable/components/ExpandCollapse.tsx +1 -1
- package/components/DataTable/helpers/chartCellMatrix.tsx +16 -2
- package/components/DataTable/helpers/customColumns.ts +25 -0
- package/components/DataTable/helpers/customSort.ts +9 -0
- package/components/DataTable/helpers/getChartCellValue.ts +2 -1
- package/components/DataTable/helpers/getDataSeriesColumns.ts +2 -1
- package/components/DataTable/helpers/getSeriesName.ts +15 -20
- package/components/DataTable/helpers/mapCellMatrix.tsx +4 -0
- package/components/DataTable/types/TableConfig.ts +11 -40
- package/components/EditorPanel/DataTableEditor.tsx +133 -0
- package/components/EditorPanel/Inputs.tsx +150 -0
- package/components/Filters.jsx +17 -15
- 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 +23 -3
- package/components/Table/components/Cell.tsx +3 -3
- package/components/Table/components/GroupRow.tsx +6 -2
- package/components/Table/components/Row.tsx +9 -2
- package/components/Table/types/RowType.ts +5 -0
- package/components/_stories/DataTable.stories.tsx +41 -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/_mocks/row_type.json +42 -0
- package/components/inputs/{InputSelect.jsx → InputSelect.tsx} +15 -5
- package/components/managers/DataDesigner.tsx +8 -8
- package/components/ui/{Icon.jsx → Icon.tsx} +3 -3
- package/components/ui/_stories/Colors.stories.tsx +92 -0
- package/components/ui/_stories/Icon.stories.tsx +17 -10
- package/helpers/DataTransform.ts +30 -2
- package/helpers/fetchRemoteData.js +5 -5
- package/helpers/getFileExtension.ts +28 -5
- package/helpers/getViewport.ts +23 -0
- package/helpers/isSolr.js +13 -0
- package/helpers/withDevTools.ts +50 -0
- package/package.json +2 -2
- package/styles/_data-table.scss +2 -5
- package/styles/_global-variables.scss +75 -0
- package/styles/base.scss +89 -69
- package/types/Action.ts +1 -0
- package/types/Axis.ts +39 -2
- package/types/BoxPlot.ts +21 -0
- package/types/Column.ts +16 -0
- package/types/FilterBehavior.ts +1 -0
- package/types/General.ts +9 -0
- package/types/Runtime.ts +21 -1
- package/types/Series.ts +1 -1
- package/types/Table.ts +21 -0
- package/types/UpdateFieldFunc.ts +1 -0
- package/types/ViewPort.ts +2 -0
- package/types/Visualization.ts +15 -9
- package/types/WCMSProps.ts +11 -0
- package/helpers/getViewport.js +0 -21
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState, useMemo } from 'react'
|
|
2
|
+
import { timeParse } from 'd3-time-format'
|
|
2
3
|
|
|
3
4
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
4
5
|
import MediaControls from '@cdc/core/components/MediaControls'
|
|
@@ -15,12 +16,14 @@ import Table from '../Table'
|
|
|
15
16
|
import chartCellMatrix from './helpers/chartCellMatrix'
|
|
16
17
|
import regionCellMatrix from './helpers/regionCellMatrix'
|
|
17
18
|
import boxplotCellMatrix from './helpers/boxplotCellMatrix'
|
|
19
|
+
import customColumns from './helpers/customColumns'
|
|
18
20
|
import { TableConfig } from './types/TableConfig'
|
|
21
|
+
import { Column } from '../../types/Column'
|
|
19
22
|
|
|
20
23
|
export type DataTableProps = {
|
|
21
24
|
applyLegendToRow?: Function
|
|
22
25
|
colorScale?: Function
|
|
23
|
-
columns?:
|
|
26
|
+
columns?: Record<string, Column>
|
|
24
27
|
config: TableConfig
|
|
25
28
|
dataConfig?: Object
|
|
26
29
|
displayDataAsText?: Function
|
|
@@ -29,20 +32,27 @@ export type DataTableProps = {
|
|
|
29
32
|
formatLegendLocation?: Function
|
|
30
33
|
groupBy?: string
|
|
31
34
|
headerColor?: string
|
|
35
|
+
imageRef?: string
|
|
32
36
|
indexTitle?: string
|
|
37
|
+
isDebug?: boolean
|
|
38
|
+
isEditor?: boolean
|
|
33
39
|
navigationHandler?: Function
|
|
40
|
+
outerContainerRef?: Function
|
|
34
41
|
rawData: Object[]
|
|
35
42
|
runtimeData: Object[] | Record<string, Object> // UNSAFE
|
|
36
43
|
setFilteredCountryCode?: Function // used for Maps only
|
|
44
|
+
showDownloadButton?: boolean
|
|
37
45
|
tabbingId: string
|
|
38
46
|
tableTitle: string
|
|
39
47
|
viewport: string
|
|
40
48
|
vizTitle?: string
|
|
49
|
+
// determines if columns should be wrapped in the table
|
|
50
|
+
wrapColumns?: boolean
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
44
54
|
const DataTable = (props: DataTableProps) => {
|
|
45
|
-
const { config, dataConfig, tableTitle, vizTitle, rawData, runtimeData, headerColor, expandDataTable, columns, viewport, formatLegendLocation, tabbingId } = props
|
|
55
|
+
const { config, dataConfig, tableTitle, vizTitle, rawData, runtimeData, headerColor, expandDataTable, columns, viewport, formatLegendLocation, tabbingId, wrapColumns } = props
|
|
46
56
|
|
|
47
57
|
const [expanded, setExpanded] = useState(expandDataTable)
|
|
48
58
|
|
|
@@ -89,19 +99,25 @@ const DataTable = (props: DataTableProps) => {
|
|
|
89
99
|
break
|
|
90
100
|
}
|
|
91
101
|
|
|
92
|
-
const
|
|
102
|
+
const _runtimeData = config.table.customTableConfig ? customColumns(rawData, config.table.excludeColumns) : runtimeData
|
|
103
|
+
|
|
104
|
+
const rawRows = Object.keys(_runtimeData).filter(column => column != 'columns')
|
|
93
105
|
const rows = isVertical
|
|
94
106
|
? rawRows.sort((a, b) => {
|
|
95
107
|
let dataA
|
|
96
108
|
let dataB
|
|
97
109
|
if (config.type === 'map' && config.columns) {
|
|
98
110
|
const sortByColName = config.columns[sortBy.column].name
|
|
99
|
-
dataA =
|
|
100
|
-
dataB =
|
|
111
|
+
dataA = _runtimeData[a][sortByColName]
|
|
112
|
+
dataB = _runtimeData[b][sortByColName]
|
|
101
113
|
}
|
|
102
114
|
if (config.type === 'chart' || config.type === 'dashboard') {
|
|
103
|
-
dataA =
|
|
104
|
-
dataB =
|
|
115
|
+
dataA = _runtimeData[a][sortBy.column]
|
|
116
|
+
dataB = _runtimeData[b][sortBy.column]
|
|
117
|
+
}
|
|
118
|
+
if (!dataA && !dataB && config.type === 'chart' && config.xAxis && config.xAxis.type === 'date' && config.xAxis.sortDates) {
|
|
119
|
+
dataA = timeParse(config.runtime.xAxis.dateParseFormat)(_runtimeData[a][config.xAxis.dataKey])
|
|
120
|
+
dataB = timeParse(config.runtime.xAxis.dateParseFormat)(_runtimeData[b][config.xAxis.dataKey])
|
|
105
121
|
}
|
|
106
122
|
return dataA && dataB ? customSort(dataA, dataB, sortBy, config) : 0
|
|
107
123
|
})
|
|
@@ -112,6 +128,8 @@ const DataTable = (props: DataTableProps) => {
|
|
|
112
128
|
OverflowY: 'scroll'
|
|
113
129
|
}
|
|
114
130
|
|
|
131
|
+
const hasRowType = !!Object.keys(rawData?.[0] || {}).find((v: string) => v.match(/row[_-]?type/i))
|
|
132
|
+
|
|
115
133
|
const caption = useMemo(() => {
|
|
116
134
|
if (config.type === 'map') {
|
|
117
135
|
return config.table.caption ? config.table.caption : `Data table showing data for the ${mapLookup[config.general.geoType]} figure.`
|
|
@@ -120,6 +138,13 @@ const DataTable = (props: DataTableProps) => {
|
|
|
120
138
|
}
|
|
121
139
|
}, [config.table.caption])
|
|
122
140
|
|
|
141
|
+
// Determines if a relative region is being shown to the user.
|
|
142
|
+
// If a relative region is found we don't want to display the data table.
|
|
143
|
+
// Takes backwards compatibility into consideration, ie !region.toType || !region.fromType
|
|
144
|
+
const noRelativeRegions = config?.regions?.every(region => {
|
|
145
|
+
return (region.toType === 'Fixed' && region.fromType === 'Fixed') || (!region.toType && !region.fromType) || (!region.toType && region.fromType === 'Fixed') || (!region.fromType && region.toType === 'Fixed')
|
|
146
|
+
})
|
|
147
|
+
|
|
123
148
|
// prettier-ignore
|
|
124
149
|
const tableData = useMemo(() => (
|
|
125
150
|
config.visualizationType === 'Pie'
|
|
@@ -151,17 +176,20 @@ const DataTable = (props: DataTableProps) => {
|
|
|
151
176
|
<ExpandCollapse expanded={expanded} setExpanded={setExpanded} tableTitle={tableTitle} />
|
|
152
177
|
<div className='table-container' style={limitHeight}>
|
|
153
178
|
<Table
|
|
154
|
-
|
|
179
|
+
wrapColumns={wrapColumns}
|
|
180
|
+
childrenMatrix={config.type === 'map' ? mapCellMatrix({ rows, wrapColumns, ...props, runtimeData: _runtimeData }) : chartCellMatrix({ rows, ...props, runtimeData: _runtimeData, isVertical, sortBy, hasRowType })}
|
|
155
181
|
tableName={config.type}
|
|
156
182
|
caption={caption}
|
|
157
183
|
stickyHeader
|
|
158
|
-
|
|
184
|
+
hasRowType={hasRowType}
|
|
185
|
+
headContent={config.type === 'map' ? <MapHeader columns={columns} {...props} sortBy={sortBy} setSortBy={setSortBy} /> : <ChartHeader data={_runtimeData} {...props} hasRowType={hasRowType} isVertical={isVertical} sortBy={sortBy} setSortBy={setSortBy} />}
|
|
159
186
|
tableOptions={{ className: `${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}${isVertical ? '' : ' horizontal'}`, 'aria-live': 'assertive', 'aria-rowcount': config?.data?.length ? config.data.length : -1, hidden: !expanded }}
|
|
160
187
|
/>
|
|
161
188
|
|
|
162
189
|
{/* REGION Data Table */}
|
|
163
|
-
{config.regions && config.regions.length > 0 && config.visualizationType !== 'Box Plot' && (
|
|
190
|
+
{noRelativeRegions && config.regions && config.regions.length > 0 && config.visualizationType !== 'Box Plot' && (
|
|
164
191
|
<Table
|
|
192
|
+
wrapColumns={wrapColumns}
|
|
165
193
|
childrenMatrix={regionCellMatrix({ config })}
|
|
166
194
|
tableName={config.visualizationType}
|
|
167
195
|
caption='Table of the highlighted regions in the visualization'
|
|
@@ -188,6 +216,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
188
216
|
<ExpandCollapse expanded={expanded} setExpanded={setExpanded} tableTitle={tableTitle} />
|
|
189
217
|
<div className='table-container' style={limitHeight}>
|
|
190
218
|
<Table
|
|
219
|
+
wrapColumns={wrapColumns}
|
|
191
220
|
childrenMatrix={boxplotCellMatrix({ rows: tableData, config })}
|
|
192
221
|
tableName={config.visualizationType}
|
|
193
222
|
caption={caption}
|
|
@@ -3,11 +3,11 @@ import { getSeriesName } from '../helpers/getSeriesName'
|
|
|
3
3
|
import { getDataSeriesColumns } from '../helpers/getDataSeriesColumns'
|
|
4
4
|
import { DownIcon, UpIcon } from './Icons'
|
|
5
5
|
|
|
6
|
-
type ChartHeaderProps = { data; isVertical; config;
|
|
6
|
+
type ChartHeaderProps = { data; isVertical; config; setSortBy; sortBy; groupBy?; hasRowType? }
|
|
7
7
|
|
|
8
|
-
const ChartHeader = ({ data, isVertical, config,
|
|
8
|
+
const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, groupBy, hasRowType }: ChartHeaderProps) => {
|
|
9
9
|
if (!data) return
|
|
10
|
-
let dataSeriesColumns = getDataSeriesColumns(config, isVertical,
|
|
10
|
+
let dataSeriesColumns = getDataSeriesColumns(config, isVertical, data)
|
|
11
11
|
if (groupBy) {
|
|
12
12
|
let groupHeaderRemoved = dataSeriesColumns.filter(col => col !== groupBy)
|
|
13
13
|
if (groupHeaderRemoved.length != dataSeriesColumns.length) {
|
|
@@ -17,6 +17,14 @@ const ChartHeader = ({ data, isVertical, config, runtimeData, setSortBy, sortBy,
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
if (isVertical) {
|
|
20
|
+
if (hasRowType) {
|
|
21
|
+
// find the row type column and place it at the beginning of the array
|
|
22
|
+
const rowTypeRegex = /row[_-]?type/i
|
|
23
|
+
const rowTypeIndex = dataSeriesColumns.findIndex(column => rowTypeRegex.test(column))
|
|
24
|
+
if (rowTypeIndex > -1) {
|
|
25
|
+
dataSeriesColumns.splice(rowTypeIndex, 1)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
20
28
|
return (
|
|
21
29
|
<tr>
|
|
22
30
|
{dataSeriesColumns.map((column, index) => {
|
|
@@ -24,15 +32,18 @@ const ChartHeader = ({ data, isVertical, config, runtimeData, setSortBy, sortBy,
|
|
|
24
32
|
|
|
25
33
|
return (
|
|
26
34
|
<th
|
|
35
|
+
style={{ minWidth: (config.table.cellMinWidth || 0) + 'px' }}
|
|
27
36
|
key={`col-header-${column}__${index}`}
|
|
28
37
|
tabIndex={0}
|
|
29
38
|
title={text}
|
|
30
39
|
role='columnheader'
|
|
31
40
|
scope='col'
|
|
32
41
|
onClick={() => {
|
|
42
|
+
if (hasRowType) return
|
|
33
43
|
setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false, colIndex: index })
|
|
34
44
|
}}
|
|
35
45
|
onKeyDown={e => {
|
|
46
|
+
if (hasRowType) return
|
|
36
47
|
if (e.keyCode === 13) {
|
|
37
48
|
setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false, colIndex: index })
|
|
38
49
|
}
|
|
@@ -54,11 +65,12 @@ const ChartHeader = ({ data, isVertical, config, runtimeData, setSortBy, sortBy,
|
|
|
54
65
|
const sliceVal = config.visualizationType === 'Pie' ? 1 : 0
|
|
55
66
|
return (
|
|
56
67
|
<tr>
|
|
57
|
-
{['__series__', ...Object.keys(
|
|
68
|
+
{['__series__', ...Object.keys(data)].slice(sliceVal).map((row, index) => {
|
|
58
69
|
let column = config.xAxis?.dataKey
|
|
59
|
-
let text = row !== '__series__' ? getChartCellValue(row, column, config,
|
|
70
|
+
let text = row !== '__series__' ? getChartCellValue(row, column, config, data) : '__series__'
|
|
60
71
|
return (
|
|
61
72
|
<th
|
|
73
|
+
style={{ minWidth: (config.table.cellMinWidth || 0) + 'px' }}
|
|
62
74
|
key={`col-header-${text}__${index}`}
|
|
63
75
|
tabIndex={0}
|
|
64
76
|
title={text}
|
|
@@ -12,9 +12,10 @@ type ChartRowsProps = DataTableProps & {
|
|
|
12
12
|
isVertical: boolean
|
|
13
13
|
sortBy: { colIndex; column }
|
|
14
14
|
groupBy?: string
|
|
15
|
+
hasRowType?: boolean
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorScale, groupBy }: ChartRowsProps): CellMatrix | GroupCellMatrix => {
|
|
18
|
+
const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorScale, groupBy, hasRowType }: ChartRowsProps): CellMatrix | GroupCellMatrix => {
|
|
18
19
|
const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
|
|
19
20
|
|
|
20
21
|
const dataSeriesColumnsSorted = () => {
|
|
@@ -55,7 +56,20 @@ const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorSc
|
|
|
55
56
|
return cellMatrix
|
|
56
57
|
} else {
|
|
57
58
|
return rows.map(row => {
|
|
58
|
-
|
|
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
|
+
}
|
|
59
73
|
})
|
|
60
74
|
}
|
|
61
75
|
} else {
|
|
@@ -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
|
|
@@ -8,7 +8,16 @@ export const customSort = (a, b, sortBy, config) => {
|
|
|
8
8
|
if (config.type === 'map') {
|
|
9
9
|
valueA = standardizeStateName(a)
|
|
10
10
|
valueB = standardizeStateName(b)
|
|
11
|
+
// sort for Regions table for Map
|
|
12
|
+
if (String(valueA).toLowerCase().includes('region') && String(valueB).toLowerCase().includes('region')) {
|
|
13
|
+
const numberA = parseInt(a.match(/\d+$/)[0], 10)
|
|
14
|
+
const numberB = parseInt(b.match(/\d+$/)[0], 10)
|
|
15
|
+
|
|
16
|
+
// Compare the numeric parts
|
|
17
|
+
return !sortBy.asc ? Number(numberA) - Number(numberB) : Number(numberB) - Number(numberA)
|
|
18
|
+
}
|
|
11
19
|
}
|
|
20
|
+
|
|
12
21
|
// Treat booleans and nulls as an empty string
|
|
13
22
|
valueA = valueA === false || valueA === true || valueA === null ? '' : valueA
|
|
14
23
|
valueB = valueB === false || valueB === true || valueB === null ? '' : valueB
|
|
@@ -24,12 +24,13 @@ const isAdditionalColumn = (column, config) => {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export const getChartCellValue = (row, column, config, runtimeData) => {
|
|
27
|
+
if (config.table.customTableConfig) return runtimeData[row][column]
|
|
27
28
|
const rowObj = runtimeData[row]
|
|
28
29
|
let cellValue // placeholder for formatting below
|
|
29
30
|
let labelValue = rowObj[column] // just raw X axis string
|
|
30
31
|
if (column === config.xAxis?.dataKey) {
|
|
31
32
|
// 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
|
+
cellValue = config.xAxis?.type === 'date' ? formatDate(config.table?.dateDisplayFormat || config.xAxis?.dateDisplayFormat, parseDate(config.xAxis?.dateParseFormat, labelValue)) : labelValue
|
|
33
34
|
} else {
|
|
34
35
|
let resolvedAxis = 'left'
|
|
35
36
|
let leftAxisItems = config.series ? config.series.filter(item => item?.axis === 'Left') : []
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const getDataSeriesColumns = (config, isVertical, runtimeData): string[] => {
|
|
2
|
+
if (config.table.customTableConfig) return runtimeData[0] ? Object.keys(runtimeData[0]) : []
|
|
2
3
|
let tmpSeriesColumns
|
|
3
4
|
if (config.visualizationType !== 'Pie') {
|
|
4
5
|
tmpSeriesColumns = isVertical ? [config.xAxis?.dataKey] : [] //, ...config.runtime.seriesLabelsAll
|
|
@@ -10,7 +11,7 @@ export const getDataSeriesColumns = (config, isVertical, runtimeData): string[]
|
|
|
10
11
|
tmpSeriesColumns = Object.keys(runtimeData[0])
|
|
11
12
|
}
|
|
12
13
|
} else {
|
|
13
|
-
tmpSeriesColumns = [config.xAxis?.dataKey, config.yAxis?.dataKey]
|
|
14
|
+
tmpSeriesColumns = isVertical ? [config.xAxis?.dataKey, config.yAxis?.dataKey] : [config.yAxis?.dataKey]
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
// then add the additional Columns
|
|
@@ -1,26 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
custLabel = tmpColumn.label
|
|
9
|
-
}
|
|
10
|
-
})
|
|
11
|
-
return custLabel
|
|
1
|
+
import { TableConfig } from '../types/TableConfig'
|
|
2
|
+
|
|
3
|
+
const getLabel = (name: string, config: TableConfig) => {
|
|
4
|
+
const columns = Object.values(config.columns || {})
|
|
5
|
+
const matchingConfiguredColumn = columns.find(col => col.name === name)
|
|
6
|
+
if (matchingConfiguredColumn?.label) {
|
|
7
|
+
return matchingConfiguredColumn.label
|
|
12
8
|
}
|
|
9
|
+
return name
|
|
13
10
|
}
|
|
14
11
|
|
|
15
|
-
export const getSeriesName = (column, config) => {
|
|
12
|
+
export const getSeriesName = (column: string, config: TableConfig) => {
|
|
16
13
|
// If a user sets the name on a series use that.
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
14
|
+
const userDefinedSeries = config.series?.find(series => series.dataKey === column)
|
|
15
|
+
if (userDefinedSeries?.name) {
|
|
16
|
+
return userDefinedSeries.name
|
|
17
|
+
}
|
|
20
18
|
if (config.runtimeSeriesLabels && config.runtimeSeriesLabels[column]) return config.runtimeSeriesLabels[column]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
let text = column === config.xAxis?.dataKey ? config.table.indexLabel : custLabel
|
|
24
|
-
|
|
25
|
-
return text
|
|
19
|
+
const columnIsDataKey = column === config.xAxis?.dataKey
|
|
20
|
+
return columnIsDataKey ? config.table.indexLabel : getLabel(column, config)
|
|
26
21
|
}
|
|
@@ -21,7 +21,11 @@ const mapCellArray = ({ rows, columns, runtimeData, config, applyLegendToRow, di
|
|
|
21
21
|
let labelValue
|
|
22
22
|
const mapZoomHandler = config.general.type === 'bubble' && config.general.allowMapZoom && config.general.geoType === 'world' ? () => setFilteredCountryCode(row) : undefined
|
|
23
23
|
if ((config.general.geoType !== 'single-state' && config.general.geoType !== 'us-county') || config.general.type === 'us-geocode') {
|
|
24
|
+
const capitalize = str => {
|
|
25
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
26
|
+
}
|
|
24
27
|
labelValue = displayGeoName(row)
|
|
28
|
+
labelValue = String(labelValue).startsWith('region') ? capitalize(labelValue) : labelValue
|
|
25
29
|
} else {
|
|
26
30
|
labelValue = formatLegendLocation(row)
|
|
27
31
|
}
|
|
@@ -1,55 +1,26 @@
|
|
|
1
1
|
import { Axis } from '@cdc/core/types/Axis'
|
|
2
2
|
import { Series } from '@cdc/core/types/Series'
|
|
3
3
|
import { Runtime } from '@cdc/core/types/Runtime'
|
|
4
|
+
import { Table } from '@cdc/core/types/Table'
|
|
5
|
+
import { BoxPlot } from '../../../types/BoxPlot'
|
|
6
|
+
import { General } from '../../../types/General'
|
|
7
|
+
import { Column } from '../../../types/Column'
|
|
4
8
|
|
|
5
9
|
export type TableConfig = {
|
|
6
10
|
type?: string
|
|
7
|
-
table:
|
|
8
|
-
showVertical?: boolean
|
|
9
|
-
indexLabel: string
|
|
10
|
-
limitHeight: boolean
|
|
11
|
-
height: string | number
|
|
12
|
-
caption: string
|
|
13
|
-
download: boolean
|
|
14
|
-
}
|
|
11
|
+
table: Table
|
|
15
12
|
xAxis?: Axis
|
|
16
13
|
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
|
-
}
|
|
14
|
+
boxplot?: BoxPlot
|
|
36
15
|
visualizationType?: string
|
|
37
|
-
general?:
|
|
38
|
-
|
|
39
|
-
type: string
|
|
40
|
-
showDownloadButton: boolean
|
|
41
|
-
allowMapZoom?: boolean
|
|
42
|
-
}
|
|
43
|
-
columns?: {
|
|
44
|
-
geo: {
|
|
45
|
-
name: string
|
|
46
|
-
}
|
|
47
|
-
}
|
|
16
|
+
general?: General
|
|
17
|
+
columns?: Record<string, Column>
|
|
48
18
|
legend?: {
|
|
49
|
-
specialClasses
|
|
19
|
+
specialClasses?: { key: string; label: string; value: string }[]
|
|
20
|
+
hide?: boolean
|
|
50
21
|
}
|
|
51
22
|
series?: Series
|
|
52
|
-
regions?: { label: string; from: string; to: string }[]
|
|
23
|
+
regions?: { label: string; from: string; to: string; fromType: 'Fixed' | 'Previous Days'; toType: 'Fixed' | 'Last Date' }[]
|
|
53
24
|
runtimeSeriesLabels?: Object
|
|
54
25
|
dataFormat?: Object
|
|
55
26
|
runtime: Runtime
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
3
|
+
import Icon from '../ui/Icon'
|
|
4
|
+
import { CheckBox, TextField } from './Inputs'
|
|
5
|
+
import type { Table } from '@cdc/core/types/Table'
|
|
6
|
+
import MultiSelect from '../MultiSelect'
|
|
7
|
+
import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
|
|
8
|
+
|
|
9
|
+
interface DataTableProps {
|
|
10
|
+
config: {
|
|
11
|
+
table: Table
|
|
12
|
+
visualizationType: string
|
|
13
|
+
}
|
|
14
|
+
updateField: UpdateFieldFunc<string | boolean | string[] | number>
|
|
15
|
+
isDashboard: boolean
|
|
16
|
+
isLoadedFromUrl: boolean
|
|
17
|
+
columns: string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const DataTable: React.FC<DataTableProps> = ({ config, updateField, isDashboard, isLoadedFromUrl, columns }) => {
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<TextField
|
|
24
|
+
value={config.table.label}
|
|
25
|
+
updateField={updateField}
|
|
26
|
+
section='table'
|
|
27
|
+
fieldName='table-label'
|
|
28
|
+
id='tableLabel'
|
|
29
|
+
label='Data Table Title'
|
|
30
|
+
placeholder='Data Table'
|
|
31
|
+
tooltip={
|
|
32
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
33
|
+
<Tooltip.Target>
|
|
34
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
35
|
+
</Tooltip.Target>
|
|
36
|
+
<Tooltip.Content>
|
|
37
|
+
<p>Label is required for Data Table for 508 Compliance</p>
|
|
38
|
+
</Tooltip.Content>
|
|
39
|
+
</Tooltip>
|
|
40
|
+
}
|
|
41
|
+
/>
|
|
42
|
+
<CheckBox
|
|
43
|
+
value={config.table.show}
|
|
44
|
+
fieldName='show'
|
|
45
|
+
label='Show Data Table'
|
|
46
|
+
section='table'
|
|
47
|
+
updateField={updateField}
|
|
48
|
+
className='column-heading'
|
|
49
|
+
tooltip={
|
|
50
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
51
|
+
<Tooltip.Target>
|
|
52
|
+
<Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
|
|
53
|
+
</Tooltip.Target>
|
|
54
|
+
<Tooltip.Content>
|
|
55
|
+
<p>Hiding the data table may affect accessibility. An alternate form of accessing visualization data is a 508 requirement.</p>
|
|
56
|
+
</Tooltip.Content>
|
|
57
|
+
</Tooltip>
|
|
58
|
+
}
|
|
59
|
+
/>
|
|
60
|
+
{config.visualizationType !== 'Box Plot' && (
|
|
61
|
+
<CheckBox
|
|
62
|
+
value={config.table.showVertical}
|
|
63
|
+
fieldName='showVertical'
|
|
64
|
+
label='Show Vertical Data'
|
|
65
|
+
section='table'
|
|
66
|
+
updateField={updateField}
|
|
67
|
+
className='column-heading'
|
|
68
|
+
tooltip={
|
|
69
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
70
|
+
<Tooltip.Target>
|
|
71
|
+
<Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
|
|
72
|
+
</Tooltip.Target>
|
|
73
|
+
<Tooltip.Content>
|
|
74
|
+
<p>This will draw the data table with vertical data instead of horizontal.</p>
|
|
75
|
+
</Tooltip.Content>
|
|
76
|
+
</Tooltip>
|
|
77
|
+
}
|
|
78
|
+
/>
|
|
79
|
+
)}
|
|
80
|
+
<TextField value={config.table.indexLabel} section='table' fieldName='indexLabel' label='Index Column Header' updateField={updateField} />
|
|
81
|
+
<TextField
|
|
82
|
+
value={config.table.caption}
|
|
83
|
+
updateField={updateField}
|
|
84
|
+
section='table'
|
|
85
|
+
type='textarea'
|
|
86
|
+
fieldName='caption'
|
|
87
|
+
label='Screen Reader Description'
|
|
88
|
+
placeholder=' Data table'
|
|
89
|
+
tooltip={
|
|
90
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
91
|
+
<Tooltip.Target>
|
|
92
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
93
|
+
</Tooltip.Target>
|
|
94
|
+
<Tooltip.Content>
|
|
95
|
+
<p>Enter a description of the data table to be read by screen readers.</p>
|
|
96
|
+
</Tooltip.Content>
|
|
97
|
+
</Tooltip>
|
|
98
|
+
}
|
|
99
|
+
/>
|
|
100
|
+
<CheckBox value={config.table.limitHeight} section='table' fieldName='limitHeight' label='Limit Table Height' updateField={updateField} />
|
|
101
|
+
<CheckBox
|
|
102
|
+
value={config.table.customTableConfig}
|
|
103
|
+
fieldName='customTableConfig'
|
|
104
|
+
label='Customize Table Config'
|
|
105
|
+
section='table'
|
|
106
|
+
updateField={updateField}
|
|
107
|
+
tooltip={
|
|
108
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
109
|
+
<Tooltip.Target>
|
|
110
|
+
<Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
|
|
111
|
+
</Tooltip.Target>
|
|
112
|
+
<Tooltip.Content>
|
|
113
|
+
<p>This will display all available columns in the data set. It will not show any columns where all of the column cells are null.</p>
|
|
114
|
+
</Tooltip.Content>
|
|
115
|
+
</Tooltip>
|
|
116
|
+
}
|
|
117
|
+
/>
|
|
118
|
+
{config.table.customTableConfig && <MultiSelect options={columns.map(c => ({ label: c, value: c }))} fieldName='excludeColumns' label='Exclude Columns' section='table' updateField={updateField} />}
|
|
119
|
+
{config.table.limitHeight && <TextField value={config.table.height} fieldName='height' label='Data Table Height' type='number' min={0} max={500} placeholder='Height(px)' updateField={updateField} />}
|
|
120
|
+
<CheckBox value={config.table.expanded} fieldName='expanded' label='Expanded by Default' section='table' updateField={updateField} />
|
|
121
|
+
{isDashboard && <CheckBox value={config.table.showDataTableLink} fieldName='showDataTableLink' label='Show Data Table Name & Link' section='table' updateField={updateField} />}
|
|
122
|
+
{isLoadedFromUrl && <CheckBox value={config.table.showDownloadUrl} fieldName='showDownloadUrl' label='Show URL to Automatically Updated Data' section='table' updateField={updateField} />}
|
|
123
|
+
<CheckBox value={config.table.download} fieldName='download' label='Show Download CSV Link' section='table' updateField={updateField} />
|
|
124
|
+
<CheckBox value={config.table.showDownloadImgButton} fieldName='showDownloadImgButton' label='Display Image Button' section='table' updateField={updateField} />
|
|
125
|
+
<label>
|
|
126
|
+
<span className='edit-label column-heading'>Table Cell Min Width</span>
|
|
127
|
+
<input type='number' value={config.table.cellMinWidth ? config.table.cellMinWidth : 0} onChange={e => updateField('table', null, 'cellMinWidth', e.target.value)} />
|
|
128
|
+
</label>
|
|
129
|
+
</>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export default DataTable
|