@cdc/core 4.25.3 → 4.25.6-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/assets/icon-close.svg +1 -1
- package/components/Alert/components/Alert.tsx +1 -1
- package/components/DataTable/DataTable.tsx +18 -16
- package/components/DataTable/DataTableStandAlone.tsx +15 -9
- package/components/DataTable/components/CellAnchor.tsx +1 -1
- package/components/DataTable/components/ChartHeader.tsx +8 -5
- package/components/DataTable/components/DataTableEditorPanel.tsx +25 -3
- package/components/DataTable/components/MapHeader.tsx +1 -0
- package/components/DataTable/helpers/chartCellMatrix.tsx +14 -10
- package/components/DataTable/helpers/getChartCellValue.ts +42 -26
- package/components/DataTable/helpers/mapCellMatrix.tsx +25 -7
- package/components/DownloadButton.tsx +17 -2
- package/components/EditorPanel/DataTableEditor.tsx +1 -1
- package/components/EditorPanel/FootnotesEditor.tsx +76 -22
- package/components/EditorPanel/Inputs.tsx +12 -4
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +3 -2
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +51 -35
- package/components/Filters/Filters.tsx +158 -461
- package/components/Filters/components/Dropdown.tsx +39 -0
- package/components/Filters/components/Tabs.tsx +82 -0
- package/components/Filters/helpers/getChangedFilters.ts +31 -0
- package/components/Filters/helpers/getNestedOptions.ts +2 -2
- package/components/Filters/helpers/handleSorting.ts +2 -2
- package/components/Filters/helpers/tests/getChangedFilters.test.ts +92 -0
- package/components/Filters/helpers/tests/getNestedOptions.test.ts +31 -0
- package/components/Filters/index.ts +1 -1
- package/components/Footnotes/Footnotes.tsx +1 -1
- package/components/Footnotes/FootnotesStandAlone.tsx +8 -33
- package/components/Layout/components/Visualization/index.tsx +4 -3
- package/components/Legend/Legend.Gradient.tsx +68 -24
- package/components/MultiSelect/MultiSelect.tsx +3 -6
- package/components/MultiSelect/multiselect.styles.css +2 -0
- package/components/NestedDropdown/NestedDropdown.tsx +21 -21
- package/components/RichTooltip/RichTooltip.tsx +37 -0
- package/components/RichTooltip/richTooltip.css +16 -0
- package/components/Table/Table.tsx +142 -142
- package/components/Table/components/Row.tsx +1 -1
- package/components/Table/table.styles.css +10 -0
- package/components/_stories/DataTable.stories.tsx +9 -2
- package/components/_stories/Table.stories.tsx +1 -1
- package/components/_stories/styles.scss +0 -4
- package/components/ui/Accordion.jsx +8 -1
- package/components/ui/Title/index.tsx +4 -1
- package/components/ui/Title/{Title.scss → title.styles.css} +0 -2
- package/components/ui/_stories/Colors.stories.mdx +220 -0
- package/components/ui/_stories/IconGallery.stories.mdx +14 -0
- package/components/ui/_stories/Title.stories.tsx +29 -4
- package/components/ui/accordion.styles.css +3 -0
- package/data/colorPalettes.js +0 -1
- package/dist/cove-main.css +3 -8
- package/dist/cove-main.css.map +1 -1
- package/helpers/constants.ts +6 -0
- package/helpers/cove/accessibility.ts +7 -8
- package/helpers/cove/number.ts +5 -3
- package/helpers/coveUpdateWorker.ts +9 -1
- package/helpers/filterOrderOptions.ts +17 -0
- package/helpers/formatConfigBeforeSave.ts +19 -32
- package/helpers/isNumber.ts +20 -0
- package/helpers/isRightAlignedTableValue.js +1 -1
- package/helpers/pivotData.ts +16 -11
- package/helpers/tests/pivotData.test.ts +74 -0
- package/helpers/updateFieldFactory.ts +1 -0
- package/helpers/ver/4.25.3.ts +25 -2
- package/helpers/ver/4.25.4.ts +110 -0
- package/helpers/ver/4.25.6.ts +36 -0
- package/helpers/ver/4.25.7.ts +26 -0
- package/helpers/ver/tests/4.25.4.test.ts +89 -0
- package/helpers/ver/tests/4.25.6.test.ts +84 -0
- package/helpers/viewports.ts +4 -0
- package/package.json +7 -6
- package/styles/_global-variables.scss +3 -0
- package/styles/_global.scss +0 -4
- package/styles/_reset.scss +0 -6
- package/styles/filters.scss +0 -4
- package/styles/v2/main.scss +0 -5
- package/types/Axis.ts +2 -0
- package/types/DataSet.ts +14 -0
- package/types/Footnotes.ts +5 -2
- package/types/General.ts +1 -0
- package/types/Legend.ts +1 -0
- package/types/Table.ts +1 -0
- package/types/Visualization.ts +3 -12
- package/types/VizFilter.ts +3 -0
- package/components/ui/_stories/Colors.stories.tsx +0 -92
- package/components/ui/_stories/Icon.stories.tsx +0 -29
- package/helpers/cove/fontSettings.ts +0 -2
- package/helpers/isNumber.js +0 -24
- package/helpers/isNumberLog.js +0 -18
- /package/helpers/{fetchRemoteData.js → fetchRemoteData.ts} +0 -0
package/assets/icon-close.svg
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" viewBox="0 0 24 24" fill="currentColor">
|
|
2
2
|
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
|
|
3
3
|
</svg>
|
|
@@ -59,7 +59,7 @@ const Alert: React.FC<AlertProps> = ({
|
|
|
59
59
|
<span dangerouslySetInnerHTML={sanitizedData()} />
|
|
60
60
|
</div>
|
|
61
61
|
{showCloseButton && (
|
|
62
|
-
<button type='button' className='close
|
|
62
|
+
<button type='button' className='close ps-5' aria-label='Close' onClick={() => onDismiss()}>
|
|
63
63
|
X
|
|
64
64
|
</button>
|
|
65
65
|
)}
|
|
@@ -27,7 +27,6 @@ import _ from 'lodash'
|
|
|
27
27
|
import { getDataSeriesColumns } from './helpers/getDataSeriesColumns'
|
|
28
28
|
|
|
29
29
|
export type DataTableProps = {
|
|
30
|
-
applyLegendToRow?: Function
|
|
31
30
|
colorScale?: Function
|
|
32
31
|
columns?: Record<string, Column>
|
|
33
32
|
config: TableConfig
|
|
@@ -35,7 +34,7 @@ export type DataTableProps = {
|
|
|
35
34
|
defaultSortBy?: string
|
|
36
35
|
displayGeoName?: (row: string) => string
|
|
37
36
|
expandDataTable: boolean
|
|
38
|
-
formatLegendLocation?: (row: string) => string
|
|
37
|
+
formatLegendLocation?: (row: string, runtimeLookup: string) => string
|
|
39
38
|
groupBy?: string
|
|
40
39
|
headerColor?: string
|
|
41
40
|
imageRef?: string
|
|
@@ -58,20 +57,20 @@ export type DataTableProps = {
|
|
|
58
57
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
59
58
|
const DataTable = (props: DataTableProps) => {
|
|
60
59
|
const {
|
|
60
|
+
columns,
|
|
61
61
|
config,
|
|
62
62
|
dataConfig,
|
|
63
63
|
defaultSortBy,
|
|
64
64
|
displayGeoName,
|
|
65
|
-
tableTitle,
|
|
66
|
-
vizTitle,
|
|
67
|
-
rawData,
|
|
68
|
-
runtimeData: parentRuntimeData,
|
|
69
|
-
headerColor,
|
|
70
65
|
expandDataTable,
|
|
71
|
-
columns,
|
|
72
|
-
viewport,
|
|
73
66
|
formatLegendLocation,
|
|
67
|
+
headerColor,
|
|
68
|
+
rawData,
|
|
69
|
+
runtimeData: parentRuntimeData,
|
|
74
70
|
tabbingId,
|
|
71
|
+
tableTitle,
|
|
72
|
+
viewport,
|
|
73
|
+
vizTitle,
|
|
75
74
|
wrapColumns
|
|
76
75
|
} = props
|
|
77
76
|
const runtimeData = useMemo(() => {
|
|
@@ -127,9 +126,6 @@ const DataTable = (props: DataTableProps) => {
|
|
|
127
126
|
case 'Box Plot':
|
|
128
127
|
if (!config.boxplot) return <Loading />
|
|
129
128
|
break
|
|
130
|
-
case 'Line' || 'Bar' || 'Combo' || 'Pie' || 'Deviation Bar' || 'Paired Bar' || 'Sankey' || 'Table':
|
|
131
|
-
if (!runtimeData) return <Loading />
|
|
132
|
-
break
|
|
133
129
|
default:
|
|
134
130
|
if (!runtimeData) return <Loading />
|
|
135
131
|
break
|
|
@@ -248,9 +244,14 @@ const DataTable = (props: DataTableProps) => {
|
|
|
248
244
|
const csvData = config.table?.downloadVisibleDataOnly ? visibleData : rawData
|
|
249
245
|
|
|
250
246
|
// only use fullGeoName on County maps and no other
|
|
251
|
-
if (config.general?.geoType === 'us-county') {
|
|
247
|
+
if (config.general?.geoType === 'us-county' || config.table.showFullGeoNameInCSV) {
|
|
252
248
|
// Add column for full Geo name along with State
|
|
253
|
-
return csvData.map(row =>
|
|
249
|
+
return csvData.map(row => {
|
|
250
|
+
return {
|
|
251
|
+
FullGeoName: formatLegendLocation(row[config.columns.geo.name]),
|
|
252
|
+
...row
|
|
253
|
+
}
|
|
254
|
+
})
|
|
254
255
|
} else {
|
|
255
256
|
return csvData
|
|
256
257
|
}
|
|
@@ -275,7 +276,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
275
276
|
|
|
276
277
|
const childrenMatrix =
|
|
277
278
|
config.type === 'map'
|
|
278
|
-
? mapCellMatrix({ rows, wrapColumns,
|
|
279
|
+
? mapCellMatrix({ ...props, rows, wrapColumns, runtimeData, viewport })
|
|
279
280
|
: chartCellMatrix({ rows, ...props, runtimeData, isVertical, sortBy, hasRowType, viewport })
|
|
280
281
|
|
|
281
282
|
// If every value in a column is a number, record the column index so the header and cells can be right-aligned
|
|
@@ -352,7 +353,8 @@ const DataTable = (props: DataTableProps) => {
|
|
|
352
353
|
}`,
|
|
353
354
|
'aria-live': 'assertive',
|
|
354
355
|
'aria-rowcount': config?.data?.length ? config.data.length : -1,
|
|
355
|
-
hidden: !expanded
|
|
356
|
+
hidden: !expanded,
|
|
357
|
+
cellMinWidth: 100
|
|
356
358
|
}}
|
|
357
359
|
rightAlignedCols={rightAlignedCols}
|
|
358
360
|
/>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
import { ViewPort } from '../../types/ViewPort'
|
|
3
|
+
import Footnotes from '../../types/Footnotes'
|
|
3
4
|
import EditorWrapper from '../EditorWrapper/EditorWrapper'
|
|
4
5
|
import DataTable from './DataTable'
|
|
5
6
|
import DataTableEditorPanel from './components/DataTableEditorPanel'
|
|
@@ -7,6 +8,8 @@ import Filters from '../Filters'
|
|
|
7
8
|
import { TableConfig } from './types/TableConfig'
|
|
8
9
|
import { filterVizData } from '../../helpers/filterVizData'
|
|
9
10
|
import { addValuesToFilters } from '../../helpers/addValuesToFilters'
|
|
11
|
+
import FootnotesStandAlone from '../Footnotes/FootnotesStandAlone'
|
|
12
|
+
import { Datasets } from '@cdc/core/types/DataSet'
|
|
10
13
|
|
|
11
14
|
type StandAloneProps = {
|
|
12
15
|
visualizationKey: string
|
|
@@ -14,6 +17,7 @@ type StandAloneProps = {
|
|
|
14
17
|
viewport?: ViewPort
|
|
15
18
|
isEditor?: boolean
|
|
16
19
|
updateConfig?: (Visualization) => void
|
|
20
|
+
datasets?: Datasets
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
@@ -21,7 +25,8 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
|
21
25
|
config,
|
|
22
26
|
updateConfig,
|
|
23
27
|
viewport,
|
|
24
|
-
isEditor
|
|
28
|
+
isEditor,
|
|
29
|
+
datasets
|
|
25
30
|
}) => {
|
|
26
31
|
const [filteredData, setFilteredData] = useState<Record<string, any>[]>(
|
|
27
32
|
filterVizData(config.filters, config.formattedData || config.data)
|
|
@@ -33,6 +38,12 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
|
33
38
|
setFilteredData(filterVizData(filters, config?.formattedData?.length > 0 ? config.formattedData : config.data))
|
|
34
39
|
}, [config.filters])
|
|
35
40
|
|
|
41
|
+
const setFilters = (newFilters: any) => {
|
|
42
|
+
const filters = addValuesToFilters(newFilters, config.data)
|
|
43
|
+
updateConfig({ ...config, filters })
|
|
44
|
+
setFilteredData(filterVizData(filters, config?.formattedData?.length > 0 ? config.formattedData : config.data))
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
if (isEditor)
|
|
37
48
|
return (
|
|
38
49
|
<EditorWrapper
|
|
@@ -43,19 +54,13 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
|
43
54
|
type={'Table'}
|
|
44
55
|
viewport={viewport}
|
|
45
56
|
>
|
|
46
|
-
<DataTableEditorPanel key={visualizationKey} config={config} updateConfig={updateConfig} />
|
|
57
|
+
<DataTableEditorPanel key={visualizationKey} config={config} updateConfig={updateConfig} datasets={datasets} />
|
|
47
58
|
</EditorWrapper>
|
|
48
59
|
)
|
|
49
60
|
|
|
50
61
|
return (
|
|
51
62
|
<>
|
|
52
|
-
<Filters
|
|
53
|
-
config={config}
|
|
54
|
-
setConfig={updateConfig}
|
|
55
|
-
setFilteredData={setFilteredData}
|
|
56
|
-
filteredData={filteredData}
|
|
57
|
-
excludedData={config.formattedData}
|
|
58
|
-
/>
|
|
63
|
+
<Filters config={config} setFilters={setFilters} excludedData={config.formattedData} />
|
|
59
64
|
<DataTable
|
|
60
65
|
expandDataTable={config.table.expanded}
|
|
61
66
|
config={config}
|
|
@@ -65,6 +70,7 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
|
65
70
|
tableTitle={config.table.label}
|
|
66
71
|
viewport={viewport || 'lg'}
|
|
67
72
|
/>
|
|
73
|
+
<FootnotesStandAlone config={config.footnotes} filters={config.filters?.filter(f => f.filterFootnotes)} />
|
|
68
74
|
</>
|
|
69
75
|
)
|
|
70
76
|
}
|
|
@@ -6,7 +6,7 @@ const CellAnchor = ({ markup, row, columns, navigationHandler, mapZoomHandler })
|
|
|
6
6
|
if (columns.navigate && row[columns.navigate.name]) {
|
|
7
7
|
return (
|
|
8
8
|
<span
|
|
9
|
-
onClick={() => navigationHandler(row[columns.navigate.name])}
|
|
9
|
+
onClick={() => navigationHandler('_blank', row[columns.navigate.name])}
|
|
10
10
|
className='table-link'
|
|
11
11
|
title='Click for more information (Opens in a new window)'
|
|
12
12
|
role='link'
|
|
@@ -4,6 +4,7 @@ import { getDataSeriesColumns } from '../helpers/getDataSeriesColumns'
|
|
|
4
4
|
import ScreenReaderText from '@cdc/core/components/elements/ScreenReaderText'
|
|
5
5
|
import { SortIcon } from './SortIcon'
|
|
6
6
|
import { getNewSortBy } from '../helpers/getNewSortBy'
|
|
7
|
+
import parse from 'html-react-parser'
|
|
7
8
|
|
|
8
9
|
type ChartHeaderProps = { data; isVertical; config; setSortBy; sortBy; hasRowType?; viewport; rightAlignedCols }
|
|
9
10
|
|
|
@@ -47,9 +48,8 @@ const ChartHeader = ({
|
|
|
47
48
|
if (columnHeaderText === notApplicableText) return
|
|
48
49
|
|
|
49
50
|
return (
|
|
50
|
-
<span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${
|
|
51
|
-
|
|
52
|
-
} order`}</span>
|
|
51
|
+
<span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${sortBy.column !== columnHeaderText ? 'ascending' : sortBy.column === 'desc' ? 'descending' : 'ascending'
|
|
52
|
+
} order`}</span>
|
|
53
53
|
)
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -75,7 +75,7 @@ const ChartHeader = ({
|
|
|
75
75
|
return (
|
|
76
76
|
<tr>
|
|
77
77
|
{dataSeriesColumns.map((column, index) => {
|
|
78
|
-
const text = getSeriesName(column, config)
|
|
78
|
+
const text = parse(getSeriesName(column, config))
|
|
79
79
|
const newSortBy = getNewSortBy(sortBy, column, index)
|
|
80
80
|
const sortByAsc = sortBy.column === column ? sortBy.asc : undefined
|
|
81
81
|
const isSortedCol = column === sortBy.column && !hasRowType
|
|
@@ -120,8 +120,11 @@ const ChartHeader = ({
|
|
|
120
120
|
return (
|
|
121
121
|
<tr>
|
|
122
122
|
{['__series__', ...Object.keys(data)].slice(sliceVal).map((row, index) => {
|
|
123
|
+
const rightAxisItems = config.series?.filter(item => item?.axis === 'Right') || []
|
|
124
|
+
const rightAxisItemsMap = new Map(rightAxisItems.map(item => [item.dataKey, item]))
|
|
125
|
+
|
|
123
126
|
let column = config.xAxis?.dataKey
|
|
124
|
-
let text = row !== '__series__' ? getChartCellValue(row, column, config, data) : '__series__'
|
|
127
|
+
let text = row !== '__series__' ? getChartCellValue(row, column, config, data, rightAxisItemsMap) : '__series__'
|
|
125
128
|
const newSortBy = getNewSortBy(sortBy, column, index)
|
|
126
129
|
const sortByAsc = sortBy.colIndex === index ? sortBy.asc : undefined
|
|
127
130
|
const isSortedCol = index === sortBy.colIndex && !hasRowType
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Accordion,
|
|
3
|
+
AccordionItem,
|
|
4
|
+
AccordionItemButton,
|
|
5
|
+
AccordionItemHeading,
|
|
6
|
+
AccordionItemPanel
|
|
7
|
+
} from 'react-accessible-accordion'
|
|
2
8
|
import DataTableEditor from '../../EditorPanel/DataTableEditor'
|
|
3
9
|
import { Visualization } from '@cdc/core/types/Visualization'
|
|
4
10
|
import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
|
|
@@ -6,13 +12,16 @@ import { useMemo } from 'react'
|
|
|
6
12
|
import ColumnsEditor from '../../EditorPanel/ColumnsEditor'
|
|
7
13
|
import VizFilterEditor from '../../EditorPanel/VizFilterEditor'
|
|
8
14
|
import _ from 'lodash'
|
|
15
|
+
import FootnotesEditor from '../../EditorPanel/FootnotesEditor'
|
|
16
|
+
import { Datasets } from '@cdc/core/types/DataSet'
|
|
9
17
|
|
|
10
18
|
type DataTableEditorProps = {
|
|
11
19
|
config: Visualization
|
|
12
20
|
updateConfig: Function
|
|
21
|
+
datasets?: Datasets
|
|
13
22
|
}
|
|
14
23
|
|
|
15
|
-
const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateConfig }) => {
|
|
24
|
+
const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateConfig, datasets }) => {
|
|
16
25
|
const updateField = useMemo(() => updateFieldFactory(config, updateConfig), [JSON.stringify(config)])
|
|
17
26
|
const deleteColumn = columnName => {
|
|
18
27
|
const newColumns = _.cloneDeep(config.columns)
|
|
@@ -33,7 +42,12 @@ const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateCo
|
|
|
33
42
|
<AccordionItemButton>Filters</AccordionItemButton>
|
|
34
43
|
</AccordionItemHeading>
|
|
35
44
|
<AccordionItemPanel>
|
|
36
|
-
<VizFilterEditor
|
|
45
|
+
<VizFilterEditor
|
|
46
|
+
config={config}
|
|
47
|
+
updateField={updateField}
|
|
48
|
+
rawData={config.originalFormattedData}
|
|
49
|
+
hasFootnotes={true}
|
|
50
|
+
/>
|
|
37
51
|
</AccordionItemPanel>
|
|
38
52
|
</AccordionItem>
|
|
39
53
|
<AccordionItem>
|
|
@@ -52,6 +66,14 @@ const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateCo
|
|
|
52
66
|
<DataTableEditor config={config} columns={columns} updateField={updateField} isDashboard={true} />
|
|
53
67
|
</AccordionItemPanel>
|
|
54
68
|
</AccordionItem>
|
|
69
|
+
<AccordionItem>
|
|
70
|
+
<AccordionItemHeading>
|
|
71
|
+
<AccordionItemButton>Footnotes</AccordionItemButton>
|
|
72
|
+
</AccordionItemHeading>
|
|
73
|
+
<AccordionItemPanel>
|
|
74
|
+
<FootnotesEditor config={config} updateField={updateField} datasets={datasets} />
|
|
75
|
+
</AccordionItemPanel>
|
|
76
|
+
</AccordionItem>
|
|
55
77
|
</Accordion>
|
|
56
78
|
)
|
|
57
79
|
}
|
|
@@ -36,6 +36,7 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy, rightAligne
|
|
|
36
36
|
return (
|
|
37
37
|
<th
|
|
38
38
|
style={{
|
|
39
|
+
minWidth: (config.table.cellMinWidth || 0) + 'px',
|
|
39
40
|
textAlign: rightAlignedCols && rightAlignedCols[index] ? 'right' : '',
|
|
40
41
|
paddingRight: '1.3em'
|
|
41
42
|
}}
|
|
@@ -7,6 +7,7 @@ import { getDataSeriesColumns } from './getDataSeriesColumns'
|
|
|
7
7
|
import { ReactNode } from 'react'
|
|
8
8
|
import { CellMatrix, GroupCellMatrix } from '../../Table/types/CellMatrix'
|
|
9
9
|
import { getRowType } from './getRowType'
|
|
10
|
+
import parse from 'html-react-parser'
|
|
10
11
|
|
|
11
12
|
type ChartRowsProps = DataTableProps & {
|
|
12
13
|
rows: string[]
|
|
@@ -25,9 +26,12 @@ const chartCellArray = ({
|
|
|
25
26
|
colorScale,
|
|
26
27
|
hasRowType
|
|
27
28
|
}: ChartRowsProps): CellMatrix | GroupCellMatrix => {
|
|
29
|
+
const rightAxisItems = config.series?.filter(item => item?.axis === 'Right') || []
|
|
30
|
+
const rightAxisItemsMap = new Map(rightAxisItems.map(item => [item.dataKey, item]))
|
|
28
31
|
const groupBy = config.table?.groupBy
|
|
29
32
|
const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
|
|
30
33
|
|
|
34
|
+
|
|
31
35
|
const dataSeriesColumnsSorted = () => {
|
|
32
36
|
if (!sortBy && sortBy.colIndex === null) return dataSeriesColumns
|
|
33
37
|
return dataSeriesColumns.sort((a, b) => {
|
|
@@ -52,9 +56,9 @@ const chartCellArray = ({
|
|
|
52
56
|
let groupValues = []
|
|
53
57
|
dataSeriesColumns.forEach((column, j) => {
|
|
54
58
|
if (groupBy === column) {
|
|
55
|
-
groupKey = getChartCellValue(row, column, config, runtimeData)
|
|
59
|
+
groupKey = getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap)
|
|
56
60
|
} else {
|
|
57
|
-
groupValues.push(getChartCellValue(row, column, config, runtimeData))
|
|
61
|
+
groupValues.push(getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap))
|
|
58
62
|
}
|
|
59
63
|
})
|
|
60
64
|
if (!cellMatrix.has(groupKey)) {
|
|
@@ -69,10 +73,10 @@ const chartCellArray = ({
|
|
|
69
73
|
return rows.map(row => {
|
|
70
74
|
if (hasRowType) {
|
|
71
75
|
const rowType = getRowType(runtimeData[row])
|
|
72
|
-
const rowValues = dataSeriesColumns.map(column => getChartCellValue(row, column, config, runtimeData))
|
|
76
|
+
const rowValues = dataSeriesColumns.map(column => getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap))
|
|
73
77
|
return [rowType, ...rowValues]
|
|
74
78
|
} else {
|
|
75
|
-
return dataSeriesColumns.map((column, j) => getChartCellValue(row, column, config, runtimeData))
|
|
79
|
+
return dataSeriesColumns.map((column, j) => getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap))
|
|
76
80
|
}
|
|
77
81
|
})
|
|
78
82
|
}
|
|
@@ -82,13 +86,13 @@ const chartCellArray = ({
|
|
|
82
86
|
let nodes: ReactNode[] =
|
|
83
87
|
config.visualizationType !== 'Pie'
|
|
84
88
|
? [
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
<>
|
|
90
|
+
{colorScale && colorScale(seriesName) && <LegendShape fill={colorScale(seriesName)} />}
|
|
91
|
+
{parse(seriesName)}
|
|
92
|
+
</>
|
|
93
|
+
]
|
|
90
94
|
: []
|
|
91
|
-
return nodes.concat(rows.map((row, i) => getChartCellValue(row, column, config, runtimeData)))
|
|
95
|
+
return nodes.concat(rows.map((row, i) => getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap)))
|
|
92
96
|
})
|
|
93
97
|
}
|
|
94
98
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseDate, formatDate } from '@cdc/core/helpers/cove/date'
|
|
2
2
|
import { formatNumber } from '../../../helpers/cove/number'
|
|
3
3
|
import { TableConfig } from '../types/TableConfig'
|
|
4
|
+
import _ from 'lodash'
|
|
4
5
|
|
|
5
6
|
const isPivotColumn = (columnName, config) => {
|
|
6
7
|
const tableHasPivotColumnConfigured = config.table.pivot?.valueColumns?.length
|
|
@@ -30,16 +31,28 @@ const isAdditionalColumn = (column: string, config, rowData) => {
|
|
|
30
31
|
return formattingParams
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
export const getChartCellValue = (row: string, column: string, config: TableConfig, runtimeData: Object[]) => {
|
|
34
|
-
if (config.visualizationType === 'Sankey' || runtimeData?.[0]?.tableData) return runtimeData[row][column]
|
|
34
|
+
export const getChartCellValue = (row: string, column: string, config: TableConfig, runtimeData: Object[], rightAxisItemsMap) => {
|
|
35
35
|
|
|
36
|
+
// Variables for xAxis config
|
|
37
|
+
const { type, dateDisplayFormat, dateParseFormat, dataKey: xAxisDataKey } = config.xAxis || {}
|
|
38
|
+
const { showMissingDataLabel } = config.general || {}
|
|
39
|
+
const { visualizationType } = config || {}
|
|
40
|
+
|
|
41
|
+
// Early returns...
|
|
36
42
|
const rowObj = runtimeData[row]
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
if (!rowObj) return
|
|
44
|
+
|
|
45
|
+
if (visualizationType === 'Sankey' || runtimeData?.[0]?.tableData) {
|
|
46
|
+
return runtimeData?.[row]?.[column]
|
|
47
|
+
}
|
|
42
48
|
|
|
49
|
+
let cellValue
|
|
50
|
+
const rightMatch = rightAxisItemsMap.get(column)
|
|
51
|
+
const resolvedAxis = rightMatch ? 'right' : 'left'
|
|
52
|
+
const labelValue = rowObj[column]
|
|
53
|
+
|
|
54
|
+
if (column === xAxisDataKey) {
|
|
55
|
+
const dateFormat = config.table?.dateDisplayFormat || dateDisplayFormat
|
|
43
56
|
if (type === 'date' || type === 'date-time') {
|
|
44
57
|
cellValue = formatDate(dateFormat, parseDate(dateParseFormat, labelValue))
|
|
45
58
|
} else if (type === 'continuous') {
|
|
@@ -48,27 +61,30 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
|
|
|
48
61
|
cellValue = labelValue
|
|
49
62
|
}
|
|
50
63
|
} else {
|
|
51
|
-
let resolvedAxis = 'left'
|
|
52
|
-
let leftAxisItems = config.series ? config.series.filter(item => item?.axis === 'Left') : []
|
|
53
|
-
let rightAxisItems = config.series ? config.series.filter(item => item?.axis === 'Right') : []
|
|
54
64
|
|
|
55
|
-
|
|
56
|
-
if (leftSeriesItem.dataKey === column) resolvedAxis = 'left'
|
|
57
|
-
})
|
|
65
|
+
let addColParams = isAdditionalColumn(column, config, rowObj)
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
let piePercent = 0
|
|
68
|
+
if (config.visualizationType === 'Pie' && !config.dataFormat.showPiePercent) {
|
|
69
|
+
piePercent = (_.toNumber(runtimeData[row][column]) / _.sumBy(runtimeData, d => _.toNumber(d[column]))) * 100 || 0
|
|
70
|
+
}
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
const valueToFormat = config.visualizationType === 'Pie' && !config.dataFormat.showPiePercent
|
|
73
|
+
? piePercent
|
|
74
|
+
: runtimeData[row][column]
|
|
75
|
+
|
|
76
|
+
const hasAdditionalParams = Object.keys(addColParams).length > 0
|
|
77
|
+
|
|
78
|
+
if (config.dataFormat) {
|
|
79
|
+
cellValue = formatNumber(
|
|
80
|
+
valueToFormat,
|
|
81
|
+
resolvedAxis,
|
|
82
|
+
false,
|
|
83
|
+
config,
|
|
84
|
+
hasAdditionalParams ? addColParams : undefined
|
|
85
|
+
)
|
|
68
86
|
} else {
|
|
69
|
-
cellValue =
|
|
70
|
-
? formatNumber(runtimeData[row][column], resolvedAxis, false, config)
|
|
71
|
-
: runtimeData[row][column]
|
|
87
|
+
cellValue = runtimeData[row][column]
|
|
72
88
|
}
|
|
73
89
|
}
|
|
74
90
|
|
|
@@ -101,6 +117,6 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
|
|
|
101
117
|
}
|
|
102
118
|
}
|
|
103
119
|
})
|
|
104
|
-
const
|
|
105
|
-
return
|
|
120
|
+
const showMissingDataCellValue = showMissingDataLabel && (!labelValue || labelValue === 'null')
|
|
121
|
+
return showMissingDataCellValue ? 'N/A' : cellValue
|
|
106
122
|
}
|
|
@@ -4,6 +4,8 @@ import { DataTableProps } from '../DataTable'
|
|
|
4
4
|
import { ReactNode } from 'react'
|
|
5
5
|
import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
|
|
6
6
|
import _ from 'lodash'
|
|
7
|
+
import { applyLegendToRow } from '@cdc/map/src/helpers/applyLegendToRow'
|
|
8
|
+
import { hashObj } from '@cdc/map/src/helpers'
|
|
7
9
|
|
|
8
10
|
type MapRowsProps = DataTableProps & {
|
|
9
11
|
rows: string[]
|
|
@@ -72,27 +74,43 @@ const mapCellArray = ({
|
|
|
72
74
|
columns,
|
|
73
75
|
runtimeData,
|
|
74
76
|
config,
|
|
75
|
-
applyLegendToRow,
|
|
76
77
|
displayGeoName,
|
|
77
78
|
formatLegendLocation,
|
|
78
79
|
navigationHandler,
|
|
79
|
-
setFilteredCountryCode
|
|
80
|
+
setFilteredCountryCode,
|
|
81
|
+
legendMemo,
|
|
82
|
+
legendSpecialClassLastMemo,
|
|
83
|
+
runtimeLegend
|
|
80
84
|
}: MapRowsProps): ReactNode[][] => {
|
|
85
|
+
const { allowMapZoom, geoType, type } = config.general
|
|
81
86
|
return rows.map(row =>
|
|
82
87
|
Object.keys(columns)
|
|
83
88
|
.filter(column => columns[column].dataTable === true && columns[column].name)
|
|
84
89
|
.map(column => {
|
|
85
90
|
if (column === 'geo') {
|
|
86
91
|
const rowObj = runtimeData[row]
|
|
87
|
-
|
|
92
|
+
if (!rowObj) {
|
|
93
|
+
throw new Error('No row object found')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const legendColor = applyLegendToRow(rowObj, config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
97
|
+
const noColor = !legendMemo.current.has(hashObj(rowObj))
|
|
98
|
+
|
|
99
|
+
if (!legendColor) {
|
|
100
|
+
console.error('No legend color found') // eslint-disable-line no-console
|
|
101
|
+
}
|
|
88
102
|
const labelValue = getGeoLabel(config, row, formatLegendLocation, displayGeoName)
|
|
89
103
|
const mapZoomHandler =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
type === 'bubble' && allowMapZoom && geoType === 'world' ? () => setFilteredCountryCode(row) : undefined
|
|
105
|
+
|
|
106
|
+
const validColor = legendColor && legendColor.length > 0 && !noColor
|
|
93
107
|
return (
|
|
94
108
|
<div className='col-12'>
|
|
95
|
-
|
|
109
|
+
{validColor ? (
|
|
110
|
+
<LegendShape fill={legendColor[0]} />
|
|
111
|
+
) : (
|
|
112
|
+
<div className='d-inline-block me-2' style={{ width: '1rem', height: '1rem' }} />
|
|
113
|
+
)}
|
|
96
114
|
<CellAnchor
|
|
97
115
|
markup={labelValue}
|
|
98
116
|
row={rowObj}
|
|
@@ -9,7 +9,12 @@ type DownloadButtonProps = {
|
|
|
9
9
|
|
|
10
10
|
const DownloadButton = ({ rawData, fileName, headerColor, skipId }: DownloadButtonProps) => {
|
|
11
11
|
const csvData = Papa.unparse(rawData)
|
|
12
|
-
|
|
12
|
+
// Prepend a Byte Order Mark (BOM) to the CSV data.
|
|
13
|
+
// The BOM is a special marker that helps applications like Excel recognize the file as UTF-8 encoded.
|
|
14
|
+
// Adding the BOM ensures that Excel interprets special characters correctly.
|
|
15
|
+
const bom = '\uFEFF'
|
|
16
|
+
const utf8EncodedCsvData = new TextEncoder().encode(bom + csvData)
|
|
17
|
+
const blob = new Blob([utf8EncodedCsvData], { type: 'text/csv;charset=utf-8;' })
|
|
13
18
|
|
|
14
19
|
const saveBlob = () => {
|
|
15
20
|
//@ts-ignore
|
|
@@ -20,7 +25,17 @@ const DownloadButton = ({ rawData, fileName, headerColor, skipId }: DownloadButt
|
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
return (
|
|
23
|
-
<a
|
|
28
|
+
<a
|
|
29
|
+
download={fileName}
|
|
30
|
+
type='button'
|
|
31
|
+
onClick={saveBlob}
|
|
32
|
+
href={URL.createObjectURL(blob)}
|
|
33
|
+
aria-label='Download this data in a CSV file format.'
|
|
34
|
+
className={`${headerColor} no-border`}
|
|
35
|
+
id={`${skipId}`}
|
|
36
|
+
data-html2canvas-ignore
|
|
37
|
+
role='button'
|
|
38
|
+
>
|
|
24
39
|
Download Data (CSV)
|
|
25
40
|
</a>
|
|
26
41
|
)
|
|
@@ -196,7 +196,7 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
196
196
|
/>
|
|
197
197
|
)}
|
|
198
198
|
{config?.visualizationType !== 'Sankey' && (
|
|
199
|
-
<label>
|
|
199
|
+
<label onClick={e => e.preventDefault()}>
|
|
200
200
|
<span className='edit-label column-heading mt-1'>Exclude Columns </span>
|
|
201
201
|
<MultiSelect
|
|
202
202
|
key={excludedColumns.join('') + 'excluded'}
|