@cdc/core 4.24.7 → 4.24.9-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.
Files changed (69) hide show
  1. package/LICENSE +201 -0
  2. package/assets/icon-gear-multi.svg +23 -0
  3. package/components/Alert/components/Alert.styles.css +15 -0
  4. package/components/Alert/components/Alert.tsx +39 -0
  5. package/components/Alert/index.tsx +3 -0
  6. package/components/DataTable/DataTable.tsx +106 -30
  7. package/components/DataTable/helpers/chartCellMatrix.tsx +3 -3
  8. package/components/DataTable/helpers/getChartCellValue.ts +1 -1
  9. package/components/DataTable/helpers/getDataSeriesColumns.ts +2 -2
  10. package/components/DataTable/helpers/mapCellMatrix.tsx +3 -3
  11. package/components/DataTable/types/TableConfig.ts +1 -1
  12. package/components/EditorPanel/Inputs.tsx +13 -4
  13. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +268 -0
  14. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +161 -82
  15. package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +31 -45
  16. package/components/Filters.tsx +223 -180
  17. package/components/Layout/components/Responsive.tsx +14 -4
  18. package/components/Layout/components/Sidebar/components/Sidebar.tsx +14 -5
  19. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +15 -16
  20. package/components/Layout/components/Visualization/index.tsx +7 -1
  21. package/components/Layout/components/Visualization/visualizations.scss +32 -26
  22. package/components/Layout/styles/editor.scss +0 -8
  23. package/components/Legend/Legend.Gradient.tsx +133 -0
  24. package/components/LegendShape.tsx +28 -0
  25. package/components/MultiSelect/MultiSelect.tsx +6 -3
  26. package/components/NestedDropdown/NestedDropdown.tsx +47 -52
  27. package/components/NestedDropdown/nesteddropdown.styles.css +19 -25
  28. package/components/Table/Table.tsx +8 -5
  29. package/components/Table/components/Cell.tsx +2 -2
  30. package/components/Table/components/Row.tsx +25 -7
  31. package/components/_stories/Layout.Debug.stories.tsx +91 -0
  32. package/components/_stories/_mocks/bar-chart-suppressed.json +474 -0
  33. package/components/_stories/styles.scss +13 -1
  34. package/components/createBarElement.jsx +4 -4
  35. package/components/ui/Icon.tsx +21 -14
  36. package/components/ui/Title/Title.scss +0 -8
  37. package/helpers/DataTransform.ts +2 -2
  38. package/helpers/addValuesToFilters.ts +95 -16
  39. package/helpers/cove/accessibility.ts +16 -4
  40. package/helpers/coveUpdateWorker.ts +24 -10
  41. package/helpers/filterVizData.ts +23 -4
  42. package/helpers/formatConfigBeforeSave.ts +22 -2
  43. package/helpers/gatherQueryParams.ts +12 -2
  44. package/helpers/getGradientLegendWidth.ts +15 -0
  45. package/helpers/getTextWidth.ts +18 -0
  46. package/helpers/scaling.ts +7 -0
  47. package/helpers/tests/addValuesToFilters.test.ts +55 -0
  48. package/helpers/tests/filterVizData.test.ts +31 -0
  49. package/helpers/tests/gatherQueryParams.test.ts +22 -0
  50. package/helpers/tests/invertValue.test.ts +35 -0
  51. package/helpers/updatePaletteNames.ts +19 -0
  52. package/helpers/{useDataVizClasses.js → useDataVizClasses.ts} +3 -2
  53. package/helpers/ver/4.24.5.ts +3 -3
  54. package/helpers/ver/4.24.7.ts +34 -3
  55. package/helpers/ver/4.24.9.ts +63 -0
  56. package/helpers/ver/tests/4.24.9.test.ts +22 -0
  57. package/helpers/ver/versionNeedsUpdate.ts +9 -0
  58. package/package.json +3 -3
  59. package/styles/_button-section.scss +1 -1
  60. package/styles/_global.scss +6 -2
  61. package/styles/filters.scss +4 -0
  62. package/types/Axis.ts +3 -0
  63. package/types/Dimensions.ts +1 -0
  64. package/types/General.ts +1 -1
  65. package/types/VizFilter.ts +24 -3
  66. package/components/LegendCircle.jsx +0 -17
  67. package/helpers/updatePaletteNames.js +0 -16
  68. /package/components/{Waiting.jsx → Waiting.tsx} +0 -0
  69. /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 = (columnName, data: any[] | Record<string, any[]>) => {
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
- return values
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 = <T>(filters: Filter[], data: any[] | Record<string, any[]>): Array<T> => {
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 filterValues = generateValuesForFilter(filter.columnName, data)
43
- filterCopy.values = filterValues
44
- if (filterValues.length > 0) {
45
- const defaultValues = filterCopy.filterStyle === 'multi-select' ? filterCopy.values : filterCopy.values[0]
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
- filterCopy.active = filterCopy.active || defaultValues
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
- }) as Array<T>
134
+ })
56
135
  }
@@ -1,16 +1,18 @@
1
1
  import chroma from 'chroma-js'
2
2
 
3
3
  /**
4
- * WCAG 2.0 Standard requires 4.5:1 contrast ratio
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 WCAG_CONTRAST_RATIO = 4.5
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) < WCAG_CONTRAST_RATIO) {
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.23.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
- genConfig = update_4_24_3(genConfig)
17
- genConfig = update_4_24_4(genConfig)
18
- genConfig = update_4_24_5(genConfig)
19
- genConfig = update_4_24_7(genConfig)
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
- return genConfig
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
- const asyncWorker = async config => {
25
- return await coveUpdateWorker(config)
39
+ return genConfig
26
40
  }
27
41
 
28
- export default asyncWorker
42
+ export default coveUpdateWorker
@@ -1,4 +1,16 @@
1
- export const filterVizData = (filters, data) => {
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,13 @@ 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], [
46
+ 'runtime',
47
+ 'formattedData',
48
+ 'data',
49
+ 'editing',
50
+ 'originalFormattedData'
51
+ ])
46
52
  })
47
53
  }
48
54
  if (config.rows) {
@@ -65,8 +71,16 @@ const cleanSharedFilters = (config: DashboardConfig) => {
65
71
  }
66
72
  }
67
73
 
74
+ const removeRuntimeDataURLs = (config: DashboardConfig) => {
75
+ if (config.datasets) {
76
+ Object.keys(config.datasets).forEach(datasetKey => {
77
+ delete config.datasets[datasetKey].runtimeDataUrl
78
+ })
79
+ }
80
+ }
81
+
68
82
  export const formatConfigBeforeSave = configToStrip => {
69
- let strippedConfig = _.cloneDeep(configToStrip)
83
+ const strippedConfig = _.cloneDeep(configToStrip)
70
84
  if (strippedConfig.type === 'dashboard') {
71
85
  if (strippedConfig.multiDashboards) {
72
86
  strippedConfig.multiDashboards.forEach((multiDashboard, i) => {
@@ -74,10 +88,16 @@ export const formatConfigBeforeSave = configToStrip => {
74
88
  cleanSharedFilters(strippedConfig.multiDashboards[i])
75
89
  cleanDashboardFootnotes(strippedConfig.multiDashboards[i])
76
90
  })
91
+ delete strippedConfig.dashboard
92
+ delete strippedConfig.rows
93
+ delete strippedConfig.visualizations
94
+ delete strippedConfig.label
95
+ delete strippedConfig.activeDashboard
77
96
  }
78
97
  cleanDashboardData(strippedConfig)
79
98
  cleanSharedFilters(strippedConfig)
80
99
  cleanDashboardFootnotes(strippedConfig)
100
+ removeRuntimeDataURLs(strippedConfig)
81
101
  } else {
82
102
  delete strippedConfig.runtime
83
103
  delete strippedConfig.formattedData
@@ -1,5 +1,15 @@
1
1
  import _ from 'lodash'
2
2
 
3
+ const strip = (paramVal: string) => {
4
+ return paramVal.replace(/"/g, '')
5
+ }
6
+
7
+ const isNumber = (value: string) => {
8
+ const hasLetters = value.match(/[a-zA-Z]/)
9
+ if (hasLetters) return false
10
+ return !isNaN(parseInt(value))
11
+ }
12
+
3
13
  export const gatherQueryParams = (baseEndpoint: string, params: { key: string; value: string }[]) => {
4
14
  const baseEndpointHasQueryParams = baseEndpoint.includes('?')
5
15
  return params
@@ -7,8 +17,8 @@ export const gatherQueryParams = (baseEndpoint: string, params: { key: string; v
7
17
  .map(({ key, value }, i) => {
8
18
  const leadingCharacter = i === 0 && !baseEndpointHasQueryParams ? '?' : '&'
9
19
  const isStatementParam = key.match(/\$.*/)
10
- if (!_.isNaN(parseInt(value)) || isStatementParam) return leadingCharacter + key + '=' + value
11
- return leadingCharacter + key + '=' + `"${value}"`
20
+ if (isNumber(value) || isStatementParam) return leadingCharacter + key + '=' + value
21
+ return leadingCharacter + key + '=' + `"${strip(value)}"`
12
22
  })
13
23
  .join('')
14
24
  }
@@ -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,7 @@
1
+ export const invertValue = (xScale, value) => {
2
+ if (xScale && typeof xScale.invert === 'function') {
3
+ return xScale.invert(value)
4
+ } else {
5
+ return null
6
+ }
7
+ }
@@ -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,22 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { gatherQueryParams } from '../gatherQueryParams'
3
+
4
+ describe('gatherQueryParams', () => {
5
+ it('should return a string of query params', () => {
6
+ const baseEndpoint = 'https://example.com'
7
+ const params = [
8
+ { key: 'key1', value: 'value1' },
9
+ { key: 'key2', value: 'value2' }
10
+ ]
11
+ expect(gatherQueryParams(baseEndpoint, params)).toEqual('?key1="value1"&key2="value2"')
12
+ })
13
+
14
+ it('should not wrap strings in extra Quotes', () => {
15
+ const baseEndpoint = 'https://example.com'
16
+ const params = [
17
+ { key: 'key1', value: 'value1' },
18
+ { key: 'key2', value: '"value2"' }
19
+ ]
20
+ expect(gatherQueryParams(baseEndpoint, params)).toEqual('?key1="value1"&key2="value2"')
21
+ })
22
+ })
@@ -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 === 'bottom' && legend?.singleRow) return 'single-row'
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 legendOuterClasses = [`${legend?.position}`, `${getListPosition()}`, `cdcdataviz-sr-focusable`, `${viewport}`]
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,
@@ -18,8 +18,8 @@ const migrateMarkupInclude = newConfig => {
18
18
  }
19
19
  }
20
20
 
21
- const update_4_24_4 = config => {
22
- const ver = '4.24.4'
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 update_4_24_4
32
+ export default update_4_24_5