@cdc/core 4.23.3 → 4.23.4

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.
@@ -83,7 +83,7 @@ const generateMedia = (state, type, elementToCapture) => {
83
83
  console.warn('COVE: pdf downloads disabled')
84
84
  break
85
85
  default:
86
- console.warn("generateMedia param 2 type must be 'image' or 'pdf'")
86
+ console.warn("COVE: generateMedia param 2 type must be 'image' or 'pdf'")
87
87
  break
88
88
  }
89
89
  }
@@ -127,7 +127,11 @@ const Link = ({ config }) => {
127
127
 
128
128
  // TODO: convert to standardized COVE section
129
129
  const Section = ({ children, classes }) => {
130
- return <section className={classes.join(' ')}>{children}</section>
130
+ return (
131
+ <section className={classes.join(' ')}>
132
+ <span>{children}</span>
133
+ </section>
134
+ )
131
135
  }
132
136
 
133
137
  const CoveMediaControls = () => null
@@ -0,0 +1,368 @@
1
+ import React, { useState, useEffect } from 'react'
2
+
3
+ // CDC
4
+ import Button from '@cdc/core/components/elements/Button'
5
+
6
+ // Third Party
7
+ import PropTypes from 'prop-types'
8
+
9
+ export const useFilters = props => {
10
+ const [showApplyButton, setShowApplyButton] = useState(false)
11
+
12
+ // Desconstructing: notice, adding more descriptive visualizationConfig name over config
13
+ // visualizationConfig feels more robust for all vis types so that its not confused with config/state/etc.
14
+ const { config: visualizationConfig, setConfig, filteredData, setFilteredData, excludedData, filterData } = props
15
+ const { type, filterBehavior, filters } = visualizationConfig
16
+
17
+ const filterStyleOptions = ['dropdown', 'pill', 'tab', 'tab bar']
18
+
19
+ const filterOrderOptions = [
20
+ {
21
+ label: 'Ascending Alphanumeric',
22
+ value: 'asc'
23
+ },
24
+ {
25
+ label: 'Descending Alphanumeric',
26
+ value: 'desc'
27
+ },
28
+ {
29
+ label: 'Custom',
30
+ value: 'cust'
31
+ }
32
+ ]
33
+
34
+ /**
35
+ * Re-orders a filter based on two indices and updates the runtime filters array and filters state
36
+ * @param {number} idx1 - The index of the original position of the filter value.
37
+ * @param {number} idx2 - The index of the new position for the filter value.
38
+ * @param {number} filterIndex - The index of the filter item within the array of filter items.
39
+ * @param {object} filter - The filter item itself, which contains an array of filter values.
40
+ * @return {void} None. This function only updates the state of the component.
41
+ *
42
+ * @modifies {object} - The filter object passed in as a parameter
43
+ * @modifies {array} - The filteredData state if visualizationConfig.type equals 'map'
44
+ * @modifies {object} - The visualizationConfig state
45
+ */
46
+ const handleFilterOrder = (idx1, idx2, filterIndex, filter) => {
47
+ // Create a shallow copy of the filter values array & update position of the values
48
+ const updatedValues = [...filter.values]
49
+ const [movedItem] = updatedValues.splice(idx1, 1)
50
+ updatedValues.splice(idx2, 0, movedItem)
51
+
52
+ const filtersCopy = visualizationConfig.type === 'chart' ? [...visualizationConfig.filters] : [...filteredData]
53
+ const filterItem = { ...filtersCopy[filterIndex] }
54
+
55
+ // Overwrite filterItem.values since thats what we map through in the editor panel
56
+ filterItem.values = updatedValues
57
+ filterItem.orderedValues = updatedValues
58
+ filterItem.active = updatedValues[0]
59
+ filterItem.order = 'cust'
60
+
61
+ // Update the filters
62
+ filtersCopy[filterIndex] = filterItem
63
+
64
+ if (visualizationConfig.type === 'map') {
65
+ setFilteredData(filtersCopy)
66
+ }
67
+
68
+ setConfig({ ...visualizationConfig, filters: filtersCopy })
69
+ }
70
+
71
+ const announceChange = text => {}
72
+
73
+ const changeFilterActive = (index, value) => {
74
+ let newFilters = visualizationConfig.type === 'map' ? [...filteredData] : [...visualizationConfig.filters]
75
+ newFilters[index].active = value
76
+
77
+ // If this is a button filter type show the button.
78
+ if (visualizationConfig.filterBehavior === 'Apply Button') {
79
+ setShowApplyButton(true)
80
+ }
81
+
82
+ // If we're not using the apply button we can set the filters right away.
83
+ if (visualizationConfig.filterBehavior !== 'Apply Button') {
84
+ setConfig({
85
+ ...visualizationConfig,
86
+ filters: newFilters
87
+ })
88
+ }
89
+
90
+ // Used for setting active filter, fromHash breaks the filteredData functionality.
91
+ if (visualizationConfig.type === 'map' && visualizationConfig.filterBehavior === 'Filter Change') {
92
+ setFilteredData(newFilters)
93
+ }
94
+
95
+ // If we're on a chart and not using the apply button
96
+ if (visualizationConfig.type === 'chart' && visualizationConfig.filterBehavior === 'Filter Change') {
97
+ setFilteredData(filterData(newFilters, excludedData))
98
+ }
99
+ }
100
+
101
+ const handleApplyButton = newFilters => {
102
+ setConfig({ ...visualizationConfig, filters: newFilters })
103
+
104
+ if (type === 'map') {
105
+ setFilteredData(newFilters, excludedData)
106
+ }
107
+
108
+ if (type === 'chart') {
109
+ setFilteredData(filterData(newFilters, excludedData))
110
+ }
111
+
112
+ setShowApplyButton(false)
113
+ }
114
+
115
+ const handleReset = e => {
116
+ let newFilters = [...visualizationConfig.filters]
117
+ e.preventDefault()
118
+
119
+ // reset to first item in values array.
120
+ newFilters.map(filter => {
121
+ filter = handleSorting(filter)
122
+ filter.active = filter.values[0]
123
+ return filter
124
+ })
125
+
126
+ if (type === 'map') {
127
+ setFilteredData(newFilters, excludedData)
128
+ } else {
129
+ setFilteredData(filterData(newFilters, excludedData))
130
+ }
131
+
132
+ setConfig({ ...visualizationConfig, filters: newFilters })
133
+ }
134
+
135
+ const filterConstants = {
136
+ buttonText: 'Apply Filters',
137
+ resetText: 'Reset All',
138
+ introText: `Make a selection from the filters to change the visualization information.`,
139
+ applyText: 'Select the apply button to update the visualization information.'
140
+ }
141
+
142
+ const handleSorting = singleFilter => {
143
+ const { order } = singleFilter
144
+
145
+ const sortAsc = (a, b) => {
146
+ return a.toString().localeCompare(b.toString(), 'en', { numeric: true })
147
+ }
148
+
149
+ const sortDesc = (a, b) => {
150
+ return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
151
+ }
152
+
153
+ if (!order || order === '') {
154
+ singleFilter.order = 'asc'
155
+ }
156
+
157
+ if (order === 'desc') {
158
+ singleFilter.values = singleFilter.values.sort(sortDesc)
159
+ }
160
+
161
+ if (order === 'asc') {
162
+ singleFilter.values = singleFilter.values.sort(sortAsc)
163
+ }
164
+ return singleFilter
165
+ }
166
+
167
+ // prettier-ignore
168
+ return {
169
+ handleApplyButton,
170
+ changeFilterActive,
171
+ announceChange,
172
+ showApplyButton,
173
+ handleReset,
174
+ filterConstants,
175
+ filterStyleOptions,
176
+ filterOrderOptions,
177
+ handleFilterOrder,
178
+ handleSorting
179
+ }
180
+ }
181
+
182
+ const Filters = props => {
183
+ const { config: visualizationConfig, filteredData, dimensions } = props
184
+ const { filters, type, general, theme, filterBehavior } = visualizationConfig
185
+ const [mobileFilterStyle, setMobileFilterStyle] = useState(false)
186
+
187
+ // useFilters hook provides data and logic for handling various filter functions
188
+ // prettier-ignore
189
+ const {
190
+ handleApplyButton,
191
+ changeFilterActive,
192
+ announceChange,
193
+ showApplyButton,
194
+ handleReset,
195
+ filterConstants,
196
+ handleSorting
197
+ } = useFilters(props)
198
+
199
+ useEffect(() => {
200
+ if (!dimensions) return
201
+ if (dimensions[0] < 768 && filters?.length > 0) {
202
+ setMobileFilterStyle(true)
203
+ } else {
204
+ setMobileFilterStyle(false)
205
+ }
206
+ }, [dimensions])
207
+
208
+ const Filters = props => props.children
209
+
210
+ const filterSectionClassList = ['filters-section', type === 'map' ? general.headerColor : theme]
211
+
212
+ // Exterior Section Wrapper
213
+ Filters.Section = props => {
214
+ return (
215
+ <section className={filterSectionClassList.join(' ')}>
216
+ <p className='filters-section__intro-text'>
217
+ {filterConstants.introText} {visualizationConfig.filterBehavior === 'Apply Button' && filterConstants.applyText}
218
+ </p>
219
+ <div className='filters-section__wrapper'>{props.children}</div>
220
+ </section>
221
+ )
222
+ }
223
+
224
+ // Apply/Reset Buttons
225
+ Filters.ApplyBehavior = props => {
226
+ if (filterBehavior !== 'Apply Button') return
227
+ const applyButtonClasses = [general?.headerColor ? general.headerColor : theme, 'apply']
228
+ return (
229
+ <div className='filters-section__buttons'>
230
+ <Button onClick={() => handleApplyButton(filters)} disabled={!showApplyButton} className={applyButtonClasses.join(' ')}>
231
+ {filterConstants.buttonText}
232
+ </Button>
233
+ <a href='#!' role='button' onClick={handleReset}>
234
+ {filterConstants.resetText}
235
+ </a>
236
+ </div>
237
+ )
238
+ }
239
+
240
+ Filters.TabBar = props => {
241
+ const { filter: singleFilter, index: outerIndex } = props
242
+ return (
243
+ <section className='single-filters__tab-bar'>
244
+ {singleFilter.values.map(filter => {
245
+ const buttonClassList = ['button__tab-bar', singleFilter.active === filter ? 'button__tab-bar--active' : '']
246
+ return (
247
+ <button className={buttonClassList.join(' ')} key={filter} onClick={e => changeFilterActive(outerIndex, filter)}>
248
+ {filter}
249
+ </button>
250
+ )
251
+ })}
252
+ </section>
253
+ )
254
+ }
255
+
256
+ Filters.Pills = props => props.pills
257
+
258
+ Filters.Tabs = props => props.tabs
259
+
260
+ Filters.Dropdown = props => {
261
+ const { index: outerIndex, label, active, filters } = props
262
+ return (
263
+ <select
264
+ id={`filter-${outerIndex}`}
265
+ name={label}
266
+ className='filter-select'
267
+ data-index='0'
268
+ value={active}
269
+ onChange={e => {
270
+ changeFilterActive(outerIndex, e.target.value)
271
+ announceChange(`Filter ${label} value has been changed to ${e.target.value}, please reference the data table to see updated values.`)
272
+ }}
273
+ >
274
+ {filters}
275
+ </select>
276
+ )
277
+ }
278
+
279
+ // Resolve Filter Styles
280
+ Filters.Style = () => {
281
+ if (filters || filteredData) {
282
+ // Here charts is using config.filters where maps is using a runtime value
283
+ let filtersToLoop = type === 'map' ? filteredData : filters
284
+
285
+ // Remove fromHash if it exists on filters to loop so we can loop nicely
286
+ delete filtersToLoop.fromHash
287
+
288
+ return filtersToLoop.map((singleFilter, outerIndex) => {
289
+ const values = []
290
+ const pillValues = []
291
+ const tabValues = []
292
+ const tabBarValues = []
293
+
294
+ const { active, label, filterStyle } = singleFilter
295
+
296
+ handleSorting(singleFilter)
297
+
298
+ singleFilter.values.forEach((filterOption, index) => {
299
+ const pillClassList = ['pill', active === filterOption ? 'pill--active' : null, theme && theme]
300
+ const tabClassList = ['tab', active === filterOption && 'tab--active', theme && theme]
301
+
302
+ pillValues.push(
303
+ <div className='pill__wrapper'>
304
+ <button className={pillClassList.join(' ')} onClick={e => changeFilterActive(outerIndex, filterOption)} name={label}>
305
+ {filterOption}
306
+ </button>
307
+ </div>
308
+ )
309
+
310
+ values.push(
311
+ <option key={index} value={filterOption}>
312
+ {filterOption}
313
+ </option>
314
+ )
315
+
316
+ tabValues.push(
317
+ <button className={tabClassList.join(' ')} onClick={e => changeFilterActive(outerIndex, filterOption)}>
318
+ {filterOption}
319
+ </button>
320
+ )
321
+
322
+ tabBarValues.push(filterOption)
323
+ })
324
+
325
+ const classList = ['single-filters', mobileFilterStyle ? 'single-filters--dropdown' : `single-filters--${filterStyle}`]
326
+
327
+ return (
328
+ <div className={classList.join(' ')} key={outerIndex}>
329
+ <>
330
+ {label && <label htmlFor={label}>{label}</label>}
331
+ {filterStyle === 'tab' && !mobileFilterStyle && <Filters.Tabs tabs={tabValues} />}
332
+ {filterStyle === 'pill' && !mobileFilterStyle && <Filters.Pills pills={pillValues} />}
333
+ {filterStyle === 'tab bar' && !mobileFilterStyle && <Filters.TabBar filter={singleFilter} index={outerIndex} />}
334
+ {(filterStyle === 'dropdown' || mobileFilterStyle) && <Filters.Dropdown index={outerIndex} label={label} active={active} filters={values} />}
335
+ </>
336
+ </div>
337
+ )
338
+ })
339
+ }
340
+ }
341
+
342
+ if (visualizationConfig?.filters?.length === 0 || props?.filteredData?.length === 0) return
343
+ return (
344
+ <Filters>
345
+ <Filters.Section>
346
+ <Filters.Style />
347
+ <Filters.ApplyBehavior />
348
+ </Filters.Section>
349
+ </Filters>
350
+ )
351
+ }
352
+
353
+ Filters.propTypes = {
354
+ // runtimeFilters in place
355
+ filteredData: PropTypes.array,
356
+ // function for updating the runtime filters
357
+ setFilteredData: PropTypes.func,
358
+ // the full apps config
359
+ config: PropTypes.object,
360
+ // updating function for setting fitlerBehavior
361
+ setConfig: PropTypes.func,
362
+ // exclusions
363
+ excludedData: PropTypes.array,
364
+ // function for filtering the data
365
+ filterData: PropTypes.func
366
+ }
367
+
368
+ export default Filters
@@ -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 (
@@ -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
@@ -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 isNumber valuetype is:",typeof value);
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('# isNumber FALSE on value, result', value, test)
13
+ console.log('# isNumberLog FALSE on value, result', value, test)
14
14
  } else {
15
- console.log('# isNumber TRUE on value, result', value, test)
15
+ console.log('# isNumberLog TRUE on value, result', value, test)
16
16
  }
17
17
  return test
18
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/core",
3
- "version": "4.23.3",
3
+ "version": "4.23.4",
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": "6fa3b11db159d38538f18023fe70b67a29e7d327"
33
+ "gitHead": "dcd395d76f70b2d113f2b4c6fe50a52522655cd1"
34
34
  }
@@ -189,3 +189,8 @@ section.footnotes {
189
189
  .cdc-chart-inner-container .subtext {
190
190
  padding: 15px;
191
191
  }
192
+
193
+ .margin-left-href {
194
+ margin-left: 15px;
195
+ }
196
+
@@ -0,0 +1,92 @@
1
+ &.cdc-map-outer-container .editor-panel .series-item,
2
+ &.type-chart .editor-panel .series-item {
3
+ list-style: none;
4
+ position: relative;
5
+
6
+ $border: 1px solid #ccc;
7
+
8
+ @at-root .accordion:first-of-type .series-item__title {
9
+ border-top: $border;
10
+ }
11
+
12
+ @at-root .accordion:last-of-type .series-item__title {
13
+ border-bottom: $border;
14
+ }
15
+
16
+ .accordion__button {
17
+ border-bottom: unset;
18
+
19
+ &.hide-arrow:before {
20
+ display: none;
21
+ }
22
+ }
23
+
24
+ .accordion__panel {
25
+ border-left: $border;
26
+ border-right: $border;
27
+ padding: 10px;
28
+ }
29
+
30
+ .series-list__remove {
31
+ margin-left: auto;
32
+ margin-right: 20px;
33
+ border: 1px solid red;
34
+ border-radius: 10px;
35
+ }
36
+
37
+ .hide-arrow .series-list__remove {
38
+ margin-right: 0px;
39
+ }
40
+
41
+ label {
42
+ font-size: 0.8rem;
43
+ font-weight: normal;
44
+ }
45
+
46
+ .series-item__title {
47
+ width: 100%;
48
+ background-color: #f5f5f5;
49
+ border-right: $border;
50
+ border-left: $border;
51
+ display: block;
52
+ padding: 5px;
53
+
54
+ .accordion__button {
55
+ border-bottom: none !important;
56
+ }
57
+ }
58
+
59
+ li {
60
+ padding: 5px;
61
+ }
62
+
63
+ .series_item__title {
64
+ font-size: 12px;
65
+ font-weight: bold;
66
+ text-transform: uppercase;
67
+ }
68
+
69
+ .series-item__dropdown {
70
+ display: block;
71
+ margin-bottom: 8px;
72
+ }
73
+
74
+ // chart series list
75
+ &--chart {
76
+ padding: 5px 5px 0;
77
+
78
+ .accordion__button {
79
+ display: flex;
80
+ padding-left: 5px !important;
81
+
82
+ .cove-icon {
83
+ padding-right: 5px;
84
+ margin-right: 10px;
85
+ }
86
+ }
87
+
88
+ &:last-child {
89
+ padding-bottom: 5px;
90
+ }
91
+ }
92
+ }
package/styles/base.scss CHANGED
@@ -52,6 +52,11 @@
52
52
  @import 'reset';
53
53
  @import 'variables';
54
54
  @import 'mixins';
55
+ @import 'filters';
56
+
57
+ body.post-type-cdc_visualization .visx-tooltip {
58
+ z-index: 1000000;
59
+ }
55
60
 
56
61
  .cdc-open-viz-module {
57
62
  position: relative;
@@ -64,6 +69,7 @@
64
69
  @import 'data-table';
65
70
  @import 'global';
66
71
  @import 'button-section';
72
+ @import 'series-list';
67
73
  }
68
74
 
69
75
  $theme: (
@@ -0,0 +1,122 @@
1
+ .filters-section {
2
+ &__wrapper {
3
+ flex-wrap: wrap;
4
+ display: flex;
5
+ gap: 7px 15px;
6
+ margin-top: 15px;
7
+ margin-bottom: 15px;
8
+ }
9
+
10
+ &__intro-text {
11
+ display: block;
12
+ width: 100%;
13
+ }
14
+
15
+ &__buttons {
16
+ width: 100%;
17
+ .apply {
18
+ margin-right: 10px;
19
+ }
20
+ }
21
+ }
22
+
23
+ // note the diff of section and no section above.
24
+ // Can't use nested selectors with top level section overwrites
25
+ section.filters-section {
26
+ // border: 1px solid #c7c7c7;
27
+ // padding: 10px;
28
+ border-radius: 10px;
29
+ margin-bottom: 10px;
30
+ display: block !important;
31
+ }
32
+
33
+ div.single-filters {
34
+ display: flex;
35
+ flex-wrap: wrap;
36
+ margin: 15px 0;
37
+
38
+ select {
39
+ width: auto !important;
40
+ }
41
+
42
+ label {
43
+ width: 100%;
44
+ }
45
+
46
+ &__tab-bar {
47
+ &:first-child {
48
+ border-top-left-radius: 15px;
49
+ border-bottom-left-radius: 15px;
50
+ }
51
+ }
52
+ }
53
+
54
+ .single-filters--dropdown {
55
+ width: auto;
56
+ display: flex;
57
+ }
58
+
59
+ .single-filters--tab {
60
+ width: 100%;
61
+ display: flex;
62
+ flex-wrap: wrap;
63
+
64
+ .tab {
65
+ width: 25%;
66
+ min-height: 40px;
67
+ border: none;
68
+ padding: 0.5em 0;
69
+ background: none;
70
+ border-top-left-radius: 5px;
71
+ border-top-right-radius: 5px;
72
+ margin-top: 5px;
73
+ }
74
+
75
+ .tab--active {
76
+ border-bottom: none;
77
+ }
78
+ }
79
+
80
+ .single-filters--pill {
81
+ width: 100%;
82
+ display: flex;
83
+ flex-wrap: wrap;
84
+
85
+ .pill {
86
+ border: none;
87
+ width: 95%;
88
+ height: 60px;
89
+ background: none;
90
+ border-radius: 30px;
91
+ box-shadow: 0 2px 5px #d3d3d3;
92
+ vertical-align: middle;
93
+ margin-bottom: 15px;
94
+ margin-right: 10px;
95
+ margin-left: 10px;
96
+ white-space: break-spaces;
97
+ word-break: break-word;
98
+
99
+ &__wrapper {
100
+ width: 25%;
101
+ }
102
+ }
103
+ }
104
+
105
+ .single-filters__tab-bar {
106
+ background: #f2f2f2;
107
+ display: flex !important;
108
+ flex-wrap: wrap;
109
+ width: 100%;
110
+ border-radius: 15px;
111
+
112
+ button.button__tab-bar {
113
+ flex: 1 1 0px;
114
+ padding: 10px;
115
+ background: none;
116
+ transition: background 2s;
117
+ &--active {
118
+ background: #fff;
119
+ border-radius: 15px;
120
+ }
121
+ }
122
+ }
@@ -147,6 +147,55 @@ $theme: (
147
147
  }
148
148
  }
149
149
 
150
+ // Charts filter styles
151
+ .cdc-open-viz-module {
152
+ @each $theme-name, $theme-colors in $theme {
153
+ &.theme-#{$theme-name} {
154
+ .single-filters--tab .tab--active {
155
+ border: 1px solid string.unquote(nth($theme-colors, 1));
156
+ border-top: 3px solid string.unquote(nth($theme-colors, 1));
157
+ border-bottom: none;
158
+ }
159
+
160
+ .single-filters--tab .tab:not(.tab--active) {
161
+ border-bottom: 1px solid string.unquote(nth($theme-colors, 1));
162
+ }
163
+
164
+ .single-filters--pill .pill--active {
165
+ background-color: string.unquote(nth($theme-colors, 1));
166
+ color: #fff;
167
+ }
168
+ .filters-section button.cove-button:not([disabled]) {
169
+ background-color: string.unquote(nth($theme-colors, 1)) !important;
170
+ }
171
+ }
172
+
173
+ .theme-#{$theme-name} {
174
+ .single-filters--tab .tab--active {
175
+ border: 1px solid string.unquote(nth($theme-colors, 1));
176
+ border-top: 3px solid string.unquote(nth($theme-colors, 1));
177
+ border-bottom: none;
178
+ }
179
+
180
+ .single-filters--tab .tab:not(.tab--active) {
181
+ border-bottom: 1px solid string.unquote(nth($theme-colors, 1));
182
+ }
183
+
184
+ .single-filters--pill .pill--active {
185
+ background-color: string.unquote(nth($theme-colors, 1));
186
+ color: #fff;
187
+ }
188
+
189
+ .button__tab-bar--active {
190
+ outline: 2px solid string.unquote(nth($theme-colors, 1));
191
+ }
192
+ .apply:not([disabled]) {
193
+ background-color: string.unquote(nth($theme-colors, 1)) !important;
194
+ }
195
+ }
196
+ }
197
+ }
198
+
150
199
  $baseColor: #333;
151
200
  $blue: #005eaa;
152
201
  $lightestGray: #f2f2f2;
@@ -1,50 +0,0 @@
1
- /**
2
- * cleanData
3
- *
4
- // This cleans a data set by:
5
- // - removing commas and $ signs from any numbers to try to plot the point
6
- // - removing any data points that are NOT composed of of all digits (but allow a decimal point)
7
- // Without this the charts "break" and do not render
8
- *
9
- * Inputs: data as array, excludeKey indicates which key to use to NOT clean
10
- * Example: "Date" should not be cleaned if part of the data
11
- *
12
- * Output: returns the cleanedData
13
- *
14
- * Set testing = true if you need to see before and after data
15
- *
16
- */
17
- export default function cleanData (data, excludeKey, testing = false) {
18
- let cleanedupData = []
19
- if (testing) console.log('## Data to clean=', data)
20
- if (excludeKey === undefined) {
21
- excludeKey = "Date" // have a default value
22
- }
23
- data.forEach(function (d, i) {
24
- if (testing) console.log("clean", i, " d", d);
25
- let cleanedBar = {}
26
- Object.keys(d).forEach(function (key) {
27
- if (key === excludeKey) {
28
- // pass thru
29
- cleanedBar[key] = d[key]
30
- } else {
31
- // remove comma and dollar signs
32
- if (testing) console.log("typeof d[key] is ", typeof d[key]);
33
- let tmp = "";
34
- if (typeof d[key] === 'string') {
35
- tmp = d[key] !== null && d[key] !== '' ? d[key].replace(/[,\$]/g, '') : ''
36
- } else {
37
- tmp = d[key] !== null && d[key] !== '' ? d[key] : ''
38
- }
39
- if ((tmp !== '' && tmp !== null && !isNaN(tmp)) || (tmp !== '' && tmp !== null && /\d+\.?\d*/.test(tmp))) {
40
- cleanedBar[key] = tmp
41
- } else { cleanedBar[key] = '' }
42
- // if you get here, then return nothing to skip bad data point
43
- }
44
- })
45
- if (testing) console.log("cleanedBar=", cleanedBar);
46
- cleanedupData.push(cleanedBar)
47
- })
48
- if (testing) console.log('## cleanedData =', cleanedupData)
49
- return cleanedupData
50
- }