@cdc/core 4.24.7 → 4.24.9
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-gear-multi.svg +23 -0
- package/components/Alert/components/Alert.styles.css +15 -0
- package/components/Alert/components/Alert.tsx +39 -0
- package/components/Alert/index.tsx +3 -0
- package/components/DataTable/DataTable.tsx +106 -30
- package/components/DataTable/helpers/chartCellMatrix.tsx +3 -3
- package/components/DataTable/helpers/getChartCellValue.ts +1 -1
- package/components/DataTable/helpers/getDataSeriesColumns.ts +2 -2
- package/components/DataTable/helpers/mapCellMatrix.tsx +3 -3
- package/components/DataTable/types/TableConfig.ts +1 -1
- package/components/EditorPanel/Inputs.tsx +13 -4
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +268 -0
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +161 -82
- package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +31 -45
- package/components/Filters.tsx +223 -180
- package/components/Layout/components/Responsive.tsx +14 -4
- package/components/Layout/components/Sidebar/components/Sidebar.tsx +14 -5
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +15 -16
- package/components/Layout/components/Visualization/index.tsx +7 -1
- package/components/Layout/components/Visualization/visualizations.scss +32 -26
- package/components/Layout/styles/editor.scss +0 -8
- package/components/Legend/Legend.Gradient.tsx +133 -0
- package/components/LegendShape.tsx +28 -0
- package/components/MultiSelect/MultiSelect.tsx +6 -3
- package/components/NestedDropdown/NestedDropdown.tsx +47 -52
- package/components/NestedDropdown/nesteddropdown.styles.css +19 -25
- package/components/Table/Table.tsx +8 -5
- package/components/Table/components/Cell.tsx +2 -2
- package/components/Table/components/Row.tsx +25 -7
- package/components/_stories/Layout.Debug.stories.tsx +91 -0
- package/components/_stories/_mocks/bar-chart-suppressed.json +474 -0
- package/components/_stories/styles.scss +13 -1
- package/components/createBarElement.jsx +4 -4
- package/components/ui/Icon.tsx +21 -14
- package/components/ui/Title/Title.scss +0 -8
- package/helpers/DataTransform.ts +2 -2
- package/helpers/addValuesToFilters.ts +95 -16
- package/helpers/cove/accessibility.ts +16 -4
- package/helpers/coveUpdateWorker.ts +24 -10
- package/helpers/filterVizData.ts +23 -4
- package/helpers/formatConfigBeforeSave.ts +7 -2
- package/helpers/getGradientLegendWidth.ts +15 -0
- package/helpers/getTextWidth.ts +18 -0
- package/helpers/scaling.ts +7 -0
- package/helpers/tests/addValuesToFilters.test.ts +55 -0
- package/helpers/tests/filterVizData.test.ts +31 -0
- package/helpers/tests/invertValue.test.ts +35 -0
- package/helpers/updatePaletteNames.ts +19 -0
- package/helpers/{useDataVizClasses.js → useDataVizClasses.ts} +3 -2
- package/helpers/ver/4.24.5.ts +3 -3
- package/helpers/ver/4.24.7.ts +34 -3
- package/helpers/ver/4.24.9.ts +63 -0
- package/helpers/ver/tests/4.24.9.test.ts +22 -0
- package/helpers/ver/versionNeedsUpdate.ts +9 -0
- package/package.json +3 -3
- package/styles/_button-section.scss +1 -1
- package/styles/_global.scss +6 -2
- package/styles/filters.scss +4 -0
- package/types/Axis.ts +3 -0
- package/types/Dimensions.ts +1 -0
- package/types/General.ts +1 -1
- package/types/VizFilter.ts +24 -3
- package/components/LegendCircle.jsx +0 -17
- package/helpers/updatePaletteNames.js +0 -16
- /package/components/{Waiting.jsx → Waiting.tsx} +0 -0
- /package/helpers/ver/{4.23.4.ts → 4.24.4.ts} +0 -0
|
@@ -1,56 +1,135 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
2
|
import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
|
|
3
|
+
import { VizFilter } from '../types/VizFilter'
|
|
3
4
|
|
|
4
5
|
type Filter = {
|
|
5
6
|
columnName: string
|
|
6
|
-
values: string[]
|
|
7
|
+
values: (string | number)[]
|
|
7
8
|
filterStyle?: string
|
|
8
|
-
active?: string | string[]
|
|
9
|
+
active?: string | number | (string | number)[]
|
|
10
|
+
parents?: (string | number)[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** MapData is an object */
|
|
14
|
+
type MapData = Record<string, any[]>
|
|
15
|
+
|
|
16
|
+
const cleanLookup = (lookup: Record<string, { values: string[]; orderedValues?: string[] }>) => {
|
|
17
|
+
// for nested-dropdown
|
|
18
|
+
// removes values from subGrouping.valuesLookup
|
|
19
|
+
// keeps orderedValues
|
|
20
|
+
return Object.fromEntries(
|
|
21
|
+
Object.entries(lookup || {}).map(([key, { orderedValues }]) => {
|
|
22
|
+
if (!orderedValues) return [key, { values: [] }]
|
|
23
|
+
return [key, { orderedValues, values: [] }]
|
|
24
|
+
})
|
|
25
|
+
)
|
|
9
26
|
}
|
|
10
27
|
|
|
11
28
|
// Gets filter values from dataset
|
|
12
|
-
const generateValuesForFilter = (
|
|
29
|
+
const generateValuesForFilter = (filter: VizFilter, data: any[] | MapData) => {
|
|
30
|
+
const columnName = filter.columnName
|
|
13
31
|
const values: string[] = []
|
|
14
|
-
|
|
32
|
+
const subGroupingColumn = filter.subGrouping?.columnName
|
|
33
|
+
const subValues = cleanLookup(filter.subGrouping?.valuesLookup)
|
|
15
34
|
if (Array.isArray(data)) {
|
|
16
35
|
data.forEach(row => {
|
|
17
36
|
const value = row[columnName]
|
|
18
|
-
if (!values.includes(value)) {
|
|
37
|
+
if (value !== undefined && !values.includes(value)) {
|
|
19
38
|
values.push(value)
|
|
20
39
|
}
|
|
40
|
+
if (subGroupingColumn) {
|
|
41
|
+
const dataValue = row[subGroupingColumn]
|
|
42
|
+
if (value === undefined) return
|
|
43
|
+
if (!subValues[value]) {
|
|
44
|
+
subValues[value] = { values: [] }
|
|
45
|
+
}
|
|
46
|
+
if (!subValues[value].values.includes(dataValue)) {
|
|
47
|
+
subValues[value].values.push(dataValue)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
21
50
|
})
|
|
22
51
|
} else {
|
|
23
52
|
// data is a dataset this loops through ALL datasets to find matching values
|
|
24
53
|
// not sure if this is desired behavior
|
|
54
|
+
// Maps Only
|
|
55
|
+
if (!data) return values
|
|
25
56
|
Object.values(data).forEach((rows: any[]) => {
|
|
26
57
|
rows.forEach(row => {
|
|
27
58
|
const value = row[columnName]
|
|
28
|
-
if (!values.includes(value)) {
|
|
59
|
+
if (value !== undefined && !values.includes(value)) {
|
|
29
60
|
values.push(value)
|
|
30
61
|
}
|
|
31
62
|
})
|
|
32
63
|
})
|
|
33
64
|
}
|
|
65
|
+
filter.values = values
|
|
66
|
+
if (subGroupingColumn) {
|
|
67
|
+
filter.subGrouping.valuesLookup = subValues
|
|
68
|
+
}
|
|
69
|
+
}
|
|
34
70
|
|
|
35
|
-
|
|
71
|
+
const handleVizParents = (filter: VizFilter, data: any[] | MapData, filtersLookup: Record<string, Filter>) => {
|
|
72
|
+
let filteredData = Array.isArray(data) ? data : Object.values(data).flat(1)
|
|
73
|
+
filter.parents.forEach(parentKey => {
|
|
74
|
+
const parent = filtersLookup[parentKey]
|
|
75
|
+
if (parent.filterStyle === 'nested-dropdown') {
|
|
76
|
+
const { subGrouping } = parent as VizFilter
|
|
77
|
+
if (subGrouping.active) {
|
|
78
|
+
filteredData = filteredData.filter(d => {
|
|
79
|
+
const matchingParentGroup = parent.active == d[parent.columnName]
|
|
80
|
+
const matchingSubGroup = subGrouping.active == d[subGrouping.columnName]
|
|
81
|
+
return matchingParentGroup && matchingSubGroup
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
} else if (parent?.active) {
|
|
85
|
+
filteredData = filteredData.filter(d => {
|
|
86
|
+
if (Array.isArray(parent.active)) {
|
|
87
|
+
return parent.active.includes(d[parent.columnName])
|
|
88
|
+
}
|
|
89
|
+
return parent.active == d[parent.columnName]
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
return filteredData
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const includes = (arr: any[], val: any): boolean => {
|
|
97
|
+
return arr.map(val => String(val)).includes(String(val))
|
|
36
98
|
}
|
|
37
99
|
|
|
38
|
-
export const addValuesToFilters =
|
|
100
|
+
export const addValuesToFilters = (filters: VizFilter[], data: any[] | MapData): Array<VizFilter> => {
|
|
101
|
+
const filtersLookup = _.keyBy(filters, 'id')
|
|
39
102
|
return filters?.map(filter => {
|
|
40
103
|
const filterCopy = _.cloneDeep(filter)
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
104
|
+
let filteredData = data
|
|
105
|
+
const isMapData = !Array.isArray(data)
|
|
106
|
+
if (filter.parents?.length && !isMapData) {
|
|
107
|
+
filteredData = handleVizParents(filter as VizFilter, data, filtersLookup)
|
|
108
|
+
}
|
|
109
|
+
generateValuesForFilter(filterCopy, filteredData)
|
|
110
|
+
if (filterCopy.values.length > 0) {
|
|
47
111
|
const queryStringFilterValue = getQueryStringFilterValue(filterCopy)
|
|
48
112
|
if (queryStringFilterValue) {
|
|
49
113
|
filterCopy.active = queryStringFilterValue
|
|
114
|
+
} else if (filterCopy.filterStyle === 'multi-select') {
|
|
115
|
+
const defaultValues = filterCopy.values
|
|
116
|
+
const active = Array.isArray(filterCopy.active) ? filterCopy.active : [filterCopy.active]
|
|
117
|
+
filterCopy.active = active.filter(val => includes(defaultValues, val))
|
|
50
118
|
} else {
|
|
51
|
-
|
|
119
|
+
const defaultValue = filterCopy.values[0]
|
|
120
|
+
const active = Array.isArray(filterCopy.active) ? filterCopy.active[0] : filterCopy.active
|
|
121
|
+
filterCopy.active = includes(filterCopy.values, active) ? active : defaultValue
|
|
52
122
|
}
|
|
53
123
|
}
|
|
124
|
+
if (filterCopy.subGrouping) {
|
|
125
|
+
const queryStringFilterValue = getQueryStringFilterValue(filterCopy.subGrouping)
|
|
126
|
+
const groupActive = filterCopy.active || filterCopy.values[0]
|
|
127
|
+
const defaultValue = filterCopy.subGrouping.valuesLookup[groupActive as string].values[0]
|
|
128
|
+
// if the value doesn't exist in the subGrouping then return the default
|
|
129
|
+
const filteredLookupValues = Object.values(filterCopy.subGrouping.valuesLookup).flatMap(v => v.values)
|
|
130
|
+
const activeValue = queryStringFilterValue || filterCopy.subGrouping.active
|
|
131
|
+
filterCopy.subGrouping.active = filteredLookupValues.includes(activeValue) ? activeValue : defaultValue
|
|
132
|
+
}
|
|
54
133
|
return filterCopy
|
|
55
|
-
})
|
|
134
|
+
})
|
|
56
135
|
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import chroma from 'chroma-js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* WCAG 2.
|
|
5
|
-
* https://www.w3.org/TR/WCAG20-TECHS/G18.html
|
|
4
|
+
* WCAG 2.1 CONSTANTS
|
|
5
|
+
* TEXT (4.5 Contrast Ratio): https://www.w3.org/TR/WCAG20-TECHS/G18.html
|
|
6
|
+
* NON-TEXT (3.5 Contrast Ratio): https://www.w3.org/TR/WCAG20-TECHS/G145.html
|
|
6
7
|
*
|
|
7
8
|
* !important - DO NOT CHANGE.
|
|
8
9
|
*/
|
|
9
|
-
export const
|
|
10
|
+
export const WCAG_TEXT_CONTRAST_RATIO = 4.5
|
|
11
|
+
export const WCAG_NON_TEXT_CONTRAST_RATIO = 3.5
|
|
10
12
|
|
|
11
13
|
export const getContrastColor = (textColor: string, bgColor: string) => {
|
|
12
14
|
if (!bgColor) return
|
|
13
|
-
if (chroma.contrast(textColor, bgColor) <
|
|
15
|
+
if (chroma.contrast(textColor, bgColor) < WCAG_TEXT_CONTRAST_RATIO) {
|
|
14
16
|
switch (textColor) {
|
|
15
17
|
case '#FFF':
|
|
16
18
|
return '#000'
|
|
@@ -22,3 +24,13 @@ export const getContrastColor = (textColor: string, bgColor: string) => {
|
|
|
22
24
|
}
|
|
23
25
|
return textColor
|
|
24
26
|
}
|
|
27
|
+
|
|
28
|
+
export const checkColorContrast = (color1: string, color2: string) => {
|
|
29
|
+
if (!chroma.valid(color1) || !chroma.valid(color2)) return false
|
|
30
|
+
return chroma.contrast(color1, color2) >= WCAG_NON_TEXT_CONTRAST_RATIO
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const getColorContrast = (color1: string, color2: string) => {
|
|
34
|
+
if (!chroma.valid(color1) || !chroma.valid(color2)) return false
|
|
35
|
+
return chroma.contrast(color1, color2)
|
|
36
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// If config key names or position in the config have been changed with a version change,
|
|
2
2
|
// process those config entries and format old values into new
|
|
3
|
-
import update_4_24_4 from './ver/4.
|
|
3
|
+
import update_4_24_4 from './ver/4.24.4'
|
|
4
4
|
import update_4_24_3 from './ver/4.24.3'
|
|
5
5
|
import update_4_24_5 from './ver/4.24.5'
|
|
6
6
|
import update_4_24_7 from './ver/4.24.7'
|
|
7
|
+
import update_4_24_9 from './ver/4.24.9'
|
|
8
|
+
import versionNeedsUpdate from './ver/versionNeedsUpdate'
|
|
9
|
+
import { UpdateFunction } from 'json-edit-react'
|
|
7
10
|
|
|
8
11
|
export const coveUpdateWorker = config => {
|
|
9
12
|
if (config.multiDashboards) {
|
|
@@ -13,16 +16,27 @@ export const coveUpdateWorker = config => {
|
|
|
13
16
|
}
|
|
14
17
|
let genConfig = config
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
const versions = [
|
|
20
|
+
['4.24.3', update_4_24_3],
|
|
21
|
+
['4.24.4', update_4_24_4],
|
|
22
|
+
['4.24.5', update_4_24_5],
|
|
23
|
+
['4.24.7', update_4_24_7, true],
|
|
24
|
+
['4.24.9', update_4_24_9]
|
|
25
|
+
]
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
versions.forEach(([version, updateFunction, alwaysRun]: [string, UpdateFunction, boolean?]) => {
|
|
28
|
+
if (versionNeedsUpdate(genConfig.version, version) || alwaysRun) {
|
|
29
|
+
genConfig = updateFunction(genConfig)
|
|
30
|
+
}
|
|
31
|
+
if (genConfig.multiDashboards) {
|
|
32
|
+
genConfig.multiDashboards.forEach((dashboard, index) => {
|
|
33
|
+
dashboard.type = 'dashboard'
|
|
34
|
+
genConfig.multiDashboards[index] = coveUpdateWorker(dashboard)
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
})
|
|
23
38
|
|
|
24
|
-
|
|
25
|
-
return await coveUpdateWorker(config)
|
|
39
|
+
return genConfig
|
|
26
40
|
}
|
|
27
41
|
|
|
28
|
-
export default
|
|
42
|
+
export default coveUpdateWorker
|
package/helpers/filterVizData.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
import { SubGrouping } from '../types/VizFilter'
|
|
2
|
+
|
|
3
|
+
type Filter = {
|
|
4
|
+
columnName: string
|
|
5
|
+
type?: string
|
|
6
|
+
values: (string | number)[]
|
|
7
|
+
filterStyle?: string
|
|
8
|
+
active?: string | number | (string | number)[]
|
|
9
|
+
parents?: (string | number)[]
|
|
10
|
+
subGrouping?: SubGrouping
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const filterVizData = (filters: Filter[], data) => {
|
|
2
14
|
if (!data) {
|
|
3
15
|
console.warn('COVE: No data to filter')
|
|
4
16
|
return []
|
|
@@ -12,8 +24,9 @@ export const filterVizData = (filters, data) => {
|
|
|
12
24
|
filters
|
|
13
25
|
.filter(filter => filter.type !== 'url')
|
|
14
26
|
.forEach(filter => {
|
|
15
|
-
const value = row[filter.columnName]
|
|
16
27
|
if (filter.active === undefined) return
|
|
28
|
+
const value = row[filter.columnName]
|
|
29
|
+
|
|
17
30
|
if (Array.isArray(filter.active)) {
|
|
18
31
|
if (!filter.active.includes(value)) {
|
|
19
32
|
add = false
|
|
@@ -21,10 +34,16 @@ export const filterVizData = (filters, data) => {
|
|
|
21
34
|
} else if (value != filter.active) {
|
|
22
35
|
add = false
|
|
23
36
|
}
|
|
37
|
+
if (filter.filterStyle === 'nested-dropdown' && filter.subGrouping && add === true) {
|
|
38
|
+
const subGroupActive = filter.subGrouping.active
|
|
39
|
+
const value = row[filter.subGrouping.columnName]
|
|
40
|
+
if (subGroupActive === undefined) return
|
|
41
|
+
if (value != subGroupActive) {
|
|
42
|
+
add = false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
24
45
|
})
|
|
25
|
-
|
|
26
46
|
if (add) filteredData.push(row)
|
|
27
47
|
})
|
|
28
|
-
|
|
29
48
|
return filteredData
|
|
30
49
|
}
|
|
@@ -42,7 +42,7 @@ const cleanDashboardData = (config: DashboardConfig) => {
|
|
|
42
42
|
}
|
|
43
43
|
if (config.visualizations) {
|
|
44
44
|
Object.keys(config.visualizations).forEach(vizKey => {
|
|
45
|
-
config.visualizations[vizKey] = _.omit(config.visualizations[vizKey], ['runtime', 'formattedData', 'data'])
|
|
45
|
+
config.visualizations[vizKey] = _.omit(config.visualizations[vizKey], ['runtime', 'formattedData', 'data', 'editing', 'originalFormattedData'])
|
|
46
46
|
})
|
|
47
47
|
}
|
|
48
48
|
if (config.rows) {
|
|
@@ -66,7 +66,7 @@ const cleanSharedFilters = (config: DashboardConfig) => {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export const formatConfigBeforeSave = configToStrip => {
|
|
69
|
-
|
|
69
|
+
const strippedConfig = _.cloneDeep(configToStrip)
|
|
70
70
|
if (strippedConfig.type === 'dashboard') {
|
|
71
71
|
if (strippedConfig.multiDashboards) {
|
|
72
72
|
strippedConfig.multiDashboards.forEach((multiDashboard, i) => {
|
|
@@ -74,6 +74,11 @@ export const formatConfigBeforeSave = configToStrip => {
|
|
|
74
74
|
cleanSharedFilters(strippedConfig.multiDashboards[i])
|
|
75
75
|
cleanDashboardFootnotes(strippedConfig.multiDashboards[i])
|
|
76
76
|
})
|
|
77
|
+
delete strippedConfig.dashboard
|
|
78
|
+
delete strippedConfig.rows
|
|
79
|
+
delete strippedConfig.visualizations
|
|
80
|
+
delete strippedConfig.label
|
|
81
|
+
delete strippedConfig.activeDashboard
|
|
77
82
|
}
|
|
78
83
|
cleanDashboardData(strippedConfig)
|
|
79
84
|
cleanSharedFilters(strippedConfig)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const getGradientLegendWidth = (containerWidth: number, currentViewport: string): number => {
|
|
2
|
+
const newWidth = Number(containerWidth)
|
|
3
|
+
switch (currentViewport) {
|
|
4
|
+
case 'lg':
|
|
5
|
+
return newWidth / 3
|
|
6
|
+
case 'md':
|
|
7
|
+
return newWidth / 2
|
|
8
|
+
case 'sm':
|
|
9
|
+
case 'xs':
|
|
10
|
+
case 'xxs':
|
|
11
|
+
return newWidth / 1.4
|
|
12
|
+
default:
|
|
13
|
+
return newWidth
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets the width of the specified text using a given font.
|
|
3
|
+
* @param text The text to measure.
|
|
4
|
+
* @param font The CSS font declaration to use, defaults to the body's font if not provided.
|
|
5
|
+
* @returns The width of the text in pixels, or undefined if the context could not be obtained.
|
|
6
|
+
*/
|
|
7
|
+
export const getTextWidth = (text: string, font?: string): number | undefined => {
|
|
8
|
+
// Create a canvas element
|
|
9
|
+
const canvas = document.createElement('canvas')
|
|
10
|
+
const context = canvas.getContext('2d')
|
|
11
|
+
if (!context) {
|
|
12
|
+
console.error('2D context not found')
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
context.font = font || getComputedStyle(document.body).font
|
|
17
|
+
return Math.ceil(context.measureText(text).width)
|
|
18
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import { VizFilter } from '../../types/VizFilter'
|
|
3
|
+
import { addValuesToFilters } from '../addValuesToFilters'
|
|
4
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
describe('addValuesToFilters', () => {
|
|
7
|
+
const parentFilter = { columnName: 'parentColumn', id: 11, active: 'apple', values: [] } as VizFilter
|
|
8
|
+
const parentFilter2 = { columnName: 'parentColumn2', id: 22, active: '1', values: [] } as VizFilter
|
|
9
|
+
const childFilter = { columnName: 'childColumn', id: 33, parents: [11, 22], values: [] } as VizFilter
|
|
10
|
+
const data = [
|
|
11
|
+
{ parentColumn: 'apple', parentColumn2: 3, childColumn: 'a' },
|
|
12
|
+
{ parentColumn: 'apple', parentColumn2: 1, childColumn: 'b' },
|
|
13
|
+
{ parentColumn: 'pear', parentColumn2: 4, childColumn: 'c' }
|
|
14
|
+
]
|
|
15
|
+
const filters: VizFilter[] = [parentFilter, childFilter, parentFilter2]
|
|
16
|
+
it('adds filter values based on parent active values', () => {
|
|
17
|
+
const filtersCopy = _.cloneDeep(filters)
|
|
18
|
+
const newFilters = addValuesToFilters(filtersCopy, data)
|
|
19
|
+
expect(newFilters[0].values).toEqual(['apple', 'pear'])
|
|
20
|
+
expect(newFilters[2].values).toEqual([3, 1, 4])
|
|
21
|
+
expect(newFilters[1].values).toEqual(['b'])
|
|
22
|
+
|
|
23
|
+
filtersCopy[0].active = 'pear'
|
|
24
|
+
const newFilters2 = addValuesToFilters(filtersCopy, data)
|
|
25
|
+
expect(newFilters2[0].values).toEqual(['apple', 'pear'])
|
|
26
|
+
expect(newFilters2[2].values).toEqual([3, 1, 4])
|
|
27
|
+
expect(newFilters2[1].values).toEqual([])
|
|
28
|
+
})
|
|
29
|
+
it('works when data is an object', () => {
|
|
30
|
+
const filtersCopy = _.cloneDeep(filters)
|
|
31
|
+
const newFilters = addValuesToFilters(filtersCopy, { '0': data })
|
|
32
|
+
expect(newFilters[0].values).toEqual(['apple', 'pear'])
|
|
33
|
+
expect(newFilters[2].values).toEqual([3, 1, 4])
|
|
34
|
+
// This test is failing
|
|
35
|
+
// data is only an object when using map data according to Adam Doe.
|
|
36
|
+
//expect(newFilters[1].values).toEqual([])
|
|
37
|
+
})
|
|
38
|
+
it('works for nested dropdowns', () => {
|
|
39
|
+
const nestedParentFilter = { ...parentFilter, subGrouping: { columnName: 'childColumn' } }
|
|
40
|
+
const newFilters = addValuesToFilters([nestedParentFilter], data)
|
|
41
|
+
expect(newFilters[0].values).toEqual(['apple', 'pear'])
|
|
42
|
+
expect(newFilters[0].subGrouping.valuesLookup).toEqual({ apple: { values: ['a', 'b'] }, pear: { values: ['c'] } })
|
|
43
|
+
nestedParentFilter.active = 'apple'
|
|
44
|
+
expect(newFilters[0].subGrouping.active).toEqual('a')
|
|
45
|
+
})
|
|
46
|
+
it('maintains nested custom order', () => {
|
|
47
|
+
const nestedParentFilter = {
|
|
48
|
+
...parentFilter,
|
|
49
|
+
subGrouping: { columnName: 'childColumn', valuesLookup: { apple: { orderedValues: ['b', 'a'] } } }
|
|
50
|
+
}
|
|
51
|
+
const newFilters = addValuesToFilters([nestedParentFilter], data)
|
|
52
|
+
expect(newFilters[0].values).toEqual(['apple', 'pear'])
|
|
53
|
+
expect(newFilters[0].subGrouping.valuesLookup.apple.orderedValues).toEqual(['b', 'a'])
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { filterVizData } from '../filterVizData'
|
|
3
|
+
describe('filterVizData', () => {
|
|
4
|
+
it('filters data when there is a nested filter', () => {
|
|
5
|
+
const nestedFilter = {
|
|
6
|
+
columnName: 'nestedColumn',
|
|
7
|
+
id: 1,
|
|
8
|
+
active: 'nestedValue',
|
|
9
|
+
values: [],
|
|
10
|
+
filterStyle: 'nested-dropdown',
|
|
11
|
+
subGrouping: {
|
|
12
|
+
active: 'subGroupValue',
|
|
13
|
+
columnName: 'subGroupColumn',
|
|
14
|
+
valuesLookup: { nestedValue: { values: ['subGroupValue'] } }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const normalFilter = { columnName: 'normalColumn', id: 2, active: 'normalValue', values: [] }
|
|
18
|
+
|
|
19
|
+
const data = [
|
|
20
|
+
{ nestedColumn: 'nestedValue', subGroupColumn: 'subGroupValue', normalColumn: 'normalValue' },
|
|
21
|
+
{ nestedColumn: 'X', subGroupColumn: 'subGroupValue', normalColumn: 'normalValue' },
|
|
22
|
+
{ nestedColumn: 'nestedValue', subGroupColumn: 'X', normalColumn: 'normalValue' },
|
|
23
|
+
{ nestedColumn: 'nestedValue', subGroupColumn: 'subGroupValue', normalColumn: 'X' },
|
|
24
|
+
{ nestedColumn: 'nestedValue', subGroupColumn: 'subGroupValue', normalColumn: 'normalValue' }
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
const filters = [nestedFilter, normalFilter]
|
|
28
|
+
const filteredData = filterVizData(filters, data)
|
|
29
|
+
expect(filteredData.length).toEqual(2)
|
|
30
|
+
})
|
|
31
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// invertValue.test.js
|
|
2
|
+
import { invertValue } from '../scaling'
|
|
3
|
+
import { expect, describe, it, vi } from 'vitest'
|
|
4
|
+
describe('invertValue', () => {
|
|
5
|
+
it('should return the inverted value if xScale has an invert function', () => {
|
|
6
|
+
const xScale = {
|
|
7
|
+
invert: vi.fn().mockReturnValue(50)
|
|
8
|
+
}
|
|
9
|
+
const value = 100
|
|
10
|
+
|
|
11
|
+
const result = invertValue(xScale, value)
|
|
12
|
+
|
|
13
|
+
expect(xScale.invert).toHaveBeenCalledWith(value)
|
|
14
|
+
expect(result).toBe(50)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should return null if xScale does not have an invert function', () => {
|
|
18
|
+
const xScale = {}
|
|
19
|
+
const value = 100
|
|
20
|
+
|
|
21
|
+
const result = invertValue(xScale, value)
|
|
22
|
+
|
|
23
|
+
expect(result).toBeNull()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should return null if xScale is null or undefined', () => {
|
|
27
|
+
const value = 100
|
|
28
|
+
|
|
29
|
+
const resultWithNull = invertValue(null, value)
|
|
30
|
+
const resultWithUndefined = invertValue(undefined, value)
|
|
31
|
+
|
|
32
|
+
expect(resultWithNull).toBeNull()
|
|
33
|
+
expect(resultWithUndefined).toBeNull()
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface ColorPalettes {
|
|
2
|
+
[paletteName: string]: string[]
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export const updatePaletteNames = (colorPalettes: ColorPalettes): ColorPalettes => {
|
|
6
|
+
// This function adds REVERSE keyword to each palette
|
|
7
|
+
delete colorPalettes.qualitative9 // Delete palette before reversing
|
|
8
|
+
|
|
9
|
+
let paletteReversed: ColorPalettes = {}
|
|
10
|
+
for (const [paletteName, hexCodeArr] of Object.entries(colorPalettes)) {
|
|
11
|
+
const paletteStr = String(paletteName)
|
|
12
|
+
|
|
13
|
+
if (!paletteStr.endsWith('reverse')) {
|
|
14
|
+
let palette = paletteStr.concat('reverse') // Add to the end of the string "reverse"
|
|
15
|
+
paletteReversed[palette] = [...hexCodeArr].reverse() // Reverses array elements and create new keys on object
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return { ...paletteReversed, ...colorPalettes }
|
|
19
|
+
}
|
|
@@ -43,7 +43,7 @@ export default function useDataVizClasses(config, viewport = null) {
|
|
|
43
43
|
// Using short circuiting to check between charts & maps for now.
|
|
44
44
|
const getListPosition = () => {
|
|
45
45
|
if (legend?.position === 'side' && legend?.singleColumn) return 'legend-container__ul--single-column'
|
|
46
|
-
if (legend?.position
|
|
46
|
+
if (legend?.position !== 'side' && legend?.singleRow) return 'single-row'
|
|
47
47
|
if (legend?.verticalSorted && !legend?.singleRow) return 'vertical-sorted'
|
|
48
48
|
return ''
|
|
49
49
|
}
|
|
@@ -53,7 +53,8 @@ export default function useDataVizClasses(config, viewport = null) {
|
|
|
53
53
|
ulClasses.push(getListPosition())
|
|
54
54
|
return ulClasses
|
|
55
55
|
}
|
|
56
|
-
const
|
|
56
|
+
const hasBorder = config.legend?.hideBorder ? 'no-border' : ''
|
|
57
|
+
const legendOuterClasses = [`${legend?.position}`, `${getListPosition()}`, `cdcdataviz-sr-focusable`, `${viewport}`, `${hasBorder}`]
|
|
57
58
|
|
|
58
59
|
const legendClasses = {
|
|
59
60
|
aside: legendOuterClasses,
|
package/helpers/ver/4.24.5.ts
CHANGED
|
@@ -18,8 +18,8 @@ const migrateMarkupInclude = newConfig => {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const
|
|
22
|
-
const ver = '4.24.
|
|
21
|
+
const update_4_24_5 = config => {
|
|
22
|
+
const ver = '4.24.5'
|
|
23
23
|
|
|
24
24
|
const newConfig = _.cloneDeep(config)
|
|
25
25
|
|
|
@@ -29,4 +29,4 @@ const update_4_24_4 = config => {
|
|
|
29
29
|
return newConfig
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export default
|
|
32
|
+
export default update_4_24_5
|
package/helpers/ver/4.24.7.ts
CHANGED
|
@@ -2,6 +2,7 @@ import _ from 'lodash'
|
|
|
2
2
|
import { DashboardFilters } from '@cdc/dashboard/src/types/DashboardFilters'
|
|
3
3
|
import { MultiDashboardConfig } from '@cdc/dashboard/src/types/MultiDashboard'
|
|
4
4
|
import { AnyVisualization } from '../../types/Visualization'
|
|
5
|
+
import versionNeedsUpdate from './versionNeedsUpdate'
|
|
5
6
|
|
|
6
7
|
export const dashboardFiltersMigrate = config => {
|
|
7
8
|
if (!config.dashboard) return config
|
|
@@ -34,8 +35,20 @@ export const dashboardFiltersMigrate = config => {
|
|
|
34
35
|
delete viz.hide
|
|
35
36
|
}
|
|
36
37
|
// 'filter-dropdowns' was renamed to 'dashboardFilters' for clarity
|
|
37
|
-
if (viz.type === 'filter-dropdowns')
|
|
38
|
-
|
|
38
|
+
if (viz.type === 'filter-dropdowns') {
|
|
39
|
+
viz.type = 'dashboardFilters'
|
|
40
|
+
viz.visualizationType = 'dashboardFilters'
|
|
41
|
+
if (!viz.sharedFilterIndexes) {
|
|
42
|
+
viz.sharedFilterIndexes = config.dashboard.sharedFilters.map((_sf, i) => i)
|
|
43
|
+
viz.filterBehavior = config.filterBehavior || 'Filter Change'
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Premature convertion to 4.24.7 made us add this fix
|
|
48
|
+
if (viz.type === 'dashboardFilters' && !viz.sharedFilterIndexes) {
|
|
49
|
+
viz.sharedFilterIndexes = config.dashboard.sharedFilters.map((_sf, i) => i)
|
|
50
|
+
viz.filterBehavior = config.filterBehavior || 'Filter Change'
|
|
51
|
+
}
|
|
39
52
|
newVisualizations[vizKey] = viz
|
|
40
53
|
})
|
|
41
54
|
|
|
@@ -57,10 +70,21 @@ export const dashboardFiltersMigrate = config => {
|
|
|
57
70
|
]
|
|
58
71
|
}
|
|
59
72
|
config.rows = [newRow, ...config.rows]
|
|
73
|
+
config.dashboard.sharedFilters = config.dashboard.sharedFilters.map(sf => {
|
|
74
|
+
if (sf.usedBy) {
|
|
75
|
+
// Fixes usedBy Rows
|
|
76
|
+
sf.usedBy = sf.usedBy.map(key => {
|
|
77
|
+
if (!(parseInt(key) > -1)) return key
|
|
78
|
+
return String(parseInt(key) + 1)
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
return sf
|
|
82
|
+
})
|
|
60
83
|
}
|
|
61
84
|
// if there's no dashboardFilters visualization but there are sharedFilters create a visualization and update rows.
|
|
62
85
|
|
|
63
86
|
config.visualizations = newVisualizations
|
|
87
|
+
delete config.filterBehavior // deprecated
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
const mapUpdates = newConfig => {
|
|
@@ -79,6 +103,12 @@ const mapUpdates = newConfig => {
|
|
|
79
103
|
return newConfig
|
|
80
104
|
}
|
|
81
105
|
|
|
106
|
+
const updateLogarithmicConfig = newConfig => {
|
|
107
|
+
if (newConfig.useLogScale) {
|
|
108
|
+
newConfig.yAxis.type === 'logarithmic'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
82
112
|
const update_4_24_7 = config => {
|
|
83
113
|
const ver = '4.24.7'
|
|
84
114
|
|
|
@@ -86,7 +116,8 @@ const update_4_24_7 = config => {
|
|
|
86
116
|
|
|
87
117
|
mapUpdates(newConfig)
|
|
88
118
|
dashboardFiltersMigrate(newConfig)
|
|
89
|
-
newConfig
|
|
119
|
+
updateLogarithmicConfig(newConfig)
|
|
120
|
+
newConfig.version = versionNeedsUpdate(config.version, ver) ? ver : config.version
|
|
90
121
|
return newConfig
|
|
91
122
|
}
|
|
92
123
|
export default update_4_24_7
|