@cdc/core 4.24.5 → 4.24.7
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/AdvancedEditor/AdvancedEditor.tsx +93 -0
- package/components/AdvancedEditor/advanced-editor-styles.css +3 -0
- package/components/AdvancedEditor/index.ts +1 -0
- package/components/DataTable/DataTable.tsx +21 -2
- package/components/DataTable/DataTableStandAlone.tsx +4 -25
- package/components/DataTable/components/DataTableEditorPanel.tsx +4 -4
- package/components/DataTable/components/ExpandCollapse.tsx +1 -1
- package/components/DataTable/helpers/chartCellMatrix.tsx +3 -9
- package/components/DataTable/helpers/getChartCellValue.ts +8 -4
- package/components/DataTable/helpers/getDataSeriesColumns.ts +8 -5
- package/components/DataTable/helpers/getRowType.ts +6 -0
- package/components/DataTable/types/TableConfig.ts +1 -0
- package/components/EditorPanel/ColumnsEditor.tsx +3 -30
- package/components/EditorPanel/DataTableEditor.tsx +66 -22
- package/components/EditorPanel/FieldSetWrapper.tsx +51 -0
- package/components/EditorPanel/FootnotesEditor.tsx +77 -0
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +227 -0
- package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +54 -0
- package/components/EditorPanel/VizFilterEditor/index.ts +1 -0
- package/components/EditorWrapper/EditorWrapper.tsx +3 -4
- package/components/EditorWrapper/index.ts +1 -0
- package/components/{Filters.jsx → Filters.tsx} +40 -24
- package/components/Footnotes/Footnotes.tsx +25 -0
- package/components/Footnotes/FootnotesStandAlone.tsx +45 -0
- package/components/Footnotes/footnotes.css +5 -0
- package/components/Footnotes/index.ts +1 -0
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +8 -4
- package/components/Layout/components/Visualization/index.tsx +12 -5
- package/components/MultiSelect/MultiSelect.tsx +36 -9
- package/components/MultiSelect/multiselect.styles.css +0 -3
- package/components/_stories/Footnotes.stories.tsx +17 -0
- package/components/_stories/styles.scss +1 -0
- package/components/inputs/InputSelect.tsx +17 -6
- package/components/ui/Icon.tsx +1 -2
- package/helpers/addValuesToFilters.ts +56 -0
- package/helpers/cove/accessibility.ts +1 -0
- package/helpers/cove/fontSettings.ts +2 -0
- package/helpers/coveUpdateWorker.ts +7 -0
- package/helpers/filterVizData.ts +30 -0
- package/helpers/formatConfigBeforeSave.ts +90 -0
- package/helpers/gatherQueryParams.ts +14 -7
- package/helpers/lineChartHelpers.js +2 -1
- package/helpers/pivotData.ts +18 -0
- package/helpers/queryStringUtils.ts +29 -0
- package/helpers/tests/updateFieldFactory.test.ts +1 -0
- package/helpers/updateFieldFactory.ts +1 -1
- package/helpers/ver/4.24.7.ts +92 -0
- package/package.json +6 -4
- package/styles/_button-section.scss +6 -1
- package/styles/_data-table.scss +0 -1
- package/styles/base.scss +4 -0
- package/styles/v2/themes/_color-definitions.scss +1 -0
- package/types/Annotation.ts +46 -0
- package/types/Axis.ts +0 -2
- package/types/ConfigureData.ts +1 -1
- package/types/Footnotes.ts +17 -0
- package/types/General.ts +5 -0
- package/types/Runtime.ts +2 -7
- package/types/Table.ts +6 -0
- package/types/Visualization.ts +31 -9
- package/types/VizFilter.ts +16 -5
- package/LICENSE +0 -201
- package/components/AdvancedEditor.jsx +0 -74
- package/components/EditorPanel/VizFilterEditor.tsx +0 -234
- package/helpers/queryStringUtils.js +0 -26
- package/types/BaseVisualizationType.ts +0 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import MapIcon from '../../assets/map-folded.svg'
|
|
3
|
+
import ChartIcon from '../../assets/icon-chart-bar.svg'
|
|
4
|
+
import MarkupIncludeIcon from '../../assets/icon-code.svg'
|
|
5
|
+
import { FilterFunction, JsonEditor, UpdateFunction } from 'json-edit-react'
|
|
6
|
+
import { formatConfigBeforeSave as stripConfig } from '../../helpers/formatConfigBeforeSave'
|
|
7
|
+
import './advanced-editor-styles.css'
|
|
8
|
+
import _ from 'lodash'
|
|
9
|
+
|
|
10
|
+
export const AdvancedEditor = ({ loadConfig, config, convertStateToConfig, onExpandCollapse = () => {} }) => {
|
|
11
|
+
const [advancedToggle, _setAdvancedToggle] = useState(false)
|
|
12
|
+
const [configTextboxValue, setConfigTextbox] = useState<Record<string, any>>({})
|
|
13
|
+
const setAdvancedToggle = val => {
|
|
14
|
+
_setAdvancedToggle(val)
|
|
15
|
+
onExpandCollapse()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const collapseFields: FilterFunction = input => {
|
|
19
|
+
if (['datasets', 'data', 'originalFormattedData', 'formattedData'].includes(String(input.key))) return true
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const onUpdate: UpdateFunction = val => {
|
|
24
|
+
setConfigTextbox(val.newData)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
let parsedConfig = stripConfig(config)
|
|
29
|
+
if (config.type !== 'dashboard') {
|
|
30
|
+
parsedConfig = convertStateToConfig()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setConfigTextbox(parsedConfig)
|
|
34
|
+
}, [config])
|
|
35
|
+
|
|
36
|
+
const typeLookup = {
|
|
37
|
+
chart: ['Charts', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/bar-chart.html', <ChartIcon />],
|
|
38
|
+
dashboard: ['Dashboard', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/bar-chart.html', <ChartIcon />],
|
|
39
|
+
map: ['Maps', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/data-map.html', <MapIcon />],
|
|
40
|
+
'markup-include': ['Markup Include', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/Markup-Include.html', <MarkupIncludeIcon />]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!config.type) return <></>
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
<a href={typeLookup[config.type][1]} target='_blank' rel='noopener noreferrer' className='guidance-link'>
|
|
47
|
+
{typeLookup[config.type][2]}
|
|
48
|
+
<div>
|
|
49
|
+
<span className='heading-3'>Get Help with {typeLookup[config.type][0]}</span>
|
|
50
|
+
<p>Examples and documentation</p>
|
|
51
|
+
</div>
|
|
52
|
+
</a>
|
|
53
|
+
<div className='advanced'>
|
|
54
|
+
<span className='advanced-toggle-link' onClick={() => setAdvancedToggle(!advancedToggle)}>
|
|
55
|
+
<span>{advancedToggle ? `— ` : `+ `}</span>Advanced Options
|
|
56
|
+
</span>
|
|
57
|
+
{advancedToggle && (
|
|
58
|
+
<React.Fragment>
|
|
59
|
+
<section className='error-box py-2 px-3 my-2'>
|
|
60
|
+
<div>
|
|
61
|
+
<strong className='pt-1'>Warning</strong>
|
|
62
|
+
<p>This can cause serious errors in your visualization.</p>
|
|
63
|
+
</div>
|
|
64
|
+
</section>
|
|
65
|
+
<p className='pb-2'>
|
|
66
|
+
This tool displays the actual <acronym title='JavaScript Object Notation'>JSON</acronym> configuration that is generated by this editor and allows you to edit properties directly and apply them.
|
|
67
|
+
</p>
|
|
68
|
+
<button
|
|
69
|
+
className='btn '
|
|
70
|
+
onClick={() => {
|
|
71
|
+
navigator.clipboard.writeText(JSON.stringify(configTextboxValue))
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
Copy to Clipboard
|
|
75
|
+
</button>
|
|
76
|
+
<JsonEditor className='advanced-json-editor' data={configTextboxValue} onUpdate={onUpdate} rootName='' collapse={collapseFields} />
|
|
77
|
+
<button
|
|
78
|
+
className='btn full-width'
|
|
79
|
+
onClick={() => {
|
|
80
|
+
loadConfig(configTextboxValue)
|
|
81
|
+
setAdvancedToggle(!advancedToggle)
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
Apply
|
|
85
|
+
</button>
|
|
86
|
+
</React.Fragment>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
</>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default AdvancedEditor
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './AdvancedEditor'
|
|
@@ -19,6 +19,7 @@ import boxplotCellMatrix from './helpers/boxplotCellMatrix'
|
|
|
19
19
|
import removeNullColumns from './helpers/removeNullColumns'
|
|
20
20
|
import { TableConfig } from './types/TableConfig'
|
|
21
21
|
import { Column } from '../../types/Column'
|
|
22
|
+
import { pivotData } from '../../helpers/pivotData'
|
|
22
23
|
|
|
23
24
|
export type DataTableProps = {
|
|
24
25
|
applyLegendToRow?: Function
|
|
@@ -53,7 +54,17 @@ export type DataTableProps = {
|
|
|
53
54
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
54
55
|
const DataTable = (props: DataTableProps) => {
|
|
55
56
|
const { config, dataConfig, tableTitle, vizTitle, rawData, runtimeData: parentRuntimeData, headerColor, expandDataTable, columns, viewport, formatLegendLocation, tabbingId, wrapColumns } = props
|
|
56
|
-
const runtimeData =
|
|
57
|
+
const runtimeData = useMemo(() => {
|
|
58
|
+
const data = removeNullColumns(parentRuntimeData)
|
|
59
|
+
if (config.table.pivot) {
|
|
60
|
+
const { columnName, valueColumn } = config.table.pivot
|
|
61
|
+
if (columnName && valueColumn) {
|
|
62
|
+
return pivotData(data, columnName, valueColumn)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return data
|
|
66
|
+
}, [parentRuntimeData, config.table.pivot?.columnName, config.table.pivot?.valueColumn])
|
|
67
|
+
|
|
57
68
|
const [expanded, setExpanded] = useState(expandDataTable)
|
|
58
69
|
|
|
59
70
|
const [sortBy, setSortBy] = useState<any>({ column: config.type === 'map' ? 'geo' : 'date', asc: false, colIndex: null })
|
|
@@ -176,9 +187,17 @@ const DataTable = (props: DataTableProps) => {
|
|
|
176
187
|
}
|
|
177
188
|
}
|
|
178
189
|
|
|
190
|
+
const getMediaControlsClasses = () => {
|
|
191
|
+
const classes = ['download-links']
|
|
192
|
+
const isLegendOnBottom = config?.legend?.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(viewport)
|
|
193
|
+
if (config.brush?.active && !isLegendOnBottom) classes.push('brush-active')
|
|
194
|
+
if (config.brush?.active && config.legend.hide) classes.push('brush-active')
|
|
195
|
+
return classes
|
|
196
|
+
}
|
|
197
|
+
|
|
179
198
|
return (
|
|
180
199
|
<ErrorBoundary component='DataTable'>
|
|
181
|
-
<MediaControls.Section classes={
|
|
200
|
+
<MediaControls.Section classes={getMediaControlsClasses()}>
|
|
182
201
|
<MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
|
|
183
202
|
{(config.table.download || config.general?.showDownloadButton) && <DownloadButton rawData={getDownloadData()} fileName={`${vizTitle || 'data-table'}.csv`} headerColor={headerColor} />}
|
|
184
203
|
</MediaControls.Section>
|
|
@@ -5,6 +5,7 @@ import DataTable from './DataTable'
|
|
|
5
5
|
import DataTableEditorPanel from './components/DataTableEditorPanel'
|
|
6
6
|
import Filters from '../Filters'
|
|
7
7
|
import { TableConfig } from './types/TableConfig'
|
|
8
|
+
import { filterVizData } from '../../helpers/filterVizData'
|
|
8
9
|
|
|
9
10
|
type StandAloneProps = {
|
|
10
11
|
visualizationKey: string
|
|
@@ -14,34 +15,12 @@ type StandAloneProps = {
|
|
|
14
15
|
updateConfig?: (Visualization) => void
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
// filterData is copied from ./packages/chart/src/helpers/filterData.ts
|
|
18
|
-
// consider moving this to a shared location
|
|
19
|
-
const filterData = (filters, data) => {
|
|
20
|
-
if (!filters) return data
|
|
21
|
-
const filteredData: any[] = []
|
|
22
|
-
|
|
23
|
-
data.forEach(row => {
|
|
24
|
-
let add = true
|
|
25
|
-
filters
|
|
26
|
-
.filter(filter => filter.type !== 'url')
|
|
27
|
-
.forEach(filter => {
|
|
28
|
-
if (row[filter.columnName] != filter.active) {
|
|
29
|
-
add = false
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
if (add) filteredData.push(row)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
return filteredData
|
|
37
|
-
}
|
|
38
|
-
|
|
39
18
|
const DataTableStandAlone: React.FC<StandAloneProps> = ({ visualizationKey, config, updateConfig, viewport, isEditor }) => {
|
|
40
|
-
const [filteredData, setFilteredData] = useState<Record<string, any>[]>(
|
|
19
|
+
const [filteredData, setFilteredData] = useState<Record<string, any>[]>(filterVizData(config.filters, config.formattedData))
|
|
41
20
|
|
|
42
21
|
useEffect(() => {
|
|
43
22
|
// when using editor changes to filter should update the data
|
|
44
|
-
setFilteredData(
|
|
23
|
+
setFilteredData(filterVizData(config.filters, config?.formattedData?.length > 0 ? config.formattedData : config.data))
|
|
45
24
|
}, [config.filters])
|
|
46
25
|
|
|
47
26
|
if (isEditor)
|
|
@@ -53,7 +32,7 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({ visualizationKey, conf
|
|
|
53
32
|
|
|
54
33
|
return (
|
|
55
34
|
<>
|
|
56
|
-
<Filters config={config} setConfig={updateConfig} setFilteredData={setFilteredData}
|
|
35
|
+
<Filters config={config} setConfig={updateConfig} setFilteredData={setFilteredData} filteredData={filteredData} excludedData={config.formattedData} />
|
|
57
36
|
<DataTable expandDataTable={true} config={config} rawData={config.data} runtimeData={filteredData} tabbingId={visualizationKey} tableTitle={config.table.label} viewport={viewport || 'lg'} />
|
|
58
37
|
</>
|
|
59
38
|
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AccordionItem, AccordionItemButton, AccordionItemHeading, AccordionItemPanel } from 'react-accessible-accordion'
|
|
1
|
+
import { Accordion, AccordionItem, AccordionItemButton, AccordionItemHeading, AccordionItemPanel } from 'react-accessible-accordion'
|
|
2
2
|
import DataTableEditor from '../../EditorPanel/DataTableEditor'
|
|
3
3
|
import { Visualization } from '@cdc/core/types/Visualization'
|
|
4
4
|
import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
|
|
@@ -25,9 +25,9 @@ const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateCo
|
|
|
25
25
|
})
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const columns = Object.keys(config.originalFormattedData[0] || {})
|
|
28
|
+
const columns = Object.keys(config.originalFormattedData?.[0] || {})
|
|
29
29
|
return (
|
|
30
|
-
|
|
30
|
+
<Accordion allowZeroExpanded={true}>
|
|
31
31
|
<AccordionItem>
|
|
32
32
|
<AccordionItemHeading>
|
|
33
33
|
<AccordionItemButton>Filters</AccordionItemButton>
|
|
@@ -52,7 +52,7 @@ const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateCo
|
|
|
52
52
|
<DataTableEditor config={config} columns={columns} updateField={updateField} isDashboard={true} />
|
|
53
53
|
</AccordionItemPanel>
|
|
54
54
|
</AccordionItem>
|
|
55
|
-
|
|
55
|
+
</Accordion>
|
|
56
56
|
)
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Icon from '../../ui/Icon'
|
|
2
|
+
import { fontSizes } from '../../../helpers/cove/fontSettings'
|
|
2
3
|
|
|
3
4
|
const ExpandCollapse = ({ expanded, setExpanded, tableTitle, fontSize, viewport }) => {
|
|
4
|
-
const fontSizes = { small: 16, medium: 18, large: 20 }
|
|
5
5
|
const titleFontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? '13px' : `${fontSizes[fontSize]}px`
|
|
6
6
|
return (
|
|
7
7
|
<div
|
|
@@ -6,6 +6,7 @@ import { getChartCellValue } from './getChartCellValue'
|
|
|
6
6
|
import { getDataSeriesColumns } from './getDataSeriesColumns'
|
|
7
7
|
import { ReactNode } from 'react'
|
|
8
8
|
import { CellMatrix, GroupCellMatrix } from '../../Table/types/CellMatrix'
|
|
9
|
+
import { getRowType } from './getRowType'
|
|
9
10
|
|
|
10
11
|
type ChartRowsProps = DataTableProps & {
|
|
11
12
|
rows: string[]
|
|
@@ -58,15 +59,8 @@ const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorSc
|
|
|
58
59
|
} else {
|
|
59
60
|
return rows.map(row => {
|
|
60
61
|
if (hasRowType) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
dataSeriesColumns.forEach((column, j) => {
|
|
64
|
-
if (column.match(/row[_-]?type/i)) {
|
|
65
|
-
rowType = getChartCellValue(row, column, config, runtimeData)
|
|
66
|
-
} else {
|
|
67
|
-
rowValues.push(getChartCellValue(row, column, config, runtimeData))
|
|
68
|
-
}
|
|
69
|
-
})
|
|
62
|
+
const rowType = getRowType(runtimeData[row])
|
|
63
|
+
const rowValues = dataSeriesColumns.map(column => getChartCellValue(row, column, config, runtimeData))
|
|
70
64
|
return [rowType, ...rowValues]
|
|
71
65
|
} else {
|
|
72
66
|
return dataSeriesColumns.map((column, j) => getChartCellValue(row, column, config, runtimeData))
|
|
@@ -60,19 +60,23 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
|
|
|
60
60
|
const isValueMatch = String(pd.value) === String(labelValue)
|
|
61
61
|
// check entered suppression column against table key
|
|
62
62
|
const isColumnMatch = !pd.column || pd.column === column
|
|
63
|
+
const barSeriesExist = config.runtime?.barSeriesKeys?.includes(column)
|
|
64
|
+
const lineSeriesExist = config.runtime?.lineSeriesKeys?.includes(column)
|
|
65
|
+
const showSymbol = config.general.showSuppressedSymbol
|
|
63
66
|
if (isValueMatch && isColumnMatch && pd.displayTable && pd.type === 'suppression') {
|
|
64
67
|
switch (config.visualizationType) {
|
|
65
68
|
case 'Combo':
|
|
66
|
-
cellValue =
|
|
69
|
+
cellValue = barSeriesExist && showSymbol ? pd.iconCode : lineSeriesExist && showSymbol ? pd.lineCode : ''
|
|
67
70
|
break
|
|
68
71
|
case 'Bar':
|
|
69
|
-
cellValue = pd.iconCode
|
|
72
|
+
cellValue = !showSymbol ? '' : pd.iconCode
|
|
70
73
|
break
|
|
71
74
|
case 'Line':
|
|
72
|
-
cellValue = pd.lineCode
|
|
75
|
+
cellValue = !showSymbol ? '' : pd.lineCode
|
|
73
76
|
break
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
})
|
|
77
|
-
|
|
80
|
+
const shoMissingDataCellValue = config.general?.showMissingDataLabel && (!labelValue || labelValue === 'null')
|
|
81
|
+
return shoMissingDataCellValue ? 'N/A' : cellValue
|
|
78
82
|
}
|
|
@@ -3,7 +3,8 @@ import _ from 'lodash'
|
|
|
3
3
|
import { Column } from '../../../types/Column'
|
|
4
4
|
|
|
5
5
|
export const getDataSeriesColumns = (config: TableConfig, isVertical: boolean, runtimeData: Object[]): string[] => {
|
|
6
|
-
|
|
6
|
+
if (config.visualizationType === 'Sankey') return Object.keys(config?.data?.[0]?.tableData[0])
|
|
7
|
+
const configColumns = _.cloneDeep(config.columns) || ({} as Record<string, Column>)
|
|
7
8
|
const excludeColumns = Object.values(configColumns)
|
|
8
9
|
.filter(column => column.dataTable === false)
|
|
9
10
|
.map(column => column.name)
|
|
@@ -21,12 +22,15 @@ export const getDataSeriesColumns = (config: TableConfig, isVertical: boolean, r
|
|
|
21
22
|
tmpSeriesColumns = isVertical ? [config.xAxis?.dataKey, config.yAxis?.dataKey] : [config.yAxis?.dataKey]
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
const dataColumns = Object.keys(runtimeData[0] || {})
|
|
26
|
+
|
|
24
27
|
// then add the additional Columns
|
|
25
|
-
Object.
|
|
26
|
-
|
|
28
|
+
Object.values(configColumns).forEach(function (value) {
|
|
29
|
+
if (!value.name) return
|
|
27
30
|
// add if not the index AND it is enabled to be added to data table
|
|
28
31
|
const alreadyAdded = tmpSeriesColumns.includes(value.name)
|
|
29
|
-
|
|
32
|
+
const columnIsInData = dataColumns.includes(value.name) // null columns are excluded from data
|
|
33
|
+
if (value.name !== config.xAxis?.dataKey && value.dataTable === true && !alreadyAdded && columnIsInData) {
|
|
30
34
|
tmpSeriesColumns.push(value.name)
|
|
31
35
|
}
|
|
32
36
|
})
|
|
@@ -53,6 +57,5 @@ export const getDataSeriesColumns = (config: TableConfig, isVertical: boolean, r
|
|
|
53
57
|
})
|
|
54
58
|
|
|
55
59
|
tmpSeriesColumns.sort((a, b) => columnOrderingHash[a] - columnOrderingHash[b])
|
|
56
|
-
|
|
57
60
|
return tmpSeriesColumns
|
|
58
61
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AccordionItem, AccordionItemButton, AccordionItemHeading, AccordionItemPanel } from 'react-accessible-accordion'
|
|
2
1
|
import Tooltip from '../ui/Tooltip'
|
|
3
2
|
import Icon from '../ui/Icon'
|
|
4
3
|
import { TextField } from './Inputs'
|
|
@@ -7,6 +6,7 @@ import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
|
|
|
7
6
|
import { Column } from '../../types/Column'
|
|
8
7
|
import _ from 'lodash'
|
|
9
8
|
import React, { useState } from 'react'
|
|
9
|
+
import FieldSetWrapper from './FieldSetWrapper'
|
|
10
10
|
|
|
11
11
|
interface ColumnsEditorProps {
|
|
12
12
|
config: Partial<Visualization>
|
|
@@ -18,10 +18,6 @@ type OpenControls = [Record<string, boolean>, Function] // useState type
|
|
|
18
18
|
|
|
19
19
|
const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenControls }> = ({ config, deleteColumn, updateField, colKey, controls }) => {
|
|
20
20
|
const [openControls, setOpenControls] = controls
|
|
21
|
-
const show = openControls[colKey]
|
|
22
|
-
const setShow = (key, value) => {
|
|
23
|
-
setOpenControls({ ...openControls, [key]: value })
|
|
24
|
-
}
|
|
25
21
|
|
|
26
22
|
const editColumn = (key, value) => {
|
|
27
23
|
if (key === 'dataTable' && value === true) {
|
|
@@ -66,31 +62,8 @@ const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenCo
|
|
|
66
62
|
|
|
67
63
|
const colName = config.columns[colKey]?.name
|
|
68
64
|
|
|
69
|
-
if (!show)
|
|
70
|
-
return (
|
|
71
|
-
<div className='mb-1'>
|
|
72
|
-
<button onClick={() => setShow(colKey, true)}>
|
|
73
|
-
<Icon display='caretDown' />
|
|
74
|
-
</button>
|
|
75
|
-
<span> {colName ? `${colName}` : 'New Column'}</span>
|
|
76
|
-
</div>
|
|
77
|
-
)
|
|
78
65
|
return (
|
|
79
|
-
<
|
|
80
|
-
<div className='d-flex justify-content-between'>
|
|
81
|
-
<button onClick={() => setShow(colKey, false)}>
|
|
82
|
-
<Icon display='caretUp' />
|
|
83
|
-
</button>
|
|
84
|
-
<button
|
|
85
|
-
className='btn btn-danger btn-sm'
|
|
86
|
-
onClick={event => {
|
|
87
|
-
event.preventDefault()
|
|
88
|
-
deleteColumn(colKey)
|
|
89
|
-
}}
|
|
90
|
-
>
|
|
91
|
-
Remove
|
|
92
|
-
</button>
|
|
93
|
-
</div>
|
|
66
|
+
<FieldSetWrapper fieldName={colName} fieldKey={colKey} fieldType='Column' controls={controls} deleteField={() => deleteColumn(colKey)}>
|
|
94
67
|
<label>
|
|
95
68
|
<span className='edit-label column-heading'>Column</span>
|
|
96
69
|
<select
|
|
@@ -231,7 +204,7 @@ const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenCo
|
|
|
231
204
|
<span className='edit-label column-heading'>Order</span>
|
|
232
205
|
<input onWheel={e => e.currentTarget.blur()} type='number' min='1' value={config.columns[colKey].order} onChange={e => updateField('columns', colKey, 'order', parseInt(e.target.value))} />
|
|
233
206
|
</label>
|
|
234
|
-
</
|
|
207
|
+
</FieldSetWrapper>
|
|
235
208
|
)
|
|
236
209
|
}
|
|
237
210
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useMemo } from 'react'
|
|
2
2
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
3
3
|
import Icon from '../ui/Icon'
|
|
4
|
-
import { CheckBox, TextField } from './Inputs'
|
|
4
|
+
import { CheckBox, TextField, Select } from './Inputs'
|
|
5
5
|
import MultiSelect from '../MultiSelect'
|
|
6
6
|
import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
|
|
7
7
|
import { Visualization } from '../../types/Visualization'
|
|
@@ -26,15 +26,13 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
26
26
|
.map(([key]) => key)
|
|
27
27
|
}, [config.columns])
|
|
28
28
|
|
|
29
|
-
const
|
|
29
|
+
const groupPivotColumns = useMemo(() => {
|
|
30
30
|
const columns: string[] = config.data.flatMap(Object.keys)
|
|
31
|
-
const configuredColumns = Object.values(config.columns).map(col => col.name)
|
|
32
31
|
const cols = _.uniq(columns).filter(key => {
|
|
33
|
-
if (configuredColumns.includes(key)) return false
|
|
34
32
|
return true
|
|
35
33
|
})
|
|
36
34
|
return cols
|
|
37
|
-
}
|
|
35
|
+
}, [config.data])
|
|
38
36
|
|
|
39
37
|
const changeGroupBy = (value: string) => {
|
|
40
38
|
if (value === PLACEHOLDER) value = undefined
|
|
@@ -123,6 +121,7 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
123
121
|
}
|
|
124
122
|
/>
|
|
125
123
|
)}
|
|
124
|
+
|
|
126
125
|
{config.type !== 'table' && (
|
|
127
126
|
<TextField
|
|
128
127
|
value={config.table.indexLabel}
|
|
@@ -163,7 +162,7 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
163
162
|
/>
|
|
164
163
|
<CheckBox value={config.table.limitHeight} section='table' fieldName='limitHeight' label=' Limit Table Height' updateField={updateField} />
|
|
165
164
|
{config.table.limitHeight && <TextField value={config.table.height} section='table' fieldName='height' label='Data Table Height' type='number' min={0} max={500} placeholder='Height(px)' updateField={updateField} />}
|
|
166
|
-
<MultiSelect key={excludedColumns.join('') + 'excluded'} options={dataColumns.map(c => ({ label: c, value: c }))} selected={excludedColumns} fieldName='dataTable' label='Exclude Columns' section='columns' updateField={excludeColumns} />
|
|
165
|
+
{config?.visualizationType !== 'Sankey' && <MultiSelect key={excludedColumns.join('') + 'excluded'} options={dataColumns.map(c => ({ label: c, value: c }))} selected={excludedColumns} fieldName='dataTable' label='Exclude Columns' section='columns' updateField={excludeColumns} />}
|
|
167
166
|
<CheckBox value={config.table.collapsible} fieldName='collapsible' label=' Collapsible' section='table' updateField={updateField} />
|
|
168
167
|
{config.table.collapsible !== false && <CheckBox value={config.table.expanded} fieldName='expanded' label=' Expanded by Default' section='table' updateField={updateField} />}
|
|
169
168
|
{isDashboard && config.type !== 'table' && <CheckBox value={config.table.showDataTableLink} fieldName='showDataTableLink' label='Show Data Table Name & Link' section='table' updateField={updateField} />}
|
|
@@ -173,29 +172,74 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
173
172
|
<span className='edit-label column-heading'>Table Cell Min Width</span>
|
|
174
173
|
<input type='number' value={config.table.cellMinWidth ? config.table.cellMinWidth : 0} onChange={e => updateField('table', null, 'cellMinWidth', e.target.value)} />
|
|
175
174
|
</label>
|
|
176
|
-
|
|
177
|
-
<
|
|
178
|
-
|
|
175
|
+
{config?.visualizationType !== 'Sankey' && (
|
|
176
|
+
<label>
|
|
177
|
+
<span className='edit-label column-heading'>
|
|
178
|
+
Group By{' '}
|
|
179
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
180
|
+
<Tooltip.Target>
|
|
181
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
182
|
+
</Tooltip.Target>
|
|
183
|
+
<Tooltip.Content>
|
|
184
|
+
<p>Choose a column to use for grouping data rows. The selected column will not be shown in the data table. You will only be able to choose a column which does not have a column configuration.</p>
|
|
185
|
+
</Tooltip.Content>
|
|
186
|
+
</Tooltip>
|
|
187
|
+
</span>
|
|
188
|
+
|
|
189
|
+
<select
|
|
190
|
+
value={config.table.groupBy}
|
|
191
|
+
onChange={event => {
|
|
192
|
+
changeGroupBy(event.target.value)
|
|
193
|
+
}}
|
|
194
|
+
>
|
|
195
|
+
{[PLACEHOLDER, ...groupPivotColumns.filter(col => col !== config.table.pivot?.columnName && col !== config.table.pivot?.valueColumn)].map(option => (
|
|
196
|
+
<option key={option}>{option}</option>
|
|
197
|
+
))}
|
|
198
|
+
</select>
|
|
199
|
+
</label>
|
|
200
|
+
)}
|
|
201
|
+
<Select
|
|
202
|
+
label='Pivot Column: '
|
|
203
|
+
tooltip={
|
|
179
204
|
<Tooltip style={{ textTransform: 'none' }}>
|
|
180
205
|
<Tooltip.Target>
|
|
181
206
|
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
182
207
|
</Tooltip.Target>
|
|
183
208
|
<Tooltip.Content>
|
|
184
|
-
<p>
|
|
209
|
+
<p>Select a Column whos data values will be pivoted to Column Values.</p>
|
|
185
210
|
</Tooltip.Content>
|
|
186
211
|
</Tooltip>
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
212
|
+
}
|
|
213
|
+
value={config.table.pivot?.columnName}
|
|
214
|
+
options={groupPivotColumns.filter(col => col !== config.table.groupBy && col !== config.table.pivot?.valueColumn)}
|
|
215
|
+
initial='-Select-'
|
|
216
|
+
section='table'
|
|
217
|
+
subsection='pivot'
|
|
218
|
+
fieldName='columnName'
|
|
219
|
+
updateField={updateField}
|
|
220
|
+
/>
|
|
221
|
+
{config.table.pivot?.columnName && (
|
|
222
|
+
<Select
|
|
223
|
+
label='Pivot Value Column: '
|
|
224
|
+
tooltip={
|
|
225
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
226
|
+
<Tooltip.Target>
|
|
227
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
228
|
+
</Tooltip.Target>
|
|
229
|
+
<Tooltip.Content>
|
|
230
|
+
<p>The column whos values will be pivoted under the column selected as the Filter.</p>
|
|
231
|
+
</Tooltip.Content>
|
|
232
|
+
</Tooltip>
|
|
233
|
+
}
|
|
234
|
+
value={config.table.pivot?.valueColumn}
|
|
235
|
+
initial='-Select-'
|
|
236
|
+
section='table'
|
|
237
|
+
options={groupPivotColumns.filter(col => col !== config.table.pivot?.columnName && col !== config.table.groupBy)}
|
|
238
|
+
subsection='pivot'
|
|
239
|
+
fieldName='valueColumn'
|
|
240
|
+
updateField={updateField}
|
|
241
|
+
/>
|
|
242
|
+
)}
|
|
199
243
|
</>
|
|
200
244
|
)
|
|
201
245
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import Icon from '../ui/Icon'
|
|
2
|
+
|
|
3
|
+
type OpenControls = [Record<string, boolean>, Function] // useState type
|
|
4
|
+
|
|
5
|
+
type FieldSetProps = {
|
|
6
|
+
fieldName: string
|
|
7
|
+
fieldKey: string | number
|
|
8
|
+
fieldType: string
|
|
9
|
+
controls: OpenControls
|
|
10
|
+
deleteField: Function
|
|
11
|
+
children: React.ReactNode
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const FieldSet: React.FC<FieldSetProps> = ({ fieldName, fieldKey, fieldType, controls, deleteField, children }) => {
|
|
15
|
+
const [openControls, setOpenControls] = controls
|
|
16
|
+
const show = openControls[fieldKey]
|
|
17
|
+
const setShow = (key, value) => {
|
|
18
|
+
setOpenControls({ ...openControls, [key]: value })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!show)
|
|
22
|
+
return (
|
|
23
|
+
<div className='mb-1'>
|
|
24
|
+
<button onClick={() => setShow(fieldKey, true)}>
|
|
25
|
+
<Icon display='caretDown' />
|
|
26
|
+
</button>
|
|
27
|
+
<span> {fieldName ? `${fieldName}` : 'New ' + fieldType}</span>
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
return (
|
|
31
|
+
<fieldset className='edit-block mb-1' key={fieldKey}>
|
|
32
|
+
<div className='d-flex justify-content-between'>
|
|
33
|
+
<button onClick={() => setShow(fieldKey, false)}>
|
|
34
|
+
<Icon display='caretUp' />
|
|
35
|
+
</button>
|
|
36
|
+
<button
|
|
37
|
+
className='btn btn-danger btn-sm'
|
|
38
|
+
onClick={event => {
|
|
39
|
+
event.preventDefault()
|
|
40
|
+
deleteField()
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
Remove
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
{children}
|
|
47
|
+
</fieldset>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default FieldSet
|