@cdc/editor 1.4.4 → 9.22.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/dist/cdceditor.js +70 -52
- package/example/valid-data-map.csv +2 -1
- package/package.json +9 -9
- package/src/CdcEditor.js +67 -6
- package/src/components/ChooseTab.js +37 -37
- package/src/components/DataImport.js +280 -333
- package/src/scss/configure-tab.scss +4 -1
- package/src/scss/data-import.scss +32 -55
- package/src/scss/main.scss +11 -1
- package/src/assets/icons/dashboard.svg +0 -8
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import React, { useState, useContext, useEffect } from 'react'
|
|
2
2
|
import { useDropzone } from 'react-dropzone'
|
|
3
3
|
import { csvParse } from 'd3'
|
|
4
|
-
import { useDebounce } from 'use-debounce'
|
|
5
4
|
import { get } from 'axios'
|
|
6
5
|
|
|
7
|
-
import { DataTransform } from '@cdc/core/
|
|
8
|
-
import Modal from '@cdc/core/components/ui/Modal'
|
|
6
|
+
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
9
7
|
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
10
8
|
|
|
11
9
|
import GlobalState from '../context'
|
|
@@ -22,10 +20,11 @@ import validMapData from '../../example/valid-data-map.csv'
|
|
|
22
20
|
import validChartData from '../../example/valid-data-chart.csv'
|
|
23
21
|
import validCountyMapData from '../../example/valid-county-data.csv'
|
|
24
22
|
|
|
23
|
+
import DataDesigner from '@cdc/core/components/managers/DataDesigner'
|
|
25
24
|
|
|
26
25
|
import '../scss/data-import.scss'
|
|
27
|
-
|
|
28
|
-
import
|
|
26
|
+
|
|
27
|
+
import '@cdc/core/styles/v2/components/data-designer.scss'
|
|
29
28
|
|
|
30
29
|
export default function DataImport() {
|
|
31
30
|
const {
|
|
@@ -47,24 +46,35 @@ export default function DataImport() {
|
|
|
47
46
|
|
|
48
47
|
const [ externalURL, setExternalURL ] = useState(config.dataFileSourceType === 'url' ? config.dataFileName : (config.dataUrl || ''))
|
|
49
48
|
|
|
50
|
-
const [ debouncedExternalURL ] = useDebounce(externalURL, 200)
|
|
51
|
-
|
|
52
49
|
const [ keepURL, setKeepURL ] = useState(!!config.dataUrl)
|
|
53
50
|
|
|
51
|
+
const [ addingDataset, setAddingDataset ] = useState(config.type === 'dashboard' || !config.data);
|
|
52
|
+
|
|
53
|
+
const [ editingDataset, setEditingDataset ] = useState();
|
|
54
|
+
|
|
54
55
|
const supportedDataTypes = {
|
|
55
56
|
'.csv': 'text/csv',
|
|
56
57
|
'.json': 'application/json'
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const displayFileName = (name) => {
|
|
61
|
+
const nameParts = name.split('/');
|
|
62
|
+
return nameParts[nameParts.length - 1];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const displaySize = (size) => {
|
|
66
|
+
if(size === undefined) return '';
|
|
67
|
+
|
|
68
|
+
if(size > Math.pow(1024, 3)){
|
|
69
|
+
return Math.round(size / Math.pow(1024, 3) * 100) / 100 + ' GB';
|
|
70
|
+
} else if(size > Math.pow(1024, 2)){
|
|
71
|
+
return Math.round(size / Math.pow(1024, 2) * 100) / 100 + ' MB';
|
|
72
|
+
} else if(size > 1024){
|
|
73
|
+
return Math.round(size / 1024 * 100) / 100 + ' KB';
|
|
62
74
|
} else {
|
|
63
|
-
|
|
64
|
-
delete newConfig.dataUrl;
|
|
65
|
-
setConfig(newConfig);
|
|
75
|
+
return size + ' B'
|
|
66
76
|
}
|
|
67
|
-
}
|
|
77
|
+
}
|
|
68
78
|
|
|
69
79
|
/**
|
|
70
80
|
* Check to see all series for the viz exists in the new dataset
|
|
@@ -126,12 +136,12 @@ export default function DataImport() {
|
|
|
126
136
|
return responseBlob
|
|
127
137
|
}
|
|
128
138
|
|
|
129
|
-
const onDrop = ([ uploadedFile ]) => loadData(uploadedFile)
|
|
139
|
+
const onDrop = ([ uploadedFile ]) => loadData(uploadedFile, editingDataset, editingDataset)
|
|
130
140
|
|
|
131
141
|
/**
|
|
132
142
|
* Handle loading data
|
|
133
143
|
*/
|
|
134
|
-
const loadData = async (fileBlob = null, fileName) => {
|
|
144
|
+
const loadData = async (fileBlob = null, fileName, editingDatasetKey) => {
|
|
135
145
|
let fileData = fileBlob
|
|
136
146
|
let fileSource = fileData?.path ?? fileName ?? null
|
|
137
147
|
let fileSourceType = 'file'
|
|
@@ -156,8 +166,10 @@ export default function DataImport() {
|
|
|
156
166
|
}
|
|
157
167
|
}
|
|
158
168
|
|
|
169
|
+
let fileSize = fileData.size;
|
|
170
|
+
|
|
159
171
|
// Check if file is too big
|
|
160
|
-
if (
|
|
172
|
+
if (fileSize > (maxFileSize * 1048576)) {
|
|
161
173
|
setErrors([ errorMessages.fileTooLarge ])
|
|
162
174
|
return
|
|
163
175
|
}
|
|
@@ -207,13 +219,35 @@ export default function DataImport() {
|
|
|
207
219
|
|
|
208
220
|
if (config.data && config.series) {
|
|
209
221
|
if (dataExists(text, config.series, config?.xAxis.dataKey)) {
|
|
210
|
-
|
|
211
|
-
...config
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
222
|
+
if(config.type === 'dashboard'){
|
|
223
|
+
let newDatasets = {...config.datasets};
|
|
224
|
+
|
|
225
|
+
Object.keys(newDatasets).forEach(datasetKey => newDatasets[datasetKey].preview = false);
|
|
226
|
+
|
|
227
|
+
newDatasets[editingDatasetKey || fileSource] = {
|
|
228
|
+
data: text, // new data
|
|
229
|
+
dataFileSize: fileSize,
|
|
230
|
+
dataFileName: fileSource, // new file source
|
|
231
|
+
dataFileSourceType: fileSourceType,// new file source type
|
|
232
|
+
dataFileFormat: fileExtension.replace('.', '').toUpperCase(),
|
|
233
|
+
preview: true
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
setConfig({
|
|
237
|
+
...config,
|
|
238
|
+
...tempConfig,
|
|
239
|
+
dataset: newDatasets
|
|
240
|
+
})
|
|
241
|
+
} else {
|
|
242
|
+
setConfig({
|
|
243
|
+
...config,
|
|
244
|
+
...tempConfig,
|
|
245
|
+
data: text, // new data
|
|
246
|
+
dataFileName: fileSource, // new file source
|
|
247
|
+
dataFileSourceType: fileSourceType, // new file source type
|
|
248
|
+
formattedData: transform.developerStandardize(text, config.dataDescription)
|
|
249
|
+
})
|
|
250
|
+
}
|
|
217
251
|
} else {
|
|
218
252
|
resetEditor({
|
|
219
253
|
data: text,
|
|
@@ -222,8 +256,38 @@ export default function DataImport() {
|
|
|
222
256
|
}, 'It appears that your data does not contain all of the columns that your last dataset contained. Continuing will reset your configuration. Do you want to continue?')
|
|
223
257
|
}
|
|
224
258
|
} else {
|
|
225
|
-
|
|
259
|
+
if(config.type === 'dashboard') {
|
|
260
|
+
let newDatasets = {...config.datasets};
|
|
261
|
+
|
|
262
|
+
Object.keys(newDatasets).forEach(datasetKey => newDatasets[datasetKey].preview = false);
|
|
263
|
+
|
|
264
|
+
newDatasets[editingDatasetKey || fileSource] = {
|
|
265
|
+
data: text, // new data
|
|
266
|
+
dataFileSize: fileSize,
|
|
267
|
+
dataFileName: fileSource, // new file source
|
|
268
|
+
dataFileSourceType: fileSourceType,// new file source type
|
|
269
|
+
dataFileFormat: fileExtension.replace('.', '').toUpperCase(),
|
|
270
|
+
preview: true
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
setConfig({ ...config, datasets: newDatasets })
|
|
274
|
+
} else {
|
|
275
|
+
setConfig({
|
|
276
|
+
...config,
|
|
277
|
+
...tempConfig,
|
|
278
|
+
data: text, // new data
|
|
279
|
+
dataFileName: fileSource, // new file source
|
|
280
|
+
dataFileSourceType: fileSourceType, // new file source type
|
|
281
|
+
formattedData: transform.developerStandardize(text, config.dataDescription)// new file source type
|
|
282
|
+
})
|
|
283
|
+
}
|
|
226
284
|
}
|
|
285
|
+
|
|
286
|
+
if(editingDataset){
|
|
287
|
+
setEditingDataset(undefined);
|
|
288
|
+
}
|
|
289
|
+
setAddingDataset(false);
|
|
290
|
+
setExternalURL('');
|
|
227
291
|
} catch (err) {
|
|
228
292
|
setErrors(err)
|
|
229
293
|
}
|
|
@@ -249,16 +313,27 @@ export default function DataImport() {
|
|
|
249
313
|
setConfig(newConfig)
|
|
250
314
|
}, [])
|
|
251
315
|
|
|
252
|
-
const updateDescriptionProp = (key, value) => {
|
|
253
|
-
|
|
254
|
-
|
|
316
|
+
const updateDescriptionProp = (visualizationKey, datasetKey, key, value) => {
|
|
317
|
+
if(config.type === 'dashboard') {
|
|
318
|
+
let dataDescription = { ...config.datasets[datasetKey].dataDescription, [key]: value }
|
|
319
|
+
let formattedData = transform.developerStandardize(config.datasets[datasetKey].data, dataDescription)
|
|
320
|
+
|
|
321
|
+
let newDatasets = {...config.datasets}
|
|
322
|
+
newDatasets[datasetKey] = {...newDatasets[datasetKey], dataDescription, formattedData};
|
|
255
323
|
|
|
256
|
-
|
|
324
|
+
setConfig({ ...config, datasets: newDatasets })
|
|
325
|
+
} else {
|
|
326
|
+
let dataDescription = { ...config.dataDescription, [key]: value }
|
|
327
|
+
let formattedData = transform.developerStandardize(config.data, dataDescription)
|
|
328
|
+
|
|
329
|
+
setConfig({ ...config, formattedData, dataDescription })
|
|
330
|
+
}
|
|
257
331
|
}
|
|
258
332
|
|
|
259
333
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })
|
|
334
|
+
const { getRootProps: getRootProps2, getInputProps: getInputProps2, isDragActive: isDragActive2 } = useDropzone({ onDrop })
|
|
260
335
|
|
|
261
|
-
const loadFileFromUrl = (url) => {
|
|
336
|
+
const loadFileFromUrl = (url, editingDatasetKey) => {
|
|
262
337
|
// const extUrl = (url) ? url : config.dataFileName // set url to what is saved in config unless the user has entered something
|
|
263
338
|
|
|
264
339
|
return (
|
|
@@ -268,7 +343,7 @@ export default function DataImport() {
|
|
|
268
343
|
placeholder="e.g., https://data.cdc.gov/resources/file.json" aria-label="Load data from external URL"
|
|
269
344
|
aria-describedby="load-data" value={externalURL} onChange={(e) => setExternalURL(e.target.value)}/>
|
|
270
345
|
<button className="input-group-text btn btn-primary px-4" type="submit" id="load-data"
|
|
271
|
-
onClick={() => loadData(null, externalURL)}>Load
|
|
346
|
+
onClick={() => loadData(null, externalURL, editingDatasetKey)}>Load
|
|
272
347
|
</button>
|
|
273
348
|
</form>
|
|
274
349
|
<label htmlFor="keep-url" className="mt-1 d-flex keep-url">
|
|
@@ -279,59 +354,178 @@ export default function DataImport() {
|
|
|
279
354
|
)
|
|
280
355
|
}
|
|
281
356
|
|
|
282
|
-
const warningModal = () => {
|
|
283
|
-
return (
|
|
284
|
-
<Modal fontTheme={'light'} headerBgColor={'#d73636'} showClose={false}>
|
|
285
|
-
<Modal.Header>
|
|
286
|
-
<center>Warning</center>
|
|
287
|
-
</Modal.Header>
|
|
288
|
-
<Modal.Content>
|
|
289
|
-
<center>
|
|
290
|
-
<p style={{ fontSize: '1rem' }}>Reseting will remove your data and settings.</p>
|
|
291
|
-
</center>
|
|
292
|
-
</Modal.Content>
|
|
293
|
-
<Modal.Footer>
|
|
294
|
-
<div style={{ textAlign: 'center' }}>
|
|
295
|
-
<p style={{
|
|
296
|
-
marginBottom: '1rem',
|
|
297
|
-
fontSize: '1rem'
|
|
298
|
-
}}>
|
|
299
|
-
Are you sure you want to continue?
|
|
300
|
-
</p>
|
|
301
|
-
<Button className="warn" style={{ marginRight: '1rem' }}
|
|
302
|
-
onClick={() => overlay.actions.toggleOverlay(false)}
|
|
303
|
-
>No, Cancel</Button>
|
|
304
|
-
<Button className="success" onClick={() => {
|
|
305
|
-
resetEditor({})
|
|
306
|
-
overlay.actions.toggleOverlay(false)
|
|
307
|
-
}}>Yes, Continue</Button>
|
|
308
|
-
</div>
|
|
309
|
-
</Modal.Footer>
|
|
310
|
-
</Modal>
|
|
311
|
-
)
|
|
312
|
-
}
|
|
313
|
-
|
|
314
357
|
const resetEditor = (config = {}, message = 'Are you sure you want to do this?') => {
|
|
315
358
|
config.newViz = true
|
|
316
|
-
|
|
317
|
-
|
|
359
|
+
|
|
360
|
+
const confirmDataReset = window.confirm(message)
|
|
361
|
+
|
|
362
|
+
if (confirmDataReset === true) {
|
|
363
|
+
setTempConfig(null)
|
|
364
|
+
setConfig(config)
|
|
365
|
+
setAddingDataset(true)
|
|
366
|
+
}
|
|
318
367
|
}
|
|
319
368
|
|
|
320
369
|
const resetButton = () => {
|
|
321
|
-
return (
|
|
322
|
-
<
|
|
323
|
-
onClick={() =>
|
|
324
|
-
|
|
325
|
-
</
|
|
370
|
+
return ( //todo convert to modal
|
|
371
|
+
<button className="btn danger"
|
|
372
|
+
onClick={() => resetEditor({type: config.type, visualizationType: config.visualizationType}, 'Reseting will remove your data and settings. Do you want to continue?')}>Clear
|
|
373
|
+
<CloseIcon/>
|
|
374
|
+
</button>
|
|
326
375
|
)
|
|
327
376
|
}
|
|
328
377
|
|
|
378
|
+
const setGlobalDatasetProp = (datasetKey, prop, value) => {
|
|
379
|
+
let newDatasets = {...config.datasets};
|
|
380
|
+
|
|
381
|
+
if(value === true){
|
|
382
|
+
Object.keys(newDatasets).forEach(datasetKeyIter => {
|
|
383
|
+
if(datasetKeyIter !== datasetKey){
|
|
384
|
+
newDatasets[datasetKeyIter][prop] = false;
|
|
385
|
+
} else {
|
|
386
|
+
newDatasets[datasetKeyIter][prop] = true;
|
|
387
|
+
}
|
|
388
|
+
})
|
|
389
|
+
} else {
|
|
390
|
+
newDatasets[datasetKey][prop] = value;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
setConfig({...config, datasets: newDatasets});
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const removeDataset = (datasetKey) => {
|
|
397
|
+
let newDatasets = {...config.datasets};
|
|
398
|
+
let newVisualizations = {...config.visualizations};
|
|
399
|
+
|
|
400
|
+
Object.keys(newVisualizations).forEach(vizKey => {
|
|
401
|
+
if(newVisualizations[vizKey].dataKey === datasetKey) {
|
|
402
|
+
delete newVisualizations[vizKey].dataKey;
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
delete newDatasets[datasetKey];
|
|
407
|
+
|
|
408
|
+
setConfig({...config, datasets: newDatasets, visualizations: newVisualizations});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const renameDataset = (oldName, newName) => {
|
|
412
|
+
if(oldName === newName) return;
|
|
413
|
+
|
|
414
|
+
let newDatasets = {...config.datasets};
|
|
415
|
+
let newVisualizations = {...config.visualizations};
|
|
416
|
+
|
|
417
|
+
let suffix = 2;
|
|
418
|
+
let originalName = newName;
|
|
419
|
+
while(newDatasets[newName]){
|
|
420
|
+
newName = originalName + '-' + suffix;
|
|
421
|
+
suffix++;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
newDatasets[newName] = newDatasets[oldName];
|
|
425
|
+
delete newDatasets[oldName];
|
|
426
|
+
|
|
427
|
+
Object.keys(newVisualizations).forEach(vizKey => {
|
|
428
|
+
if(newVisualizations[vizKey].dataKey === oldName) {
|
|
429
|
+
newVisualizations[vizKey].dataKey = newName;
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
setConfig({...config, datasets: newDatasets, visualizations: newVisualizations});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
let previewData, configureData, readyToConfigure = false;
|
|
437
|
+
if(config.type === 'dashboard'){
|
|
438
|
+
readyToConfigure = Object.keys(config.datasets).length > 0;
|
|
439
|
+
Object.keys(config.datasets).forEach(datasetKey => {
|
|
440
|
+
if(config.datasets[datasetKey].preview){
|
|
441
|
+
previewData = config.datasets[datasetKey].data;
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
} else {
|
|
445
|
+
previewData = config.data;
|
|
446
|
+
configureData = config;
|
|
447
|
+
readyToConfigure = !!config.formattedData;
|
|
448
|
+
}
|
|
449
|
+
|
|
329
450
|
return (
|
|
330
451
|
<>
|
|
331
452
|
<div className="left-col">
|
|
332
|
-
{
|
|
453
|
+
{config.type === 'dashboard' && Object.keys(config.datasets).length > 0 && (
|
|
454
|
+
<>
|
|
455
|
+
<div className="heading-3">Data Sources</div>
|
|
456
|
+
<table>
|
|
457
|
+
<thead>
|
|
458
|
+
<tr>
|
|
459
|
+
<th>Name</th><th>Size</th><th>Type</th><th colSpan="4">Actions</th>
|
|
460
|
+
</tr>
|
|
461
|
+
</thead>
|
|
462
|
+
<tbody>
|
|
463
|
+
{Object.keys(config.datasets).map(datasetKey => config.datasets[datasetKey].dataFileName && (
|
|
464
|
+
<tr key={`tr-${datasetKey}`}>
|
|
465
|
+
<td><input className="dataset-name-input" type="text" defaultValue={datasetKey} onBlur={(e) => renameDataset(datasetKey, e.target.value)}/></td>
|
|
466
|
+
<td>{displaySize(config.datasets[datasetKey].dataFileSize)}</td>
|
|
467
|
+
<td>{config.datasets[datasetKey].dataFileFormat}</td>
|
|
468
|
+
<td><button className="btn btn-primary" onClick={() => setGlobalDatasetProp(datasetKey, 'preview', true)}>Preview Data</button></td>
|
|
469
|
+
<td><button className="btn btn-primary" onClick={() => {
|
|
470
|
+
if(editingDataset === datasetKey){
|
|
471
|
+
setEditingDataset(undefined);
|
|
472
|
+
} else {
|
|
473
|
+
setEditingDataset(datasetKey);
|
|
474
|
+
}
|
|
475
|
+
}}>Edit Data</button></td>
|
|
476
|
+
<td><button className="btn btn-primary" onClick={() => removeDataset(datasetKey)}>X</button></td>
|
|
477
|
+
</tr>
|
|
478
|
+
))}
|
|
479
|
+
</tbody>
|
|
480
|
+
</table>
|
|
481
|
+
</>
|
|
482
|
+
)}
|
|
483
|
+
|
|
484
|
+
{configureData && configureData.data && (
|
|
485
|
+
<>
|
|
486
|
+
{config.type !== 'dashboard' && (
|
|
487
|
+
<>
|
|
488
|
+
<div className="heading-3">Data Source</div>
|
|
489
|
+
<div className="file-loaded-area">
|
|
490
|
+
{config.dataFileSourceType === 'file' && (
|
|
491
|
+
<div className="data-source-options">
|
|
492
|
+
<div
|
|
493
|
+
className={isDragActive2 ? 'drag-active cdcdataviz-file-selector loaded-file' : 'cdcdataviz-file-selector loaded-file'} {...getRootProps2()}>
|
|
494
|
+
<input {...getInputProps2()} />
|
|
495
|
+
{
|
|
496
|
+
isDragActive2 ?
|
|
497
|
+
<p>Drop file here</p> :
|
|
498
|
+
<p><FileUploadIcon/> <span>{config.dataFileName ?? 'Replace data file'}</span></p>
|
|
499
|
+
}
|
|
500
|
+
</div>
|
|
501
|
+
<div>
|
|
502
|
+
{resetButton()}
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
)}
|
|
506
|
+
|
|
507
|
+
{config.dataFileSourceType === 'url' && (
|
|
508
|
+
<div className="url-source-options">
|
|
509
|
+
<div>
|
|
510
|
+
{loadFileFromUrl(externalURL)}
|
|
511
|
+
</div>
|
|
512
|
+
<div>
|
|
513
|
+
{resetButton()}
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
)}
|
|
517
|
+
</div>
|
|
518
|
+
</>
|
|
519
|
+
)}
|
|
520
|
+
|
|
521
|
+
<DataDesigner visuzliationKey={null} dataKey={configureData.dataFileName} configureData={configureData} updateDescriptionProp={updateDescriptionProp} />
|
|
522
|
+
</>
|
|
523
|
+
)}
|
|
524
|
+
|
|
525
|
+
{(editingDataset || addingDataset) && ( // dataFileSourceType needs to be checked here since earlier versions did not track this state
|
|
333
526
|
<div className="load-data-area">
|
|
334
|
-
<
|
|
527
|
+
<div className="heading-3">{editingDataset ? `Editing ${editingDataset}` : 'Add Dataset'}</div>
|
|
528
|
+
<Tabs startingTab={editingDataset && config.datasets[editingDataset].dataFileSourceType === 'url' ? 1 : 0}>
|
|
335
529
|
<TabPane title="Upload File" icon={<FileUploadIcon className="inline-icon"/>}>
|
|
336
530
|
{sharepath &&
|
|
337
531
|
<p className="alert--info">
|
|
@@ -349,7 +543,7 @@ export default function DataImport() {
|
|
|
349
543
|
</div>
|
|
350
544
|
</TabPane>
|
|
351
545
|
<TabPane title="Load from URL" icon={<LinkIcon className="inline-icon"/>}>
|
|
352
|
-
{loadFileFromUrl(externalURL)}
|
|
546
|
+
{loadFileFromUrl(editingDataset && config.datasets[editingDataset].dataFileSourceType === 'url' ? config.datasets[editingDataset].dataFileName : externalURL, editingDataset)}
|
|
353
547
|
</TabPane>
|
|
354
548
|
</Tabs>
|
|
355
549
|
{errors && (errors.map ? errors.map((message, index) => (
|
|
@@ -364,282 +558,35 @@ export default function DataImport() {
|
|
|
364
558
|
<span className="heading-3">Load Sample Data:</span>
|
|
365
559
|
<ul className="sample-data-list">
|
|
366
560
|
<li
|
|
367
|
-
onClick={() => loadData(new Blob([ validMapData ], { type: 'text/csv' }), 'valid-data-map.csv')}>United
|
|
561
|
+
onClick={() => loadData(new Blob([ validMapData ], { type: 'text/csv' }), 'valid-data-map.csv', editingDataset)}>United
|
|
368
562
|
States Sample Data #1
|
|
369
563
|
</li>
|
|
370
564
|
<li
|
|
371
|
-
onClick={() => loadData(new Blob([ validChartData ], { type: 'text/csv' }), 'valid-data-chart.csv')}>Chart
|
|
565
|
+
onClick={() => loadData(new Blob([ validChartData ], { type: 'text/csv' }), 'valid-data-chart.csv', editingDataset)}>Chart
|
|
372
566
|
Sample Data
|
|
373
567
|
</li>
|
|
374
568
|
<li
|
|
375
|
-
onClick={() => loadData(new Blob([ validCountyMapData ], { type: 'text/csv' }), 'valid-county-data.csv')}>United
|
|
569
|
+
onClick={() => loadData(new Blob([ validCountyMapData ], { type: 'text/csv' }), 'valid-county-data.csv', editingDataset)}>United
|
|
376
570
|
States Counties Sample Data
|
|
377
571
|
</li>
|
|
378
572
|
</ul>
|
|
379
|
-
<a href="https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/data-map.html" target="_blank"
|
|
380
|
-
rel="noopener noreferrer" className="guidance-link">
|
|
381
|
-
<div>
|
|
382
|
-
<h3>Get Help</h3>
|
|
383
|
-
<p>Documentation and examples on formatting data and configuring visualizations.</p>
|
|
384
|
-
</div>
|
|
385
|
-
</a>
|
|
386
573
|
</div>
|
|
387
574
|
)}
|
|
388
575
|
|
|
389
|
-
{config.
|
|
576
|
+
{config.type === 'dashboard' && !addingDataset && <p><button className="btn btn-primary" onClick={() => setAddingDataset(true)}>+ Add More Files</button></p>}
|
|
577
|
+
|
|
578
|
+
{readyToConfigure && <p><button className="btn btn-primary" onClick={() => setGlobalActive(2)}>Configure your visualization</button></p>}
|
|
579
|
+
|
|
580
|
+
<a href="https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/data-map.html" target="_blank"
|
|
581
|
+
rel="noopener noreferrer" className="guidance-link">
|
|
390
582
|
<div>
|
|
391
|
-
<
|
|
392
|
-
<
|
|
393
|
-
{config.dataFileSourceType === 'file' && (
|
|
394
|
-
<div className="data-source-options">
|
|
395
|
-
<div
|
|
396
|
-
className={isDragActive ? 'drag-active cdcdataviz-file-selector loaded-file' : 'cdcdataviz-file-selector loaded-file'} {...getRootProps()}>
|
|
397
|
-
<input {...getInputProps()} />
|
|
398
|
-
{
|
|
399
|
-
isDragActive ?
|
|
400
|
-
<p>Drop file here</p> :
|
|
401
|
-
<p><FileUploadIcon/> <span>{config.dataFileName ?? 'Replace data file'}</span></p>
|
|
402
|
-
}
|
|
403
|
-
</div>
|
|
404
|
-
<div>
|
|
405
|
-
{resetButton()}
|
|
406
|
-
</div>
|
|
407
|
-
</div>
|
|
408
|
-
)}
|
|
409
|
-
|
|
410
|
-
{config.dataFileSourceType === 'url' && (
|
|
411
|
-
<div className="url-source-options">
|
|
412
|
-
<div>
|
|
413
|
-
{loadFileFromUrl(externalURL)}
|
|
414
|
-
</div>
|
|
415
|
-
<div>
|
|
416
|
-
{resetButton()}
|
|
417
|
-
</div>
|
|
418
|
-
</div>
|
|
419
|
-
)}
|
|
420
|
-
</div>
|
|
421
|
-
<div className="question">
|
|
422
|
-
<div className="heading-3">Describe Data</div>
|
|
423
|
-
<div className="heading-4 data-question">Data Orientation</div>
|
|
424
|
-
<div className="table-button-container">
|
|
425
|
-
<div
|
|
426
|
-
className={'table-button' + (config.dataDescription && config.dataDescription.horizontal === false ? ' active' : '')}
|
|
427
|
-
onClick={() => {
|
|
428
|
-
updateDescriptionProp('horizontal', false)
|
|
429
|
-
}}>
|
|
430
|
-
<strong>Vertical</strong>
|
|
431
|
-
<p>Values for map geography or chart date/category axis are contained in a single <em>column</em>.</p>
|
|
432
|
-
<table>
|
|
433
|
-
<tbody>
|
|
434
|
-
<tr>
|
|
435
|
-
<th>Date</th>
|
|
436
|
-
<th>Value</th>
|
|
437
|
-
<th>...</th>
|
|
438
|
-
</tr>
|
|
439
|
-
<tr>
|
|
440
|
-
<td>01/01/2020</td>
|
|
441
|
-
<td>150</td>
|
|
442
|
-
<td>...</td>
|
|
443
|
-
</tr>
|
|
444
|
-
<tr>
|
|
445
|
-
<td>02/01/2020</td>
|
|
446
|
-
<td>150</td>
|
|
447
|
-
<td>...</td>
|
|
448
|
-
</tr>
|
|
449
|
-
</tbody>
|
|
450
|
-
</table>
|
|
451
|
-
<table>
|
|
452
|
-
<tbody>
|
|
453
|
-
<tr>
|
|
454
|
-
<th>State</th>
|
|
455
|
-
<th>Value</th>
|
|
456
|
-
<th>...</th>
|
|
457
|
-
</tr>
|
|
458
|
-
<tr>
|
|
459
|
-
<td>Georgia</td>
|
|
460
|
-
<td>150</td>
|
|
461
|
-
<td>...</td>
|
|
462
|
-
</tr>
|
|
463
|
-
<tr>
|
|
464
|
-
<td>Florida</td>
|
|
465
|
-
<td>150</td>
|
|
466
|
-
<td>...</td>
|
|
467
|
-
</tr>
|
|
468
|
-
</tbody>
|
|
469
|
-
</table>
|
|
470
|
-
</div>
|
|
471
|
-
<div
|
|
472
|
-
className={'table-button' + (config.dataDescription && config.dataDescription.horizontal === true ? ' active' : '')}
|
|
473
|
-
onClick={() => {
|
|
474
|
-
updateDescriptionProp('horizontal', true)
|
|
475
|
-
}}>
|
|
476
|
-
<strong>Horizontal</strong>
|
|
477
|
-
<p>Values for map geography or chart date/category axis are contained in a single <em>row</em></p>
|
|
478
|
-
<table>
|
|
479
|
-
<tbody>
|
|
480
|
-
<tr>
|
|
481
|
-
<th>Date</th>
|
|
482
|
-
<td>01/01/2020</td>
|
|
483
|
-
<td>02/01/2020</td>
|
|
484
|
-
<td>...</td>
|
|
485
|
-
</tr>
|
|
486
|
-
<tr>
|
|
487
|
-
<th>Value</th>
|
|
488
|
-
<td>100</td>
|
|
489
|
-
<td>150</td>
|
|
490
|
-
<td>...</td>
|
|
491
|
-
</tr>
|
|
492
|
-
</tbody>
|
|
493
|
-
</table>
|
|
494
|
-
<table>
|
|
495
|
-
<tbody>
|
|
496
|
-
<tr>
|
|
497
|
-
<th>State</th>
|
|
498
|
-
<td>Georgia</td>
|
|
499
|
-
<td>Florida</td>
|
|
500
|
-
<td>...</td>
|
|
501
|
-
</tr>
|
|
502
|
-
<tr>
|
|
503
|
-
<th>Value</th>
|
|
504
|
-
<td>100</td>
|
|
505
|
-
<td>150</td>
|
|
506
|
-
<td>...</td>
|
|
507
|
-
</tr>
|
|
508
|
-
</tbody>
|
|
509
|
-
</table>
|
|
510
|
-
</div>
|
|
511
|
-
</div>
|
|
512
|
-
</div>
|
|
513
|
-
{config.dataDescription && (
|
|
514
|
-
<>
|
|
515
|
-
<div className="question">
|
|
516
|
-
<div className="heading-4 data-question">Are there multiple series represented in your data?</div>
|
|
517
|
-
<div>
|
|
518
|
-
<button className={config.dataDescription.series === true ? 'btn btn-primary active' : 'btn btn-primary'} style={{ marginRight: '.5em' }} onClick={() => { updateDescriptionProp('series', true) }}>Yes</button>
|
|
519
|
-
<button className={config.dataDescription.series === false ? 'btn btn-primary active' : 'btn btn-primary'} onClick={() => {updateDescriptionProp('series', false)}}>No</button>
|
|
520
|
-
</div>
|
|
521
|
-
</div>
|
|
522
|
-
{config.dataDescription.horizontal === true && config.dataDescription.series === true && (
|
|
523
|
-
<div className="question">
|
|
524
|
-
<div className="heading-4 data-question">Which property in the dataset represents which series the row is describing?</div>
|
|
525
|
-
<select onChange={(e) => {updateDescriptionProp('seriesKey', e.target.value)}} value={config.dataDescription.seriesKey}>
|
|
526
|
-
<option value="">Choose an option</option>
|
|
527
|
-
{Object.keys(config.data[0]).map((value, index) => <option value={value} key={index}>{value}</option>)}
|
|
528
|
-
</select>
|
|
529
|
-
</div>
|
|
530
|
-
)}
|
|
531
|
-
{config.dataDescription.horizontal === false && config.dataDescription.series === true && (
|
|
532
|
-
<>
|
|
533
|
-
<div className="question">
|
|
534
|
-
<div className="heading-4 data-question">Are the series values in your data represented in a single row, or across multiple rows?</div>
|
|
535
|
-
<div className="table-button-container">
|
|
536
|
-
<div className={'table-button' + (config.dataDescription.singleRow === true ? ' active' : '')} onClick={() => {updateDescriptionProp('singleRow', true)}}>
|
|
537
|
-
<p>Each row contains the data for an individual series in itself.</p>
|
|
538
|
-
<table>
|
|
539
|
-
<tbody>
|
|
540
|
-
<tr>
|
|
541
|
-
<th>Date</th>
|
|
542
|
-
<th>Virus 1</th>
|
|
543
|
-
<th>Virus 2</th>
|
|
544
|
-
<th>...</th>
|
|
545
|
-
</tr>
|
|
546
|
-
<tr>
|
|
547
|
-
<td>01/01/2020</td>
|
|
548
|
-
<td>100</td>
|
|
549
|
-
<td>150</td>
|
|
550
|
-
<td>...</td>
|
|
551
|
-
</tr>
|
|
552
|
-
<tr>
|
|
553
|
-
<td>02/01/2020</td>
|
|
554
|
-
<td>15</td>
|
|
555
|
-
<td>20</td>
|
|
556
|
-
<td>...</td>
|
|
557
|
-
</tr>
|
|
558
|
-
</tbody>
|
|
559
|
-
</table>
|
|
560
|
-
</div>
|
|
561
|
-
<div className={'table-button' + (config.dataDescription.singleRow === false ? ' active' : '')} onClick={() => {updateDescriptionProp('singleRow', false)}}>
|
|
562
|
-
<p>Each series data is broken out into multiple rows.</p>
|
|
563
|
-
<table>
|
|
564
|
-
<tbody>
|
|
565
|
-
<tr>
|
|
566
|
-
<th>Virus</th>
|
|
567
|
-
<th>Date</th>
|
|
568
|
-
<th>Value</th>
|
|
569
|
-
</tr>
|
|
570
|
-
<tr>
|
|
571
|
-
<td>Virus 1</td>
|
|
572
|
-
<td>01/01/2020</td>
|
|
573
|
-
<td>100</td>
|
|
574
|
-
</tr>
|
|
575
|
-
<tr>
|
|
576
|
-
<td>Virus 1</td>
|
|
577
|
-
<td>02/01/2020</td>
|
|
578
|
-
<td>150</td>
|
|
579
|
-
</tr>
|
|
580
|
-
<tr>
|
|
581
|
-
<td>...</td>
|
|
582
|
-
<td>...</td>
|
|
583
|
-
<td>...</td>
|
|
584
|
-
</tr>
|
|
585
|
-
<tr>
|
|
586
|
-
<td>Virus 2</td>
|
|
587
|
-
<td>01/01/2020</td>
|
|
588
|
-
<td>15</td>
|
|
589
|
-
</tr>
|
|
590
|
-
<tr>
|
|
591
|
-
<td>Virus 2</td>
|
|
592
|
-
<td>02/01/2020</td>
|
|
593
|
-
<td>20</td>
|
|
594
|
-
</tr>
|
|
595
|
-
<tr>
|
|
596
|
-
<td>...</td>
|
|
597
|
-
<td>...</td>
|
|
598
|
-
<td>...</td>
|
|
599
|
-
</tr>
|
|
600
|
-
</tbody>
|
|
601
|
-
</table>
|
|
602
|
-
</div>
|
|
603
|
-
</div>
|
|
604
|
-
</div>
|
|
605
|
-
{config.dataDescription.singleRow === false && (
|
|
606
|
-
<>
|
|
607
|
-
<div className="question">
|
|
608
|
-
<div className="heading-4 data-question">Which property in the dataset represents which series the row is describing?</div>
|
|
609
|
-
<select onChange={(e) => {updateDescriptionProp('seriesKey', e.target.value)}}>
|
|
610
|
-
<option value="">Choose an option</option>
|
|
611
|
-
{Object.keys(config.data[0]).map((value, index) => <option value={value} key={index}>{value}</option>)}
|
|
612
|
-
</select>
|
|
613
|
-
</div>
|
|
614
|
-
<div className="question">
|
|
615
|
-
<div className="heading-4 data-question">Which property in the dataset represents the values for the category/date axis or map geography?</div>
|
|
616
|
-
<select onChange={(e) => {updateDescriptionProp('xKey', e.target.value)}}>
|
|
617
|
-
<option value="">Choose an option</option>
|
|
618
|
-
{Object.keys(config.data[0]).map((value, index) => <option value={value} key={index}>{value}</option>)}
|
|
619
|
-
</select>
|
|
620
|
-
</div>
|
|
621
|
-
<div className="question">
|
|
622
|
-
<div className="heading-4 data-question">Which property in the dataset represents the numeric value?</div>
|
|
623
|
-
<select onChange={(e) => {updateDescriptionProp('valueKey', e.target.value)}}>
|
|
624
|
-
<option value="">Choose an option</option>
|
|
625
|
-
{Object.keys(config.data[0]).map((value, index) => <option value={value} key={index}>{value}</option>)}
|
|
626
|
-
</select>
|
|
627
|
-
</div>
|
|
628
|
-
</>
|
|
629
|
-
)}
|
|
630
|
-
</>
|
|
631
|
-
)}
|
|
632
|
-
</>
|
|
633
|
-
)}
|
|
634
|
-
{config.formattedData && (
|
|
635
|
-
<button className="btn btn-primary" style={{ float: 'right', marginBottom: '2em' }}
|
|
636
|
-
onClick={() => setGlobalActive(1)}>Select your visualization type »</button>
|
|
637
|
-
)}
|
|
583
|
+
<h3>Get Help</h3>
|
|
584
|
+
<p>Documentation and examples on formatting data and configuring visualizations.</p>
|
|
638
585
|
</div>
|
|
639
|
-
|
|
586
|
+
</a>
|
|
640
587
|
</div>
|
|
641
588
|
<div className="right-col">
|
|
642
|
-
<PreviewDataTable data={
|
|
589
|
+
<PreviewDataTable data={previewData}/>
|
|
643
590
|
</div>
|
|
644
591
|
</>
|
|
645
592
|
)
|