@cdc/core 4.23.11 → 4.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/DataTable/DataTable.tsx +27 -9
- 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/getChartCellValue.ts +1 -0
- package/components/DataTable/helpers/getDataSeriesColumns.ts +1 -0
- package/components/DataTable/types/TableConfig.ts +5 -10
- package/components/EditorPanel/DataTableEditor.tsx +133 -0
- package/components/EditorPanel/Inputs.tsx +150 -0
- package/components/Filters.jsx +3 -3
- 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/helpers/DataTransform.ts +30 -2
- package/helpers/getFileExtension.ts +28 -5
- package/package.json +2 -2
- package/styles/_data-table.scss +2 -0
- package/types/Axis.ts +37 -2
- package/types/Column.ts +15 -0
- package/types/FilterBehavior.ts +1 -0
- package/types/Runtime.ts +21 -1
- package/types/Series.ts +1 -1
- package/types/Table.ts +18 -0
- package/types/UpdateFieldFunc.ts +1 -0
- package/types/Visualization.ts +9 -9
|
@@ -15,12 +15,15 @@ import Table from '../Table'
|
|
|
15
15
|
import chartCellMatrix from './helpers/chartCellMatrix'
|
|
16
16
|
import regionCellMatrix from './helpers/regionCellMatrix'
|
|
17
17
|
import boxplotCellMatrix from './helpers/boxplotCellMatrix'
|
|
18
|
+
import customColumns from './helpers/customColumns'
|
|
18
19
|
import { TableConfig } from './types/TableConfig'
|
|
19
20
|
|
|
20
21
|
export type DataTableProps = {
|
|
21
22
|
applyLegendToRow?: Function
|
|
22
23
|
colorScale?: Function
|
|
23
24
|
columns?: { navigate: { name: string } }
|
|
25
|
+
// determines if columns should be wrapped in the table
|
|
26
|
+
wrapColumns?: boolean
|
|
24
27
|
config: TableConfig
|
|
25
28
|
dataConfig?: Object
|
|
26
29
|
displayDataAsText?: Function
|
|
@@ -42,7 +45,7 @@ export type DataTableProps = {
|
|
|
42
45
|
|
|
43
46
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
44
47
|
const DataTable = (props: DataTableProps) => {
|
|
45
|
-
const { config, dataConfig, tableTitle, vizTitle, rawData, runtimeData, headerColor, expandDataTable, columns, viewport, formatLegendLocation, tabbingId } = props
|
|
48
|
+
const { config, dataConfig, tableTitle, vizTitle, rawData, runtimeData, headerColor, expandDataTable, columns, viewport, formatLegendLocation, tabbingId, wrapColumns } = props
|
|
46
49
|
|
|
47
50
|
const [expanded, setExpanded] = useState(expandDataTable)
|
|
48
51
|
|
|
@@ -89,19 +92,21 @@ const DataTable = (props: DataTableProps) => {
|
|
|
89
92
|
break
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
const
|
|
95
|
+
const _runtimeData = config.table.customTableConfig ? customColumns(runtimeData, config.table.excludeColumns) : runtimeData
|
|
96
|
+
|
|
97
|
+
const rawRows = Object.keys(_runtimeData)
|
|
93
98
|
const rows = isVertical
|
|
94
99
|
? rawRows.sort((a, b) => {
|
|
95
100
|
let dataA
|
|
96
101
|
let dataB
|
|
97
102
|
if (config.type === 'map' && config.columns) {
|
|
98
103
|
const sortByColName = config.columns[sortBy.column].name
|
|
99
|
-
dataA =
|
|
100
|
-
dataB =
|
|
104
|
+
dataA = _runtimeData[a][sortByColName]
|
|
105
|
+
dataB = _runtimeData[b][sortByColName]
|
|
101
106
|
}
|
|
102
107
|
if (config.type === 'chart' || config.type === 'dashboard') {
|
|
103
|
-
dataA =
|
|
104
|
-
dataB =
|
|
108
|
+
dataA = _runtimeData[a][sortBy.column]
|
|
109
|
+
dataB = _runtimeData[b][sortBy.column]
|
|
105
110
|
}
|
|
106
111
|
return dataA && dataB ? customSort(dataA, dataB, sortBy, config) : 0
|
|
107
112
|
})
|
|
@@ -112,6 +117,8 @@ const DataTable = (props: DataTableProps) => {
|
|
|
112
117
|
OverflowY: 'scroll'
|
|
113
118
|
}
|
|
114
119
|
|
|
120
|
+
const hasRowType = !!Object.keys(rawData[0] || {}).find((v: string) => v.match(/row[_-]?type/i))
|
|
121
|
+
|
|
115
122
|
const caption = useMemo(() => {
|
|
116
123
|
if (config.type === 'map') {
|
|
117
124
|
return config.table.caption ? config.table.caption : `Data table showing data for the ${mapLookup[config.general.geoType]} figure.`
|
|
@@ -120,6 +127,13 @@ const DataTable = (props: DataTableProps) => {
|
|
|
120
127
|
}
|
|
121
128
|
}, [config.table.caption])
|
|
122
129
|
|
|
130
|
+
// Determines if a relative region is being shown to the user.
|
|
131
|
+
// If a relative region is found we don't want to display the data table.
|
|
132
|
+
// Takes backwards compatibility into consideration, ie !region.toType || !region.fromType
|
|
133
|
+
const noRelativeRegions = config?.regions?.every(region => {
|
|
134
|
+
return (region.toType === 'Fixed' && region.fromType === 'Fixed') || (!region.toType && !region.fromType) || (!region.toType && region.fromType === 'Fixed') || (!region.fromType && region.toType === 'Fixed')
|
|
135
|
+
})
|
|
136
|
+
|
|
123
137
|
// prettier-ignore
|
|
124
138
|
const tableData = useMemo(() => (
|
|
125
139
|
config.visualizationType === 'Pie'
|
|
@@ -151,17 +165,20 @@ const DataTable = (props: DataTableProps) => {
|
|
|
151
165
|
<ExpandCollapse expanded={expanded} setExpanded={setExpanded} tableTitle={tableTitle} />
|
|
152
166
|
<div className='table-container' style={limitHeight}>
|
|
153
167
|
<Table
|
|
154
|
-
|
|
168
|
+
wrapColumns={wrapColumns}
|
|
169
|
+
childrenMatrix={config.type === 'map' ? mapCellMatrix({ rows, wrapColumns, ...props, runtimeData: _runtimeData }) : chartCellMatrix({ rows, ...props, runtimeData: _runtimeData, isVertical, sortBy, hasRowType })}
|
|
155
170
|
tableName={config.type}
|
|
156
171
|
caption={caption}
|
|
157
172
|
stickyHeader
|
|
158
|
-
|
|
173
|
+
hasRowType={hasRowType}
|
|
174
|
+
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
175
|
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
176
|
/>
|
|
161
177
|
|
|
162
178
|
{/* REGION Data Table */}
|
|
163
|
-
{config.regions && config.regions.length > 0 && config.visualizationType !== 'Box Plot' && (
|
|
179
|
+
{noRelativeRegions && config.regions && config.regions.length > 0 && config.visualizationType !== 'Box Plot' && (
|
|
164
180
|
<Table
|
|
181
|
+
wrapColumns={wrapColumns}
|
|
165
182
|
childrenMatrix={regionCellMatrix({ config })}
|
|
166
183
|
tableName={config.visualizationType}
|
|
167
184
|
caption='Table of the highlighted regions in the visualization'
|
|
@@ -188,6 +205,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
188
205
|
<ExpandCollapse expanded={expanded} setExpanded={setExpanded} tableTitle={tableTitle} />
|
|
189
206
|
<div className='table-container' style={limitHeight}>
|
|
190
207
|
<Table
|
|
208
|
+
wrapColumns={wrapColumns}
|
|
191
209
|
childrenMatrix={boxplotCellMatrix({ rows: tableData, config })}
|
|
192
210
|
tableName={config.visualizationType}
|
|
193
211
|
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
|
|
@@ -24,6 +24,7 @@ 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
|
|
@@ -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
|
|
@@ -1,17 +1,11 @@
|
|
|
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'
|
|
4
5
|
|
|
5
6
|
export type TableConfig = {
|
|
6
7
|
type?: string
|
|
7
|
-
table:
|
|
8
|
-
showVertical?: boolean
|
|
9
|
-
indexLabel: string
|
|
10
|
-
limitHeight: boolean
|
|
11
|
-
height: string | number
|
|
12
|
-
caption: string
|
|
13
|
-
download: boolean
|
|
14
|
-
}
|
|
8
|
+
table: Table
|
|
15
9
|
xAxis?: Axis
|
|
16
10
|
yAxis?: Axis
|
|
17
11
|
boxplot?: {
|
|
@@ -46,10 +40,11 @@ export type TableConfig = {
|
|
|
46
40
|
}
|
|
47
41
|
}
|
|
48
42
|
legend?: {
|
|
49
|
-
specialClasses
|
|
43
|
+
specialClasses?: { key: string; label: string; value: string }[]
|
|
44
|
+
hide?: boolean
|
|
50
45
|
}
|
|
51
46
|
series?: Series
|
|
52
|
-
regions?: { label: string; from: string; to: string }[]
|
|
47
|
+
regions?: { label: string; from: string; to: string; fromType: 'Fixed' | 'Previous Days'; toType: 'Fixed' | 'Last Date' }[]
|
|
53
48
|
runtimeSeriesLabels?: Object
|
|
54
49
|
dataFormat?: Object
|
|
55
50
|
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
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { memo, useEffect, useState } from 'react'
|
|
2
|
+
import { useDebounce } from 'use-debounce'
|
|
3
|
+
|
|
4
|
+
export type Input = {
|
|
5
|
+
label: string
|
|
6
|
+
tooltip?: any
|
|
7
|
+
section?: any
|
|
8
|
+
placeholder?: string
|
|
9
|
+
subsection?: any
|
|
10
|
+
updateField?: Function
|
|
11
|
+
fieldName?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type TextFieldProps = {
|
|
15
|
+
className?: string
|
|
16
|
+
value: string | number
|
|
17
|
+
type?: 'text' | 'number' | 'textarea' | 'date'
|
|
18
|
+
min?: number
|
|
19
|
+
max?: number
|
|
20
|
+
i?: number
|
|
21
|
+
id?: string
|
|
22
|
+
} & Input
|
|
23
|
+
|
|
24
|
+
export type CheckboxProps = {
|
|
25
|
+
value?: boolean
|
|
26
|
+
min?: number
|
|
27
|
+
i?: number
|
|
28
|
+
className?: string
|
|
29
|
+
} & Input
|
|
30
|
+
|
|
31
|
+
export type SelectProps = {
|
|
32
|
+
value?: string
|
|
33
|
+
options?: string[]
|
|
34
|
+
required?: boolean
|
|
35
|
+
initial?: string
|
|
36
|
+
|
|
37
|
+
// all other props
|
|
38
|
+
[x: string]: any
|
|
39
|
+
} & Input
|
|
40
|
+
|
|
41
|
+
const TextField = memo((props: TextFieldProps) => {
|
|
42
|
+
const { label, tooltip, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'text', i = null, min = null, ...attributes } = props
|
|
43
|
+
const [value, setValue] = useState(stateValue)
|
|
44
|
+
const [debouncedValue] = useDebounce(value, 500)
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
|
|
48
|
+
updateField(section, subsection, fieldName, debouncedValue, i)
|
|
49
|
+
}
|
|
50
|
+
}, [debouncedValue])
|
|
51
|
+
|
|
52
|
+
let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
|
|
53
|
+
|
|
54
|
+
const onChange = e => {
|
|
55
|
+
if ('number' !== type || min === null) {
|
|
56
|
+
setValue(e.target.value)
|
|
57
|
+
} else {
|
|
58
|
+
if (!e.target.value || min <= parseFloat(e.target.value)) {
|
|
59
|
+
setValue(e.target.value)
|
|
60
|
+
} else {
|
|
61
|
+
setValue(min.toString())
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />
|
|
67
|
+
|
|
68
|
+
if ('textarea' === type) {
|
|
69
|
+
formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if ('number' === type) {
|
|
73
|
+
formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if ('date' === type) {
|
|
77
|
+
formElement = <input type='date' name={name} onChange={onChange} {...attributes} value={value} />
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<label>
|
|
82
|
+
<span className='edit-label column-heading'>
|
|
83
|
+
{label}
|
|
84
|
+
{tooltip}
|
|
85
|
+
</span>
|
|
86
|
+
{formElement}
|
|
87
|
+
</label>
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const CheckBox = memo((props: CheckboxProps) => {
|
|
92
|
+
const { label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes } = props
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<label className='checkbox column-heading'>
|
|
96
|
+
<input
|
|
97
|
+
type='checkbox'
|
|
98
|
+
name={fieldName}
|
|
99
|
+
checked={value}
|
|
100
|
+
onChange={e => {
|
|
101
|
+
updateField(section, subsection, fieldName, !value)
|
|
102
|
+
}}
|
|
103
|
+
{...attributes}
|
|
104
|
+
/>
|
|
105
|
+
<span className='edit-label'>
|
|
106
|
+
{label}
|
|
107
|
+
{tooltip}
|
|
108
|
+
</span>
|
|
109
|
+
</label>
|
|
110
|
+
)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const Select = memo((props: SelectProps) => {
|
|
114
|
+
const { label, value, options, fieldName, section = null, subsection = null, required = false, tooltip, updateField, initial: initialValue, ...attributes } = props
|
|
115
|
+
let optionsJsx = options.map((optionName, index) => (
|
|
116
|
+
<option value={optionName} key={index}>
|
|
117
|
+
{optionName}
|
|
118
|
+
</option>
|
|
119
|
+
))
|
|
120
|
+
|
|
121
|
+
if (initialValue) {
|
|
122
|
+
optionsJsx.unshift(
|
|
123
|
+
<option value='' key='initial'>
|
|
124
|
+
{initialValue}
|
|
125
|
+
</option>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<label>
|
|
131
|
+
<span className='edit-label'>
|
|
132
|
+
{label}
|
|
133
|
+
{tooltip}
|
|
134
|
+
</span>
|
|
135
|
+
<select
|
|
136
|
+
className={required && !value ? 'warning' : ''}
|
|
137
|
+
name={fieldName}
|
|
138
|
+
value={value}
|
|
139
|
+
onChange={event => {
|
|
140
|
+
updateField(section, subsection, fieldName, event.target.value)
|
|
141
|
+
}}
|
|
142
|
+
{...attributes}
|
|
143
|
+
>
|
|
144
|
+
{optionsJsx}
|
|
145
|
+
</select>
|
|
146
|
+
</label>
|
|
147
|
+
)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
export { Select, CheckBox, TextField }
|
package/components/Filters.jsx
CHANGED
|
@@ -89,7 +89,7 @@ export const useFilters = props => {
|
|
|
89
89
|
filters: newFilters
|
|
90
90
|
})
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
// Used for setting active filter, fromHash breaks the filteredData functionality.
|
|
94
94
|
if (visualizationConfig.type === 'map' && visualizationConfig.filterBehavior === 'Filter Change') {
|
|
95
95
|
setFilteredData(newFilters)
|
|
@@ -320,7 +320,7 @@ const Filters = props => {
|
|
|
320
320
|
const tabValues = []
|
|
321
321
|
const tabBarValues = []
|
|
322
322
|
|
|
323
|
-
const { active, label, filterStyle } = singleFilter
|
|
323
|
+
const { active, queuedActive, label, filterStyle } = singleFilter
|
|
324
324
|
|
|
325
325
|
handleSorting(singleFilter)
|
|
326
326
|
|
|
@@ -387,7 +387,7 @@ const Filters = props => {
|
|
|
387
387
|
{filterStyle === 'tab' && !mobileFilterStyle && <Filters.Tabs tabs={tabValues} />}
|
|
388
388
|
{filterStyle === 'pill' && !mobileFilterStyle && <Filters.Pills pills={pillValues} />}
|
|
389
389
|
{filterStyle === 'tab bar' && !mobileFilterStyle && <Filters.TabBar filter={singleFilter} index={outerIndex} />}
|
|
390
|
-
{(filterStyle === 'dropdown' || mobileFilterStyle) && <Filters.Dropdown filter={singleFilter} index={outerIndex} label={label} active={active} filters={values} />}
|
|
390
|
+
{(filterStyle === 'dropdown' || mobileFilterStyle) && <Filters.Dropdown filter={singleFilter} index={outerIndex} label={label} active={queuedActive || active} filters={values} />}
|
|
391
391
|
</>
|
|
392
392
|
</div>
|
|
393
393
|
)
|
|
@@ -59,7 +59,7 @@ const generateMedia = (state, type, elementToCapture) => {
|
|
|
59
59
|
|
|
60
60
|
switch (type) {
|
|
61
61
|
case 'image':
|
|
62
|
-
html2canvas(baseSvg, {foreignObjectRendering: true}).then(canvas => {
|
|
62
|
+
html2canvas(baseSvg, {foreignObjectRendering: true, x: -1 * (window.pageXOffset + baseSvg.getBoundingClientRect().left), y: -1 * (window.pageYOffset + baseSvg.getBoundingClientRect().top)}).then(canvas => {
|
|
63
63
|
saveImageAs(canvas.toDataURL(), filename + '.png')
|
|
64
64
|
})
|
|
65
65
|
return
|