@cdc/core 4.23.1 → 4.23.2

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.
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import html2pdf from 'html2pdf.js'
2
+ // import html2pdf from 'html2pdf.js'
3
3
  import html2canvas from 'html2canvas'
4
4
 
5
5
  const buttonText = {
@@ -64,22 +64,23 @@ const generateMedia = (state, type, elementToCapture) => {
64
64
  })
65
65
  return
66
66
  case 'pdf':
67
- let opt = {
68
- margin: 0.2,
69
- filename: filename + '.pdf',
70
- image: { type: 'png' },
71
- html2canvas: { scale: 2, logging: false },
72
- jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
73
- }
74
-
75
- html2pdf()
76
- .set(opt)
77
- .from(baseSvg)
78
- .save()
79
- .then(() => {
80
- generatedImage.remove() // Remove generated png
81
- baseSvg.style.display = null // Re-display initial svg map
82
- })
67
+ // let opt = {
68
+ // margin: 0.2,
69
+ // filename: filename + '.pdf',
70
+ // image: { type: 'png' },
71
+ // html2canvas: { scale: 2, logging: false },
72
+ // jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
73
+ // }
74
+
75
+ // html2pdf()
76
+ // .set(opt)
77
+ // .from(baseSvg)
78
+ // .save()
79
+ // .then(() => {
80
+ // generatedImage.remove() // Remove generated png
81
+ // baseSvg.style.display = null // Re-display initial svg map
82
+ // })
83
+ console.warn('COVE: pdf downloads disabled')
83
84
  break
84
85
  default:
85
86
  console.warn("generateMedia param 2 type must be 'image' or 'pdf'")
@@ -8,10 +8,10 @@ import '../../styles/v2/components/button.scss'
8
8
  const Button = ({ style, role, hoverStyle = {}, fluid = false, loading = false, loadingText = 'Loading...', flexCenter, active = false, onClick, children, ...attributes }) => {
9
9
  const buttonRef = useRef(null)
10
10
 
11
- const [buttonState, setButtonState] = useState('out')
12
- const [customStyles, setCustomStyles] = useState({ ...style })
13
- const [childrenWidth, setChildrenWidth] = useState()
14
- const [loadtextWidth, setLoadtextWidth] = useState()
11
+ const [ buttonState, setButtonState ] = useState('out')
12
+ const [ customStyles, setCustomStyles ] = useState({ ...style })
13
+ const [ childrenWidth, setChildrenWidth ] = useState()
14
+ const [ loadtextWidth, setLoadtextWidth ] = useState()
15
15
 
16
16
  const attributesObj = {
17
17
  ...attributes,
@@ -23,6 +23,7 @@ const Button = ({ style, role, hoverStyle = {}, fluid = false, loading = false,
23
23
  onBlur: () => setButtonState('out')
24
24
  }
25
25
 
26
+
26
27
  useEffect(() => {
27
28
  if ('loader' === role && buttonRef.current) {
28
29
  //Create ghost object and text nodes for children
@@ -53,18 +54,19 @@ const Button = ({ style, role, hoverStyle = {}, fluid = false, loading = false,
53
54
  buttonRef.current.parentNode.removeChild(ghostSpan)
54
55
  buttonRef.current.parentNode.removeChild(ghostLoaderSpan)
55
56
  }
56
- }, [])
57
+ return () => {
58
+ }
59
+ }, [ buttonRef, children, loadingText, role ])
57
60
 
58
61
  useEffect(() => {
59
62
  //Adjust button styles depending on cursor, focus, and active, states
60
- return buttonState === 'in'
61
- ? setCustomStyles(stateStyles => ({ ...stateStyles, ...hoverStyle }))
62
- : buttonState === 'out'
63
- ? active //If button state is out, check if its 'active'; we want to keep hover styles applied to 'active' buttons
64
- ? null //Button is active, so leave the 'hover' styles in place
65
- : setCustomStyles({ ...style }) //Button is not 'active', so reset display styles back to default
66
- : false //Button state is neither 'in' nor 'out' - do nothing
67
- }, [buttonState, active])
63
+ if (buttonState === 'in') setCustomStyles(stateStyles => ({ ...stateStyles, ...hoverStyle }))
64
+
65
+ // If button state is out, check if its 'active'; we want to keep hover styles applied to 'active' buttons
66
+ if (buttonState === 'out')
67
+ // If button state is out, and not 'active', reset display styles back to default
68
+ if (!active) setCustomStyles({ ...style })
69
+ }, [ buttonState, active, style ])
68
70
 
69
71
  return (
70
72
  <button
@@ -80,16 +82,16 @@ const Button = ({ style, role, hoverStyle = {}, fluid = false, loading = false,
80
82
  <>
81
83
  {'loader' === role && (
82
84
  <>
83
- <span className='cove-button__text' style={loading ? { width: loadtextWidth + 'px' } : { width: childrenWidth + 'px' }}>
84
- <div className='cove-button__text--loading' style={loading ? { opacity: 1 } : null}>
85
+ <span className="cove-button__text" style={loading ? { width: loadtextWidth + 'px' } : { width: childrenWidth + 'px' }}>
86
+ <div className="cove-button__text--loading" style={loading ? { opacity: 1 } : null}>
85
87
  {loadingText}
86
88
  </div>
87
- <div className='cove-button__text--children' style={loading ? { opacity: 0 } : null}>
89
+ <div className="cove-button__text--children" style={loading ? { opacity: 0 } : null}>
88
90
  {children}
89
91
  </div>
90
92
  </span>
91
- <div className='cove-button__load-spin' style={loading ? { width: '28px', opacity: 1 } : null}>
92
- <LoadSpin className='ml-1' size={20} />
93
+ <div className="cove-button__load-spin" style={loading ? { width: '28px', opacity: 1 } : null}>
94
+ <LoadSpin className="ml-1" size={20}/>
93
95
  </div>
94
96
  </>
95
97
  )}
@@ -102,7 +104,7 @@ const Button = ({ style, role, hoverStyle = {}, fluid = false, loading = false,
102
104
 
103
105
  Button.propTypes = {
104
106
  /** Specify special role type for button */
105
- role: PropTypes.oneOf(['loader']),
107
+ role: PropTypes.oneOf([ 'loader' ]),
106
108
  /** Provide object with styles that overwrite base styles when hovered */
107
109
  hoverStyle: PropTypes.object,
108
110
  /** Enables button to stretch to the full width of the content */
@@ -24,9 +24,6 @@ const InputCheckbox = memo(
24
24
  const [value, setValue] = useState(stateValue)
25
25
 
26
26
  let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
27
- if (fieldName === 'border') {
28
- console.table({ fieldName, value, stateValue })
29
- }
30
27
 
31
28
  useEffect(() => {
32
29
  if (stateValue !== undefined && stateValue !== value) {
@@ -3,12 +3,13 @@ import React from 'react'
3
3
  import Button from '../elements/Button'
4
4
  import Card from '../elements/Card'
5
5
 
6
- import { DATA_TABLE_VERTICAL, DATA_TABLE_HORIZONTAL, DATA_TABLE_SINGLE_ROW, DATA_TABLE_MULTI_ROW } from '../../data/dataDesignerTables'
6
+ import { DATA_TABLE_VERTICAL, DATA_TABLE_HORIZONTAL, DATA_TABLE_SINGLE_ROW, DATA_TABLE_MULTI_ROW } from '../../templates/dataDesignerTables'
7
7
  import '../../styles/v2/components/data-designer.scss'
8
8
 
9
9
  const DataDesigner = props => {
10
10
  const { configureData, updateDescriptionProp, visualizationKey, dataKey } = props
11
11
 
12
+
12
13
  return (
13
14
  <div className='cove-data-designer__container'>
14
15
  <div className='mb-2'>
@@ -165,16 +166,57 @@ const DataDesigner = props => {
165
166
  </select>
166
167
  </div>
167
168
  <div className='mb-2'>
168
- <div className='mb-1'>Which property in the dataset represents the numeric value?</div>
169
+ <div className='mb-1'>Which properties in the dataset represent the numeric value? (all remaining properties will be treated as filters)</div>
170
+ {configureData.dataDescription.valueKeys && configureData.dataDescription.valueKeys.length > 0 && (
171
+ <ul className="value-list">
172
+ {configureData.dataDescription.valueKeys.map((valueKey, index) => (
173
+ <li key={`value-keys-list-${index}`}>{valueKey}<button onClick={() => {
174
+ let newValueKeys = configureData.dataDescription.valueKeys;
175
+ newValueKeys.splice(index, 1);
176
+ updateDescriptionProp(visualizationKey, dataKey, 'valueKeys', newValueKeys)
177
+ }}>X</button></li>
178
+ ))}
179
+ </ul>
180
+ )}
169
181
  <select
170
182
  onChange={e => {
171
- updateDescriptionProp(visualizationKey, dataKey, 'valueKey', e.target.value)
183
+ if(e.target.value && (!configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(e.target.value) === -1)){
184
+ updateDescriptionProp(visualizationKey, dataKey, 'valueKeys', [...(configureData.dataDescription.valueKeys || []), e.target.value])
185
+ }
172
186
  }}
173
- defaultValue={configureData.dataDescription.valueKey}
174
187
  >
175
188
  <option value=''>Choose an option</option>
176
- {Object.keys(configureData.data[0]).map((value, index) => (
177
- <option value={value} key={index}>
189
+ {Object.keys(configureData.data[0]).filter(value => !configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(value) === -1).map((value, index) => (
190
+ <option value={value} key={`value-keys-option-${index}`}>
191
+ {value}
192
+ </option>
193
+ ))}
194
+ </select>
195
+ </div>
196
+ <div className='mb-2'>
197
+ <div className='mb-1'>(Optional) Which properties in the dataset should be ignored? (will not be used or treated as filters)</div>
198
+ {configureData.dataDescription.ignoredKeys && configureData.dataDescription.ignoredKeys.length > 0 && (
199
+ <ul className="value-list">
200
+ {configureData.dataDescription.ignoredKeys.map((ignoredKey, index) => (
201
+ <li key={`value-keys-list-${index}`}>{ignoredKey}<button onClick={() => {
202
+ let newIgnoredKeys = configureData.dataDescription.ignoredKeys;
203
+ newIgnoredKeys.splice(index, 1);
204
+ updateDescriptionProp(visualizationKey, dataKey, 'ignoredKeys', newIgnoredKeys)
205
+ }}>X</button></li>
206
+ ))}
207
+ </ul>
208
+ )}
209
+ <select
210
+ onChange={e => {
211
+ if(e.target.value){
212
+ updateDescriptionProp(visualizationKey, dataKey, 'ignoredKeys', [...(configureData.dataDescription.ignoredKeys || []), e.target.value])
213
+ }
214
+ e.target.value = '';
215
+ }}
216
+ >
217
+ <option value=''>Choose an option</option>
218
+ {Object.keys(configureData.data[0]).filter(value => !configureData.dataDescription.ignoredKeys || configureData.dataDescription.ignoredKeys.indexOf(value) === -1).map((value, index) => (
219
+ <option value={value} key={`ignored-keys-option-${index}`}>
178
220
  {value}
179
221
  </option>
180
222
  ))}
@@ -28,6 +28,8 @@ import iconWarningTriangle from '../../assets/icon-warning-triangle.svg'
28
28
  import iconGear from '../../assets/icon-gear.svg'
29
29
  import iconTools from '../../assets/icon-tools.svg'
30
30
  import iconText from '../../assets/filtered-text.svg'
31
+ import iconPlus from '../../assets/icon-plus.svg'
32
+ import iconMinus from '../../assets/icon-minus.svg'
31
33
 
32
34
  import '../../styles/v2/components/icon.scss'
33
35
 
@@ -58,6 +60,8 @@ const iconHash = {
58
60
  warningTriangle: iconWarningTriangle,
59
61
  gear: iconGear,
60
62
  tools: iconTools,
63
+ plus: iconPlus,
64
+ minus: iconMinus,
61
65
  'filtered-text': iconText
62
66
  }
63
67
 
@@ -1,37 +1,53 @@
1
- import React from 'react'
2
- import ReactTooltip from 'react-tooltip'
1
+ import { useId } from 'react'
2
+
3
+ // Third Party
4
+ import { Tooltip as ReactTooltip } from 'react-tooltip'
5
+
6
+ // Styles
7
+ import '../../styles/v2/components/ui/tooltip.scss'
3
8
 
4
9
  const TooltipTarget = () => null
5
10
  const TooltipContent = () => null
6
11
 
7
- const Tooltip = ({ position = 'top', trigger = 'hover focus', float = false, shadow = true, border = null, borderColor = '#bdbdbd', hideOnScroll = true, children, style, ...attributes }) => {
12
+ const Tooltip = ({
13
+ place = 'top',
14
+ trigger = 'hover',
15
+ float = false,
16
+ shadow = true,
17
+ border = false,
18
+ children,
19
+ style,
20
+ ...attributes
21
+ }) => {
22
+
8
23
  const tooltipTargetChildren = children.find(el => el.type === TooltipTarget)
9
24
  const tooltipContentChildren = children.find(el => el.type === TooltipContent)
10
25
 
11
- const uid = 'tooltip-' + Math.floor(Math.random() * 100000)
26
+ const uid = 'tooltip-' + useId()
27
+
28
+ const generateTriggerEvent = (trigger) => {
29
+ const eventList = {
30
+ 'hover': null,
31
+ 'focus': 'focus',
32
+ 'click': 'click focus'
33
+ }
34
+ return eventList[trigger]
35
+ }
12
36
 
13
37
  return (
14
- <span className='cove-tooltip' style={style} {...attributes}>
15
- <button
16
- className='cove-tooltip--target'
17
- data-for={uid}
18
- data-place={position}
19
- data-event={trigger}
20
- data-effect={float ? 'float' : 'solid'}
21
- data-scroll-hide={hideOnScroll}
22
- data-tip
23
- onClick={e => {
24
- e.preventDefault()
25
- }}
26
- onMouseEnter={e => ReactTooltip.show(e.target)}
27
- onMouseLeave={e => ReactTooltip.hide(e.target)}
28
- onFocus={e => ReactTooltip.show(e.target)}
29
- onBlur={e => ReactTooltip.hide(e.target)}
30
- style={{ background: 'transparent', border: 'none' }}
38
+ <span className="cove-tooltip" style={style} {...attributes}>
39
+ <a id={uid} className="cove-tooltip--target"
40
+ data-tooltip-float={float}
41
+ data-tooltip-place={place}
42
+ data-tooltip-events={generateTriggerEvent()}
31
43
  >
32
44
  {tooltipTargetChildren ? tooltipTargetChildren.props.children : null}
33
- </button>
34
- <ReactTooltip id={uid} className={'cove-tooltip__content' + (trigger === 'click' ? ' interactive' : '') + (border ? ' cove-tooltip--border-' + border : '') + (shadow ? ' has-shadow' : '')} type='light' effect='solid' border={!!border} borderColor={borderColor} globalEventOff='click'>
45
+ </a>
46
+ <ReactTooltip
47
+ id={uid} anchorId={uid}
48
+ className={'cove-tooltip__content' + (' place-' + place) + (!float ? ' cove-tooltip__content--animated' : '') + (trigger === 'click' ? ' interactive' : '') + (border ? (' cove-tooltip--border') : '') + (shadow ? ' has-shadow' : '')}
49
+ globalEventOff="click"
50
+ >
35
51
  {tooltipContentChildren ? tooltipContentChildren.props.children : null}
36
52
  </ReactTooltip>
37
53
  </span>
@@ -11,109 +11,109 @@ export class DataTransform {
11
11
 
12
12
  //Performs standardizations that can be completed automatically without use input
13
13
  autoStandardize(data) {
14
- const errorsFound = []
14
+ const errorsFound = [];
15
15
 
16
16
  // Empty data
17
17
  if (0 === data.length) {
18
- errorsFound.push(this.constants.errorMessageEmptyData)
18
+ errorsFound.push(this.constants.errorMessageEmptyData);
19
19
  }
20
20
 
21
21
  // Does it have the correct data structure?
22
22
  if (!data.filter || data.filter(row => typeof row !== 'object').length > 0) {
23
- errorsFound.push(this.constants.errorMessageFormat)
23
+ errorsFound.push(this.constants.errorMessageFormat);
24
24
  }
25
25
 
26
26
  if (errorsFound.length > 0) {
27
- console.error(errorsFound)
28
- return undefined
27
+ console.error(errorsFound);
28
+ return undefined;
29
29
  }
30
30
 
31
31
  //Convert array of arrays, to array of objects
32
32
  if (data.filter(row => row.constructor !== Object).length > 0) {
33
- let standardizedData = []
33
+ let standardizedData = [];
34
34
  for (let row = 1; row < data.length; row++) {
35
- let standardizedRow = {}
35
+ let standardizedRow = {};
36
36
  data[row].forEach((datum, col) => {
37
- standardizedRow[data[0][col]] = datum
37
+ standardizedRow[data[0][col]] = datum;
38
38
  })
39
- standardizedData.push(standardizedRow)
39
+ standardizedData.push(standardizedRow);
40
40
  }
41
- data = standardizedData
41
+ data = standardizedData;
42
42
  }
43
43
 
44
- return data
44
+ return data;
45
45
  }
46
46
 
47
47
  //Performs standardizations based on developer provided description of the data
48
48
  developerStandardize(data, description) {
49
49
  //Validate the description object
50
50
  if (!description) {
51
- return undefined
51
+ return undefined;
52
52
  }
53
53
 
54
54
  if (description.horizontal === undefined || description.series === undefined) {
55
- return undefined
55
+ return undefined;
56
56
  }
57
57
 
58
58
  if (description.series === true && description.horizontal === false && description.singleRow === undefined) {
59
- return undefined
59
+ return undefined;
60
60
  }
61
61
 
62
62
  if (description.horizontal === true) {
63
63
  if (description.series === true) {
64
64
  if (!description.seriesKey) {
65
- return undefined
65
+ return undefined;
66
66
  }
67
67
 
68
- let standardizedMapped = {}
69
- let standardized = []
68
+ let standardizedMapped = {};
69
+ let standardized = [];
70
70
  data.forEach(row => {
71
- let nonNumericKeys = []
71
+ let nonNumericKeys = [];
72
72
  Object.keys(row).forEach(key => {
73
73
  if (key !== description.seriesKey && isNaN(parseFloat(row[key]))) {
74
- nonNumericKeys.push(key)
74
+ nonNumericKeys.push(key);
75
75
  }
76
76
  })
77
77
 
78
78
  Object.keys(row).forEach(key => {
79
79
  if (key !== description.seriesKey && nonNumericKeys.indexOf(key) === -1) {
80
- let uniqueKey = key + '|' + nonNumericKeys.map(nonNumericKey => nonNumericKey + '=' + row[nonNumericKey])
80
+ let uniqueKey = key + '|' + nonNumericKeys.map(nonNumericKey => nonNumericKey + '=' + row[nonNumericKey]);
81
81
  if (!standardizedMapped[uniqueKey]) {
82
- standardizedMapped[uniqueKey] = { [row[description.seriesKey]]: row[key], key }
82
+ standardizedMapped[uniqueKey] = { [row[description.seriesKey]]: row[key], key };
83
83
  nonNumericKeys.forEach(nonNumericKey => {
84
- standardizedMapped[uniqueKey][nonNumericKey] = row[nonNumericKey]
84
+ standardizedMapped[uniqueKey][nonNumericKey] = row[nonNumericKey];
85
85
  })
86
86
  }
87
- standardizedMapped[uniqueKey][row[description.seriesKey]] = row[key]
87
+ standardizedMapped[uniqueKey][row[description.seriesKey]] = row[key];
88
88
  }
89
89
  })
90
90
  })
91
91
 
92
92
  Object.keys(standardizedMapped).forEach(key => {
93
- standardized.push(standardizedMapped[key])
93
+ standardized.push(standardizedMapped[key]);
94
94
  })
95
95
 
96
- return standardized
96
+ return standardized;
97
97
  } else {
98
- let standardized = []
98
+ let standardized = [];
99
99
 
100
100
  data.forEach(row => {
101
- let nonNumericKeys = []
101
+ let nonNumericKeys = [];
102
102
  Object.keys(row).forEach(key => {
103
103
  if (isNaN(parseFloat(row[key]))) {
104
- nonNumericKeys.push(key)
104
+ nonNumericKeys.push(key);
105
105
  }
106
106
  })
107
107
 
108
108
  Object.keys(row).forEach(key => {
109
109
  if (nonNumericKeys.indexOf(key) === -1) {
110
- let newRow = { key, value: row[key] }
110
+ let newRow = { key, value: row[key] };
111
111
 
112
112
  nonNumericKeys.forEach(nonNumericKey => {
113
- newRow[nonNumericKey] = row[nonNumericKey]
113
+ newRow[nonNumericKey] = row[nonNumericKey];
114
114
  })
115
115
 
116
- standardized.push(newRow)
116
+ standardized.push(newRow);
117
117
  }
118
118
  })
119
119
  })
@@ -121,41 +121,80 @@ export class DataTransform {
121
121
  return standardized
122
122
  }
123
123
  } else if (description.series === true && description.singleRow === false) {
124
- if (description.seriesKey !== undefined && description.xKey !== undefined && description.valueKey !== undefined) {
125
- let standardizedMapped = {}
126
- let standardized = []
124
+ if (description.seriesKey !== undefined && description.xKey !== undefined && (description.valueKey !== undefined || (description.valueKeys !== undefined && description.valueKeys.length > 0))) {
125
+ if(description.valueKeys !== undefined){
126
+ let standardizedMapped = {};
127
+ let standardized = [];
128
+ let valueKeys = description.valueKeys;
129
+ if(description.ignoredKeys && description.ignoredKeys.length > 0){
130
+ valueKeys = valueKeys.concat(description.ignoredKeys);
131
+ }
127
132
 
128
- data.forEach(row => {
129
- let extraKeys = []
130
- let uniqueKey = row[description.xKey]
131
- Object.keys(row).forEach(key => {
132
- if (key !== description.xKey && key !== description.seriesKey && key !== description.valueKey) {
133
- uniqueKey += '|' + key + '=' + row[key]
134
- extraKeys.push(key)
133
+ data.forEach(row => {
134
+ valueKeys.forEach(valueKey => {
135
+ let extraKeys = [];
136
+ let uniqueKey = row[description.xKey] + '|' + valueKey;
137
+ Object.keys(row).forEach(key => {
138
+ if (key !== description.xKey && key !== description.seriesKey && valueKeys.indexOf(key) === -1) {
139
+ uniqueKey += '|' + key + '=' + row[key];
140
+ extraKeys.push(key);
141
+ }
142
+ })
143
+
144
+ if(!standardizedMapped[uniqueKey]){
145
+ standardizedMapped[uniqueKey] = { [description.xKey]: row[description.xKey], '**Numeric Value Property**': valueKey };
146
+ extraKeys.forEach(key => {
147
+ standardizedMapped[uniqueKey][key] = row[key];
148
+ })
149
+ }
150
+
151
+ standardizedMapped[uniqueKey][row[description.seriesKey]] = row[valueKey];
152
+ });
153
+ })
154
+
155
+ Object.keys(standardizedMapped).forEach(key => {
156
+ if(!description.ignoredKeys || description.ignoredKeys.indexOf(standardizedMapped[key]['**Numeric Value Property**']) === -1){
157
+ standardized.push(standardizedMapped[key]);
135
158
  }
136
159
  })
137
160
 
138
- if (standardizedMapped[uniqueKey]) {
139
- standardizedMapped[uniqueKey][row[description.seriesKey]] = row[description.valueKey]
140
- } else {
141
- standardizedMapped[uniqueKey] = { [description.xKey]: row[description.xKey], [row[description.seriesKey]]: row[description.valueKey] }
142
- extraKeys.forEach(key => {
143
- standardizedMapped[uniqueKey][key] = row[key]
161
+ return standardized;
162
+ } else {
163
+ let standardizedMapped = {}
164
+ let standardized = []
165
+
166
+ data.forEach(row => {
167
+ let extraKeys = []
168
+ let uniqueKey = row[description.xKey]
169
+ Object.keys(row).forEach(key => {
170
+ if (key !== description.xKey && key !== description.seriesKey && key !== description.valueKey) {
171
+ uniqueKey += '|' + key + '=' + row[key]
172
+ extraKeys.push(key)
173
+ }
144
174
  })
145
- }
146
- })
147
175
 
148
- Object.keys(standardizedMapped).forEach(key => {
149
- standardized.push(standardizedMapped[key])
150
- })
176
+ if (standardizedMapped[uniqueKey]) {
177
+ standardizedMapped[uniqueKey][row[description.seriesKey]] = row[description.valueKey]
178
+ } else {
179
+ standardizedMapped[uniqueKey] = { [description.xKey]: row[description.xKey], [row[description.seriesKey]]: row[description.valueKey] }
180
+ extraKeys.forEach(key => {
181
+ standardizedMapped[uniqueKey][key] = row[key]
182
+ })
183
+ }
184
+ })
151
185
 
152
- return standardized
186
+ Object.keys(standardizedMapped).forEach(key => {
187
+ standardized.push(standardizedMapped[key])
188
+ })
189
+
190
+ return standardized
191
+ }
153
192
  } else {
154
- return undefined
193
+ return undefined;
155
194
  }
156
195
  }
157
196
 
158
- return data
197
+ return data;
159
198
  }
160
199
  }
161
200
 
@@ -0,0 +1,50 @@
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
+ }
@@ -2,9 +2,6 @@ import Papa from 'papaparse'
2
2
 
3
3
  export default async function (url, visualizationType = '') {
4
4
  try {
5
- // Using URL Object to get pathname without URL paramaters on regex.
6
- if (visualizationType === 'map') url = decodeURI(url)
7
-
8
5
  url = new URL(url)
9
6
 
10
7
  const path = url.pathname
@@ -0,0 +1,24 @@
1
+ /**
2
+ * isNumber
3
+ *
4
+ * There are many ways this could be written but this one is working
5
+ * to check if something is a number or not
6
+ * Input: value
7
+ *
8
+ * Output: boolean: true or false
9
+ */
10
+ export default function isNumber(value = '') {
11
+ // if you need to check data to see if there is junk in there that can't be handled
12
+ // you can run the points through this and see values on the console
13
+ // in debugging I saw cases where inbound was a 'number'
14
+ // and other times a 'string' so might as well take care of both here
15
+ if (typeof value === 'number') {
16
+ return !Number.isNaN(value)
17
+ }
18
+ if (typeof value === 'string') {
19
+ return value !== null && value !== '' && /\d+\.?\d*/.test(value)
20
+ // regex explanation: if 1 or more digits, and a decimal followed by 0 or more digits,
21
+ // then its a number
22
+ }
23
+ return false // if we get here something is wrong so return false
24
+ }
@@ -0,0 +1,18 @@
1
+ export default function isNumberLog(value = '', state = null) {
2
+ // if you need to check data to see if there is junk in there that can't be handled
3
+ // you can run the points through this and see values on the console
4
+ console.log("entering isNumber valuetype is:",typeof value);
5
+ var test;
6
+ if (typeof value === 'number') {
7
+ test = !Number.isNaN(value)
8
+ }
9
+ if (typeof value === 'string') {
10
+ test = value !== null && value !== '' && /\d+\.?\d*/.test(value)
11
+ }
12
+ if (test === false) {
13
+ console.log('# isNumber FALSE on value, result', value, test)
14
+ } else {
15
+ console.log('# isNumber TRUE on value, result', value, test)
16
+ }
17
+ return test
18
+ }
package/package.json CHANGED
@@ -1,36 +1,34 @@
1
1
  {
2
2
  "name": "@cdc/core",
3
- "version": "4.23.1",
4
- "description": "Core elements of the CDC Open Visualization project",
5
- "author": "Daniel Immke <npm@daniel.do>",
6
- "homepage": "https://github.com/CDCgov/cdc-open-viz#readme",
7
- "license": "ISC",
3
+ "version": "4.23.2",
4
+ "description": "Core components, styles, hooks, and helpers, for the CDC Open Visualization project",
5
+ "moduleName": "CdcCore",
6
+ "main": "dist/cdccore",
7
+ "scripts": {
8
+ "test": "echo \"Error: run tests from root\" && exit 1"
9
+ },
8
10
  "repository": {
9
11
  "type": "git",
10
12
  "url": "git+https://github.com/CDCgov/cdc-open-viz.git"
11
13
  },
12
- "scripts": {
13
- "test": "echo \"Error: run tests from root\" && exit 1"
14
- },
14
+ "author": "Rob Shelnutt <rob@blackairplane.com>",
15
+ "homepage": "https://github.com/CDCgov/cdc-open-viz#readme",
15
16
  "bugs": {
16
17
  "url": "https://github.com/CDCgov/cdc-open-viz/issues"
17
18
  },
18
- "peerDependencies": {
19
- "react": "^17.0.2",
20
- "react-dom": ">=16"
21
- },
19
+ "license": "Apache-2.0",
22
20
  "dependencies": {
23
21
  "html2canvas": "^1.4.1",
24
- "html2pdf.js": "^0.10.1",
25
22
  "papaparse": "^5.3.0",
26
23
  "prop-types": "^15.8.1",
27
- "react-accessible-accordion": "^3.3.4",
24
+ "react-accessible-accordion": "^5.0.0",
28
25
  "react-select": "^5.3.1",
29
- "react-tooltip": "4.2.8",
26
+ "react-tooltip": "5.8.2-beta.3",
30
27
  "use-debounce": "^6.0.1"
31
28
  },
32
- "resolutions": {
33
- "@types/react": "17.x"
29
+ "peerDependencies": {
30
+ "react": "^18.2.0",
31
+ "react-dom": "^18.2.0"
34
32
  },
35
- "gitHead": "58163844cc9ce2c379235413b19f49679eab4bef"
33
+ "gitHead": "cd4216f47b1c41bfbc1de3b704f70c52cc7293c2"
36
34
  }
@@ -9,26 +9,25 @@
9
9
  }
10
10
 
11
11
  div.data-table-heading {
12
+ position: relative;
12
13
  background: rgba(0, 0, 0, 0.05);
13
14
  padding: 0.5em 0.7em;
14
15
  border: $lightGray 1px solid;
15
16
  border-bottom: 0;
16
17
  cursor: pointer;
17
- background-image: url(~@cdc/core/assets/icon-minus.svg);
18
- background-size: 15px 15px; // Need to define both for IE11
19
- background-position: right 0.7em center;
20
- background-repeat: no-repeat;
18
+
19
+ svg {
20
+ position: absolute;
21
+ height: 100%;
22
+ width: 15px;
23
+ top: 0;
24
+ right: 1em;
25
+ }
26
+
21
27
  &:focus {
22
28
  z-index: 2;
23
29
  position: relative;
24
30
  }
25
- &.collapsed {
26
- background-image: url(~@cdc/core/assets/icon-plus.svg);
27
- background-size: 15px 15px; // Need to define both for IE11
28
- background-position: right 0.7em center;
29
- background-repeat: no-repeat;
30
- border-bottom: $lightGray 1px solid;
31
- }
32
31
  }
33
32
 
34
33
  table.data-table {
@@ -96,12 +95,12 @@ table.data-table {
96
95
 
97
96
  th.sort-asc,
98
97
  td.sort-asc {
99
- background-image: url(~@cdc/core/assets/icon-caret-filled-up.svg);
98
+ background-image: url(../assets/icon-caret-filled-up.svg);
100
99
  }
101
100
 
102
101
  th.sort-desc,
103
102
  td.sort-desc {
104
- background-image: url(~@cdc/core/assets/icon-caret-filled-down.svg);
103
+ background-image: url(../assets/icon-caret-filled-down.svg);
105
104
  }
106
105
 
107
106
  th:last-child,
@@ -218,7 +217,7 @@ table.data-table {
218
217
  button.btn-next {
219
218
  &::before {
220
219
  content: ' ';
221
- background-image: url(~@cdc/core/assets/icon-caret-filled-up.svg);
220
+ background-image: url(../assets/icon-caret-filled-up.svg);
222
221
  background-size: 10px 5px;
223
222
  width: 10px;
224
223
  height: 5px;
@@ -229,7 +228,7 @@ table.data-table {
229
228
  button.btn-prev {
230
229
  &::before {
231
230
  content: ' ';
232
- background-image: url(~@cdc/core/assets/icon-caret-filled-up.svg);
231
+ background-image: url(../assets/icon-caret-filled-up.svg);
233
232
  background-size: 10px 5px;
234
233
  width: 10px;
235
234
  height: 5px;
@@ -33,7 +33,7 @@
33
33
  }
34
34
 
35
35
  &:disabled {
36
- background-color: #adadad !important;
36
+ background-color: #959595 !important;
37
37
  cursor: not-allowed;
38
38
  }
39
39
 
@@ -0,0 +1,154 @@
1
+ $cove-tooltip-bg: #fff;
2
+ $cove-tooltip-color: #333;
3
+ $cove-tooltip-animation: 500ms cubic-bezier(0.16, 1, 0.3, 1) 50ms 1 forwards;
4
+
5
+ .cove-tooltip {
6
+ display: inline-block;
7
+ position: relative;
8
+ line-height: 1em;
9
+
10
+ &.cove-tooltip--border {
11
+ border: 1px solid #bdbdbd;
12
+ }
13
+
14
+ @at-root {
15
+ .cove-label + .cove-tooltip {
16
+ top: 1px;
17
+ margin-left: 0.5rem;
18
+ font-size: 0.75rem;
19
+ }
20
+
21
+ .cove-accordion__button .cove-tooltip {
22
+ display: inline-flex;
23
+ right: 1.5rem;
24
+ line-height: inherit;
25
+ }
26
+
27
+ .cove-list-group__item .cove-tooltip {
28
+ width: 100%;
29
+ display: block;
30
+ line-height: inherit;
31
+ }
32
+ }
33
+ }
34
+
35
+ .cove-tooltip--target {
36
+ display: inherit;
37
+ cursor: pointer;
38
+
39
+ @at-root {
40
+ .cove-accordion__button + .cove-tooltip--target {
41
+ display: inline-flex;
42
+ line-height: inherit;
43
+ }
44
+ }
45
+ }
46
+
47
+ .cove-tooltip__content {
48
+ max-width: 280px;
49
+ padding: 10px 8px;
50
+ font-size: 0.875rem;
51
+ line-height: 1.125rem;
52
+ text-align: left;
53
+ color: $cove-tooltip-color;
54
+ background-color: $cove-tooltip-bg;
55
+ border-radius: 5px;
56
+ user-select: none;
57
+ opacity: 0;
58
+ cursor: default;
59
+ z-index: 1;
60
+
61
+ &.place-top {
62
+ &.has-shadow {
63
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15), 0 4px 8px rgba(0, 0, 0, 0.1);
64
+ }
65
+
66
+ &.cove-tooltip__content--animated[class*='styles-module_show__'] {
67
+ animation: tooltip-btt $cove-tooltip-animation;
68
+ }
69
+ }
70
+
71
+ &.place-right {
72
+ &.has-shadow {
73
+ box-shadow: -4px 4px 14px rgba(0, 0, 0, 0.15), -4px 4px 8px rgba(0, 0, 0, 0.1);
74
+ }
75
+
76
+ &.cove-tooltip__content--animated[class*='styles-module_show__'] {
77
+ animation: tooltip-ltr $cove-tooltip-animation;
78
+ }
79
+ }
80
+
81
+ &.place-bottom {
82
+ &.has-shadow {
83
+ box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.15), 0 8px 8px rgba(0, 0, 0, 0.1);
84
+ }
85
+
86
+ &.cove-tooltip__content--animated[class*='styles-module_show__'] {
87
+ animation: tooltip-ttb $cove-tooltip-animation;
88
+ }
89
+ }
90
+
91
+ &.place-left {
92
+ &.has-shadow {
93
+ box-shadow: 4px 4px 14px rgba(0, 0, 0, 0.15), 4px 4px 8px rgba(0, 0, 0, 0.1);
94
+ }
95
+
96
+ &.cove-tooltip__content--animated[class*='styles-module_show__'] {
97
+ animation: tooltip-rtl $cove-tooltip-animation;
98
+ }
99
+ }
100
+ }
101
+
102
+ .interactive {
103
+ a {
104
+ pointer-events: all;
105
+ }
106
+ }
107
+
108
+ @keyframes tooltip-ltr {
109
+ 0% {
110
+ opacity: 0;
111
+ transform: translateX(-8px);
112
+ }
113
+
114
+ 100% {
115
+ opacity: 1;
116
+ transform: translateX(0);
117
+ }
118
+ }
119
+
120
+ @keyframes tooltip-rtl {
121
+ 0% {
122
+ opacity: 0;
123
+ transform: translateX(8px);
124
+ }
125
+
126
+ 100% {
127
+ opacity: 1;
128
+ transform: translateX(0);
129
+ }
130
+ }
131
+
132
+ @keyframes tooltip-ttb {
133
+ 0% {
134
+ opacity: 0;
135
+ transform: translateY(-8px);
136
+ }
137
+
138
+ 100% {
139
+ opacity: 1;
140
+ transform: translateY(0);
141
+ }
142
+ }
143
+
144
+ @keyframes tooltip-btt {
145
+ 0% {
146
+ opacity: 0;
147
+ transform: translateY(8px);
148
+ }
149
+
150
+ 100% {
151
+ opacity: 1;
152
+ transform: translateY(0);
153
+ }
154
+ }