@cdc/core 4.25.5-1 → 4.25.6-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/components/Alert/components/Alert.tsx +1 -1
- package/components/DataTable/DataTable.tsx +0 -3
- package/components/DataTable/DataTableStandAlone.tsx +15 -9
- package/components/DataTable/components/ChartHeader.tsx +6 -4
- package/components/DataTable/components/DataTableEditorPanel.tsx +25 -3
- package/components/DataTable/helpers/chartCellMatrix.tsx +13 -10
- package/components/DataTable/helpers/getChartCellValue.ts +42 -26
- package/components/DataTable/helpers/mapCellMatrix.tsx +9 -1
- package/components/EditorPanel/FootnotesEditor.tsx +76 -22
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +1 -1
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +48 -34
- package/components/Filters/Filters.tsx +27 -69
- package/components/Filters/components/Dropdown.tsx +1 -1
- package/components/Footnotes/Footnotes.tsx +1 -1
- package/components/Footnotes/FootnotesStandAlone.tsx +8 -33
- package/components/Layout/components/Visualization/index.tsx +2 -1
- package/components/Legend/Legend.Gradient.tsx +3 -2
- package/components/MultiSelect/MultiSelect.tsx +3 -6
- package/components/NestedDropdown/NestedDropdown.tsx +19 -19
- package/helpers/cove/number.ts +5 -3
- package/helpers/coveUpdateWorker.ts +4 -0
- package/helpers/formatConfigBeforeSave.ts +19 -32
- package/helpers/updateFieldFactory.ts +1 -0
- package/helpers/ver/4.25.4.ts +77 -0
- package/helpers/ver/4.25.6.ts +36 -0
- package/helpers/ver/4.25.7.ts +26 -0
- package/helpers/ver/tests/4.25.4.test.ts +66 -1
- package/helpers/ver/tests/4.25.6.test.ts +84 -0
- package/package.json +7 -5
- package/styles/_global.scss +0 -4
- package/styles/filters.scss +0 -4
- package/types/Axis.ts +2 -0
- package/types/DataSet.ts +14 -0
- package/types/Footnotes.ts +5 -2
- package/types/Table.ts +1 -0
- package/types/Visualization.ts +3 -12
- package/types/VizFilter.ts +3 -0
- package/components/Filters/helpers/getNewRuntime.ts +0 -35
- package/components/Filters/helpers/tests/getNewRuntime.test.ts +0 -82
- /package/helpers/{fetchRemoteData.js → fetchRemoteData.ts} +0 -0
|
@@ -1,75 +1,129 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
1
2
|
import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
|
|
2
3
|
import _ from 'lodash'
|
|
3
4
|
import Footnotes, { Footnote } from '../../types/Footnotes'
|
|
4
5
|
import { footnotesSymbols } from '../../helpers/footnoteSymbols'
|
|
5
6
|
import InputSelect from '../inputs/InputSelect'
|
|
6
7
|
import { TextField } from './Inputs'
|
|
8
|
+
import { Datasets } from '@cdc/core/types/DataSet'
|
|
9
|
+
import DataTransform from '../../helpers/DataTransform'
|
|
10
|
+
import fetchRemoteData from '../../helpers/fetchRemoteData'
|
|
11
|
+
import Loader from '../Loader'
|
|
12
|
+
import { AnyVisualization } from '../../types/Visualization'
|
|
7
13
|
interface FootnotesEditorProps {
|
|
8
|
-
config:
|
|
9
|
-
updateField: UpdateFieldFunc<Footnote[]>
|
|
14
|
+
config: AnyVisualization
|
|
15
|
+
updateField: UpdateFieldFunc<Footnote[] | Object>
|
|
16
|
+
datasets: Datasets
|
|
10
17
|
}
|
|
11
18
|
|
|
12
|
-
const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField }) => {
|
|
19
|
+
const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField, datasets }) => {
|
|
20
|
+
const footnotesConfig = config.footnotes || {}
|
|
21
|
+
const [errorMessage, setErrorMessage] = useState('')
|
|
22
|
+
const [loadingAPIData, setLoadingAPIData] = useState(false)
|
|
23
|
+
const [datasetsCache, setDatasetsCache] = useState(datasets || {})
|
|
24
|
+
const transform = new DataTransform()
|
|
25
|
+
|
|
26
|
+
const fetchData = async datasetKey => {
|
|
27
|
+
const { data, dataUrl } = datasetsCache[datasetKey]
|
|
28
|
+
if (!dataUrl) return data
|
|
29
|
+
let newData = data
|
|
30
|
+
const noCachedData = dataUrl && !data
|
|
31
|
+
const dataSetChanged = datasetKey !== footnotesConfig.dataKey
|
|
32
|
+
setErrorMessage('')
|
|
33
|
+
if (dataSetChanged || noCachedData) {
|
|
34
|
+
setLoadingAPIData(true)
|
|
35
|
+
try {
|
|
36
|
+
newData = await fetchRemoteData(dataUrl)
|
|
37
|
+
newData = transform.autoStandardize(newData)
|
|
38
|
+
} catch (e) {
|
|
39
|
+
setErrorMessage('There was an issue loading the data source. Please check the datasource URL and try again.')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setLoadingAPIData(false)
|
|
43
|
+
}
|
|
44
|
+
return newData
|
|
45
|
+
}
|
|
46
|
+
|
|
13
47
|
const addStaticFootnote = () => {
|
|
14
|
-
const newStaticNotes = [...(
|
|
15
|
-
updateField(
|
|
48
|
+
const newStaticNotes = [...(footnotesConfig.staticFootnotes || []), { text: 'Add Footnote Text' }]
|
|
49
|
+
updateField('footnotes', null, 'staticFootnotes', newStaticNotes)
|
|
16
50
|
}
|
|
17
51
|
|
|
18
52
|
const updateStaticFootnote = (footnoteIndex, footnoteUpdate: Footnote) => {
|
|
19
|
-
const footnoteCopy = _.cloneDeep(
|
|
53
|
+
const footnoteCopy = _.cloneDeep(footnotesConfig.staticFootnotes)
|
|
20
54
|
footnoteCopy[footnoteIndex] = footnoteUpdate
|
|
21
|
-
updateField(
|
|
55
|
+
updateField('footnotes', null, 'staticFootnotes', footnoteCopy)
|
|
22
56
|
}
|
|
23
57
|
|
|
24
58
|
const deleteStaticFootnote = footnoteIndex => {
|
|
25
|
-
const footnoteCopy = _.cloneDeep(
|
|
59
|
+
const footnoteCopy = _.cloneDeep(footnotesConfig.staticFootnotes)
|
|
26
60
|
footnoteCopy.splice(footnoteIndex, 1)
|
|
27
|
-
updateField(
|
|
61
|
+
updateField('footnotes', null, 'staticFootnotes', footnoteCopy)
|
|
28
62
|
}
|
|
29
63
|
|
|
30
64
|
const getOptions = (opts: string[]) => {
|
|
31
65
|
return [['', '--Select--']].concat(opts.map(key => [key, key]))
|
|
32
66
|
}
|
|
33
67
|
|
|
34
|
-
const
|
|
68
|
+
const dataColumns = footnotesConfig.dataKey
|
|
69
|
+
? getOptions(Object.keys(datasetsCache[footnotesConfig.dataKey]?.data?.[0] || {}))
|
|
70
|
+
: []
|
|
71
|
+
const dataSetOptions = getOptions(Object.keys(datasetsCache))
|
|
72
|
+
|
|
73
|
+
const changeFootnoteDataKey = async value => {
|
|
74
|
+
if (value) {
|
|
75
|
+
if (!datasetsCache[value]) {
|
|
76
|
+
const newData = await fetchData(value)
|
|
77
|
+
setDatasetsCache({ ...datasetsCache, [value]: { ...datasetsCache[value], data: newData } })
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
updateField('footnotes', null, 'dynamicFootnotes', {})
|
|
81
|
+
}
|
|
35
82
|
|
|
36
|
-
|
|
37
|
-
|
|
83
|
+
updateField('footnotes', null, 'dataKey', value)
|
|
84
|
+
}
|
|
38
85
|
return (
|
|
39
86
|
<>
|
|
87
|
+
{loadingAPIData && <Loader fullScreen />}
|
|
40
88
|
<em>Dynamic Footnotes</em>
|
|
41
89
|
<div className='row border p-2'>
|
|
42
90
|
<InputSelect
|
|
43
91
|
label='Select a Footnote Dataset'
|
|
44
|
-
value={
|
|
92
|
+
value={footnotesConfig.dataKey}
|
|
45
93
|
options={dataSetOptions}
|
|
46
94
|
fieldName='dataKey'
|
|
47
|
-
updateField={
|
|
95
|
+
updateField={(section, subsection, fieldname, dataKey) => {
|
|
96
|
+
changeFootnoteDataKey(dataKey)
|
|
97
|
+
}}
|
|
48
98
|
/>
|
|
99
|
+
{errorMessage && <p className='text-danger'>{errorMessage}</p>}
|
|
49
100
|
|
|
50
|
-
{
|
|
101
|
+
{footnotesConfig.dataKey && (
|
|
51
102
|
<div className='p-3'>
|
|
52
103
|
<InputSelect
|
|
53
104
|
label='Footnote Symbol Column'
|
|
54
|
-
value={
|
|
105
|
+
value={footnotesConfig.dynamicFootnotes?.symbolColumn}
|
|
55
106
|
options={dataColumns}
|
|
56
|
-
section='
|
|
107
|
+
section='footnotes'
|
|
108
|
+
subsection='dynamicFootnotes'
|
|
57
109
|
fieldName='symbolColumn'
|
|
58
110
|
updateField={updateField}
|
|
59
111
|
/>
|
|
60
112
|
<InputSelect
|
|
61
113
|
label='Footnote Text Column'
|
|
62
|
-
value={
|
|
114
|
+
value={footnotesConfig.dynamicFootnotes?.textColumn}
|
|
63
115
|
options={dataColumns}
|
|
64
|
-
section='
|
|
116
|
+
section='footnotes'
|
|
117
|
+
subsection='dynamicFootnotes'
|
|
65
118
|
fieldName='textColumn'
|
|
66
119
|
updateField={updateField}
|
|
67
120
|
/>
|
|
68
121
|
<InputSelect
|
|
69
122
|
label='Footnote Order Column'
|
|
70
|
-
value={
|
|
123
|
+
value={footnotesConfig.dynamicFootnotes?.orderColumn}
|
|
71
124
|
options={dataColumns}
|
|
72
|
-
section='
|
|
125
|
+
section='footnotes'
|
|
126
|
+
subsection='dynamicFootnotes'
|
|
73
127
|
fieldName='orderColumn'
|
|
74
128
|
updateField={updateField}
|
|
75
129
|
/>
|
|
@@ -81,7 +135,7 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField }
|
|
|
81
135
|
|
|
82
136
|
<em>Static Footnotes</em>
|
|
83
137
|
|
|
84
|
-
{
|
|
138
|
+
{footnotesConfig.staticFootnotes?.map((note, index) => (
|
|
85
139
|
<div key={index} className='row border p-2'>
|
|
86
140
|
<div className='col-8'>
|
|
87
141
|
<InputSelect
|
|
@@ -267,7 +267,7 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
|
|
|
267
267
|
const orderedSubGroupValues = subGrouping.valuesLookup[groupName].orderedValues
|
|
268
268
|
return (
|
|
269
269
|
<div key={`group-subgroup-values-${groupName}-${i}`}>
|
|
270
|
-
<span className='font-weight-bold'>{groupName}</span>
|
|
270
|
+
<span className='font-weight-bold fw-bold'>{groupName}</span>
|
|
271
271
|
<FilterOrder
|
|
272
272
|
key={`subgroup-values-${groupName}-${i}`}
|
|
273
273
|
orderedValues={orderedSubGroupValues}
|
|
@@ -19,9 +19,10 @@ type VizFilterProps = {
|
|
|
19
19
|
config: Visualization
|
|
20
20
|
updateField: UpdateFieldFunc<string | VizFilter[] | VizFilter>
|
|
21
21
|
rawData: Object[]
|
|
22
|
+
hasFootnotes?: boolean
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawData }) => {
|
|
25
|
+
const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawData, hasFootnotes }) => {
|
|
25
26
|
const openControls = useState({})
|
|
26
27
|
const dataColumns = useMemo(() => {
|
|
27
28
|
return _.uniq(_.flatten(rawData?.map(row => Object.keys(row))))
|
|
@@ -171,20 +172,6 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
171
172
|
options={filterStyleOptions}
|
|
172
173
|
/>
|
|
173
174
|
|
|
174
|
-
<Select
|
|
175
|
-
value={filter.defaultValue}
|
|
176
|
-
options={
|
|
177
|
-
filter.resetLabel
|
|
178
|
-
? [filter.resetLabel, ...config.filters?.[filterIndex].values]
|
|
179
|
-
: config.filters?.[filterIndex].values
|
|
180
|
-
}
|
|
181
|
-
updateField={(_section, _subSection, _key, value) => {
|
|
182
|
-
updateFilterDefaultValue(filterIndex, value)
|
|
183
|
-
}}
|
|
184
|
-
label='Filter Default Value'
|
|
185
|
-
initial='Select'
|
|
186
|
-
/>
|
|
187
|
-
|
|
188
175
|
{filter.filterStyle !== 'nested-dropdown' ? (
|
|
189
176
|
<>
|
|
190
177
|
<Select
|
|
@@ -196,16 +183,21 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
196
183
|
initial='- Select Option -'
|
|
197
184
|
/>
|
|
198
185
|
|
|
199
|
-
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
186
|
+
{filter.columnName && (
|
|
187
|
+
<Select
|
|
188
|
+
value={filter.defaultValue}
|
|
189
|
+
options={
|
|
190
|
+
filter.resetLabel
|
|
191
|
+
? [filter.resetLabel, ...config.filters?.[filterIndex].values]
|
|
192
|
+
: config.filters?.[filterIndex].values
|
|
193
|
+
}
|
|
194
|
+
updateField={(_section, _subSection, _key, value) => {
|
|
195
|
+
updateFilterDefaultValue(filterIndex, value)
|
|
206
196
|
}}
|
|
197
|
+
label='Filter Default Value'
|
|
198
|
+
initial='Select'
|
|
207
199
|
/>
|
|
208
|
-
|
|
200
|
+
)}
|
|
209
201
|
|
|
210
202
|
<label>
|
|
211
203
|
<span className='edit-label column-heading'>Label</span>
|
|
@@ -240,17 +232,6 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
240
232
|
/>
|
|
241
233
|
)}
|
|
242
234
|
|
|
243
|
-
<label>
|
|
244
|
-
<span className='edit-label column-heading'>Default Value Set By Query String Parameter</span>
|
|
245
|
-
<input
|
|
246
|
-
type='text'
|
|
247
|
-
value={filter.setByQueryParameter}
|
|
248
|
-
onChange={e => {
|
|
249
|
-
updateFilterProp('setByQueryParameter', filterIndex, e.target.value)
|
|
250
|
-
}}
|
|
251
|
-
/>
|
|
252
|
-
</label>
|
|
253
|
-
|
|
254
235
|
<Select
|
|
255
236
|
value={filter.order || 'asc'}
|
|
256
237
|
fieldName='order'
|
|
@@ -278,6 +259,27 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
278
259
|
options={dataColumns}
|
|
279
260
|
/>
|
|
280
261
|
)}
|
|
262
|
+
<label>
|
|
263
|
+
<span className='edit-label column-heading'>Default Value Set By Query String Parameter</span>
|
|
264
|
+
<input
|
|
265
|
+
type='text'
|
|
266
|
+
value={filter.setByQueryParameter}
|
|
267
|
+
onChange={e => {
|
|
268
|
+
updateFilterProp('setByQueryParameter', filterIndex, e.target.value)
|
|
269
|
+
}}
|
|
270
|
+
/>
|
|
271
|
+
</label>
|
|
272
|
+
|
|
273
|
+
<label>
|
|
274
|
+
<input
|
|
275
|
+
type='checkbox'
|
|
276
|
+
checked={filter.showDropdown === undefined ? true : filter.showDropdown}
|
|
277
|
+
onChange={e => {
|
|
278
|
+
updateFilterProp('showDropdown', filterIndex, e.target.checked)
|
|
279
|
+
}}
|
|
280
|
+
/>
|
|
281
|
+
<span className='edit-showDropdown column-heading'>Show Filter</span>
|
|
282
|
+
</label>
|
|
281
283
|
</>
|
|
282
284
|
) : (
|
|
283
285
|
<NestedDropdownEditor
|
|
@@ -291,6 +293,18 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
291
293
|
updateFilterStyle={updateFilterStyle}
|
|
292
294
|
/>
|
|
293
295
|
)}
|
|
296
|
+
{hasFootnotes && (
|
|
297
|
+
<label>
|
|
298
|
+
<input
|
|
299
|
+
type='checkbox'
|
|
300
|
+
checked={!!filter.filterFootnotes}
|
|
301
|
+
onChange={e => {
|
|
302
|
+
updateFilterProp('filterFootnotes', filterIndex, e.target.checked)
|
|
303
|
+
}}
|
|
304
|
+
/>
|
|
305
|
+
<span className='edit-showDropdown column-heading'>Filter Footnotes</span>
|
|
306
|
+
</label>
|
|
307
|
+
)}
|
|
294
308
|
<label>
|
|
295
309
|
<span className='edit-label column-heading'>
|
|
296
310
|
Filter Parents{' '}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useMemo, useRef, useId } from 'react'
|
|
2
2
|
import _ from 'lodash'
|
|
3
|
+
import parse from 'html-react-parser'
|
|
3
4
|
|
|
4
5
|
// CDC
|
|
5
6
|
import Button from '../elements/Button'
|
|
@@ -13,8 +14,6 @@ import { getNestedOptions } from './helpers/getNestedOptions'
|
|
|
13
14
|
import { getWrappingStatuses } from './helpers/filterWrapping'
|
|
14
15
|
import { handleSorting } from './helpers/handleSorting'
|
|
15
16
|
import { getChangedFilters } from './helpers/getChangedFilters'
|
|
16
|
-
import { getNewRuntime } from './helpers/getNewRuntime'
|
|
17
|
-
import { filterVizData } from '../../helpers/filterVizData'
|
|
18
17
|
import { getQueryParams, updateQueryString } from '../../helpers/queryStringUtils'
|
|
19
18
|
import { applyQueuedActive } from './helpers/applyQueuedActive'
|
|
20
19
|
import Tabs from './components/Tabs'
|
|
@@ -40,25 +39,19 @@ const BUTTON_TEXT = {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
type FilterProps = {
|
|
43
|
-
|
|
44
|
-
dimensions: DimensionsType
|
|
42
|
+
dimensions?: DimensionsType
|
|
45
43
|
config: Visualization
|
|
46
|
-
|
|
47
|
-
setFilteredData: Function
|
|
48
|
-
// updating function for setting fitlerBehavior
|
|
49
|
-
setConfig: Function
|
|
44
|
+
setFilters: Function
|
|
50
45
|
standaloneMap?: boolean
|
|
51
46
|
excludedData?: Object[]
|
|
52
|
-
getUniqueValues
|
|
47
|
+
getUniqueValues?: Function
|
|
53
48
|
}
|
|
54
49
|
|
|
55
50
|
const Filters: React.FC<FilterProps> = ({
|
|
56
51
|
config: visualizationConfig,
|
|
57
|
-
filteredData,
|
|
58
52
|
dimensions,
|
|
59
53
|
standaloneMap,
|
|
60
|
-
|
|
61
|
-
setFilteredData,
|
|
54
|
+
setFilters,
|
|
62
55
|
excludedData,
|
|
63
56
|
getUniqueValues
|
|
64
57
|
}) => {
|
|
@@ -82,7 +75,7 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
82
75
|
// end of Handle Wrapping Filters
|
|
83
76
|
|
|
84
77
|
const initialActiveFilters = useMemo(() => {
|
|
85
|
-
if (!filteredData) return []
|
|
78
|
+
//if (!filteredData) return []
|
|
86
79
|
return filters.map(filter => filter.active)
|
|
87
80
|
}, [])
|
|
88
81
|
|
|
@@ -92,36 +85,10 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
92
85
|
}, [filters])
|
|
93
86
|
|
|
94
87
|
const changeFilterActive = (index, value) => {
|
|
95
|
-
|
|
88
|
+
if (filterBehavior === 'Apply Button') setShowApplyButton(true)
|
|
96
89
|
|
|
97
|
-
newFilters = getChangedFilters(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (!visualizationConfig.dynamicSeries) {
|
|
101
|
-
const _newFilters = addValuesToFilters(newFilters, excludedData)
|
|
102
|
-
setConfig({
|
|
103
|
-
...visualizationConfig,
|
|
104
|
-
filters: _newFilters
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (visualizationConfig.filterBehavior === 'Filter Change') {
|
|
109
|
-
if (standaloneMap) {
|
|
110
|
-
setFilteredData(newFilters)
|
|
111
|
-
} else {
|
|
112
|
-
const newFilteredData = filterVizData(newFilters, excludedData)
|
|
113
|
-
setFilteredData(newFilteredData)
|
|
114
|
-
|
|
115
|
-
if (visualizationConfig.dynamicSeries) {
|
|
116
|
-
const runtime = getNewRuntime(visualizationConfig, newFilteredData)
|
|
117
|
-
setConfig({
|
|
118
|
-
...visualizationConfig,
|
|
119
|
-
filters: newFilters,
|
|
120
|
-
runtime
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
90
|
+
const newFilters = getChangedFilters([...filters], index, value, filterBehavior)
|
|
91
|
+
setFilters(newFilters)
|
|
125
92
|
}
|
|
126
93
|
|
|
127
94
|
const handleApplyButton = newFilters => {
|
|
@@ -140,19 +107,13 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
140
107
|
updateQueryString(queryParams)
|
|
141
108
|
}
|
|
142
109
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (standaloneMap) {
|
|
146
|
-
setFilteredData(newFilters, excludedData)
|
|
147
|
-
} else {
|
|
148
|
-
setFilteredData(filterVizData(newFilters, excludedData))
|
|
149
|
-
}
|
|
110
|
+
setFilters(newFilters)
|
|
150
111
|
|
|
151
112
|
setShowApplyButton(false)
|
|
152
113
|
}
|
|
153
114
|
|
|
154
115
|
const handleReset = e => {
|
|
155
|
-
let newFilters = [...
|
|
116
|
+
let newFilters = [...filters]
|
|
156
117
|
e.preventDefault()
|
|
157
118
|
|
|
158
119
|
// reset to first item in values array.
|
|
@@ -175,18 +136,12 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
175
136
|
updateQueryString(queryParams)
|
|
176
137
|
}
|
|
177
138
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (standaloneMap) {
|
|
181
|
-
setFilteredData(newFilters, excludedData)
|
|
182
|
-
} else {
|
|
183
|
-
setFilteredData(filterVizData(newFilters, excludedData))
|
|
184
|
-
}
|
|
139
|
+
setFilters(newFilters)
|
|
185
140
|
}
|
|
186
141
|
|
|
187
142
|
const mobileFilterStyle = useMemo(() => {
|
|
188
|
-
if (!dimensions) false
|
|
189
|
-
const [width] = dimensions
|
|
143
|
+
if (!dimensions) return false
|
|
144
|
+
const [width] = dimensions
|
|
190
145
|
const isMobile = Number(width) < 768
|
|
191
146
|
const isTabSimple = filters?.some(filter => filter.filterStyle === VIZ_FILTER_STYLE.tabSimple)
|
|
192
147
|
|
|
@@ -195,13 +150,12 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
195
150
|
|
|
196
151
|
const vizFiltersWithValues = useMemo(() => {
|
|
197
152
|
// Here charts is using config.filters where maps is using a runtime value
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}, [filters, filteredData])
|
|
153
|
+
if (!filters) return []
|
|
154
|
+
if (filters.fromHash) delete filters.fromHash // support for Maps config
|
|
155
|
+
return addValuesToFilters(filters as VizFilter[], visualizationConfig.data)
|
|
156
|
+
}, [filters])
|
|
203
157
|
|
|
204
|
-
if (visualizationConfig?.filters?.length === 0) return
|
|
158
|
+
if (visualizationConfig?.filters?.length === 0) return <></>
|
|
205
159
|
|
|
206
160
|
const getClasses = () => {
|
|
207
161
|
const { visualizationType, legend } = visualizationConfig || {}
|
|
@@ -220,13 +174,14 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
220
174
|
return (
|
|
221
175
|
<section className={getClasses().join(' ')}>
|
|
222
176
|
{visualizationConfig.filterIntro && (
|
|
223
|
-
<p className='filters-section__intro-text mb-3'>{visualizationConfig.filterIntro}</p>
|
|
177
|
+
<p className='filters-section__intro-text mb-3'>{parse(visualizationConfig.filterIntro)}</p>
|
|
224
178
|
)}
|
|
225
179
|
<div className='d-flex flex-wrap w-100 filters-section__wrapper align-items-end'>
|
|
226
180
|
<>
|
|
227
181
|
{vizFiltersWithValues.map((singleFilter: VizFilter, outerIndex) => {
|
|
228
182
|
if (singleFilter.showDropdown === false) return
|
|
229
183
|
const { label, filterStyle, columnName } = singleFilter as VizFilter
|
|
184
|
+
const [nestedActiveGroup, nestedActiveSubGroup] = getNestedGroup(singleFilter)
|
|
230
185
|
|
|
231
186
|
handleSorting(singleFilter)
|
|
232
187
|
|
|
@@ -239,7 +194,6 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
239
194
|
const { isDropdown } = wrappingFilters[columnName] || {}
|
|
240
195
|
const showDefaultDropdown =
|
|
241
196
|
((filterStyle === 'dropdown' || mobileFilterStyle) && !mobileExempt) || isDropdown
|
|
242
|
-
const [nestedActiveGroup, nestedActiveSubGroup] = getNestedGroup(singleFilter)
|
|
243
197
|
const hideLabelMargin = singleFilter.filterStyle === 'tab-simple' && !showDefaultDropdown
|
|
244
198
|
return (
|
|
245
199
|
<div
|
|
@@ -249,7 +203,7 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
249
203
|
>
|
|
250
204
|
{label && (
|
|
251
205
|
<label
|
|
252
|
-
className={`font-weight-bold mb-${hideLabelMargin ? '0' : '2'}`}
|
|
206
|
+
className={`font-weight-bold fw-bold mb-${hideLabelMargin ? '0' : '2'}`}
|
|
253
207
|
htmlFor={`filter-${outerIndex}`}
|
|
254
208
|
>
|
|
255
209
|
{label}
|
|
@@ -284,7 +238,11 @@ const Filters: React.FC<FilterProps> = ({
|
|
|
284
238
|
<MultiSelect
|
|
285
239
|
options={singleFilter.values.map(v => ({ value: v, label: v }))}
|
|
286
240
|
fieldName={outerIndex}
|
|
287
|
-
updateField={(_section, _subSection, fieldName, value) =>
|
|
241
|
+
updateField={(_section, _subSection, fieldName, value) => {
|
|
242
|
+
const defaultSelection = singleFilter.defaultValue || [singleFilter.values[0]]
|
|
243
|
+
const selection = value?.length ? value : defaultSelection
|
|
244
|
+
changeFilterActive(fieldName, selection)
|
|
245
|
+
}}
|
|
288
246
|
selected={singleFilter.active as string[]}
|
|
289
247
|
limit={(singleFilter as MultiSelectFilter).selectLimit || 5}
|
|
290
248
|
/>
|
|
@@ -18,7 +18,7 @@ const Dropdown: React.FC<DropdownProps> = ({ index: outerIndex, label, filter, c
|
|
|
18
18
|
name={label}
|
|
19
19
|
aria-label={`Filter by ${label}`}
|
|
20
20
|
className={`cove-form-select ${DROPDOWN_STYLES}`}
|
|
21
|
-
style={{ backgroundColor: 'white
|
|
21
|
+
style={{ backgroundColor: 'white' }}
|
|
22
22
|
data-index='0'
|
|
23
23
|
value={queuedActive || active}
|
|
24
24
|
onChange={e => {
|
|
@@ -11,7 +11,7 @@ const Footnotes: React.FC<FootnotesProps> = ({ footnotes }) => {
|
|
|
11
11
|
<ul className='cove-footnotes'>
|
|
12
12
|
{footnotes.map((note, i) => {
|
|
13
13
|
return (
|
|
14
|
-
<li key={note.symbol
|
|
14
|
+
<li key={`${note.symbol || 'footnote-'}${i}`} className='mb-1'>
|
|
15
15
|
{note.symbol && <span className='me-1'>{note.symbol}</span>}
|
|
16
16
|
{note.text}
|
|
17
17
|
</li>
|
|
@@ -1,45 +1,20 @@
|
|
|
1
|
-
import EditorWrapper from '../EditorWrapper'
|
|
2
1
|
import Footnotes from './Footnotes'
|
|
3
|
-
import
|
|
4
|
-
import { ViewPort } from '../../types/ViewPort'
|
|
5
|
-
import FootnotesConfig, { Footnote } from '../../types/Footnotes'
|
|
2
|
+
import FootnotesConfig from '../../types/Footnotes'
|
|
6
3
|
import _ from 'lodash'
|
|
7
4
|
import { useMemo } from 'react'
|
|
8
|
-
import {
|
|
5
|
+
import { filterVizData } from '../../helpers/filterVizData'
|
|
6
|
+
import { VizFilter } from '../../types/VizFilter'
|
|
9
7
|
|
|
10
8
|
type StandAloneProps = {
|
|
11
|
-
isEditor?: boolean
|
|
12
|
-
visualizationKey: string
|
|
13
9
|
config: FootnotesConfig
|
|
14
|
-
|
|
15
|
-
viewport?: ViewPort
|
|
10
|
+
filters?: VizFilter[]
|
|
16
11
|
}
|
|
17
12
|
|
|
18
|
-
const FootnotesStandAlone: React.FC<StandAloneProps> = ({
|
|
19
|
-
|
|
20
|
-
config,
|
|
21
|
-
viewport,
|
|
22
|
-
isEditor,
|
|
23
|
-
updateConfig
|
|
24
|
-
}) => {
|
|
25
|
-
const updateField = updateFieldFactory<Footnote[]>(config, updateConfig)
|
|
26
|
-
if (isEditor)
|
|
27
|
-
return (
|
|
28
|
-
<EditorWrapper
|
|
29
|
-
component={FootnotesStandAlone}
|
|
30
|
-
visualizationKey={visualizationKey}
|
|
31
|
-
visualizationConfig={config}
|
|
32
|
-
updateConfig={updateConfig}
|
|
33
|
-
type={'Footnotes'}
|
|
34
|
-
viewport={viewport}
|
|
35
|
-
>
|
|
36
|
-
<FootnotesEditor key={visualizationKey} config={config} updateField={updateField} />
|
|
37
|
-
</EditorWrapper>
|
|
38
|
-
)
|
|
39
|
-
|
|
13
|
+
const FootnotesStandAlone: React.FC<StandAloneProps> = ({ config, filters }) => {
|
|
14
|
+
if (!config) return null
|
|
40
15
|
// get the api footnotes from the config
|
|
41
16
|
const apiFootnotes = useMemo(() => {
|
|
42
|
-
const configData =
|
|
17
|
+
const configData = filterVizData(filters, config.data)
|
|
43
18
|
if (configData && config.dataKey && config.dynamicFootnotes) {
|
|
44
19
|
const { symbolColumn, textColumn, orderColumn } = config.dynamicFootnotes
|
|
45
20
|
const _data = configData.map(row => _.pick(row, [symbolColumn, textColumn, orderColumn]))
|
|
@@ -47,7 +22,7 @@ const FootnotesStandAlone: React.FC<StandAloneProps> = ({
|
|
|
47
22
|
return _data.map(row => ({ symbol: row[symbolColumn], text: row[textColumn] }))
|
|
48
23
|
}
|
|
49
24
|
return []
|
|
50
|
-
}, [config.dynamicFootnotes, config.
|
|
25
|
+
}, [config.dynamicFootnotes, config.data, filters])
|
|
51
26
|
|
|
52
27
|
// get static footnotes from the config.footnotes
|
|
53
28
|
const staticFootnotes = config.staticFootnotes || []
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { ChartConfig } from '@cdc/chart/src/types/ChartConfig'
|
|
3
3
|
import React, { forwardRef } from 'react'
|
|
4
4
|
import { Config as DataBiteConfig } from '@cdc/data-bite/src/types/Config'
|
|
5
|
+
import { Config as DataTableConfig } from '@cdc/data-table/src/types/Config'
|
|
5
6
|
import './visualizations.scss'
|
|
6
7
|
import { Config as WaffleChartConfig } from '@cdc/waffle-chart/src/types/Config'
|
|
7
8
|
import { MarkupIncludeConfig } from '@cdc/core/types/MarkupInclude'
|
|
@@ -10,7 +11,7 @@ import { MapConfig } from '@cdc/map/src/types/MapConfig'
|
|
|
10
11
|
|
|
11
12
|
type VisualizationWrapper = {
|
|
12
13
|
children: React.ReactNode
|
|
13
|
-
config: ChartConfig | DataBiteConfig | WaffleChartConfig | MarkupIncludeConfig | DashboardFilters | MapConfig
|
|
14
|
+
config: ChartConfig | DataBiteConfig | WaffleChartConfig | MarkupIncludeConfig | DashboardFilters | MapConfig | DataTableConfig
|
|
14
15
|
currentViewport?: string
|
|
15
16
|
imageId?: string
|
|
16
17
|
isEditor: boolean
|
|
@@ -98,7 +98,8 @@ const LegendGradient = ({
|
|
|
98
98
|
|
|
99
99
|
if (style === 'gradient') {
|
|
100
100
|
return (
|
|
101
|
-
|
|
101
|
+
// TODO: figure out why bootstrap 'overflow: visible' is not working consistently
|
|
102
|
+
<svg className={'w-100 overflow-visible'} height={newHeight} style={{ overflow: 'visible' }} width={width}>
|
|
102
103
|
{/* background border*/}
|
|
103
104
|
<rect x={0} y={0} width={legendWidth + MARGIN * 2} height={boxHeight + MARGIN * 2} fill={BORDER_COLOR} />
|
|
104
105
|
{/* Define the gradient */}
|
|
@@ -161,7 +162,7 @@ const LegendGradient = ({
|
|
|
161
162
|
x2={xPosition + separatorSize / 2}
|
|
162
163
|
y1={-3}
|
|
163
164
|
y2={boxHeight + MARGIN + 3}
|
|
164
|
-
stroke={'var(--colors-gray-cool-40'}
|
|
165
|
+
stroke={'var(--colors-gray-cool-40,#8d9297)'}
|
|
165
166
|
strokeWidth={1}
|
|
166
167
|
strokeDasharray='5,3'
|
|
167
168
|
strokeDashoffset={1}
|
|
@@ -36,9 +36,7 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
|
|
|
36
36
|
tooltip,
|
|
37
37
|
loading
|
|
38
38
|
}) => {
|
|
39
|
-
const
|
|
40
|
-
const [selectedItems, setSelectedItems] = useState<Option[]>()
|
|
41
|
-
const items = selectedItems || preselectedItems
|
|
39
|
+
const items = useMemo(() => options.filter(opt => selected.includes(opt.value)).slice(0, limit), [options])
|
|
42
40
|
const [expanded, setExpanded] = useState(false)
|
|
43
41
|
const multiSelectRef = useRef(null)
|
|
44
42
|
|
|
@@ -68,14 +66,12 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
|
|
|
68
66
|
if (e && e.type === 'keyup' && e.key !== 'Enter') return
|
|
69
67
|
if (limit && items?.length >= limit) return
|
|
70
68
|
const newItems = [...items, option]
|
|
71
|
-
setSelectedItems(newItems)
|
|
72
69
|
update(newItems)
|
|
73
70
|
}
|
|
74
71
|
|
|
75
72
|
const handleItemRemove = (option: Option, e = null) => {
|
|
76
73
|
if (e && e.type === 'keyup' && e.key !== 'Enter') return
|
|
77
74
|
const newItems = items.filter(item => item.value !== option.value)
|
|
78
|
-
setSelectedItems(newItems)
|
|
79
75
|
update(newItems)
|
|
80
76
|
}
|
|
81
77
|
|
|
@@ -99,6 +95,7 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
|
|
|
99
95
|
items.map(item => (
|
|
100
96
|
<div key={item.value} aria-labelledby={label ? multiID + label : undefined}>
|
|
101
97
|
{item.label}
|
|
98
|
+
|
|
102
99
|
<button
|
|
103
100
|
aria-label='Remove'
|
|
104
101
|
onClick={e => {
|
|
@@ -114,7 +111,7 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
|
|
|
114
111
|
</div>
|
|
115
112
|
))
|
|
116
113
|
) : (
|
|
117
|
-
<span className='
|
|
114
|
+
<span className='ps-1 pt-1'>{loading ? 'Loading...' : '- Select -'}</span>
|
|
118
115
|
)}
|
|
119
116
|
<button
|
|
120
117
|
aria-label={expanded ? 'Collapse' : 'Expand'}
|