@cdc/core 4.24.12 → 4.25.2-25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/DataTable/DataTable.tsx +58 -45
- package/components/DataTable/DataTableStandAlone.tsx +3 -3
- package/components/DataTable/components/ChartHeader.tsx +26 -6
- package/components/DataTable/components/ExpandCollapse.tsx +1 -4
- package/components/DataTable/components/MapHeader.tsx +5 -1
- package/components/DataTable/data-table.css +3 -8
- package/components/DataTable/helpers/chartCellMatrix.tsx +14 -5
- package/components/DataTable/helpers/customSort.ts +2 -2
- package/components/DataTable/helpers/mapCellMatrix.tsx +83 -60
- package/components/DataTable/types/TableConfig.ts +0 -1
- package/components/EditorPanel/FootnotesEditor.tsx +49 -7
- package/components/EditorPanel/Inputs.tsx +4 -0
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +16 -3
- package/components/Filters/Filters.tsx +95 -51
- package/components/Filters/helpers/filterWrapping.ts +43 -0
- package/components/Filters/helpers/handleSorting.ts +6 -0
- package/components/Filters/helpers/tests/handleSorting.test.ts +26 -0
- package/components/Footnotes/Footnotes.tsx +1 -1
- package/components/Layout/components/Visualization/index.tsx +18 -4
- package/components/Layout/components/Visualization/visualizations.scss +1 -1
- package/components/Legend/Legend.Gradient.tsx +1 -4
- package/components/Legend/index.tsx +1 -1
- package/components/LegendShape.tsx +2 -3
- package/components/MediaControls.jsx +32 -8
- package/components/NestedDropdown/NestedDropdown.tsx +25 -17
- package/components/NestedDropdown/nesteddropdown.styles.css +13 -7
- package/components/Table/Table.tsx +11 -11
- package/components/Table/components/Row.tsx +14 -5
- package/components/_stories/DataTable.stories.tsx +1 -2
- package/components/elements/Button.jsx +38 -19
- package/components/elements/Confirm.tsx +45 -0
- package/components/elements/Error.tsx +24 -0
- package/components/managers/DataDesigner.tsx +198 -143
- package/components/ui/Title/Title.scss +12 -5
- package/components/ui/Title/index.tsx +1 -1
- package/dist/cove-main.css +260 -723
- package/dist/cove-main.css.map +1 -1
- package/helpers/DataTransform.ts +55 -61
- package/helpers/addValuesToFilters.ts +45 -16
- package/helpers/cove/accessibility.ts +24 -0
- package/helpers/cove/fontSettings.ts +1 -1
- package/helpers/cove/number.ts +1 -7
- package/helpers/coveUpdateWorker.ts +5 -1
- package/helpers/displayDataAsText.ts +64 -0
- package/helpers/filterVizData.ts +2 -2
- package/helpers/formatConfigBeforeSave.ts +17 -2
- package/helpers/isOlderVersion.ts +20 -0
- package/helpers/isRightAlignedTableValue.js +14 -0
- package/helpers/missingRequiredSections.ts +20 -0
- package/helpers/queryStringUtils.ts +7 -0
- package/helpers/tests/addValuesToFilters.test.ts +19 -1
- package/helpers/useDataVizClasses.ts +8 -4
- package/helpers/ver/4.24.10.ts +12 -0
- package/helpers/ver/4.24.11.ts +18 -0
- package/helpers/ver/4.24.7.ts +19 -1
- package/helpers/ver/4.25.1.ts +18 -0
- package/package.json +2 -2
- package/styles/_button-section.scss +2 -5
- package/styles/_global-variables.scss +17 -7
- package/styles/_global.scss +8 -12
- package/styles/_reset.scss +4 -5
- package/styles/_typography.scss +0 -20
- package/styles/_variables.scss +0 -3
- package/styles/base.scss +44 -5
- package/styles/cove-main.scss +1 -1
- package/styles/filters.scss +65 -6
- package/styles/v2/base/_general.scss +3 -2
- package/styles/v2/components/button.scss +0 -1
- package/styles/v2/main.scss +3 -4
- package/styles/v2/themes/_color-definitions.scss +4 -4
- package/styles/v2/utils/index.scss +0 -1
- package/types/BoxPlot.ts +1 -0
- package/types/Runtime.ts +1 -0
- package/types/Table.ts +0 -1
- package/types/Version.ts +1 -1
- package/types/VizFilter.ts +3 -1
- package/styles/v2/utils/_spacers.scss +0 -31
|
@@ -39,13 +39,40 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField }
|
|
|
39
39
|
<>
|
|
40
40
|
<em>Dynamic Footnotes</em>
|
|
41
41
|
<div className='row border p-2'>
|
|
42
|
-
<InputSelect
|
|
42
|
+
<InputSelect
|
|
43
|
+
label='Select a Footnote Dataset'
|
|
44
|
+
value={config.dataKey}
|
|
45
|
+
options={dataSetOptions}
|
|
46
|
+
fieldName='dataKey'
|
|
47
|
+
updateField={updateField}
|
|
48
|
+
/>
|
|
43
49
|
|
|
44
50
|
{config.dataKey && (
|
|
45
51
|
<div className='p-3'>
|
|
46
|
-
<InputSelect
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
<InputSelect
|
|
53
|
+
label='Footnote Symbol Column'
|
|
54
|
+
value={config.dynamicFootnotes?.symbolColumn}
|
|
55
|
+
options={dataColumns}
|
|
56
|
+
section='dynamicFootnotes'
|
|
57
|
+
fieldName='symbolColumn'
|
|
58
|
+
updateField={updateField}
|
|
59
|
+
/>
|
|
60
|
+
<InputSelect
|
|
61
|
+
label='Footnote Text Column'
|
|
62
|
+
value={config.dynamicFootnotes?.textColumn}
|
|
63
|
+
options={dataColumns}
|
|
64
|
+
section='dynamicFootnotes'
|
|
65
|
+
fieldName='textColumn'
|
|
66
|
+
updateField={updateField}
|
|
67
|
+
/>
|
|
68
|
+
<InputSelect
|
|
69
|
+
label='Footnote Order Column'
|
|
70
|
+
value={config.dynamicFootnotes?.orderColumn}
|
|
71
|
+
options={dataColumns}
|
|
72
|
+
section='dynamicFootnotes'
|
|
73
|
+
fieldName='orderColumn'
|
|
74
|
+
updateField={updateField}
|
|
75
|
+
/>
|
|
49
76
|
</div>
|
|
50
77
|
)}
|
|
51
78
|
</div>
|
|
@@ -57,10 +84,25 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField }
|
|
|
57
84
|
{config.staticFootnotes?.map((note, index) => (
|
|
58
85
|
<div key={index} className='row border p-2'>
|
|
59
86
|
<div className='col-8'>
|
|
60
|
-
<InputSelect
|
|
61
|
-
|
|
87
|
+
<InputSelect
|
|
88
|
+
label='Symbol'
|
|
89
|
+
value={note.symbol}
|
|
90
|
+
options={[['', '--Select--'], ...footnotesSymbols]}
|
|
91
|
+
fieldName='symbol'
|
|
92
|
+
updateField={(section, subsection, fieldName, value) =>
|
|
93
|
+
updateStaticFootnote(index, { ...note, symbol: value })
|
|
94
|
+
}
|
|
95
|
+
/>{' '}
|
|
96
|
+
<TextField
|
|
97
|
+
label='Text'
|
|
98
|
+
value={note.text}
|
|
99
|
+
fieldName='text'
|
|
100
|
+
updateField={(section, subsection, fieldName, value) =>
|
|
101
|
+
updateStaticFootnote(index, { ...note, text: value })
|
|
102
|
+
}
|
|
103
|
+
/>
|
|
62
104
|
</div>
|
|
63
|
-
<div className='col-2
|
|
105
|
+
<div className='col-2 ms-4'>
|
|
64
106
|
<button className='btn btn-danger p-1' onClick={() => deleteStaticFootnote(index)}>
|
|
65
107
|
Delete
|
|
66
108
|
</button>
|
|
@@ -55,6 +55,10 @@ const TextField = memo((props: TextFieldProps) => {
|
|
|
55
55
|
}
|
|
56
56
|
}, [debouncedValue])
|
|
57
57
|
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
setValue(stateValue) // Update local state when props change
|
|
60
|
+
}, [stateValue])
|
|
61
|
+
|
|
58
62
|
let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
|
|
59
63
|
|
|
60
64
|
const onChange = e => {
|
|
@@ -91,7 +91,7 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
91
91
|
// Overwrite filterItem.values since thats what we map through in the editor panel
|
|
92
92
|
filterItem.values = updatedValues
|
|
93
93
|
filterItem.orderedValues = updatedValues
|
|
94
|
-
filterItem.active = updatedValues[0]
|
|
94
|
+
filterItem.active = filterItem.defaultValue ? filterItem.defaultValue : updatedValues[0]
|
|
95
95
|
|
|
96
96
|
filterItem.order = 'cust'
|
|
97
97
|
|
|
@@ -230,9 +230,10 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
230
230
|
value={filter.order || 'asc'}
|
|
231
231
|
fieldName='order'
|
|
232
232
|
label='Filter Order'
|
|
233
|
-
updateField={(_section, _subSection, _field, value) =>
|
|
233
|
+
updateField={(_section, _subSection, _field, value) => {
|
|
234
234
|
updateFilterProp('order', filterIndex, value)
|
|
235
|
-
|
|
235
|
+
if (filter.orderColumn && value !== 'column') updateFilterProp('orderColumn', filterIndex, '')
|
|
236
|
+
}}
|
|
236
237
|
options={filterOrderOptions}
|
|
237
238
|
/>
|
|
238
239
|
{filter.order === 'cust' && (
|
|
@@ -241,6 +242,17 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
241
242
|
handleFilterOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
|
|
242
243
|
/>
|
|
243
244
|
)}
|
|
245
|
+
{filter.order === 'column' && (
|
|
246
|
+
<Select
|
|
247
|
+
value={filter.orderColumn}
|
|
248
|
+
fieldName='orderColumn'
|
|
249
|
+
label='Order Column'
|
|
250
|
+
updateField={(_section, _subSection, _field, value) =>
|
|
251
|
+
updateFilterProp('orderColumn', filterIndex, value)
|
|
252
|
+
}
|
|
253
|
+
options={dataColumns}
|
|
254
|
+
/>
|
|
255
|
+
)}
|
|
244
256
|
</>
|
|
245
257
|
) : (
|
|
246
258
|
<NestedDropdownEditor
|
|
@@ -274,6 +286,7 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
274
286
|
updateFilterProp('parents', filterIndex, value)
|
|
275
287
|
}}
|
|
276
288
|
options={getParentFilterOptions(filterIndex)}
|
|
289
|
+
selected={config.filters[filterIndex].parents}
|
|
277
290
|
/>
|
|
278
291
|
</label>
|
|
279
292
|
</FieldSetWrapper>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useState, useEffect, useMemo } from 'react'
|
|
2
|
-
import
|
|
1
|
+
import { useState, useEffect, useMemo, useRef, useId } from 'react'
|
|
2
|
+
import _ from 'lodash'
|
|
3
3
|
|
|
4
4
|
// CDC
|
|
5
5
|
import Button from '../elements/Button'
|
|
@@ -11,16 +11,17 @@ import { filterVizData } from '../../helpers/filterVizData'
|
|
|
11
11
|
import { addValuesToFilters } from '../../helpers/addValuesToFilters'
|
|
12
12
|
import { DimensionsType } from '../../types/Dimensions'
|
|
13
13
|
import NestedDropdown from '../NestedDropdown'
|
|
14
|
-
import _ from 'lodash'
|
|
15
14
|
import { getNestedOptions } from './helpers/getNestedOptions'
|
|
16
15
|
import { applyQueuedActive } from './helpers/applyQueuedActive'
|
|
17
16
|
import { handleSorting } from './helpers/handleSorting'
|
|
17
|
+
import { getWrappingStatuses } from './helpers/filterWrapping'
|
|
18
18
|
|
|
19
19
|
export const VIZ_FILTER_STYLE = {
|
|
20
20
|
dropdown: 'dropdown',
|
|
21
21
|
nestedDropdown: 'nested-dropdown',
|
|
22
22
|
pill: 'pill',
|
|
23
23
|
tab: 'tab',
|
|
24
|
+
tabSimple: 'tab-simple',
|
|
24
25
|
tabBar: 'tab bar',
|
|
25
26
|
multiSelect: 'multi-select'
|
|
26
27
|
} as const
|
|
@@ -41,18 +42,25 @@ export const filterOrderOptions: { label: string; value: OrderBy }[] = [
|
|
|
41
42
|
{
|
|
42
43
|
label: 'Custom',
|
|
43
44
|
value: 'cust'
|
|
44
|
-
}
|
|
45
|
+
},
|
|
46
|
+
{ label: 'Order By Data Column', value: 'column' }
|
|
45
47
|
]
|
|
46
48
|
|
|
47
|
-
const hasStandardFilterBehavior = ['chart', 'table']
|
|
48
|
-
|
|
49
49
|
export const useFilters = props => {
|
|
50
50
|
const [showApplyButton, setShowApplyButton] = useState(false)
|
|
51
51
|
|
|
52
52
|
// Desconstructing: notice, adding more descriptive visualizationConfig name over config
|
|
53
53
|
// visualizationConfig feels more robust for all vis types so that its not confused with config/state/etc.
|
|
54
|
-
const {
|
|
55
|
-
|
|
54
|
+
const {
|
|
55
|
+
config: visualizationConfig,
|
|
56
|
+
setConfig,
|
|
57
|
+
filteredData,
|
|
58
|
+
setFilteredData,
|
|
59
|
+
excludedData,
|
|
60
|
+
getUniqueValues,
|
|
61
|
+
standaloneMap
|
|
62
|
+
} = props
|
|
63
|
+
const { data } = visualizationConfig
|
|
56
64
|
|
|
57
65
|
/**
|
|
58
66
|
* Re-orders a filter based on two indices and updates the runtime filters array and filters state
|
|
@@ -72,21 +80,19 @@ export const useFilters = props => {
|
|
|
72
80
|
const [movedItem] = updatedValues.splice(idx1, 1)
|
|
73
81
|
updatedValues.splice(idx2, 0, movedItem)
|
|
74
82
|
|
|
75
|
-
const filtersCopy =
|
|
76
|
-
? [...visualizationConfig.filters]
|
|
77
|
-
: [...filteredData]
|
|
83
|
+
const filtersCopy = !standaloneMap ? [...visualizationConfig.filters] : [...filteredData]
|
|
78
84
|
const filterItem = { ...filtersCopy[filterIndex] }
|
|
79
85
|
|
|
80
86
|
// Overwrite filterItem.values since thats what we map through in the editor panel
|
|
81
87
|
filterItem.values = updatedValues
|
|
82
88
|
filterItem.orderedValues = updatedValues
|
|
83
|
-
filterItem.active = updatedValues[0]
|
|
89
|
+
if (!filterItem.active) filterItem.active = filterItem.defaultValue ? filterItem.defaultValue : updatedValues[0]
|
|
84
90
|
filterItem.order = 'cust'
|
|
85
91
|
|
|
86
92
|
// Update the filters
|
|
87
93
|
filtersCopy[filterIndex] = filterItem
|
|
88
94
|
|
|
89
|
-
if (
|
|
95
|
+
if (standaloneMap) {
|
|
90
96
|
setFilteredData(filtersCopy)
|
|
91
97
|
}
|
|
92
98
|
|
|
@@ -94,13 +100,13 @@ export const useFilters = props => {
|
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
const changeFilterActive = (index, value) => {
|
|
97
|
-
let newFilters =
|
|
103
|
+
let newFilters = standaloneMap ? [...filteredData] : [...visualizationConfig.filters]
|
|
98
104
|
|
|
105
|
+
const newFilter = newFilters[index]
|
|
99
106
|
if (visualizationConfig.filterBehavior === 'Apply Button') {
|
|
100
|
-
|
|
107
|
+
newFilter.queuedActive = value
|
|
101
108
|
setShowApplyButton(true)
|
|
102
109
|
} else {
|
|
103
|
-
const newFilter = newFilters[index]
|
|
104
110
|
if (newFilter.filterStyle !== 'nested-dropdown') {
|
|
105
111
|
newFilter.active = value
|
|
106
112
|
} else {
|
|
@@ -120,7 +126,7 @@ export const useFilters = props => {
|
|
|
120
126
|
queryParams[newFilter?.subGrouping?.setByQueryParameter] = newFilter.subGrouping.active
|
|
121
127
|
updateQueryString(queryParams)
|
|
122
128
|
}
|
|
123
|
-
setFilteredData(newFilters
|
|
129
|
+
setFilteredData(newFilters)
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
if (!visualizationConfig.dynamicSeries) {
|
|
@@ -132,15 +138,12 @@ export const useFilters = props => {
|
|
|
132
138
|
}
|
|
133
139
|
|
|
134
140
|
// Used for setting active filter, fromHash breaks the filteredData functionality.
|
|
135
|
-
if (
|
|
141
|
+
if (standaloneMap && visualizationConfig.filterBehavior === 'Filter Change') {
|
|
136
142
|
setFilteredData(newFilters)
|
|
137
143
|
}
|
|
138
144
|
|
|
139
145
|
// If we're on a chart and not using the apply button
|
|
140
|
-
if (
|
|
141
|
-
hasStandardFilterBehavior.includes(visualizationConfig.type) &&
|
|
142
|
-
visualizationConfig.filterBehavior === 'Filter Change'
|
|
143
|
-
) {
|
|
146
|
+
if (!standaloneMap && visualizationConfig.filterBehavior === 'Filter Change') {
|
|
144
147
|
const newFilteredData = filterVizData(newFilters, excludedData)
|
|
145
148
|
setFilteredData(newFilteredData)
|
|
146
149
|
|
|
@@ -204,11 +207,9 @@ export const useFilters = props => {
|
|
|
204
207
|
|
|
205
208
|
setConfig({ ...visualizationConfig, filters: newFilters })
|
|
206
209
|
|
|
207
|
-
if (
|
|
210
|
+
if (standaloneMap) {
|
|
208
211
|
setFilteredData(newFilters, excludedData)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (hasStandardFilterBehavior.includes(visualizationConfig.type)) {
|
|
212
|
+
} else {
|
|
212
213
|
setFilteredData(filterVizData(newFilters, excludedData))
|
|
213
214
|
}
|
|
214
215
|
|
|
@@ -226,6 +227,7 @@ export const useFilters = props => {
|
|
|
226
227
|
if (!filter.values || filter.values.length === 0) {
|
|
227
228
|
filter.values = getUniqueValues(data, filter.columnName)
|
|
228
229
|
}
|
|
230
|
+
|
|
229
231
|
newFilters[i].active = handleSorting(filter).values[0]
|
|
230
232
|
|
|
231
233
|
if (filter.setByQueryParameter && queryParams[filter.setByQueryParameter] !== filter.active) {
|
|
@@ -240,7 +242,7 @@ export const useFilters = props => {
|
|
|
240
242
|
|
|
241
243
|
setConfig({ ...visualizationConfig, filters: newFilters })
|
|
242
244
|
|
|
243
|
-
if (
|
|
245
|
+
if (standaloneMap) {
|
|
244
246
|
setFilteredData(newFilters, excludedData)
|
|
245
247
|
} else {
|
|
246
248
|
setFilteredData(filterVizData(newFilters, excludedData))
|
|
@@ -274,17 +276,25 @@ type FilterProps = {
|
|
|
274
276
|
setFilteredData: Function
|
|
275
277
|
// updating function for setting fitlerBehavior
|
|
276
278
|
setConfig: Function
|
|
277
|
-
|
|
278
|
-
exclusions: any[]
|
|
279
|
+
standaloneMap?: boolean
|
|
279
280
|
}
|
|
280
281
|
|
|
281
282
|
const Filters = (props: FilterProps) => {
|
|
282
|
-
const { config: visualizationConfig, filteredData, dimensions } = props
|
|
283
|
-
const { filters,
|
|
283
|
+
const { config: visualizationConfig, filteredData, dimensions, standaloneMap } = props
|
|
284
|
+
const { filters, general, theme, filterBehavior } = visualizationConfig
|
|
284
285
|
const [mobileFilterStyle, setMobileFilterStyle] = useState(false)
|
|
285
286
|
const [selectedFilter, setSelectedFilter] = useState<EventTarget>(null)
|
|
287
|
+
const [wrappingFilters, setWrappingFilters] = useState({})
|
|
286
288
|
const id = useId()
|
|
287
289
|
|
|
290
|
+
const wrappingFilterRefs = useRef({})
|
|
291
|
+
const filterWrappingStatusesToUpdate = getWrappingStatuses(wrappingFilterRefs, wrappingFilters, filters)
|
|
292
|
+
|
|
293
|
+
if (filterWrappingStatusesToUpdate.length) {
|
|
294
|
+
const validStatuses = filterWrappingStatusesToUpdate.filter(Boolean) as [string, any][]
|
|
295
|
+
setWrappingFilters({ ...wrappingFilters, ...Object.fromEntries(validStatuses) })
|
|
296
|
+
}
|
|
297
|
+
|
|
288
298
|
// useFilters hook provides data and logic for handling various filter functions
|
|
289
299
|
// prettier-ignore
|
|
290
300
|
const {
|
|
@@ -298,13 +308,32 @@ const Filters = (props: FilterProps) => {
|
|
|
298
308
|
|
|
299
309
|
useEffect(() => {
|
|
300
310
|
if (!dimensions) return
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
311
|
+
const [width] = dimensions
|
|
312
|
+
|
|
313
|
+
const isMobile = Number(width) < 768
|
|
314
|
+
const isTabSimple = filters?.some(filter => filter.filterStyle === VIZ_FILTER_STYLE.tabSimple)
|
|
315
|
+
|
|
316
|
+
const defaultToMobile = isMobile && filters?.length && !isTabSimple
|
|
317
|
+
|
|
318
|
+
setMobileFilterStyle(defaultToMobile)
|
|
306
319
|
}, [dimensions])
|
|
307
320
|
|
|
321
|
+
useEffect(() => {
|
|
322
|
+
const noLongerTabSimple = Object.keys(wrappingFilters).filter(columnName => {
|
|
323
|
+
const filter = filters.find(filter => filter.columnName === columnName)
|
|
324
|
+
if (!filter) return false
|
|
325
|
+
return filter.filterStyle !== VIZ_FILTER_STYLE.tabSimple
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
if (!noLongerTabSimple.length) return
|
|
329
|
+
|
|
330
|
+
setWrappingFilters(
|
|
331
|
+
Object.fromEntries(
|
|
332
|
+
Object.entries(wrappingFilters).filter(([columnName]) => !noLongerTabSimple.includes(columnName))
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
}, [filters])
|
|
336
|
+
|
|
308
337
|
useEffect(() => {
|
|
309
338
|
if (selectedFilter) {
|
|
310
339
|
const el = document.getElementById(selectedFilter.id)
|
|
@@ -363,7 +392,7 @@ const Filters = (props: FilterProps) => {
|
|
|
363
392
|
|
|
364
393
|
const vizFiltersWithValues = useMemo(() => {
|
|
365
394
|
// Here charts is using config.filters where maps is using a runtime value
|
|
366
|
-
let vizfilters =
|
|
395
|
+
let vizfilters = standaloneMap ? filteredData : filters
|
|
367
396
|
if (!vizfilters) return []
|
|
368
397
|
if (vizfilters.fromHash) delete vizfilters.fromHash // support for Maps config
|
|
369
398
|
return addValuesToFilters(vizfilters as VizFilter[], visualizationConfig.data)
|
|
@@ -377,13 +406,17 @@ const Filters = (props: FilterProps) => {
|
|
|
377
406
|
const DropdownOptions = []
|
|
378
407
|
const Pills = []
|
|
379
408
|
const Tabs = []
|
|
409
|
+
const isTabSimple = singleFilter.filterStyle === 'tab-simple'
|
|
380
410
|
|
|
381
|
-
const { active, queuedActive, label, filterStyle } = singleFilter as VizFilter
|
|
411
|
+
const { active, queuedActive, label, filterStyle, columnName } = singleFilter as VizFilter
|
|
412
|
+
const { isDropdown } = wrappingFilters[columnName] || {}
|
|
382
413
|
|
|
383
414
|
handleSorting(singleFilter)
|
|
384
415
|
singleFilter.values?.forEach((filterOption, index) => {
|
|
385
|
-
const
|
|
386
|
-
|
|
416
|
+
const isActive = active === filterOption
|
|
417
|
+
|
|
418
|
+
const pillClassList = ['pill', isActive ? 'pill--active' : null, theme && theme]
|
|
419
|
+
const tabClassList = ['tab', isActive && 'tab--active', theme && theme, isTabSimple && 'tab--simple']
|
|
387
420
|
|
|
388
421
|
Pills.push(
|
|
389
422
|
<div className='pill__wrapper' key={`pill-${index}`}>
|
|
@@ -437,20 +470,31 @@ const Filters = (props: FilterProps) => {
|
|
|
437
470
|
|
|
438
471
|
const classList = [
|
|
439
472
|
'single-filters',
|
|
440
|
-
'form-group
|
|
473
|
+
'form-group',
|
|
441
474
|
mobileFilterStyle ? 'single-filters--dropdown' : `single-filters--${filterStyle}`
|
|
442
475
|
]
|
|
443
|
-
const mobileExempt = ['nested-dropdown', 'multi-select'].includes(filterStyle)
|
|
444
|
-
const showDefaultDropdown = (filterStyle === 'dropdown' || mobileFilterStyle) && !mobileExempt
|
|
476
|
+
const mobileExempt = ['nested-dropdown', 'multi-select', VIZ_FILTER_STYLE.tabSimple].includes(filterStyle)
|
|
477
|
+
const showDefaultDropdown = ((filterStyle === 'dropdown' || mobileFilterStyle) && !mobileExempt) || isDropdown
|
|
478
|
+
const [nestedActiveGroup, nestedActiveSubGroup] = useMemo<string[]>(() => {
|
|
479
|
+
if (filterStyle !== 'nested-dropdown') return []
|
|
480
|
+
return (singleFilter.queuedActive || [singleFilter.active, singleFilter.subGrouping?.active]) as [
|
|
481
|
+
string,
|
|
482
|
+
string
|
|
483
|
+
]
|
|
484
|
+
}, [singleFilter])
|
|
485
|
+
const hideLabelMargin = isTabSimple && !showDefaultDropdown
|
|
445
486
|
return (
|
|
446
|
-
<div className={classList.join(' ')} key={outerIndex}>
|
|
487
|
+
<div className={classList.join(' ')} key={outerIndex} ref={el => (wrappingFilterRefs.current[columnName] = el)}>
|
|
447
488
|
<>
|
|
448
489
|
{label && (
|
|
449
|
-
<label className=
|
|
490
|
+
<label className={`font-weight-bold mb-${hideLabelMargin ? '0' : '2'}`} htmlFor={`filter-${outerIndex}`}>
|
|
450
491
|
{label}
|
|
451
492
|
</label>
|
|
452
493
|
)}
|
|
453
494
|
{filterStyle === 'tab' && !mobileFilterStyle && Tabs}
|
|
495
|
+
{filterStyle === 'tab-simple' && !showDefaultDropdown && (
|
|
496
|
+
<div className='tab-simple-container d-flex w-100'>{Tabs}</div>
|
|
497
|
+
)}
|
|
454
498
|
{filterStyle === 'pill' && !mobileFilterStyle && Pills}
|
|
455
499
|
{filterStyle === 'tab bar' && !mobileFilterStyle && <TabBar filter={singleFilter} index={outerIndex} />}
|
|
456
500
|
{filterStyle === 'multi-select' && (
|
|
@@ -464,8 +508,8 @@ const Filters = (props: FilterProps) => {
|
|
|
464
508
|
)}
|
|
465
509
|
{filterStyle === 'nested-dropdown' && (
|
|
466
510
|
<NestedDropdown
|
|
467
|
-
activeGroup={
|
|
468
|
-
activeSubGroup={
|
|
511
|
+
activeGroup={nestedActiveGroup}
|
|
512
|
+
activeSubGroup={nestedActiveSubGroup}
|
|
469
513
|
filterIndex={outerIndex}
|
|
470
514
|
options={getNestedOptions(singleFilter)}
|
|
471
515
|
listLabel={label}
|
|
@@ -492,18 +536,18 @@ const Filters = (props: FilterProps) => {
|
|
|
492
536
|
const getClasses = () => {
|
|
493
537
|
const { visualizationType, legend } = visualizationConfig || {}
|
|
494
538
|
const baseClass = 'filters-section'
|
|
495
|
-
const conditionalClass =
|
|
539
|
+
const conditionalClass = standaloneMap ? general.headerColor : visualizationType === 'Spark Line' ? null : theme
|
|
496
540
|
const legendClass = legend && !legend.hide && legend.position === 'top' ? 'mb-0' : null
|
|
497
541
|
|
|
498
|
-
return [baseClass, conditionalClass, legendClass].filter(Boolean)
|
|
542
|
+
return [baseClass, conditionalClass, legendClass, 'w-100'].filter(Boolean)
|
|
499
543
|
}
|
|
500
544
|
|
|
501
545
|
return (
|
|
502
546
|
<section className={getClasses().join(' ')}>
|
|
503
547
|
{visualizationConfig.filterIntro && (
|
|
504
|
-
<p className='filters-section__intro-text'>{visualizationConfig.filterIntro}</p>
|
|
548
|
+
<p className='filters-section__intro-text mb-3'>{visualizationConfig.filterIntro}</p>
|
|
505
549
|
)}
|
|
506
|
-
<div className='d-flex flex-wrap w-100 filters-section__wrapper'>
|
|
550
|
+
<div className='d-flex flex-wrap w-100 mb-4 pb-2 filters-section__wrapper align-items-end'>
|
|
507
551
|
{' '}
|
|
508
552
|
<>
|
|
509
553
|
<Style />
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { VIZ_FILTER_STYLE } from '../Filters'
|
|
2
|
+
|
|
3
|
+
const WRAPPING_HEIGHT_THRESHOLD_NO_LABEL = 60
|
|
4
|
+
const WRAPPING_HEIGHT_THRESHOLD_WITH_LABEL = 100
|
|
5
|
+
export const getWrappingStatuses = (wrappingFilterRefs, wrappingFilters, allFilters) =>
|
|
6
|
+
Object.entries(wrappingFilterRefs.current)
|
|
7
|
+
.map(([columnValue, ref]) => {
|
|
8
|
+
if (!ref) return false
|
|
9
|
+
|
|
10
|
+
const filter = allFilters.find(filter => filter.columnName === columnValue)
|
|
11
|
+
const { filterStyle, label } = filter || {}
|
|
12
|
+
|
|
13
|
+
if (!filterStyle || filterStyle !== VIZ_FILTER_STYLE.tabSimple) return false
|
|
14
|
+
|
|
15
|
+
const filterStyleClass = (ref as HTMLElement).className
|
|
16
|
+
.split(' ')
|
|
17
|
+
.find(className => className.includes(filterStyle))
|
|
18
|
+
?.split('single-filters--')[1]
|
|
19
|
+
|
|
20
|
+
const classMatchesStyle = filterStyleClass && filterStyleClass === filterStyle
|
|
21
|
+
|
|
22
|
+
if (!classMatchesStyle) return false
|
|
23
|
+
|
|
24
|
+
const wrappingState = wrappingFilters[columnValue]
|
|
25
|
+
const { height, width } = (ref as HTMLElement).getBoundingClientRect()
|
|
26
|
+
const wrappingThreshold = label ? WRAPPING_HEIGHT_THRESHOLD_WITH_LABEL : WRAPPING_HEIGHT_THRESHOLD_NO_LABEL
|
|
27
|
+
const isWrapped = height > wrappingThreshold
|
|
28
|
+
|
|
29
|
+
if (!wrappingState) return [columnValue, { highestWrappedWidth: isWrapped ? width : 0, isDropdown: isWrapped }]
|
|
30
|
+
|
|
31
|
+
const { highestWrappedWidth, isDropdown: wasDropdown } = wrappingState
|
|
32
|
+
const isDropdown = width <= highestWrappedWidth
|
|
33
|
+
const widthIsLarger = width > highestWrappedWidth
|
|
34
|
+
const largestWidth = Math.max(highestWrappedWidth, width)
|
|
35
|
+
|
|
36
|
+
if ((isDropdown || isWrapped) && !wasDropdown) {
|
|
37
|
+
return [columnValue, { highestWrappedWidth: largestWidth, isDropdown: true }]
|
|
38
|
+
}
|
|
39
|
+
if (wasDropdown && widthIsLarger) {
|
|
40
|
+
return [columnValue, { highestWrappedWidth, isDropdown: false }]
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
.filter(Boolean)
|
|
@@ -7,12 +7,18 @@ export const handleSorting = singleFilter => {
|
|
|
7
7
|
return singleFilter
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
if (singleFilter.order === 'column') {
|
|
11
|
+
// sorting is done in the generateValuesForFilter function
|
|
12
|
+
return singleFilter
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
const sort = (a, b) => {
|
|
11
16
|
const asc = singleFilter.order !== 'desc'
|
|
12
17
|
return String(asc ? a : b).localeCompare(String(asc ? b : a), 'en', { numeric: true })
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
singleFilter.values = singleFilterValues.sort(sort)
|
|
21
|
+
singleFilter.orderedValues = singleFilterValues.sort(sort)
|
|
16
22
|
|
|
17
23
|
return singleFilter
|
|
18
24
|
}
|
|
@@ -16,6 +16,32 @@ describe('handleSorting', () => {
|
|
|
16
16
|
expect(result.values).toEqual(['value1', 'value2', 'value3'])
|
|
17
17
|
})
|
|
18
18
|
|
|
19
|
+
it('should sort orderedValues in asc order if order is asc"', () => {
|
|
20
|
+
const singleFilter = {
|
|
21
|
+
values: ['value3', 'value1', 'value2'],
|
|
22
|
+
orderedValues: ['value2', 'value1', 'value3'],
|
|
23
|
+
order: 'asc',
|
|
24
|
+
filterStyle: 'nested-dropdown'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const result = handleSorting(singleFilter)
|
|
28
|
+
|
|
29
|
+
expect(result.orderedValues).toEqual(['value1', 'value2', 'value3'])
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should sort orderedValues in desc order if order is desc"', () => {
|
|
33
|
+
const singleFilter = {
|
|
34
|
+
values: ['value3', 'value1', 'value2'],
|
|
35
|
+
orderedValues: ['value1', 'value2', 'value1'],
|
|
36
|
+
order: 'desc',
|
|
37
|
+
filterStyle: 'nested-dropdown'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = handleSorting(singleFilter)
|
|
41
|
+
|
|
42
|
+
expect(result.orderedValues).toEqual(['value3', 'value2', 'value1'])
|
|
43
|
+
})
|
|
44
|
+
|
|
19
45
|
it('should sort values in ascending order by default', () => {
|
|
20
46
|
const singleFilter = {
|
|
21
47
|
values: ['value3', 'value1', 'value2'],
|
|
@@ -12,7 +12,7 @@ const Footnotes: React.FC<FootnotesProps> = ({ footnotes }) => {
|
|
|
12
12
|
{footnotes.map((note, i) => {
|
|
13
13
|
return (
|
|
14
14
|
<li key={note.symbol + i} className='mb-1'>
|
|
15
|
-
{note.symbol && <span className='
|
|
15
|
+
{note.symbol && <span className='me-1'>{note.symbol}</span>}
|
|
16
16
|
{note.text}
|
|
17
17
|
</li>
|
|
18
18
|
)
|
|
@@ -17,10 +17,17 @@ type VisualizationWrapper = {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const Visualization: React.FC<VisualizationWrapper> = forwardRef((props, ref) => {
|
|
20
|
-
const {
|
|
20
|
+
const {
|
|
21
|
+
config = {},
|
|
22
|
+
isEditor = false,
|
|
23
|
+
currentViewport = 'lg',
|
|
24
|
+
imageId = '',
|
|
25
|
+
showEditorPanel = true,
|
|
26
|
+
className
|
|
27
|
+
} = props
|
|
21
28
|
|
|
22
29
|
const getWrappingClasses = () => {
|
|
23
|
-
let classes = ['cdc-open-viz-module', `${currentViewport}`,
|
|
30
|
+
let classes = ['cdc-open-viz-module', `${currentViewport}`, `${config?.theme}`]
|
|
24
31
|
|
|
25
32
|
if (className) {
|
|
26
33
|
classes.push(className)
|
|
@@ -40,7 +47,7 @@ const Visualization: React.FC<VisualizationWrapper> = forwardRef((props, ref) =>
|
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
if (config.type === 'filtered-text') {
|
|
43
|
-
classes.push('type-filtered-text')
|
|
50
|
+
classes.push('type-filtered-text', `font-${config.fontSize}`)
|
|
44
51
|
classes = classes.filter(item => item !== 'cove-component__content')
|
|
45
52
|
return classes
|
|
46
53
|
}
|
|
@@ -67,7 +74,14 @@ const Visualization: React.FC<VisualizationWrapper> = forwardRef((props, ref) =>
|
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
if (config.type === 'waffle-chart') {
|
|
70
|
-
classes.push(
|
|
77
|
+
classes.push(
|
|
78
|
+
'cove',
|
|
79
|
+
'cdc-open-viz-module',
|
|
80
|
+
'type-waffle-chart',
|
|
81
|
+
currentViewport,
|
|
82
|
+
config.theme,
|
|
83
|
+
'font-' + config.overallFontSize
|
|
84
|
+
)
|
|
71
85
|
|
|
72
86
|
if (isEditor) {
|
|
73
87
|
classes.push('is-editor')
|
|
@@ -92,10 +92,7 @@ const LegendGradient = ({
|
|
|
92
92
|
|
|
93
93
|
if (style === 'gradient') {
|
|
94
94
|
return (
|
|
95
|
-
<svg
|
|
96
|
-
style={{ overflow: 'visible', width: '100%', marginTop: 10, marginBottom: hideBorder ? 10 : 0 }}
|
|
97
|
-
height={newHeight}
|
|
98
|
-
>
|
|
95
|
+
<svg className={'w-100 overflow-visible'} height={newHeight}>
|
|
99
96
|
{/* background border*/}
|
|
100
97
|
<rect x={0} y={0} width={legendWidth + MARGIN * 2} height={boxHeight + MARGIN * 2} fill='#d3d3d3' />
|
|
101
98
|
{/* Define the gradient */}
|
|
@@ -28,7 +28,7 @@ const ResetButton = props => {
|
|
|
28
28
|
if (config.runtime.disabledAmt === 0) return <></>
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
<button onClick={handleReset} className={legendClasses.
|
|
31
|
+
<button onClick={handleReset} className={legendClasses.showAllButton.join(' ') || ''}>
|
|
32
32
|
Reset
|
|
33
33
|
</button>
|
|
34
34
|
)
|
|
@@ -10,9 +10,8 @@ interface LegendShapeProps {
|
|
|
10
10
|
const LegendShape: React.FC<LegendShapeProps> = props => {
|
|
11
11
|
const { fill, borderColor, display = 'inline-block', shape = 'circle' } = props
|
|
12
12
|
const dimensions = { width: '1em', height: '1em' }
|
|
13
|
-
const
|
|
13
|
+
const isCircleOrSquare = ['circle', 'square'].includes(shape)
|
|
14
14
|
const styles = {
|
|
15
|
-
marginRight: marginRight,
|
|
16
15
|
borderRadius: shape === 'circle' ? '50%' : '0px',
|
|
17
16
|
verticalAlign: 'middle',
|
|
18
17
|
display: display,
|
|
@@ -22,7 +21,7 @@ const LegendShape: React.FC<LegendShapeProps> = props => {
|
|
|
22
21
|
backgroundColor: fill
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
return <span className=
|
|
24
|
+
return <span className={`legend-item ${isCircleOrSquare ? 'me-2' : ''}`} style={styles} />
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
export default LegendShape
|