@cdc/core 4.23.3 → 4.23.5
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/external-link.svg +1 -0
- package/components/CoveMediaControls.jsx +6 -2
- package/components/DataTable.jsx +630 -0
- package/components/Filters.jsx +421 -0
- package/components/LegendCircle.jsx +2 -2
- package/components/ui/Icon.jsx +2 -1
- package/data/colorPalettes.js +3 -3
- package/helpers/DataTransform.js +54 -0
- package/helpers/isNumberLog.js +3 -3
- package/helpers/validateFipsCodeLength.js +1 -1
- package/package.json +2 -2
- package/styles/_data-table.scss +10 -0
- package/styles/_global.scss +5 -0
- package/styles/_series-list.scss +92 -0
- package/styles/base.scss +6 -0
- package/styles/filters.scss +122 -0
- package/styles/v2/themes/_color-definitions.scss +49 -0
- package/helpers/cleanData.js +0 -50
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react'
|
|
2
|
+
import { useId } from 'react'
|
|
3
|
+
|
|
4
|
+
// CDC
|
|
5
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
6
|
+
|
|
7
|
+
// Third Party
|
|
8
|
+
import PropTypes from 'prop-types'
|
|
9
|
+
|
|
10
|
+
export const useFilters = props => {
|
|
11
|
+
const [showApplyButton, setShowApplyButton] = useState(false)
|
|
12
|
+
|
|
13
|
+
// Desconstructing: notice, adding more descriptive visualizationConfig name over config
|
|
14
|
+
// visualizationConfig feels more robust for all vis types so that its not confused with config/state/etc.
|
|
15
|
+
const { config: visualizationConfig, setConfig, filteredData, setFilteredData, excludedData, filterData } = props
|
|
16
|
+
const { type, filterBehavior, filters } = visualizationConfig
|
|
17
|
+
|
|
18
|
+
const filterStyleOptions = ['dropdown', 'pill', 'tab', 'tab bar']
|
|
19
|
+
|
|
20
|
+
const filterOrderOptions = [
|
|
21
|
+
{
|
|
22
|
+
label: 'Ascending Alphanumeric',
|
|
23
|
+
value: 'asc'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
label: 'Descending Alphanumeric',
|
|
27
|
+
value: 'desc'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
label: 'Custom',
|
|
31
|
+
value: 'cust'
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Re-orders a filter based on two indices and updates the runtime filters array and filters state
|
|
37
|
+
* @param {number} idx1 - The index of the original position of the filter value.
|
|
38
|
+
* @param {number} idx2 - The index of the new position for the filter value.
|
|
39
|
+
* @param {number} filterIndex - The index of the filter item within the array of filter items.
|
|
40
|
+
* @param {object} filter - The filter item itself, which contains an array of filter values.
|
|
41
|
+
* @return {void} None. This function only updates the state of the component.
|
|
42
|
+
*
|
|
43
|
+
* @modifies {object} - The filter object passed in as a parameter
|
|
44
|
+
* @modifies {array} - The filteredData state if visualizationConfig.type equals 'map'
|
|
45
|
+
* @modifies {object} - The visualizationConfig state
|
|
46
|
+
*/
|
|
47
|
+
const handleFilterOrder = (idx1, idx2, filterIndex, filter) => {
|
|
48
|
+
// Create a shallow copy of the filter values array & update position of the values
|
|
49
|
+
const updatedValues = [...filter.values]
|
|
50
|
+
const [movedItem] = updatedValues.splice(idx1, 1)
|
|
51
|
+
updatedValues.splice(idx2, 0, movedItem)
|
|
52
|
+
|
|
53
|
+
const filtersCopy = visualizationConfig.type === 'chart' ? [...visualizationConfig.filters] : [...filteredData]
|
|
54
|
+
const filterItem = { ...filtersCopy[filterIndex] }
|
|
55
|
+
|
|
56
|
+
// Overwrite filterItem.values since thats what we map through in the editor panel
|
|
57
|
+
filterItem.values = updatedValues
|
|
58
|
+
filterItem.orderedValues = updatedValues
|
|
59
|
+
filterItem.active = updatedValues[0]
|
|
60
|
+
filterItem.order = 'cust'
|
|
61
|
+
|
|
62
|
+
// Update the filters
|
|
63
|
+
filtersCopy[filterIndex] = filterItem
|
|
64
|
+
|
|
65
|
+
if (visualizationConfig.type === 'map') {
|
|
66
|
+
setFilteredData(filtersCopy)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setConfig({ ...visualizationConfig, filters: filtersCopy })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const announceChange = text => {}
|
|
73
|
+
|
|
74
|
+
const changeFilterActive = (index, value) => {
|
|
75
|
+
let newFilters = visualizationConfig.type === 'map' ? [...filteredData] : [...visualizationConfig.filters]
|
|
76
|
+
|
|
77
|
+
newFilters[index].active = value
|
|
78
|
+
setConfig({ ...visualizationConfig })
|
|
79
|
+
|
|
80
|
+
// If this is a button filter type show the button.
|
|
81
|
+
if (visualizationConfig.filterBehavior === 'Apply Button') {
|
|
82
|
+
setShowApplyButton(true)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// If we're not using the apply button we can set the filters right away.
|
|
86
|
+
if (visualizationConfig.filterBehavior !== 'Apply Button') {
|
|
87
|
+
setConfig({
|
|
88
|
+
...visualizationConfig,
|
|
89
|
+
filters: newFilters
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Used for setting active filter, fromHash breaks the filteredData functionality.
|
|
94
|
+
if (visualizationConfig.type === 'map' && visualizationConfig.filterBehavior === 'Filter Change') {
|
|
95
|
+
setFilteredData(newFilters)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// If we're on a chart and not using the apply button
|
|
99
|
+
if (visualizationConfig.type === 'chart' && visualizationConfig.filterBehavior === 'Filter Change') {
|
|
100
|
+
setFilteredData(filterData(newFilters, excludedData))
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const handleApplyButton = newFilters => {
|
|
105
|
+
setConfig({ ...visualizationConfig, filters: newFilters })
|
|
106
|
+
|
|
107
|
+
if (type === 'map') {
|
|
108
|
+
setFilteredData(newFilters, excludedData)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (type === 'chart') {
|
|
112
|
+
setFilteredData(filterData(newFilters, excludedData))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
setShowApplyButton(false)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const handleReset = e => {
|
|
119
|
+
let newFilters = [...visualizationConfig.filters]
|
|
120
|
+
e.preventDefault()
|
|
121
|
+
|
|
122
|
+
// reset to first item in values array.
|
|
123
|
+
newFilters.map(filter => {
|
|
124
|
+
filter = handleSorting(filter)
|
|
125
|
+
filter.active = filter.values[0]
|
|
126
|
+
return filter
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
if (type === 'map') {
|
|
130
|
+
setFilteredData(newFilters, excludedData)
|
|
131
|
+
} else {
|
|
132
|
+
setFilteredData(filterData(newFilters, excludedData))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
setConfig({ ...visualizationConfig, filters: newFilters })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const filterConstants = {
|
|
139
|
+
buttonText: 'Apply Filters',
|
|
140
|
+
resetText: 'Reset All',
|
|
141
|
+
introText: `Make a selection from the filters to change the visualization information.`,
|
|
142
|
+
applyText: 'Select the apply button to update the visualization information.'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const handleSorting = singleFilter => {
|
|
146
|
+
const { order } = singleFilter
|
|
147
|
+
|
|
148
|
+
const sortAsc = (a, b) => {
|
|
149
|
+
return a.toString().localeCompare(b.toString(), 'en', { numeric: true })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const sortDesc = (a, b) => {
|
|
153
|
+
return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!order || order === '') {
|
|
157
|
+
singleFilter.order = 'asc'
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (order === 'desc') {
|
|
161
|
+
singleFilter.values = singleFilter.values.sort(sortDesc)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (order === 'asc') {
|
|
165
|
+
singleFilter.values = singleFilter.values.sort(sortAsc)
|
|
166
|
+
}
|
|
167
|
+
return singleFilter
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// prettier-ignore
|
|
171
|
+
return {
|
|
172
|
+
handleApplyButton,
|
|
173
|
+
changeFilterActive,
|
|
174
|
+
announceChange,
|
|
175
|
+
showApplyButton,
|
|
176
|
+
handleReset,
|
|
177
|
+
filterConstants,
|
|
178
|
+
filterStyleOptions,
|
|
179
|
+
filterOrderOptions,
|
|
180
|
+
handleFilterOrder,
|
|
181
|
+
handleSorting
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const Filters = props => {
|
|
186
|
+
const { config: visualizationConfig, filteredData, dimensions } = props
|
|
187
|
+
const { filters, type, general, theme, filterBehavior } = visualizationConfig
|
|
188
|
+
const [mobileFilterStyle, setMobileFilterStyle] = useState(false)
|
|
189
|
+
const [selectedFilter, setSelectedFilter] = useState('')
|
|
190
|
+
const id = useId()
|
|
191
|
+
|
|
192
|
+
// useFilters hook provides data and logic for handling various filter functions
|
|
193
|
+
// prettier-ignore
|
|
194
|
+
const {
|
|
195
|
+
handleApplyButton,
|
|
196
|
+
changeFilterActive,
|
|
197
|
+
announceChange,
|
|
198
|
+
showApplyButton,
|
|
199
|
+
handleReset,
|
|
200
|
+
filterConstants,
|
|
201
|
+
handleSorting
|
|
202
|
+
} = useFilters(props)
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (!dimensions) return
|
|
206
|
+
if (dimensions[0] < 768 && filters?.length > 0) {
|
|
207
|
+
setMobileFilterStyle(true)
|
|
208
|
+
} else {
|
|
209
|
+
setMobileFilterStyle(false)
|
|
210
|
+
}
|
|
211
|
+
}, [dimensions])
|
|
212
|
+
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
if (selectedFilter) {
|
|
215
|
+
let el = document.getElementById(selectedFilter.id)
|
|
216
|
+
if (el) el.focus()
|
|
217
|
+
}
|
|
218
|
+
}, [changeFilterActive, selectedFilter])
|
|
219
|
+
|
|
220
|
+
const Filters = props => props.children
|
|
221
|
+
|
|
222
|
+
const filterSectionClassList = ['filters-section', type === 'map' ? general.headerColor : theme]
|
|
223
|
+
|
|
224
|
+
// Exterior Section Wrapper
|
|
225
|
+
Filters.Section = props => {
|
|
226
|
+
return (
|
|
227
|
+
<section className={filterSectionClassList.join(' ')}>
|
|
228
|
+
<p className='filters-section__intro-text'>
|
|
229
|
+
{filterConstants.introText} {visualizationConfig.filterBehavior === 'Apply Button' && filterConstants.applyText}
|
|
230
|
+
</p>
|
|
231
|
+
<div className='filters-section__wrapper'>{props.children}</div>
|
|
232
|
+
</section>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Apply/Reset Buttons
|
|
237
|
+
Filters.ApplyBehavior = props => {
|
|
238
|
+
if (filterBehavior !== 'Apply Button') return
|
|
239
|
+
const applyButtonClasses = [general?.headerColor ? general.headerColor : theme, 'apply']
|
|
240
|
+
return (
|
|
241
|
+
<div className='filters-section__buttons'>
|
|
242
|
+
<Button onClick={() => handleApplyButton(filters)} disabled={!showApplyButton} className={applyButtonClasses.join(' ')}>
|
|
243
|
+
{filterConstants.buttonText}
|
|
244
|
+
</Button>
|
|
245
|
+
<a href='#!' role='button' onClick={handleReset}>
|
|
246
|
+
{filterConstants.resetText}
|
|
247
|
+
</a>
|
|
248
|
+
</div>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
Filters.TabBar = props => {
|
|
253
|
+
const { filter: singleFilter, index: outerIndex } = props
|
|
254
|
+
return (
|
|
255
|
+
<section className='single-filters__tab-bar'>
|
|
256
|
+
{singleFilter.values.map((filter, index) => {
|
|
257
|
+
const buttonClassList = ['button__tab-bar', singleFilter.active === filter ? 'button__tab-bar--active' : '']
|
|
258
|
+
return (
|
|
259
|
+
<button
|
|
260
|
+
id={`${filter}-${outerIndex}-${index}-${id}`}
|
|
261
|
+
className={buttonClassList.join(' ')}
|
|
262
|
+
key={filter}
|
|
263
|
+
onClick={e => {
|
|
264
|
+
changeFilterActive(outerIndex, filter)
|
|
265
|
+
setSelectedFilter(e.target)
|
|
266
|
+
}}
|
|
267
|
+
onKeyDown={e => {
|
|
268
|
+
if (e.keyCode === 13) {
|
|
269
|
+
changeFilterActive(outerIndex, filter)
|
|
270
|
+
setSelectedFilter(e.target)
|
|
271
|
+
}
|
|
272
|
+
}}
|
|
273
|
+
>
|
|
274
|
+
{filter}
|
|
275
|
+
</button>
|
|
276
|
+
)
|
|
277
|
+
})}
|
|
278
|
+
</section>
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
Filters.Pills = props => props.pills
|
|
283
|
+
|
|
284
|
+
Filters.Tabs = props => props.tabs
|
|
285
|
+
|
|
286
|
+
Filters.Dropdown = props => {
|
|
287
|
+
const { index: outerIndex, label, active, filters } = props
|
|
288
|
+
return (
|
|
289
|
+
<select
|
|
290
|
+
id={`filter-${outerIndex}`}
|
|
291
|
+
name={label}
|
|
292
|
+
className='filter-select'
|
|
293
|
+
data-index='0'
|
|
294
|
+
value={active}
|
|
295
|
+
onChange={e => {
|
|
296
|
+
changeFilterActive(outerIndex, e.target.value)
|
|
297
|
+
announceChange(`Filter ${label} value has been changed to ${e.target.value}, please reference the data table to see updated values.`)
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
300
|
+
{filters}
|
|
301
|
+
</select>
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Resolve Filter Styles
|
|
306
|
+
Filters.Style = () => {
|
|
307
|
+
if (filters || filteredData) {
|
|
308
|
+
// Here charts is using config.filters where maps is using a runtime value
|
|
309
|
+
let filtersToLoop = type === 'map' ? filteredData : filters
|
|
310
|
+
|
|
311
|
+
// Remove fromHash if it exists on filters to loop so we can loop nicely
|
|
312
|
+
delete filtersToLoop.fromHash
|
|
313
|
+
|
|
314
|
+
return filtersToLoop.map((singleFilter, outerIndex) => {
|
|
315
|
+
const values = []
|
|
316
|
+
const pillValues = []
|
|
317
|
+
const tabValues = []
|
|
318
|
+
const tabBarValues = []
|
|
319
|
+
|
|
320
|
+
const { active, label, filterStyle } = singleFilter
|
|
321
|
+
|
|
322
|
+
handleSorting(singleFilter)
|
|
323
|
+
|
|
324
|
+
singleFilter.values.forEach((filterOption, index) => {
|
|
325
|
+
const pillClassList = ['pill', active === filterOption ? 'pill--active' : null, theme && theme]
|
|
326
|
+
const tabClassList = ['tab', active === filterOption && 'tab--active', theme && theme]
|
|
327
|
+
|
|
328
|
+
pillValues.push(
|
|
329
|
+
<div className='pill__wrapper' key={`pill-${index}`}>
|
|
330
|
+
<button
|
|
331
|
+
id={`${filterOption}-${outerIndex}-${index}-${id}`}
|
|
332
|
+
className={pillClassList.join(' ')}
|
|
333
|
+
onKeyDown={e => {
|
|
334
|
+
if (e.keyCode === 13) {
|
|
335
|
+
changeFilterActive(outerIndex, filterOption)
|
|
336
|
+
setSelectedFilter(e.target)
|
|
337
|
+
}
|
|
338
|
+
}}
|
|
339
|
+
onClick={e => {
|
|
340
|
+
changeFilterActive(outerIndex, filterOption)
|
|
341
|
+
setSelectedFilter(e.target)
|
|
342
|
+
}}
|
|
343
|
+
name={label}
|
|
344
|
+
>
|
|
345
|
+
{filterOption}
|
|
346
|
+
</button>
|
|
347
|
+
</div>
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
values.push(
|
|
351
|
+
<option key={index} value={filterOption}>
|
|
352
|
+
{singleFilter.labels && singleFilter.labels[filterOption] ? singleFilter.labels[filterOption] : filterOption}
|
|
353
|
+
</option>
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
tabValues.push(
|
|
357
|
+
<button
|
|
358
|
+
id={`${filterOption}-${outerIndex}-${index}-${id}`}
|
|
359
|
+
className={tabClassList.join(' ')}
|
|
360
|
+
onClick={e => {
|
|
361
|
+
changeFilterActive(outerIndex, filterOption)
|
|
362
|
+
setSelectedFilter(e.target)
|
|
363
|
+
}}
|
|
364
|
+
onKeyDown={e => {
|
|
365
|
+
if (e.keyCode === 13) {
|
|
366
|
+
changeFilterActive(outerIndex, filterOption)
|
|
367
|
+
setSelectedFilter(e.target)
|
|
368
|
+
}
|
|
369
|
+
}}
|
|
370
|
+
>
|
|
371
|
+
{filterOption}
|
|
372
|
+
</button>
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
tabBarValues.push(filterOption)
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
const classList = ['single-filters', mobileFilterStyle ? 'single-filters--dropdown' : `single-filters--${filterStyle}`]
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
<div className={classList.join(' ')} key={outerIndex}>
|
|
382
|
+
<>
|
|
383
|
+
{label && <label htmlFor={label}>{label}</label>}
|
|
384
|
+
{filterStyle === 'tab' && !mobileFilterStyle && <Filters.Tabs tabs={tabValues} />}
|
|
385
|
+
{filterStyle === 'pill' && !mobileFilterStyle && <Filters.Pills pills={pillValues} />}
|
|
386
|
+
{filterStyle === 'tab bar' && !mobileFilterStyle && <Filters.TabBar filter={singleFilter} index={outerIndex} />}
|
|
387
|
+
{(filterStyle === 'dropdown' || mobileFilterStyle) && <Filters.Dropdown index={outerIndex} label={label} active={active} filters={values} />}
|
|
388
|
+
</>
|
|
389
|
+
</div>
|
|
390
|
+
)
|
|
391
|
+
})
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (visualizationConfig?.filters?.length === 0 || props?.filteredData?.length === 0) return
|
|
396
|
+
return (
|
|
397
|
+
<Filters>
|
|
398
|
+
<Filters.Section>
|
|
399
|
+
<Filters.Style />
|
|
400
|
+
<Filters.ApplyBehavior />
|
|
401
|
+
</Filters.Section>
|
|
402
|
+
</Filters>
|
|
403
|
+
)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
Filters.propTypes = {
|
|
407
|
+
// runtimeFilters in place
|
|
408
|
+
filteredData: PropTypes.array,
|
|
409
|
+
// function for updating the runtime filters
|
|
410
|
+
setFilteredData: PropTypes.func,
|
|
411
|
+
// the full apps config
|
|
412
|
+
config: PropTypes.object,
|
|
413
|
+
// updating function for setting fitlerBehavior
|
|
414
|
+
setConfig: PropTypes.func,
|
|
415
|
+
// exclusions
|
|
416
|
+
excludedData: PropTypes.array,
|
|
417
|
+
// function for filtering the data
|
|
418
|
+
filterData: PropTypes.func
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export default Filters
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
-
export default function LegendCircle({ fill }) {
|
|
3
|
+
export default function LegendCircle({ fill, borderColor }) {
|
|
4
4
|
const styles = {
|
|
5
5
|
marginRight: '5px',
|
|
6
6
|
borderRadius: '300px',
|
|
@@ -8,7 +8,7 @@ export default function LegendCircle({ fill }) {
|
|
|
8
8
|
display: 'inline-block',
|
|
9
9
|
height: '1em',
|
|
10
10
|
width: '1em',
|
|
11
|
-
border: 'rgba(0,0,0,.3) 1px solid',
|
|
11
|
+
border: borderColor ? `${borderColor} 1px solid` : 'rgba(0,0,0,.3) 1px solid',
|
|
12
12
|
backgroundColor: fill
|
|
13
13
|
}
|
|
14
14
|
|
package/components/ui/Icon.jsx
CHANGED
|
@@ -74,7 +74,8 @@ const Icon = ({ display = null, base, alt = '', size, color, style, ...attribute
|
|
|
74
74
|
const styles = {
|
|
75
75
|
...style,
|
|
76
76
|
color: color ? color : null,
|
|
77
|
-
width: size ? size + 'px' : null
|
|
77
|
+
width: size ? size + 'px' : null,
|
|
78
|
+
cursor: display === 'move' ? 'move' : 'default'
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
return (
|
package/data/colorPalettes.js
CHANGED
|
@@ -51,16 +51,16 @@ export const colorPalettes3 = {
|
|
|
51
51
|
'monochrome-4': ['#C2C0FC', '#6a3d9a'],
|
|
52
52
|
'monochrome-5': ['#fedab8', '#bf5b17'],
|
|
53
53
|
'cool-1': ['#b2df8a', '#1f78b4'],
|
|
54
|
-
'cool-2': ['#a6cee3', '#
|
|
54
|
+
'cool-2': ['#a6cee3', '#72D66B'],
|
|
55
55
|
'cool-3': ['#C2C0FC', '#386cb0'],
|
|
56
|
-
'cool-4': ['#
|
|
56
|
+
'cool-4': ['#72D66B', '#6a3d9a'],
|
|
57
57
|
'cool-5': ['#a6cee3', '#6a3d9a'],
|
|
58
58
|
'warm-1': ['#e31a1c', '#fedab8'],
|
|
59
59
|
'complementary-1': ['#1f78b4', '#e6ab02'],
|
|
60
60
|
'complementary-2': ['#1f78b4', '#ff7f00'],
|
|
61
61
|
'complementary-3': ['#6a3d9a', '#ff7f00'],
|
|
62
62
|
'complementary-4': ['#6a3d9a', '#e6ab02'],
|
|
63
|
-
'complementary-5': ['#
|
|
63
|
+
'complementary-5': ['#df168c', '#1EB386']
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export const colorPalettesChart = updatePaletteNames(colorPalettes2)
|
package/helpers/DataTransform.js
CHANGED
|
@@ -196,6 +196,60 @@ export class DataTransform {
|
|
|
196
196
|
|
|
197
197
|
return data;
|
|
198
198
|
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* cleanData
|
|
202
|
+
*
|
|
203
|
+
// This cleans a data set by:
|
|
204
|
+
// - removing commas and $ signs from any numbers to try to plot the point
|
|
205
|
+
// - removing any data points that are NOT composed of of all digits (but allow a decimal point)
|
|
206
|
+
// Without this the charts "break" and do not render
|
|
207
|
+
*
|
|
208
|
+
* Inputs: data as array, excludeKey indicates which key to use to NOT clean
|
|
209
|
+
* Example: "Date" should not be cleaned if part of the data
|
|
210
|
+
*
|
|
211
|
+
* Output: returns the cleanedData
|
|
212
|
+
*
|
|
213
|
+
* Set testing = true if you need to see before and after data
|
|
214
|
+
*
|
|
215
|
+
*/
|
|
216
|
+
cleanData (data, excludeKey, testing = false) {
|
|
217
|
+
let cleanedupData = []
|
|
218
|
+
if (testing) console.log('## Data to clean=', data)
|
|
219
|
+
if (excludeKey === undefined) {
|
|
220
|
+
console.log('COVE: cleanData excludeKey undefined')
|
|
221
|
+
return data // because no excludeKey
|
|
222
|
+
}
|
|
223
|
+
data.forEach(function (d, i) {
|
|
224
|
+
if (testing) console.log("clean", i, " d", d);
|
|
225
|
+
let cleaned = {}
|
|
226
|
+
Object.keys(d).forEach(function (key) {
|
|
227
|
+
if (key === excludeKey) {
|
|
228
|
+
// pass thru
|
|
229
|
+
cleaned[key] = d[key]
|
|
230
|
+
} else {
|
|
231
|
+
// remove comma and dollar signs
|
|
232
|
+
if (testing) console.log("typeof d[key] is ", typeof d[key]);
|
|
233
|
+
let tmp = "";
|
|
234
|
+
if (typeof d[key] === 'string') {
|
|
235
|
+
tmp = d[key] !== null && d[key] !== '' ? d[key].replace(/[,\$]/g, '') : ''
|
|
236
|
+
} else {
|
|
237
|
+
tmp = d[key] !== null && d[key] !== '' ? d[key] : ''
|
|
238
|
+
}
|
|
239
|
+
if ((tmp !== '' && tmp !== null && !isNaN(tmp)) || (tmp !== '' && tmp !== null && /\d+\.?\d*/.test(tmp))) {
|
|
240
|
+
cleaned[key] = tmp
|
|
241
|
+
} else { cleaned[key] = '' }
|
|
242
|
+
// if you get here, then return nothing to skip bad data point
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
if (testing) console.log("cleaned=", cleaned)
|
|
246
|
+
cleanedupData.push(cleaned)
|
|
247
|
+
})
|
|
248
|
+
if (testing) console.log('## cleanedData =', cleanedupData)
|
|
249
|
+
return cleanedupData
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
199
253
|
}
|
|
200
254
|
|
|
201
255
|
export default DataTransform
|
package/helpers/isNumberLog.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default function isNumberLog(value = '', state = null) {
|
|
2
2
|
// if you need to check data to see if there is junk in there that can't be handled
|
|
3
3
|
// you can run the points through this and see values on the console
|
|
4
|
-
console.log("entering
|
|
4
|
+
console.log("entering isNumberLog value, valuetype:",value,typeof value);
|
|
5
5
|
var test;
|
|
6
6
|
if (typeof value === 'number') {
|
|
7
7
|
test = !Number.isNaN(value)
|
|
@@ -10,9 +10,9 @@ export default function isNumberLog(value = '', state = null) {
|
|
|
10
10
|
test = value !== null && value !== '' && /\d+\.?\d*/.test(value)
|
|
11
11
|
}
|
|
12
12
|
if (test === false) {
|
|
13
|
-
console.log('#
|
|
13
|
+
console.log('# isNumberLog FALSE on value, result', value, test)
|
|
14
14
|
} else {
|
|
15
|
-
console.log('#
|
|
15
|
+
console.log('# isNumberLog TRUE on value, result', value, test)
|
|
16
16
|
}
|
|
17
17
|
return test
|
|
18
18
|
}
|
|
@@ -28,7 +28,7 @@ export default function validateFipsCodeLength(stateOrData) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// Only includes data - get column name from somewhere else
|
|
31
|
-
if (Array.isArray(stateOrData)) {
|
|
31
|
+
if (Array.isArray(stateOrData) && stateOrData.length > 0) {
|
|
32
32
|
let columns = Object.keys(stateOrData[0])
|
|
33
33
|
|
|
34
34
|
let potentialColumnNames = ['fips', 'FIPS', 'fips codes', 'FIPS CODES', 'Fips Codes', 'fips Codes', 'Fips codes', 'FIPS Codes']
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/core",
|
|
3
|
-
"version": "4.23.
|
|
3
|
+
"version": "4.23.5",
|
|
4
4
|
"description": "Core components, styles, hooks, and helpers, for the CDC Open Visualization project",
|
|
5
5
|
"moduleName": "CdcCore",
|
|
6
6
|
"main": "dist/cdccore",
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
"react": "^18.2.0",
|
|
31
31
|
"react-dom": "^18.2.0"
|
|
32
32
|
},
|
|
33
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "34add3436994ca3cf13e51f313add4d70377f53e"
|
|
34
34
|
}
|
package/styles/_data-table.scss
CHANGED
|
@@ -39,6 +39,7 @@ table.data-table {
|
|
|
39
39
|
border-collapse: collapse;
|
|
40
40
|
overflow: auto;
|
|
41
41
|
appearance: none;
|
|
42
|
+
table-layout: fixed;
|
|
42
43
|
* {
|
|
43
44
|
box-sizing: border-box;
|
|
44
45
|
}
|
|
@@ -172,6 +173,15 @@ table.data-table {
|
|
|
172
173
|
margin-left: 5px;
|
|
173
174
|
}
|
|
174
175
|
}
|
|
176
|
+
|
|
177
|
+
.boxplot-td {
|
|
178
|
+
//display: inline-block;
|
|
179
|
+
//box-sizing: border-box;
|
|
180
|
+
table-layout: fixed;
|
|
181
|
+
width: 200;
|
|
182
|
+
//min-width: 150px;
|
|
183
|
+
//max-width: 400px;
|
|
184
|
+
}
|
|
175
185
|
}
|
|
176
186
|
|
|
177
187
|
.no-data {
|