@cdc/core 4.23.7 → 4.23.9
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/icon-chart-forecast.svg +1 -0
- package/assets/icon-chart-forest-plot.svg +1 -0
- package/assets/icon-chart-stacked-area.svg +1 -0
- package/components/DataTable.jsx +50 -14
- package/components/Legend/index.tsx +39 -0
- package/components/managers/DataDesigner.jsx +165 -29
- package/helpers/DataTransform.js +17 -0
- package/helpers/cove/number.js +30 -24
- package/helpers/useDataVizClasses.js +29 -2
- package/package.json +1 -1
- package/styles/_data-table.scss +14 -0
- package/styles/v2/layout/_data-table.scss +1 -10
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96.31 82.55"><path d="M89.56,65.41H16.36V8.8c0-1.1-.9-2-2-2h-5.48c-1.1,0-2,.9-2,2V72.89c0,1.1,.9,2,2,2H89.56c1.1,0,2-.9,2-2v-5.48c0-1.1-.9-2-2-2Z"/><path d="M54.66,55.34c-17.76,0-33.46-17.8-34.12-18.56l-.98-1.13v-4.55l3.25,3.72c.15,.18,15.47,17.53,31.86,17.53h0c7.31,0,12.37-1.68,17.27-3.3,4.31-1.42,8.77-2.9,14.44-3.05l1.5-.04v3l-1.42,.04c-5.24,.14-9.29,1.48-13.59,2.9-5.13,1.69-10.43,3.45-18.22,3.45h0Z"/><path d="M87.84,59.04c-4.73-1.24-7.41-1.66-13.05-1.93-2.82-.14-5.22,.16-8,.5-2.73,.33-5.81,.71-10.06,.79-13.13,.27-23.77-4.63-37.17-17.31v-1.57c13.19,12.48,24.31,18.13,37.15,17.88,4.2-.08,7.26-.46,9.96-.79,2.83-.34,5.27-.64,8.17-.5,5.73,.28,8.2,.62,13,1.88v1.05Z"/><path d="M57.02,49.28c-19.55,0-30.91-14.65-37.69-23.41v-1.54c6.67,8.61,18.64,23.95,37.69,23.95,5.87,0,14.11-3.23,18.39-6.23,2.43-1.7,3.84-2.77,4.87-3.55,2.24-1.7,2.7-2.05,7.71-4.44l-.03,1.12c-4.92,2.34-4.91,2.47-7.08,4.12-1.04,.79-2.45,1.86-4.9,3.57-4.4,3.08-12.9,6.41-18.96,6.41Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96.31 82.55"><defs><style>.b{stroke-width:3px;}.b,.c,.d{stroke-linecap:round;}.b,.c,.d,.e{stroke:#000;}.c,.d,.e{fill:none;}.d{stroke-dasharray:0 0 0 4.07;}.e{stroke-width:2px;}</style></defs><path d="M14.15,8.39v63.29c0,.87,.89,1.59,1.98,1.59h5.42c1.09,0,1.98-.72,1.98-1.59l-.13-63.29c0-.87-.89-1.59-1.98-1.59h-5.28c-1.09,0-1.98,.72-1.98,1.59Z"/><g><line class="c" x1="53.51" y1="10.09" x2="53.51" y2="10.09"/><line class="d" x1="53.51" y1="14.16" x2="53.51" y2="69.05"/><line class="c" x1="53.51" y1="71.08" x2="53.51" y2="71.08"/></g><g><path class="b" d="M29.78,16.97h34.43"/><path class="b" d="M29.78,21.48V12.45"/><path class="b" d="M65.64,21.48V12.45"/></g><g><path class="b" d="M50.62,33.5h28.85"/><path class="b" d="M50.62,38.02v-9.03"/><path class="b" d="M80.67,38.02v-9.03"/></g><g><path class="b" d="M28.21,50.03h19.26"/><path class="b" d="M28.21,54.55v-9.03"/><path class="b" d="M48.27,54.55v-9.03"/></g><g><path class="b" d="M40.69,66.57h33.99"/><path class="b" d="M40.69,71.08v-9.03"/><path class="b" d="M76.1,71.08v-9.03"/></g><circle class="e" cx="47.47" cy="16.97" r="2.2"/><circle class="e" cx="65.64" cy="33.5" r="2.2"/><circle class="e" cx="38.49" cy="50.03" r="2.2"/><circle class="e" cx="58.31" cy="66.57" r="2.2"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96.31 82.55"><defs><style>.b{fill:none;}.b,.c{stroke:#000;stroke-linejoin:round;}</style></defs><path d="M89.93,65.41H16.73V8.8c0-1.1-.9-2-2-2h-5.48c-1.1,0-2,.9-2,2V72.89c0,1.1,.9,2,2,2H89.93c1.1,0,2-.9,2-2v-5.48c0-1.1-.9-2-2-2Z"/><polygon points="22.7 54.03 22.7 59.31 89.66 59.31 89.66 37.5 70.66 47.11 52.54 42.02 43.1 48.03 35.85 44.59 22.7 54.03"/><polygon class="b" points="22.7 47.3 22.7 59.31 89.66 59.31 89.66 25.48 70.66 35.35 52.54 35.3 43.1 40.03 36.46 31.3 22.7 47.3"/><polygon class="c" points="70.66 23.24 52.54 15.09 43.1 21.1 35.85 25.48 22.7 27.11 22.7 47.3 36.46 31.3 43.1 40.03 52.54 35.3 70.66 35.35 89.66 25.48 89.66 10.58 70.66 23.24"/></svg>
|
package/components/DataTable.jsx
CHANGED
|
@@ -37,6 +37,25 @@ const DataTable = props => {
|
|
|
37
37
|
// Catch all sorting method used on load by default but also on user click
|
|
38
38
|
// Having a custom method means we can add in any business logic we want going forward
|
|
39
39
|
const customSort = (a, b) => {
|
|
40
|
+
const isDateA = Date.parse(a)
|
|
41
|
+
const isDateB = Date.parse(b)
|
|
42
|
+
|
|
43
|
+
const isNumberA = !isNaN(a)
|
|
44
|
+
const isNumberB = !isNaN(b)
|
|
45
|
+
|
|
46
|
+
if (isDateA && isDateB) {
|
|
47
|
+
return sortBy.asc ? new Date(a) - new Date(b) : new Date(b) - new Date(a)
|
|
48
|
+
}
|
|
49
|
+
if (isNumberA && isNumberB) {
|
|
50
|
+
return sortBy.asc ? Number(a) - Number(b) : Number(b) - Number(a)
|
|
51
|
+
}
|
|
52
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
53
|
+
return sortBy.asc ? a.localeCompare(b) : b.localeCompare(a)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return 0
|
|
57
|
+
}
|
|
58
|
+
const customSortX = (a, b) => {
|
|
40
59
|
const digitRegex = /\d+/
|
|
41
60
|
|
|
42
61
|
const hasNumber = value => digitRegex.test(value)
|
|
@@ -45,6 +64,11 @@ const DataTable = props => {
|
|
|
45
64
|
a = a === null || a === undefined ? '' : a
|
|
46
65
|
b = b === null || b === undefined ? '' : b
|
|
47
66
|
|
|
67
|
+
// check for dates first
|
|
68
|
+
if (!isNaN(Date.parse(a)) && !isNaN(Date.parse(b))) {
|
|
69
|
+
return Date.parse(a) - Date.parse(b)
|
|
70
|
+
}
|
|
71
|
+
|
|
48
72
|
// convert any strings that are actually numbers to proper data type
|
|
49
73
|
const aNum = Number(a)
|
|
50
74
|
|
|
@@ -145,15 +169,13 @@ const DataTable = props => {
|
|
|
145
169
|
const DownloadButton = memo(() => {
|
|
146
170
|
if (rawData !== undefined) {
|
|
147
171
|
let csvData
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
csvData = Papa.unparse(rawData)
|
|
151
|
-
} else if ((config.general.geoType !== 'single-state' && config.general.geoType !== 'us-county') || config.general.type === 'us-geocode') {
|
|
152
|
-
// Unparse + Add column for full Geo name
|
|
153
|
-
csvData = Papa.unparse(rawData.map(row => ({ FullGeoName: displayGeoName(row[config.columns.geo.name]), ...row })))
|
|
154
|
-
} else {
|
|
172
|
+
// only use fullGeoName on County maps and no other
|
|
173
|
+
if (config.general.geoType === 'us-county') {
|
|
155
174
|
// Unparse + Add column for full Geo name along with State
|
|
156
175
|
csvData = Papa.unparse(rawData.map(row => ({ FullGeoName: formatLegendLocation(row[config.columns.geo.name]), ...row })))
|
|
176
|
+
} else {
|
|
177
|
+
// Just Unparse
|
|
178
|
+
csvData = Papa.unparse(rawData)
|
|
157
179
|
}
|
|
158
180
|
|
|
159
181
|
const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' })
|
|
@@ -204,17 +226,18 @@ const DataTable = props => {
|
|
|
204
226
|
}
|
|
205
227
|
|
|
206
228
|
const rows = Object.keys(runtimeData).sort((a, b) => {
|
|
207
|
-
let sortVal
|
|
229
|
+
let sortVal = 0
|
|
208
230
|
if (config.type === 'map' && config.columns) {
|
|
209
231
|
sortVal = customSort(runtimeData[a][config.columns[sortBy.column].name], runtimeData[b][config.columns[sortBy.column].name])
|
|
210
232
|
}
|
|
211
233
|
if (config.type === 'chart') {
|
|
212
234
|
sortVal = customSort(runtimeData[a][sortBy.column], runtimeData[b][sortBy.column])
|
|
213
235
|
}
|
|
214
|
-
|
|
215
|
-
if (
|
|
216
|
-
if (sortVal
|
|
217
|
-
return
|
|
236
|
+
return sortVal
|
|
237
|
+
// if (!sortBy.asc) return sortVal
|
|
238
|
+
// if (sortVal === 0) return 0
|
|
239
|
+
// if (sortVal < 0) return 1
|
|
240
|
+
// return -1
|
|
218
241
|
})
|
|
219
242
|
|
|
220
243
|
const genMapRows = rows => {
|
|
@@ -341,6 +364,7 @@ const DataTable = props => {
|
|
|
341
364
|
{...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
|
|
342
365
|
>
|
|
343
366
|
{text}
|
|
367
|
+
{sortBy.column === column && <span className={'sort-icon'}>{!sortBy.asc ? upIcon : downIcon}</span>}
|
|
344
368
|
<button>
|
|
345
369
|
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
|
|
346
370
|
</button>
|
|
@@ -395,9 +419,9 @@ const DataTable = props => {
|
|
|
395
419
|
|
|
396
420
|
let addColParams = isAdditionalColumn(column)
|
|
397
421
|
if (addColParams) {
|
|
398
|
-
cellValue = formatNumber(runtimeData[row][column], resolvedAxis,
|
|
422
|
+
cellValue = formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams)
|
|
399
423
|
} else {
|
|
400
|
-
cellValue = formatNumber(runtimeData[row][column], resolvedAxis,
|
|
424
|
+
cellValue = formatNumber(runtimeData[row][column], resolvedAxis, false, config)
|
|
401
425
|
}
|
|
402
426
|
}
|
|
403
427
|
|
|
@@ -413,6 +437,17 @@ const DataTable = props => {
|
|
|
413
437
|
return allRows
|
|
414
438
|
}
|
|
415
439
|
|
|
440
|
+
const upIcon = (
|
|
441
|
+
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'>
|
|
442
|
+
<path d='M0 5l5-5 5 5z' />
|
|
443
|
+
</svg>
|
|
444
|
+
)
|
|
445
|
+
const downIcon = (
|
|
446
|
+
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'>
|
|
447
|
+
<path d='M0 0l5 5 5-5z' />
|
|
448
|
+
</svg>
|
|
449
|
+
)
|
|
450
|
+
|
|
416
451
|
const limitHeight = {
|
|
417
452
|
maxHeight: config.table.limitHeight && `${config.table.height}px`,
|
|
418
453
|
overflowY: 'scroll'
|
|
@@ -472,6 +507,7 @@ const DataTable = props => {
|
|
|
472
507
|
{...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
|
|
473
508
|
>
|
|
474
509
|
{text}
|
|
510
|
+
{sortBy.column === column && <span className={'sort-icon'}>{!sortBy.asc ? upIcon : downIcon}</span>}
|
|
475
511
|
<button>
|
|
476
512
|
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
|
|
477
513
|
</button>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import useDataVizClasses from '../../helpers/useDataVizClasses'
|
|
2
|
+
|
|
3
|
+
const Legend = props => {
|
|
4
|
+
const { config } = props
|
|
5
|
+
|
|
6
|
+
if (!config) return
|
|
7
|
+
const { legendClasses } = useDataVizClasses(config)
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<aside id='legend' className={legendClasses.aside.join(' ')} tabIndex={0} aria-label='legend' role='region'>
|
|
11
|
+
<section className={legendClasses.section.join(' ') || ''}>
|
|
12
|
+
<Legend.ResetButton />
|
|
13
|
+
</section>
|
|
14
|
+
</aside>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ResetButton = props => {
|
|
19
|
+
const { config, resetLegendToggles, setAccessibleStatus } = props
|
|
20
|
+
const { legendClasses } = useDataVizClasses(config)
|
|
21
|
+
|
|
22
|
+
const handleReset = e => {
|
|
23
|
+
e.preventDefault()
|
|
24
|
+
resetLegendToggles()
|
|
25
|
+
setAccessibleStatus('Legend has been reset, please reference the data table to see updated values.')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (config.runtime.disabledAmt === 0) return <></>
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<button onClick={handleReset} className={legendClasses.resetButton.join(' ') || ''}>
|
|
32
|
+
Reset
|
|
33
|
+
</button>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Legend.ResetButton = ResetButton
|
|
38
|
+
|
|
39
|
+
export default Legend
|
|
@@ -7,8 +7,7 @@ import { DATA_TABLE_VERTICAL, DATA_TABLE_HORIZONTAL, DATA_TABLE_SINGLE_ROW, DATA
|
|
|
7
7
|
import '../../styles/v2/components/data-designer.scss'
|
|
8
8
|
|
|
9
9
|
const DataDesigner = props => {
|
|
10
|
-
const { configureData, updateDescriptionProp, visualizationKey, dataKey } = props
|
|
11
|
-
|
|
10
|
+
const { configureData, updateDescriptionProp, visualizationKey, dataKey, config, setConfig } = props
|
|
12
11
|
|
|
13
12
|
return (
|
|
14
13
|
<div className='cove-data-designer__container'>
|
|
@@ -166,66 +165,203 @@ const DataDesigner = props => {
|
|
|
166
165
|
</select>
|
|
167
166
|
</div>
|
|
168
167
|
<div className='mb-2'>
|
|
169
|
-
<div className='mb-1'>Which properties in the dataset represent the numeric value?
|
|
168
|
+
<div className='mb-1'>Which properties in the dataset represent the numeric value? (all remaining properties will be treated as filters)</div>
|
|
170
169
|
{configureData.dataDescription.valueKeys && configureData.dataDescription.valueKeys.length > 0 && (
|
|
171
|
-
<ul className=
|
|
170
|
+
<ul className='value-list'>
|
|
172
171
|
{configureData.dataDescription.valueKeys.map((valueKey, index) => (
|
|
173
|
-
<li key={`value-keys-list-${index}`}>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
172
|
+
<li key={`value-keys-list-${index}`}>
|
|
173
|
+
{valueKey}
|
|
174
|
+
<button
|
|
175
|
+
onClick={() => {
|
|
176
|
+
let newValueKeys = configureData.dataDescription.valueKeys
|
|
177
|
+
newValueKeys.splice(index, 1)
|
|
178
|
+
updateDescriptionProp(visualizationKey, dataKey, 'valueKeys', newValueKeys)
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
X
|
|
182
|
+
</button>
|
|
183
|
+
</li>
|
|
178
184
|
))}
|
|
179
185
|
</ul>
|
|
180
186
|
)}
|
|
181
187
|
<select
|
|
182
188
|
onChange={e => {
|
|
183
|
-
if(e.target.value && (!configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(e.target.value) === -1)){
|
|
189
|
+
if (e.target.value && (!configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(e.target.value) === -1)) {
|
|
184
190
|
updateDescriptionProp(visualizationKey, dataKey, 'valueKeys', [...(configureData.dataDescription.valueKeys || []), e.target.value])
|
|
185
191
|
}
|
|
186
192
|
}}
|
|
187
193
|
>
|
|
188
194
|
<option value=''>Choose an option</option>
|
|
189
|
-
{Object.keys(configureData.data[0])
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
195
|
+
{Object.keys(configureData.data[0])
|
|
196
|
+
.filter(value => !configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(value) === -1)
|
|
197
|
+
.map((value, index) => (
|
|
198
|
+
<option value={value} key={`value-keys-option-${index}`}>
|
|
199
|
+
{value}
|
|
200
|
+
</option>
|
|
201
|
+
))}
|
|
194
202
|
</select>
|
|
195
203
|
</div>
|
|
196
204
|
<div className='mb-2'>
|
|
197
|
-
<div className='mb-1'>(Optional) Which properties in the dataset should be ignored?
|
|
205
|
+
<div className='mb-1'>(Optional) Which properties in the dataset should be ignored? (will not be used or treated as filters)</div>
|
|
198
206
|
{configureData.dataDescription.ignoredKeys && configureData.dataDescription.ignoredKeys.length > 0 && (
|
|
199
|
-
<ul className=
|
|
207
|
+
<ul className='value-list'>
|
|
200
208
|
{configureData.dataDescription.ignoredKeys.map((ignoredKey, index) => (
|
|
201
|
-
<li key={`value-keys-list-${index}`}>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
209
|
+
<li key={`value-keys-list-${index}`}>
|
|
210
|
+
{ignoredKey}
|
|
211
|
+
<button
|
|
212
|
+
onClick={() => {
|
|
213
|
+
let newIgnoredKeys = configureData.dataDescription.ignoredKeys
|
|
214
|
+
newIgnoredKeys.splice(index, 1)
|
|
215
|
+
updateDescriptionProp(visualizationKey, dataKey, 'ignoredKeys', newIgnoredKeys)
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
X
|
|
219
|
+
</button>
|
|
220
|
+
</li>
|
|
206
221
|
))}
|
|
207
222
|
</ul>
|
|
208
223
|
)}
|
|
209
224
|
<select
|
|
210
225
|
onChange={e => {
|
|
211
|
-
if(e.target.value){
|
|
226
|
+
if (e.target.value) {
|
|
212
227
|
updateDescriptionProp(visualizationKey, dataKey, 'ignoredKeys', [...(configureData.dataDescription.ignoredKeys || []), e.target.value])
|
|
213
228
|
}
|
|
214
|
-
e.target.value = ''
|
|
229
|
+
e.target.value = ''
|
|
215
230
|
}}
|
|
216
231
|
>
|
|
217
232
|
<option value=''>Choose an option</option>
|
|
218
|
-
{Object.keys(configureData.data[0])
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
233
|
+
{Object.keys(configureData.data[0])
|
|
234
|
+
.filter(value => !configureData.dataDescription.ignoredKeys || configureData.dataDescription.ignoredKeys.indexOf(value) === -1)
|
|
235
|
+
.map((value, index) => (
|
|
236
|
+
<option value={value} key={`ignored-keys-option-${index}`}>
|
|
237
|
+
{value}
|
|
238
|
+
</option>
|
|
239
|
+
))}
|
|
223
240
|
</select>
|
|
224
241
|
</div>
|
|
225
242
|
</>
|
|
226
243
|
)}
|
|
227
244
|
</>
|
|
228
245
|
)}
|
|
246
|
+
|
|
247
|
+
{config?.visualizationType === 'Forest Plot' && (
|
|
248
|
+
<>
|
|
249
|
+
<div className='mb-2'>
|
|
250
|
+
<div className='mb-1'>Which column represents the date/category column?</div>
|
|
251
|
+
<select
|
|
252
|
+
onChange={e => {
|
|
253
|
+
setConfig({
|
|
254
|
+
...config,
|
|
255
|
+
xAxis: {
|
|
256
|
+
...config.xAxis,
|
|
257
|
+
dataKey: e.target.value
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
}}
|
|
261
|
+
defaultValue={'Select'}
|
|
262
|
+
>
|
|
263
|
+
<option value=''>Choose an option</option>
|
|
264
|
+
{Object.keys(configureData.data[0]).map((value, index) => (
|
|
265
|
+
<option value={value} key={index}>
|
|
266
|
+
{value}
|
|
267
|
+
</option>
|
|
268
|
+
))}
|
|
269
|
+
</select>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<div className='mb-2'>
|
|
273
|
+
<div className='mb-1'>Which column represents your estimate field?</div>
|
|
274
|
+
<select
|
|
275
|
+
onChange={e => {
|
|
276
|
+
setConfig({
|
|
277
|
+
...config,
|
|
278
|
+
forestPlot: {
|
|
279
|
+
...config.forestPlot,
|
|
280
|
+
estimateField: e.target.value
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
}}
|
|
284
|
+
defaultValue={'Select'}
|
|
285
|
+
>
|
|
286
|
+
<option value=''>Choose an option</option>
|
|
287
|
+
{Object.keys(configureData.data[0]).map((value, index) => (
|
|
288
|
+
<option value={value} key={index}>
|
|
289
|
+
{value}
|
|
290
|
+
</option>
|
|
291
|
+
))}
|
|
292
|
+
</select>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<div className='mb-2'>
|
|
296
|
+
<div className='mb-1'>Which column represents the low confidence interval?</div>
|
|
297
|
+
<select
|
|
298
|
+
onChange={e => {
|
|
299
|
+
setConfig({
|
|
300
|
+
...config,
|
|
301
|
+
forestPlot: {
|
|
302
|
+
...config.forestPlot,
|
|
303
|
+
lower: e.target.value
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
}}
|
|
307
|
+
defaultValue={'Select'}
|
|
308
|
+
>
|
|
309
|
+
<option value=''>Choose an option</option>
|
|
310
|
+
{Object.keys(configureData.data[0]).map((value, index) => (
|
|
311
|
+
<option value={value} key={index}>
|
|
312
|
+
{value}
|
|
313
|
+
</option>
|
|
314
|
+
))}
|
|
315
|
+
</select>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<div className='mb-2'>
|
|
319
|
+
<div className='mb-1'>Which column represents the high confidence interval?</div>
|
|
320
|
+
<select
|
|
321
|
+
onChange={e => {
|
|
322
|
+
setConfig({
|
|
323
|
+
...config,
|
|
324
|
+
forestPlot: {
|
|
325
|
+
...config.forestPlot,
|
|
326
|
+
upper: e.target.value
|
|
327
|
+
}
|
|
328
|
+
})
|
|
329
|
+
}}
|
|
330
|
+
defaultValue={'Select'}
|
|
331
|
+
>
|
|
332
|
+
<option value=''>Choose an option</option>
|
|
333
|
+
{Object.keys(configureData.data[0]).map((value, index) => (
|
|
334
|
+
<option value={value} key={index}>
|
|
335
|
+
{value}
|
|
336
|
+
</option>
|
|
337
|
+
))}
|
|
338
|
+
</select>
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
<div className='mb-2'>
|
|
342
|
+
<div className='mb-1'>Which shape do you want to use in your forest plot?</div>
|
|
343
|
+
<select
|
|
344
|
+
onChange={e => {
|
|
345
|
+
setConfig({
|
|
346
|
+
...config,
|
|
347
|
+
forestPlot: {
|
|
348
|
+
...config.forestPlot,
|
|
349
|
+
shape: e.target.value
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
}}
|
|
353
|
+
defaultValue={'Select'}
|
|
354
|
+
>
|
|
355
|
+
<option value=''>Choose an option</option>
|
|
356
|
+
{['text', 'circle', 'square', 'diamond'].map((value, index) => (
|
|
357
|
+
<option value={value} key={index}>
|
|
358
|
+
{value}
|
|
359
|
+
</option>
|
|
360
|
+
))}
|
|
361
|
+
</select>
|
|
362
|
+
</div>
|
|
363
|
+
</>
|
|
364
|
+
)}
|
|
229
365
|
</>
|
|
230
366
|
)}
|
|
231
367
|
{configureData.dataDescription && configureData.formattedData && <div>Data configured successfully</div>}
|
package/helpers/DataTransform.js
CHANGED
|
@@ -250,6 +250,23 @@ export class DataTransform {
|
|
|
250
250
|
if (testing) console.log('## cleanedData =', cleanedupData)
|
|
251
251
|
return cleanedupData
|
|
252
252
|
}
|
|
253
|
+
|
|
254
|
+
// clean out %, $, commas from numbers when needing to do sorting!
|
|
255
|
+
cleanDataPoint(data, testing = false) {
|
|
256
|
+
if (testing) console.log('clean', data)
|
|
257
|
+
let cleaned = ''
|
|
258
|
+
|
|
259
|
+
// remove comma and dollar signs
|
|
260
|
+
let tmp = ''
|
|
261
|
+
if (typeof data === 'string') {
|
|
262
|
+
tmp = data!== null && data !== '' ? data.replace(/[,\$\%]/g, '') : ''
|
|
263
|
+
} else {
|
|
264
|
+
tmp = data !== null && data !== '' ? data : ''
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (testing) console.log('## cleanedData =', tmp)
|
|
268
|
+
return tmp
|
|
269
|
+
}
|
|
253
270
|
}
|
|
254
271
|
|
|
255
272
|
export default DataTransform
|
package/helpers/cove/number.js
CHANGED
|
@@ -30,7 +30,7 @@ const formatNumber = (num, axis, shouldAbbreviate = false, config = null, addCol
|
|
|
30
30
|
if (isNegative) {
|
|
31
31
|
num = Math.abs(num)
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
// destructure dataFormat values
|
|
35
35
|
let {
|
|
36
36
|
dataFormat: { commas, abbreviated, roundTo, prefix, suffix, rightRoundTo, bottomRoundTo, rightPrefix, rightSuffix, bottomPrefix, bottomSuffix, bottomAbbreviated }
|
|
@@ -45,27 +45,27 @@ const formatNumber = (num, axis, shouldAbbreviate = false, config = null, addCol
|
|
|
45
45
|
let original = num
|
|
46
46
|
let stringFormattingOptions
|
|
47
47
|
if (axis === 'left') {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
48
|
+
let roundToPlace
|
|
49
|
+
if (addColRoundTo !== undefined) {
|
|
50
|
+
// if its an Additional Column
|
|
51
|
+
roundToPlace = addColRoundTo ? Number(addColRoundTo) : 0
|
|
52
|
+
} else {
|
|
53
|
+
roundToPlace = roundTo ? Number(roundTo) : 0
|
|
54
|
+
}
|
|
55
|
+
// Need to prevent negative values in rounding
|
|
56
|
+
if (roundToPlace < 0) roundToPlace = 0
|
|
57
|
+
let useCommas
|
|
58
|
+
if (addColCommas !== undefined) {
|
|
59
|
+
// if its an Additional Column
|
|
60
|
+
useCommas = addColCommas ? true : false
|
|
61
|
+
} else {
|
|
62
|
+
useCommas = config.dataFormat.commas ? true : false
|
|
63
|
+
}
|
|
64
|
+
stringFormattingOptions = {
|
|
65
|
+
useGrouping: useCommas,
|
|
66
|
+
minimumFractionDigits: roundToPlace,
|
|
67
|
+
maximumFractionDigits: roundToPlace
|
|
68
|
+
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
if (axis === 'right') {
|
|
@@ -124,7 +124,7 @@ const formatNumber = (num, axis, shouldAbbreviate = false, config = null, addCol
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
if (addColPrefix !== undefined && axis === 'left') {
|
|
127
|
-
|
|
127
|
+
result = addColPrefix + result
|
|
128
128
|
} else {
|
|
129
129
|
if (prefix && axis === 'left') {
|
|
130
130
|
result = prefix + result
|
|
@@ -163,4 +163,10 @@ const formatNumber = (num, axis, shouldAbbreviate = false, config = null, addCol
|
|
|
163
163
|
return String(result)
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
const getFontSize = (size = 'medium') => {
|
|
167
|
+
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
168
|
+
|
|
169
|
+
return fontSize[size]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export { formatNumber, getFontSize }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export default function useDataVizClasses(config) {
|
|
1
|
+
export default function useDataVizClasses(config, viewport = null) {
|
|
2
|
+
const { legend } = config
|
|
2
3
|
let lineDatapointClass = ''
|
|
3
4
|
let barBorderClass = ''
|
|
4
5
|
|
|
@@ -36,5 +37,31 @@ export default function useDataVizClasses(config) {
|
|
|
36
37
|
height: '100px'
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
// Starting work on combining legend classes.
|
|
41
|
+
// Using short circuiting to check between charts & maps for now.
|
|
42
|
+
const getListPosition = () => {
|
|
43
|
+
if (legend?.position === 'side' && legend?.singleColumn) return 'legend-container__ul--single-column'
|
|
44
|
+
if (legend?.position === 'bottom' && legend?.singleRow) return 'single-row'
|
|
45
|
+
if (legend?.verticalSorted && !legend?.singleRow) return 'vertical-sorted'
|
|
46
|
+
return ''
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const getUlClasses = () => {
|
|
50
|
+
const ulClasses = ['legend-container__ul']
|
|
51
|
+
ulClasses.push(getListPosition())
|
|
52
|
+
return ulClasses
|
|
53
|
+
}
|
|
54
|
+
const legendOuterClasses = [`${legend?.position}`, `${getListPosition()}`, `cdcdataviz-sr-focusable`, `${viewport}`]
|
|
55
|
+
|
|
56
|
+
const legendClasses = {
|
|
57
|
+
aside: legendOuterClasses,
|
|
58
|
+
section: ['legend-container'],
|
|
59
|
+
ul: getUlClasses(),
|
|
60
|
+
li: ['single-legend-item', 'legend-container__li'],
|
|
61
|
+
title: ['legend-container__title'],
|
|
62
|
+
resetButton: ['legend-container__reset-button', 'btn', 'clear'],
|
|
63
|
+
description: ['legend-container__description']
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { innerContainerClasses, contentClasses, barBorderClass, lineDatapointClass, sparkLineStyles, legendClasses }
|
|
40
67
|
}
|
package/package.json
CHANGED
package/styles/_data-table.scss
CHANGED
|
@@ -94,6 +94,7 @@ table.data-table {
|
|
|
94
94
|
background-size: 10px 5px;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/* doesnt work
|
|
97
98
|
th.sort-asc,
|
|
98
99
|
td.sort-asc {
|
|
99
100
|
background-image: url(../assets/icon-caret-filled-up.svg);
|
|
@@ -103,6 +104,19 @@ table.data-table {
|
|
|
103
104
|
td.sort-desc {
|
|
104
105
|
background-image: url(../assets/icon-caret-filled-down.svg);
|
|
105
106
|
}
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
// format the white triangle sort icon in data table headers
|
|
110
|
+
.sort-icon {
|
|
111
|
+
fill: white;
|
|
112
|
+
width: 30px;
|
|
113
|
+
float: right;
|
|
114
|
+
align-self: center;
|
|
115
|
+
position: absolute;
|
|
116
|
+
right: 10px;
|
|
117
|
+
top: 50%;
|
|
118
|
+
transform: translateY(-50%);
|
|
119
|
+
}
|
|
106
120
|
|
|
107
121
|
th:last-child,
|
|
108
122
|
td:last-child {
|
|
@@ -150,16 +150,6 @@
|
|
|
150
150
|
background-size: 10px 5px;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
.sort-asc {
|
|
154
|
-
//TODO find fix
|
|
155
|
-
//background-image: url(~@cdc/core/assets/icon-caret-filled-up.svg);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.sort-desc {
|
|
159
|
-
//TODO find fix
|
|
160
|
-
//background-image: url(~@cdc/core/assets/icon-caret-filled-down.svg);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
153
|
.resizer {
|
|
164
154
|
cursor: e-resize;
|
|
165
155
|
width: 10px;
|
|
@@ -171,6 +161,7 @@
|
|
|
171
161
|
}
|
|
172
162
|
}
|
|
173
163
|
|
|
164
|
+
|
|
174
165
|
.cove-data-table__footer {
|
|
175
166
|
margin-top: 1rem;
|
|
176
167
|
}
|