@cdc/core 4.25.3-6 → 4.25.5-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/AdvancedEditor/AdvancedEditor.tsx +11 -9
- package/components/DataTable/DataTable.tsx +50 -31
- package/components/DataTable/components/CellAnchor.tsx +1 -1
- package/components/DataTable/components/ChartHeader.tsx +3 -2
- package/components/DataTable/components/MapHeader.tsx +1 -0
- package/components/DataTable/helpers/chartCellMatrix.tsx +2 -1
- package/components/DataTable/helpers/getChartCellValue.ts +11 -5
- package/components/DataTable/helpers/getDataSeriesColumns.ts +7 -3
- package/components/DataTable/helpers/mapCellMatrix.tsx +80 -39
- package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +80 -0
- package/components/DownloadButton.tsx +17 -2
- package/components/EditorPanel/DataTableEditor.tsx +29 -19
- package/components/EditorPanel/Inputs.tsx +13 -4
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +2 -1
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +26 -1
- package/components/Filters/Filters.tsx +172 -421
- 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/getNewRuntime.ts +35 -0
- 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/helpers/tests/getNewRuntime.test.ts +82 -0
- package/components/Filters/index.ts +1 -1
- package/components/Layout/components/Visualization/index.tsx +3 -3
- package/components/Layout/components/Visualization/visualizations.scss +1 -1
- package/components/Legend/Legend.Gradient.tsx +66 -23
- package/components/MediaControls.jsx +14 -7
- package/components/MultiSelect/multiselect.styles.css +2 -0
- package/components/NestedDropdown/NestedDropdown.tsx +2 -2
- 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/elements/Button.jsx +4 -2
- 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 +101 -159
- package/dist/cove-main.css.map +1 -1
- package/helpers/DataTransform.ts +2 -2
- package/helpers/addValuesToFilters.ts +1 -1
- package/helpers/constants.ts +6 -0
- package/helpers/cove/accessibility.ts +7 -8
- package/helpers/coveUpdateWorker.ts +17 -8
- package/helpers/filterOrderOptions.ts +17 -0
- package/helpers/formatConfigBeforeSave.ts +30 -8
- package/helpers/isNumber.ts +20 -0
- package/helpers/isRightAlignedTableValue.js +6 -2
- package/helpers/isSolr.ts +13 -0
- package/helpers/labelHash.ts +21 -0
- package/helpers/pivotData.ts +30 -18
- package/helpers/tests/formatConfigBeforeSave.test.ts +68 -0
- package/helpers/tests/pivotData.test.ts +96 -18
- package/helpers/ver/4.25.3.ts +43 -0
- package/helpers/ver/4.25.4.ts +33 -0
- package/helpers/ver/tests/4.25.4.test.ts +24 -0
- package/helpers/ver/tests/versionNeedsUpdate.test.ts +28 -0
- package/helpers/viewports.ts +4 -0
- package/package.json +2 -3
- package/styles/_global-variables.scss +5 -1
- package/styles/_global.scss +18 -9
- package/styles/_reset.scss +0 -6
- package/styles/base.scss +42 -0
- package/styles/filters.scss +5 -11
- package/styles/v2/components/button.scss +48 -12
- package/styles/v2/main.scss +0 -5
- package/styles/v2/themes/_color-definitions.scss +1 -4
- package/types/General.ts +1 -1
- package/types/Legend.ts +1 -0
- package/types/Table.ts +2 -0
- package/LICENSE +0 -201
- 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/isSolr.js +0 -13
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>
|
|
@@ -35,20 +35,22 @@ export const AdvancedEditor = ({ loadConfig, config, convertStateToConfig, onExp
|
|
|
35
35
|
}, [config])
|
|
36
36
|
|
|
37
37
|
const typeLookup = {
|
|
38
|
-
chart: ['Charts', 'https://www.cdc.gov/
|
|
39
|
-
dashboard: ['Dashboard', 'https://www.cdc.gov/
|
|
40
|
-
map: ['Maps', 'https://www.cdc.gov/
|
|
41
|
-
'markup-include': [
|
|
42
|
-
'Markup Include',
|
|
43
|
-
'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/Markup-Include.html',
|
|
44
|
-
<MarkupIncludeIcon />
|
|
45
|
-
]
|
|
38
|
+
chart: ['Charts', 'https://www.cdc.gov/cove/index.html', <ChartIcon />],
|
|
39
|
+
dashboard: ['Dashboard', 'https://www.cdc.gov/cove/index.html', <ChartIcon />],
|
|
40
|
+
map: ['Maps', 'https://www.cdc.gov/cove/index.html', <MapIcon />],
|
|
41
|
+
'markup-include': ['Markup Include', 'https://www.cdc.gov/cove/index.html', <MarkupIncludeIcon />]
|
|
46
42
|
}
|
|
47
43
|
|
|
48
44
|
if (!config.type) return <></>
|
|
49
45
|
return (
|
|
50
46
|
<>
|
|
51
|
-
<a
|
|
47
|
+
<a
|
|
48
|
+
href={typeLookup[config.type][1]}
|
|
49
|
+
target='_blank'
|
|
50
|
+
rel='noopener noreferrer'
|
|
51
|
+
className='guidance-link'
|
|
52
|
+
style={{ cursor: 'pointer !important' }}
|
|
53
|
+
>
|
|
52
54
|
{typeLookup[config.type][2]}
|
|
53
55
|
<div>
|
|
54
56
|
<span className='heading-3'>Get Help with {typeLookup[config.type][0]}</span>
|
|
@@ -11,7 +11,7 @@ import BoxplotHeader from './components/BoxplotHeader'
|
|
|
11
11
|
import MapHeader from './components/MapHeader'
|
|
12
12
|
import SkipTo from '../elements/SkipTo'
|
|
13
13
|
import ExpandCollapse from './components/ExpandCollapse'
|
|
14
|
-
import mapCellMatrix from './helpers/mapCellMatrix'
|
|
14
|
+
import mapCellMatrix, { getMapRowData } from './helpers/mapCellMatrix'
|
|
15
15
|
import Table from '../Table'
|
|
16
16
|
import chartCellMatrix from './helpers/chartCellMatrix'
|
|
17
17
|
import regionCellMatrix from './helpers/regionCellMatrix'
|
|
@@ -24,17 +24,17 @@ import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
|
24
24
|
import isRightAlignedTableValue from '@cdc/core/helpers/isRightAlignedTableValue'
|
|
25
25
|
import './data-table.css'
|
|
26
26
|
import _ from 'lodash'
|
|
27
|
+
import { getDataSeriesColumns } from './helpers/getDataSeriesColumns'
|
|
27
28
|
|
|
28
29
|
export type DataTableProps = {
|
|
29
|
-
applyLegendToRow?: Function
|
|
30
30
|
colorScale?: Function
|
|
31
31
|
columns?: Record<string, Column>
|
|
32
32
|
config: TableConfig
|
|
33
33
|
dataConfig?: Object
|
|
34
34
|
defaultSortBy?: string
|
|
35
|
-
displayGeoName?:
|
|
35
|
+
displayGeoName?: (row: string) => string
|
|
36
36
|
expandDataTable: boolean
|
|
37
|
-
formatLegendLocation?:
|
|
37
|
+
formatLegendLocation?: (row: string, runtimeLookup: string) => string
|
|
38
38
|
groupBy?: string
|
|
39
39
|
headerColor?: string
|
|
40
40
|
imageRef?: string
|
|
@@ -45,8 +45,7 @@ export type DataTableProps = {
|
|
|
45
45
|
outerContainerRef?: Function
|
|
46
46
|
rawData: Object[]
|
|
47
47
|
runtimeData: Object[] & Record<string, Object>
|
|
48
|
-
setFilteredCountryCode?:
|
|
49
|
-
showDownloadButton?: boolean
|
|
48
|
+
setFilteredCountryCode?: string // used for Maps only
|
|
50
49
|
tabbingId: string
|
|
51
50
|
tableTitle: string
|
|
52
51
|
viewport: 'lg' | 'md' | 'sm' | 'xs' | 'xxs'
|
|
@@ -58,36 +57,33 @@ 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
|
-
|
|
65
|
-
vizTitle,
|
|
66
|
-
rawData,
|
|
67
|
-
runtimeData: parentRuntimeData,
|
|
68
|
-
headerColor,
|
|
64
|
+
displayGeoName,
|
|
69
65
|
expandDataTable,
|
|
70
|
-
columns,
|
|
71
|
-
viewport,
|
|
72
66
|
formatLegendLocation,
|
|
67
|
+
headerColor,
|
|
68
|
+
rawData,
|
|
69
|
+
runtimeData: parentRuntimeData,
|
|
73
70
|
tabbingId,
|
|
71
|
+
tableTitle,
|
|
72
|
+
viewport,
|
|
73
|
+
vizTitle,
|
|
74
74
|
wrapColumns
|
|
75
75
|
} = props
|
|
76
76
|
const runtimeData = useMemo(() => {
|
|
77
77
|
const data = removeNullColumns(parentRuntimeData)
|
|
78
|
-
|
|
78
|
+
const { columnName, valueColumns } = config.table.pivot || {}
|
|
79
|
+
if (columnName && valueColumns) {
|
|
79
80
|
const excludeColumns = Object.values(config.columns || {})
|
|
80
81
|
.filter(column => column.dataTable === false)
|
|
81
82
|
.map(col => col.name)
|
|
82
|
-
|
|
83
|
-
if (columnName && valueColumns) {
|
|
84
|
-
// remove excluded columns so that they aren't included in the pivot calculation
|
|
85
|
-
const _data = data.map(row => _.omit(row, excludeColumns))
|
|
86
|
-
return pivotData(_data, columnName, valueColumns)
|
|
87
|
-
}
|
|
83
|
+
return pivotData(data, columnName, valueColumns, excludeColumns)
|
|
88
84
|
}
|
|
89
85
|
return data
|
|
90
|
-
}, [parentRuntimeData, config.table.pivot?.columnName, config.table.pivot?.
|
|
86
|
+
}, [parentRuntimeData, config.table.pivot?.columnName, config.table.pivot?.valueColumns])
|
|
91
87
|
|
|
92
88
|
const [expanded, setExpanded] = useState(expandDataTable)
|
|
93
89
|
const [sortBy, setSortBy] = useState<any>({
|
|
@@ -208,13 +204,10 @@ const DataTable = (props: DataTableProps) => {
|
|
|
208
204
|
[config.runtime?.seriesKeys]) // eslint-disable-line
|
|
209
205
|
|
|
210
206
|
const hasNoData = runtimeData.length === 0
|
|
211
|
-
|
|
212
207
|
const getClassNames = (): string => {
|
|
213
208
|
const classes = ['data-table-container']
|
|
214
209
|
|
|
215
|
-
const hasDownloadLinkAbove =
|
|
216
|
-
(config.table.download || config.general?.showDownloadButton) && !config.table.showDownloadLinkBelow
|
|
217
|
-
|
|
210
|
+
const hasDownloadLinkAbove = config.table.download && !config.table.showDownloadLinkBelow
|
|
218
211
|
const isStandaloneTable = config.type === 'table'
|
|
219
212
|
|
|
220
213
|
if (!hasDownloadLinkAbove && !isStandaloneTable) {
|
|
@@ -233,12 +226,37 @@ const DataTable = (props: DataTableProps) => {
|
|
|
233
226
|
|
|
234
227
|
if (config.visualizationType !== 'Box Plot') {
|
|
235
228
|
const getDownloadData = () => {
|
|
229
|
+
const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
|
|
230
|
+
const sharedFilterColumns = config.table?.sharedFilterColumns || []
|
|
231
|
+
const vizFilterColumns = (config.filters || []).map(filter => filter.columnName)
|
|
232
|
+
const filterColumns = [...sharedFilterColumns, ...vizFilterColumns]
|
|
233
|
+
const visibleData =
|
|
234
|
+
config.type === 'map'
|
|
235
|
+
? getMapRowData(
|
|
236
|
+
rows,
|
|
237
|
+
columns,
|
|
238
|
+
config,
|
|
239
|
+
formatLegendLocation,
|
|
240
|
+
runtimeData as Record<string, Object>,
|
|
241
|
+
displayGeoName,
|
|
242
|
+
filterColumns
|
|
243
|
+
)
|
|
244
|
+
: runtimeData.map(d => {
|
|
245
|
+
return _.pick(d, [...filterColumns, ...dataSeriesColumns])
|
|
246
|
+
})
|
|
247
|
+
const csvData = config.table?.downloadVisibleDataOnly ? visibleData : rawData
|
|
248
|
+
|
|
236
249
|
// only use fullGeoName on County maps and no other
|
|
237
|
-
if (config.general?.geoType === 'us-county') {
|
|
250
|
+
if (config.general?.geoType === 'us-county' || config.table.showFullGeoNameInCSV) {
|
|
238
251
|
// Add column for full Geo name along with State
|
|
239
|
-
return
|
|
252
|
+
return csvData.map(row => {
|
|
253
|
+
return {
|
|
254
|
+
FullGeoName: formatLegendLocation(row[config.columns.geo.name]),
|
|
255
|
+
...row
|
|
256
|
+
}
|
|
257
|
+
})
|
|
240
258
|
} else {
|
|
241
|
-
return
|
|
259
|
+
return csvData
|
|
242
260
|
}
|
|
243
261
|
}
|
|
244
262
|
|
|
@@ -261,7 +279,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
261
279
|
|
|
262
280
|
const childrenMatrix =
|
|
263
281
|
config.type === 'map'
|
|
264
|
-
? mapCellMatrix({ rows, wrapColumns,
|
|
282
|
+
? mapCellMatrix({ ...props, rows, wrapColumns, runtimeData, viewport })
|
|
265
283
|
: chartCellMatrix({ rows, ...props, runtimeData, isVertical, sortBy, hasRowType, viewport })
|
|
266
284
|
|
|
267
285
|
// If every value in a column is a number, record the column index so the header and cells can be right-aligned
|
|
@@ -276,7 +294,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
276
294
|
: {}
|
|
277
295
|
|
|
278
296
|
const TableMediaControls = ({ belowTable }) => {
|
|
279
|
-
const hasDownloadLink = config.table.download
|
|
297
|
+
const hasDownloadLink = config.table.download
|
|
280
298
|
return (
|
|
281
299
|
<MediaControls.Section classes={getMediaControlsClasses(belowTable, hasDownloadLink)}>
|
|
282
300
|
<MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
|
|
@@ -338,7 +356,8 @@ const DataTable = (props: DataTableProps) => {
|
|
|
338
356
|
}`,
|
|
339
357
|
'aria-live': 'assertive',
|
|
340
358
|
'aria-rowcount': config?.data?.length ? config.data.length : -1,
|
|
341
|
-
hidden: !expanded
|
|
359
|
+
hidden: !expanded,
|
|
360
|
+
cellMinWidth: 100
|
|
342
361
|
}}
|
|
343
362
|
rightAlignedCols={rightAlignedCols}
|
|
344
363
|
/>
|
|
@@ -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
|
|
|
@@ -54,7 +55,7 @@ const ChartHeader = ({
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
const ColumnHeadingText = ({ column, text, config }) => {
|
|
57
|
-
if (text === '
|
|
58
|
+
if (text === '_pivotedFrom') return ''
|
|
58
59
|
let notApplicableText = 'Not Applicable'
|
|
59
60
|
if (text === '__series__' && config.table.indexLabel) return `${config.table.indexLabel} `
|
|
60
61
|
if (text === '__series__' && !config.table.indexLabel)
|
|
@@ -75,7 +76,7 @@ const ChartHeader = ({
|
|
|
75
76
|
return (
|
|
76
77
|
<tr>
|
|
77
78
|
{dataSeriesColumns.map((column, index) => {
|
|
78
|
-
const text = getSeriesName(column, config)
|
|
79
|
+
const text = parse(getSeriesName(column, config))
|
|
79
80
|
const newSortBy = getNewSortBy(sortBy, column, index)
|
|
80
81
|
const sortByAsc = sortBy.column === column ? sortBy.asc : undefined
|
|
81
82
|
const isSortedCol = column === sortBy.column && !hasRowType
|
|
@@ -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[]
|
|
@@ -84,7 +85,7 @@ const chartCellArray = ({
|
|
|
84
85
|
? [
|
|
85
86
|
<>
|
|
86
87
|
{colorScale && colorScale(seriesName) && <LegendShape fill={colorScale(seriesName)} />}
|
|
87
|
-
{seriesName}
|
|
88
|
+
{parse(seriesName)}
|
|
88
89
|
</>
|
|
89
90
|
]
|
|
90
91
|
: []
|
|
@@ -2,16 +2,22 @@ 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
4
|
|
|
5
|
+
const isPivotColumn = (columnName, config) => {
|
|
6
|
+
const tableHasPivotColumnConfigured = config.table.pivot?.valueColumns?.length
|
|
7
|
+
const originalColumnNames = Object.keys(config.data?.[0] || {})
|
|
8
|
+
const columnIsPivot = originalColumnNames.length && !originalColumnNames.includes(columnName)
|
|
9
|
+
return tableHasPivotColumnConfigured && columnIsPivot
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
// if its additional column, return formatting params
|
|
6
|
-
const isAdditionalColumn = (column, config) => {
|
|
7
|
-
|
|
13
|
+
const isAdditionalColumn = (column: string, config, rowData) => {
|
|
14
|
+
const columnName = isPivotColumn(column, config) ? rowData._pivotedFrom : column
|
|
8
15
|
let formattingParams = {}
|
|
9
16
|
const { columns } = config
|
|
10
17
|
if (columns) {
|
|
11
18
|
Object.keys(columns).forEach(keycol => {
|
|
12
19
|
const col = columns[keycol]
|
|
13
|
-
if (col.name ===
|
|
14
|
-
inthere = true
|
|
20
|
+
if (col.name === columnName) {
|
|
15
21
|
formattingParams = {
|
|
16
22
|
addColPrefix: col.prefix,
|
|
17
23
|
addColSuffix: col.suffix,
|
|
@@ -54,7 +60,7 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
|
|
|
54
60
|
if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
|
|
55
61
|
})
|
|
56
62
|
|
|
57
|
-
let addColParams = isAdditionalColumn(column, config)
|
|
63
|
+
let addColParams = isAdditionalColumn(column, config, rowObj)
|
|
58
64
|
if (addColParams) {
|
|
59
65
|
cellValue = config.dataFormat
|
|
60
66
|
? formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams)
|
|
@@ -57,9 +57,13 @@ export const getDataSeriesColumns = (config: TableConfig, isVertical: boolean, r
|
|
|
57
57
|
})
|
|
58
58
|
|
|
59
59
|
tmpSeriesColumns.sort((a, b) => {
|
|
60
|
-
if (a === '
|
|
61
|
-
if (b === '
|
|
60
|
+
if (a === '_pivotedFrom') return -1
|
|
61
|
+
if (b === '_pivotedFrom') return 1
|
|
62
62
|
return columnOrderingHash[a] - columnOrderingHash[b]
|
|
63
63
|
})
|
|
64
|
-
|
|
64
|
+
|
|
65
|
+
return tmpSeriesColumns.filter(colName => {
|
|
66
|
+
// remove metadata added by pivotData function
|
|
67
|
+
return colName !== '_pivotedFrom'
|
|
68
|
+
})
|
|
65
69
|
}
|
|
@@ -3,52 +3,106 @@ import CellAnchor from '../components/CellAnchor'
|
|
|
3
3
|
import { DataTableProps } from '../DataTable'
|
|
4
4
|
import { ReactNode } from 'react'
|
|
5
5
|
import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
|
|
6
|
+
import _ from 'lodash'
|
|
7
|
+
import { applyLegendToRow } from '@cdc/map/src/helpers/applyLegendToRow'
|
|
6
8
|
|
|
7
9
|
type MapRowsProps = DataTableProps & {
|
|
8
10
|
rows: string[]
|
|
9
11
|
}
|
|
10
12
|
|
|
13
|
+
const getGeoLabel = (config, row, formatLegendLocation, displayGeoName) => {
|
|
14
|
+
const { geoType, type } = config.general
|
|
15
|
+
let labelValue
|
|
16
|
+
if (!['single-state', 'us-county'].includes(geoType) || type === 'us-geocode') {
|
|
17
|
+
labelValue = displayGeoName(row)
|
|
18
|
+
labelValue = String(labelValue).startsWith('region') ? _.capitalize(labelValue) : labelValue
|
|
19
|
+
} else {
|
|
20
|
+
labelValue = formatLegendLocation(row)
|
|
21
|
+
}
|
|
22
|
+
return labelValue
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const getDataValue = (config, rowData, column) => {
|
|
26
|
+
// check for special classes
|
|
27
|
+
let specialValFound = ''
|
|
28
|
+
let columnName = config.columns[column]?.name
|
|
29
|
+
const { specialClasses } = config.legend
|
|
30
|
+
if (specialClasses && specialClasses.length && typeof specialClasses[0] === 'object') {
|
|
31
|
+
specialClasses.forEach(specialClass => {
|
|
32
|
+
if (specialClass.key === columnName) {
|
|
33
|
+
if (String(rowData[specialClass.key]) === specialClass.value) {
|
|
34
|
+
specialValFound = specialClass.label
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
return specialValFound || rowData[columnName]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const getMapRowData = (
|
|
43
|
+
rows: string[],
|
|
44
|
+
columns: Record<string, { label; name?; dataTable }>,
|
|
45
|
+
config: Record<string, Object>,
|
|
46
|
+
formatLegendLocation: (row: string) => string,
|
|
47
|
+
runtimeData: Record<string, Object>,
|
|
48
|
+
displayGeoName: (row: string) => string,
|
|
49
|
+
filterColumns: string[]
|
|
50
|
+
) => {
|
|
51
|
+
return rows.map((row: string) => {
|
|
52
|
+
const dataRow = {}
|
|
53
|
+
;[
|
|
54
|
+
...filterColumns,
|
|
55
|
+
...Object.keys(columns).filter(column => columns[column].dataTable === true && columns[column].name)
|
|
56
|
+
].map(column => {
|
|
57
|
+
const label = columns[column]?.label || columns[column]?.name || column
|
|
58
|
+
if (column === 'geo') {
|
|
59
|
+
dataRow[label] = getGeoLabel(config, row, formatLegendLocation, displayGeoName)
|
|
60
|
+
} else if (filterColumns.includes(column)) {
|
|
61
|
+
dataRow[label] = runtimeData[row][column]
|
|
62
|
+
} else {
|
|
63
|
+
const dataValue = getDataValue(config, runtimeData[row], column)
|
|
64
|
+
dataRow[label] = displayDataAsText(dataValue, column, config)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
return dataRow
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
11
71
|
const mapCellArray = ({
|
|
12
72
|
rows,
|
|
13
73
|
columns,
|
|
14
74
|
runtimeData,
|
|
15
75
|
config,
|
|
16
|
-
applyLegendToRow,
|
|
17
76
|
displayGeoName,
|
|
18
77
|
formatLegendLocation,
|
|
19
78
|
navigationHandler,
|
|
20
|
-
setFilteredCountryCode
|
|
79
|
+
setFilteredCountryCode,
|
|
80
|
+
legendMemo,
|
|
81
|
+
legendSpecialClassLastMemo,
|
|
82
|
+
runtimeLegend
|
|
21
83
|
}: MapRowsProps): ReactNode[][] => {
|
|
84
|
+
const { allowMapZoom, geoType, type } = config.general
|
|
22
85
|
return rows.map(row =>
|
|
23
86
|
Object.keys(columns)
|
|
24
87
|
.filter(column => columns[column].dataTable === true && columns[column].name)
|
|
25
88
|
.map(column => {
|
|
26
|
-
let cellValue
|
|
27
|
-
|
|
28
89
|
if (column === 'geo') {
|
|
29
90
|
const rowObj = runtimeData[row]
|
|
30
|
-
|
|
91
|
+
if (!rowObj) {
|
|
92
|
+
throw new Error('No row object found')
|
|
93
|
+
}
|
|
31
94
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
: undefined
|
|
37
|
-
if (
|
|
38
|
-
(config.general.geoType !== 'single-state' && config.general.geoType !== 'us-county') ||
|
|
39
|
-
config.general.type === 'us-geocode'
|
|
40
|
-
) {
|
|
41
|
-
const capitalize = str => {
|
|
42
|
-
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
43
|
-
}
|
|
44
|
-
labelValue = displayGeoName(row)
|
|
45
|
-
labelValue = String(labelValue).startsWith('region') ? capitalize(labelValue) : labelValue
|
|
46
|
-
} else {
|
|
47
|
-
labelValue = formatLegendLocation(row)
|
|
95
|
+
const legendColor = applyLegendToRow(rowObj, config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
96
|
+
|
|
97
|
+
if (!legendColor) {
|
|
98
|
+
console.error('No legend color found') // eslint-disable-line no-console
|
|
48
99
|
}
|
|
49
|
-
|
|
100
|
+
const labelValue = getGeoLabel(config, row, formatLegendLocation, displayGeoName)
|
|
101
|
+
const mapZoomHandler =
|
|
102
|
+
type === 'bubble' && allowMapZoom && geoType === 'world' ? () => setFilteredCountryCode(row) : undefined
|
|
103
|
+
return (
|
|
50
104
|
<div className='col-12'>
|
|
51
|
-
<LegendShape fill={legendColor[0]} />
|
|
105
|
+
{legendColor && legendColor.length > 0 && <LegendShape fill={legendColor[0]} />}
|
|
52
106
|
<CellAnchor
|
|
53
107
|
markup={labelValue}
|
|
54
108
|
row={rowObj}
|
|
@@ -59,23 +113,10 @@ const mapCellArray = ({
|
|
|
59
113
|
</div>
|
|
60
114
|
)
|
|
61
115
|
} else {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const { specialClasses } = config.legend
|
|
66
|
-
if (specialClasses && specialClasses.length && typeof specialClasses[0] === 'object') {
|
|
67
|
-
specialClasses.forEach(specialClass => {
|
|
68
|
-
if (specialClass.key === columnName) {
|
|
69
|
-
if (String(runtimeData[row][specialClass.key]) === specialClass.value) {
|
|
70
|
-
specialValFound = specialClass.label
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
cellValue = displayDataAsText(specialValFound || runtimeData[row][columnName], column, config)
|
|
116
|
+
const rowData = runtimeData[row]
|
|
117
|
+
const dataValue = getDataValue(config, rowData, column)
|
|
118
|
+
return displayDataAsText(dataValue, column, config)
|
|
76
119
|
}
|
|
77
|
-
|
|
78
|
-
return cellValue
|
|
79
120
|
})
|
|
80
121
|
)
|
|
81
122
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { getMapRowData } from '../mapCellMatrix'
|
|
3
|
+
|
|
4
|
+
describe('getMapRowData', () => {
|
|
5
|
+
const columns = {
|
|
6
|
+
geo: { dataTable: true, name: 'geo', label: 'Geo' },
|
|
7
|
+
column1: { dataTable: true, name: 'column1', label: 'Column 1' },
|
|
8
|
+
column2: { dataTable: true, name: 'column2', label: 'Column 2' }
|
|
9
|
+
}
|
|
10
|
+
const config = {
|
|
11
|
+
general: { geoType: 'us-state', type: 'map' },
|
|
12
|
+
columns: {
|
|
13
|
+
geo: { name: 'geo' },
|
|
14
|
+
column1: { name: 'column1' },
|
|
15
|
+
column2: { name: 'column2' }
|
|
16
|
+
},
|
|
17
|
+
legend: { specialClasses: [] }
|
|
18
|
+
}
|
|
19
|
+
const formatLegendLocation = row => row
|
|
20
|
+
const rows = ['row2', 'row1']
|
|
21
|
+
const runtimeData = {
|
|
22
|
+
row1: { geo: 'region1', column1: 'data1', column2: 'data2', somethingElse: 'data5' },
|
|
23
|
+
row2: { geo: 'region2', column1: 'data3', column2: 'data4', somethingElse: 'data6' }
|
|
24
|
+
}
|
|
25
|
+
const displayGeoName = row => `displayGeoName -> ${row}`
|
|
26
|
+
const sharedFilterColumns = ['somethingElse']
|
|
27
|
+
|
|
28
|
+
it('should map rows to data rows correctly', () => {
|
|
29
|
+
const result = getMapRowData(
|
|
30
|
+
rows,
|
|
31
|
+
columns,
|
|
32
|
+
config,
|
|
33
|
+
formatLegendLocation,
|
|
34
|
+
runtimeData,
|
|
35
|
+
displayGeoName,
|
|
36
|
+
sharedFilterColumns
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
expect(result).toEqual([
|
|
40
|
+
{
|
|
41
|
+
Geo: 'displayGeoName -> row2',
|
|
42
|
+
'Column 1': 'data3',
|
|
43
|
+
'Column 2': 'data4',
|
|
44
|
+
somethingElse: 'data6'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
Geo: 'displayGeoName -> row1',
|
|
48
|
+
'Column 1': 'data1',
|
|
49
|
+
'Column 2': 'data2',
|
|
50
|
+
somethingElse: 'data5'
|
|
51
|
+
}
|
|
52
|
+
])
|
|
53
|
+
|
|
54
|
+
config.general.geoType = 'us-county'
|
|
55
|
+
const result2 = getMapRowData(
|
|
56
|
+
rows,
|
|
57
|
+
columns,
|
|
58
|
+
config,
|
|
59
|
+
formatLegendLocation,
|
|
60
|
+
runtimeData,
|
|
61
|
+
displayGeoName,
|
|
62
|
+
sharedFilterColumns
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
expect(result2).toEqual([
|
|
66
|
+
{
|
|
67
|
+
Geo: 'row2',
|
|
68
|
+
'Column 1': 'data3',
|
|
69
|
+
'Column 2': 'data4',
|
|
70
|
+
somethingElse: 'data6'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
Geo: 'row1',
|
|
74
|
+
'Column 1': 'data1',
|
|
75
|
+
'Column 2': 'data2',
|
|
76
|
+
somethingElse: 'data5'
|
|
77
|
+
}
|
|
78
|
+
])
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -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
|
)
|