@cdc/editor 1.4.2 → 1.4.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.
@@ -1,23 +1,31 @@
1
- import React, { useState, useContext, useEffect } from 'react';
2
- import {useDropzone} from 'react-dropzone'
3
- import {csvParse} from 'd3';
4
- import { useDebounce } from 'use-debounce';
5
- import { get } from 'axios';
1
+ import React, { useState, useContext, useEffect } from 'react'
2
+ import { useDropzone } from 'react-dropzone'
3
+ import { csvParse } from 'd3'
4
+ import { useDebounce } from 'use-debounce'
5
+ import { get } from 'axios'
6
6
 
7
- import GlobalState from '../context';
8
- import '../scss/data-import.scss';
9
- import TabPane from './TabPane';
10
- import Tabs from './Tabs';
11
- import PreviewDataTable from './PreviewDataTable';
7
+ import { DataTransform } from '@cdc/core/components/DataTransform'
8
+ import Modal from '@cdc/core/components/ui/Modal'
9
+ import { useGlobalContext } from '@cdc/core/components/GlobalContext'
12
10
 
13
- import LinkIcon from '../assets/icons/link.svg';
14
- import FileUploadIcon from '../assets/icons/file-upload-solid.svg';
15
- import CloseIcon from '@cdc/core/assets/icon-close.svg';
11
+ import GlobalState from '../context'
16
12
 
17
- import validMapData from '../../example/valid-data-map.csv';
18
- import validChartData from '../../example/valid-data-chart.csv';
13
+ import TabPane from './TabPane'
14
+ import Tabs from './Tabs'
15
+ import PreviewDataTable from './PreviewDataTable'
16
+ import LinkIcon from '../assets/icons/link.svg'
19
17
 
20
- import { DataTransform } from '@cdc/core/components/DataTransform';
18
+ import FileUploadIcon from '../assets/icons/file-upload-solid.svg'
19
+ import CloseIcon from '@cdc/core/assets/icon-close.svg'
20
+ import validMapData from '../../example/valid-data-map.csv'
21
+
22
+ import validChartData from '../../example/valid-data-chart.csv'
23
+ import validCountyMapData from '../../example/valid-county-data.csv'
24
+
25
+
26
+ import '../scss/data-import.scss'
27
+ import Button from '@cdc/core/components/elements/Button'
28
+ import Icon from '@cdc/core/components/ui/Icon'
21
29
 
22
30
  export default function DataImport() {
23
31
  const {
@@ -29,275 +37,347 @@ export default function DataImport() {
29
37
  maxFileSize,
30
38
  setGlobalActive,
31
39
  tempConfig,
32
- setTempConfig
33
- } = useContext(GlobalState);
40
+ setTempConfig,
41
+ sharepath
42
+ } = useContext(GlobalState)
34
43
 
35
- const transform = new DataTransform();
44
+ const { overlay } = useGlobalContext()
36
45
 
37
- const [externalURL, setExternalURL] = useState('')
46
+ const transform = new DataTransform()
38
47
 
39
- const [ debouncedExternalURL ] = useDebounce(externalURL, 200);
48
+ const [ externalURL, setExternalURL ] = useState(config.dataFileSourceType === 'url' ? config.dataFileName : (config.dataUrl || ''))
40
49
 
41
- const [keepURL, setKeepURL] = useState(config.dataUrl || false)
50
+ const [ debouncedExternalURL ] = useDebounce(externalURL, 200)
51
+
52
+ const [ keepURL, setKeepURL ] = useState(!!config.dataUrl)
42
53
 
43
54
  const supportedDataTypes = {
44
55
  '.csv': 'text/csv',
45
56
  '.json': 'application/json'
46
- };
57
+ }
47
58
 
48
59
  useEffect(() => {
49
- if(true === keepURL) {
50
- setConfig({...config, dataUrl: debouncedExternalURL})
60
+ if (false !== keepURL) {
61
+ setConfig({ ...config, dataUrl: debouncedExternalURL || externalURL })
62
+ } else {
63
+ let newConfig = {...config};
64
+ delete newConfig.dataUrl;
65
+ setConfig(newConfig);
51
66
  }
52
- }, [debouncedExternalURL, keepURL])
67
+ }, [ debouncedExternalURL, keepURL ])
53
68
 
54
69
  /**
55
70
  * Check to see all series for the viz exists in the new dataset
56
71
  */
57
72
  const dataExists = (newData, oldSeries, oldAxisX) => {
58
-
73
+
59
74
  // Loop through old series to make sure each exists in the new data
60
- oldSeries.map(function(currentValue, index, newData){
61
- if( ! newData.find( element => element.dataKey === currentValue.dataKey ) )
62
- return false;
75
+ oldSeries.map(function (currentValue, index, newData) {
76
+ if (!newData.find(element => element.dataKey === currentValue.dataKey))
77
+ return false
63
78
  })
64
79
 
65
80
  // Is the X Axis still in the dataset?
66
- if( newData.columns.indexOf(oldAxisX) < 0 )
67
- return false;
81
+ if (newData.columns.indexOf(oldAxisX) < 0)
82
+ return false
68
83
 
69
- return true;
84
+ return true
70
85
  }
71
86
 
72
87
  const loadExternal = async () => {
73
- let dataURL = '';
88
+ let dataURL = ''
74
89
  // Is URL valid?
75
90
  try {
76
- dataURL = new URL(externalURL);
91
+ dataURL = new URL(externalURL)
77
92
  } catch {
78
93
  throw errorMessages.urlInvalid
79
94
  }
80
- let responseBlob = null;
95
+ let responseBlob = null
81
96
 
82
97
  const fileExtension = Object.keys(supportedDataTypes).find(extension => dataURL.pathname.endsWith(extension))
83
98
 
84
99
  try {
85
- const response = await get( dataURL,
100
+ const response = await get(dataURL,
86
101
  {
87
102
  responseType: 'blob'
88
103
  })
89
104
  .then((response) => {
90
- responseBlob = response.data;
105
+ responseBlob = response.data
91
106
 
92
107
  // Sometimes the files are coming in as plain text types... Maybe when saved from Macs
93
- if ( fileExtension === ".csv" && responseBlob.type === "text/plain" ) {
94
- responseBlob = responseBlob.slice(0, responseBlob.size, "text/csv")
95
- } else if ( fileExtension === ".json" && responseBlob.type === "text/plain" ) {
96
- responseBlob = responseBlob.slice(0, responseBlob.size, "application/json")
108
+ if (fileExtension === '.csv' && responseBlob.type === 'text/plain') {
109
+ responseBlob = responseBlob.slice(0, responseBlob.size, 'text/csv')
110
+ } else if (fileExtension === '.json' && responseBlob.type === 'text/plain') {
111
+ responseBlob = responseBlob.slice(0, responseBlob.size, 'application/json')
97
112
  }
98
- });
113
+ })
99
114
  } catch (err) {
100
115
  console.error(err)
101
116
 
102
- const error = err.toString();
117
+ const error = err.toString()
103
118
 
104
- if( Object.values(errorMessages).includes(err) ) {
119
+ if (Object.values(errorMessages).includes(err)) {
105
120
  throw error
106
121
  }
107
122
 
108
123
  throw errorMessages.failedFetch
109
124
  }
110
-
111
- return responseBlob;
125
+
126
+ return responseBlob
112
127
  }
113
128
 
114
- const onDrop = ([uploadedFile]) => loadData(uploadedFile)
129
+ const onDrop = ([ uploadedFile ]) => loadData(uploadedFile)
115
130
 
116
131
  /**
117
132
  * Handle loading data
118
133
  */
119
134
  const loadData = async (fileBlob = null, fileName) => {
120
- let fileData = fileBlob;
121
- let fileSource = fileData?.path ?? fileName ?? null;
122
- let fileSourceType = 'file';
135
+ let fileData = fileBlob
136
+ let fileSource = fileData?.path ?? fileName ?? null
137
+ let fileSourceType = 'file'
123
138
 
124
139
  // Get the raw data as text from the file
125
- if(null === fileData) {
126
- fileSourceType = 'url';
140
+ if (null === fileData) {
141
+ // const round = 1000 * 60 * 15;
142
+ // const date = new Date();
143
+ // const rounded = new Date(date.getTime() - (date.getTime() % round));
144
+ // const trimmedDate = rounded.toString().replace(/\s+/g, "");
145
+
146
+ const newUrl = new URL(fileName);
147
+ // newUrl.searchParams.append("v", trimmedDate);
148
+
149
+ fileSourceType = "url";
127
150
  try {
128
- fileData = await loadExternal();
129
- fileSource = externalURL;
151
+ fileData = await loadExternal()
152
+ fileSource = externalURL
130
153
  } catch (error) {
131
- setErrors([error]);
132
- return;
154
+ setErrors([ error ])
155
+ return
133
156
  }
134
157
  }
135
158
 
136
159
  // Check if file is too big
137
- if(fileData.size > (maxFileSize * 1048576) ) {
138
- setErrors([errorMessages.fileTooLarge]);
139
- return;
160
+ if (fileData.size > (maxFileSize * 1048576)) {
161
+ setErrors([ errorMessages.fileTooLarge ])
162
+ return
140
163
  }
141
164
 
142
165
  let path = fileBlob?.name || externalURL || fileName
143
- let fileExtension = path.match(/(?:\.([^.]+))?$/g);
166
+ let fileExtension = path.match(/(?:\.([^.]+))?$/g)
144
167
 
145
- if(fileExtension.length === 0) {
168
+ if (fileExtension.length === 0) {
146
169
  fileExtension = '.csv'
147
170
  } else {
148
171
  fileExtension = fileExtension[0]
149
172
  }
150
173
 
151
- let mimeType = supportedDataTypes[fileExtension];
174
+ let mimeType = supportedDataTypes[fileExtension]
152
175
 
153
176
  // Convert from blob into raw text
154
177
  // Have to use FileReader instead of just .text because IE11 and the polyfills for this are bugged
155
- let filereader = new FileReader();
178
+ let filereader = new FileReader()
156
179
 
157
180
  // Set encoding for CSV files - needed to render special characters properly
158
- let encoding = ( mimeType === 'text/csv' ) ? 'ISO-8859-1' : '';
181
+ let encoding = (mimeType === 'text/csv') ? 'ISO-8859-1' : ''
159
182
 
160
- filereader.onload = function() {
183
+ filereader.onload = function () {
161
184
  let text = this.result
162
185
 
163
186
  switch (mimeType) {
164
- case 'text/csv':
165
- text = csvParse(text);
166
- break;
187
+ case 'text/csv':
188
+ text = csvParse(text)
189
+ break
167
190
  case 'text/plain':
168
191
  case 'application/json':
169
192
  try {
170
- text = JSON.parse(text);
171
- } catch (errors) {
172
- setErrors([errorMessages.formatting]);
173
- return;
193
+ text = JSON.parse(text)
194
+ } catch (errors) {
195
+ setErrors([ errorMessages.formatting ])
196
+ return
174
197
  }
175
- break;
198
+ break
176
199
  default:
177
- setErrors([errorMessages.fileType]);
178
- return;
200
+ setErrors([ errorMessages.fileType ])
201
+ return
179
202
  }
180
-
203
+
181
204
  // Validate parsed data and set if no issues.
182
205
  try {
183
- text = transform.autoStandardize(text);
206
+ text = transform.autoStandardize(text)
184
207
 
185
208
  if (config.data && config.series) {
186
209
  if (dataExists(text, config.series, config?.xAxis.dataKey)) {
187
210
  setConfig({
188
- ...config,
189
- ...tempConfig,
190
- data:text, // new data
191
- dataFileName:fileSource, // new file source
192
- dataFileSourceType:fileSourceType ,// new file source type
211
+ ...config,
212
+ ...tempConfig,
213
+ data: text, // new data
214
+ dataFileName: fileSource, // new file source
215
+ dataFileSourceType: fileSourceType,// new file source type
193
216
  })
194
217
  } else {
195
- resetEditor({data:text, dataFileName:fileSource, dataFileSourceType:fileSourceType }, '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?');
218
+ resetEditor({
219
+ data: text,
220
+ dataFileName: fileSource,
221
+ dataFileSourceType: fileSourceType
222
+ }, '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?')
196
223
  }
197
224
  } else {
198
- setConfig({...config, data:text, dataFileName:fileSource, dataFileSourceType:fileSourceType });
225
+ setConfig({ ...config, data: text, dataFileName: fileSource, dataFileSourceType: fileSourceType })
199
226
  }
200
227
  } catch (err) {
201
- setErrors(err);
228
+ setErrors(err)
202
229
  }
203
230
 
204
231
  }
205
- filereader.readAsText(fileData, encoding)
232
+ filereader.readAsText(fileData, encoding)
206
233
  }
207
234
 
208
235
  useEffect(() => {
209
- let newConfig = {...config}
210
- if(tempConfig !== null) {
211
- newConfig = {...tempConfig}
212
-
236
+ let newConfig = { ...config }
237
+ if (tempConfig !== null) {
238
+ newConfig = { ...tempConfig }
213
239
  }
214
240
 
215
- if(undefined === config.formattedData && config.dataDescription) {
241
+ if (undefined === config.formattedData && config.dataDescription) {
216
242
  const formattedData = transform.developerStandardize(config.data, config.dataDescription)
217
-
218
- if(formattedData) newConfig.formattedData = formattedData
243
+
244
+ if (formattedData) newConfig.formattedData = formattedData
219
245
  }
220
246
 
221
- if(tempConfig !== null) setTempConfig(null)
247
+ if (tempConfig !== null) setTempConfig(null)
222
248
 
223
249
  setConfig(newConfig)
224
- }, []);
250
+ }, [])
225
251
 
226
252
  const updateDescriptionProp = (key, value) => {
227
- let dataDescription = {...config.dataDescription, [key]: value}
253
+ let dataDescription = { ...config.dataDescription, [key]: value }
228
254
  let formattedData = transform.developerStandardize(config.data, dataDescription)
229
255
 
230
- setConfig({...config, formattedData, dataDescription})
231
- };
256
+ setConfig({ ...config, formattedData, dataDescription })
257
+ }
232
258
 
233
- const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop});
259
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })
234
260
 
235
261
  const loadFileFromUrl = (url) => {
236
- const extUrl = (url) ? url : config.dataFileName; // set url to what is saved in config unless the user has entered something
262
+ // const extUrl = (url) ? url : config.dataFileName // set url to what is saved in config unless the user has entered something
263
+
237
264
  return (
238
265
  <>
239
266
  <form className="input-group d-flex" onSubmit={(e) => e.preventDefault()}>
240
- <input id="external-data" type="text" className="form-control flex-grow-1 border-right-0" placeholder="e.g., https://data.cdc.gov/resources/file.json" aria-label="Load data from external URL" aria-describedby="load-data" value={extUrl} onChange={(e) => setExternalURL(e.target.value)} />
241
- <button className="input-group-text btn btn-primary px-4" type="submit" id="load-data" onClick={() => loadData( null, externalURL )}>Load</button>
267
+ <input id="external-data" type="text" className="form-control flex-grow-1 border-right-0"
268
+ placeholder="e.g., https://data.cdc.gov/resources/file.json" aria-label="Load data from external URL"
269
+ aria-describedby="load-data" value={externalURL} onChange={(e) => setExternalURL(e.target.value)}/>
270
+ <button className="input-group-text btn btn-primary px-4" type="submit" id="load-data"
271
+ onClick={() => loadData(null, externalURL)}>Load
272
+ </button>
242
273
  </form>
243
274
  <label htmlFor="keep-url" className="mt-1 d-flex keep-url">
244
- <input type="checkbox" id="keep-url" defaultChecked={keepURL} onClick={() => setKeepURL(!keepURL)} /> Always load from URL (normally will only pull once)
275
+ <input type="checkbox" id="keep-url" checked={keepURL} onChange={() => setKeepURL(!keepURL)}/> Always
276
+ load from URL (normally will only pull once)
245
277
  </label>
246
278
  </>
247
279
  )
248
280
  }
249
281
 
250
- const resetEditor = ( config = {}, message = 'Are you sure you want to do this?' ) => {
251
- config.newViz = true;
252
- const confirmDataReset = window.confirm(message);
253
-
254
- if (confirmDataReset === true) {
255
- setTempConfig(null);
256
- setConfig(config);
257
- }
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
+ const resetEditor = (config = {}, message = 'Are you sure you want to do this?') => {
315
+ config.newViz = true
316
+ setTempConfig(null)
317
+ setConfig(config)
258
318
  }
259
319
 
260
320
  const resetButton = () => {
261
- return ( //todo convert to modal
262
- <button className="btn danger" onClick={() => resetEditor( {}, 'Reseting will remove your data and settings. Do you want to continue?') }>Clear
263
- <CloseIcon />
264
- </button>
321
+ return (
322
+ <Button className="warn" style={{ height: '2.5rem', display: 'inline-flex', justifyContent: 'center', alignItems: 'center' }}
323
+ onClick={() => overlay.actions.openOverlay(warningModal(), true)}>
324
+ Clear <Icon display="close" style={{marginLeft: '0.5rem'}}/>
325
+ </Button>
265
326
  )
266
327
  }
267
328
 
268
329
  return (
269
330
  <>
270
331
  <div className="left-col">
271
- { (!config.data || !config.dataFileSourceType) && ( // dataFileSourceType needs to be checked here since earlier versions did not track this state
332
+ {(!config.data || !config.dataFileSourceType) && ( // dataFileSourceType needs to be checked here since earlier versions did not track this state
272
333
  <div className="load-data-area">
273
334
  <Tabs>
274
- <TabPane title="Upload File" icon={<FileUploadIcon className="inline-icon" />}>
275
- <div className={isDragActive ? 'drag-active cdcdataviz-file-selector' : 'cdcdataviz-file-selector'} {...getRootProps()}>
335
+ <TabPane title="Upload File" icon={<FileUploadIcon className="inline-icon"/>}>
336
+ {sharepath &&
337
+ <p className="alert--info">
338
+ The share path set for this website is: {sharepath}
339
+ </p>
340
+ }
341
+ <div
342
+ className={isDragActive ? 'drag-active cdcdataviz-file-selector' : 'cdcdataviz-file-selector'} {...getRootProps()}>
276
343
  <input {...getInputProps()} />
277
344
  {
278
345
  isDragActive ?
279
- <p>Drop file here</p> :
280
- <p>Drag file to this area, or <span>select a file</span>.</p>
346
+ <p>Drop file here</p> :
347
+ <p>Drag file to this area, or <span>select a file</span>.</p>
281
348
  }
282
349
  </div>
283
350
  </TabPane>
284
- <TabPane title="Load from URL" icon={<LinkIcon className="inline-icon" />}>
351
+ <TabPane title="Load from URL" icon={<LinkIcon className="inline-icon"/>}>
285
352
  {loadFileFromUrl(externalURL)}
286
353
  </TabPane>
287
354
  </Tabs>
288
355
  {errors && (errors.map ? errors.map((message, index) => (
289
356
  <div className="error-box slim mt-2" key={`error-${message}`}>
290
- <span>{message}</span> <CloseIcon className='inline-icon dismiss-error' onClick={() => setErrors( errors.filter((val, i) => i !== index) )} />
357
+ <span>{message}</span> <CloseIcon className="inline-icon dismiss-error"
358
+ onClick={() => setErrors(errors.filter((val, i) => i !== index))}/>
291
359
  </div>
292
360
  )) : errors.message)}
293
- <p className="footnote">Supported file types: {Object.keys(supportedDataTypes).join(', ')}. Maximum file size {maxFileSize}MB.</p>
361
+ <p className="footnote">Supported file types: {Object.keys(supportedDataTypes).join(', ')}. Maximum file
362
+ size {maxFileSize}MB.</p>
294
363
  {/* TODO: Add more sample data in, but this will do for now. */}
295
364
  <span className="heading-3">Load Sample Data:</span>
296
365
  <ul className="sample-data-list">
297
- <li onClick={() => loadData(new Blob([validMapData], {type : 'text/csv'}), 'valid-data-map.csv')}>United States Sample Data #1</li>
298
- <li onClick={() => loadData(new Blob([validChartData], {type : 'text/csv'}), 'valid-data-chart.csv')}>Chart Sample Data</li>
366
+ <li
367
+ onClick={() => loadData(new Blob([ validMapData ], { type: 'text/csv' }), 'valid-data-map.csv')}>United
368
+ States Sample Data #1
369
+ </li>
370
+ <li
371
+ onClick={() => loadData(new Blob([ validChartData ], { type: 'text/csv' }), 'valid-data-chart.csv')}>Chart
372
+ Sample Data
373
+ </li>
374
+ <li
375
+ onClick={() => loadData(new Blob([ validCountyMapData ], { type: 'text/csv' }), 'valid-county-data.csv')}>United
376
+ States Counties Sample Data
377
+ </li>
299
378
  </ul>
300
- <a href="https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/data-map.html" target="_blank" rel="noopener noreferrer" className="guidance-link">
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">
301
381
  <div>
302
382
  <h3>Get Help</h3>
303
383
  <p>Documentation and examples on formatting data and configuring visualizations.</p>
@@ -310,82 +390,91 @@ export default function DataImport() {
310
390
  <div>
311
391
  <div className="heading-3">Data Source</div>
312
392
  <div className="file-loaded-area">
313
- {config.dataFileSourceType === 'file' && (
314
- <div className="data-source-options">
315
- <div className={isDragActive ? 'drag-active cdcdataviz-file-selector loaded-file' : 'cdcdataviz-file-selector loaded-file'} {...getRootProps()}>
316
- <input {...getInputProps()} />
317
- {
318
- isDragActive ?
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 ?
319
400
  <p>Drop file here</p> :
320
- <p><FileUploadIcon /> <span>{config.dataFileName ?? 'Replace data file'}</span></p>
321
- }
322
- </div>
323
- <div>
401
+ <p><FileUploadIcon/> <span>{config.dataFileName ?? 'Replace data file'}</span></p>
402
+ }
403
+ </div>
404
+ <div>
324
405
  {resetButton()}
325
- </div>
326
406
  </div>
327
- )}
328
-
329
- {config.dataFileSourceType === 'url' && (
330
- <div className="url-source-options">
331
- <div>
332
- {loadFileFromUrl(externalURL)}
333
- </div>
334
- <div>
335
- {resetButton()}
336
- </div>
407
+ </div>
408
+ )}
409
+
410
+ {config.dataFileSourceType === 'url' && (
411
+ <div className="url-source-options">
412
+ <div>
413
+ {loadFileFromUrl(externalURL)}
337
414
  </div>
338
- )}
415
+ <div>
416
+ {resetButton()}
417
+ </div>
418
+ </div>
419
+ )}
339
420
  </div>
340
421
  <div className="question">
341
422
  <div className="heading-3">Describe Data</div>
342
423
  <div className="heading-4 data-question">Data Orientation</div>
343
424
  <div className="table-button-container">
344
- <div className={'table-button' + (config.dataDescription && config.dataDescription.horizontal === false ? ' active' : '')} onClick={() => {updateDescriptionProp('horizontal', false)}}>
425
+ <div
426
+ className={'table-button' + (config.dataDescription && config.dataDescription.horizontal === false ? ' active' : '')}
427
+ onClick={() => {
428
+ updateDescriptionProp('horizontal', false)
429
+ }}>
345
430
  <strong>Vertical</strong>
346
- <p>Geography/X-axis values are contained on a single column.</p>
431
+ <p>Values for map geography or chart date/category axis are contained in a single <em>column</em>.</p>
347
432
  <table>
348
433
  <tbody>
349
- <tr>
350
- <th>Date</th>
351
- <th>Value</th>
352
- <th>...</th>
353
- </tr>
354
- <tr>
355
- <td>01/01/2020</td>
356
- <td>150</td>
357
- <td>...</td>
358
- </tr>
359
- <tr>
360
- <td>02/01/2020</td>
361
- <td>150</td>
362
- <td>...</td>
363
- </tr>
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>
364
449
  </tbody>
365
450
  </table>
366
451
  <table>
367
452
  <tbody>
368
- <tr>
369
- <th>State</th>
370
- <th>Value</th>
371
- <th>...</th>
372
- </tr>
373
- <tr>
374
- <td>Georgia</td>
375
- <td>150</td>
376
- <td>...</td>
377
- </tr>
378
- <tr>
379
- <td>Florida</td>
380
- <td>150</td>
381
- <td>...</td>
382
- </tr>
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>
383
468
  </tbody>
384
469
  </table>
385
470
  </div>
386
- <div className={'table-button' + (config.dataDescription && config.dataDescription.horizontal === true ? ' active' : '')} onClick={() => {updateDescriptionProp('horizontal', true)}}>
471
+ <div
472
+ className={'table-button' + (config.dataDescription && config.dataDescription.horizontal === true ? ' active' : '')}
473
+ onClick={() => {
474
+ updateDescriptionProp('horizontal', true)
475
+ }}>
387
476
  <strong>Horizontal</strong>
388
- <p>Geography/X-axis values are contained on a single row.</p>
477
+ <p>Values for map geography or chart date/category axis are contained in a single <em>row</em></p>
389
478
  <table>
390
479
  <tbody>
391
480
  <tr>
@@ -404,153 +493,154 @@ export default function DataImport() {
404
493
  </table>
405
494
  <table>
406
495
  <tbody>
407
- <tr>
408
- <th>State</th>
409
- <td>Georgia</td>
410
- <td>Florida</td>
411
- <td>...</td>
412
- </tr>
413
- <tr>
414
- <th>Value</th>
415
- <td>100</td>
416
- <td>150</td>
417
- <td>...</td>
418
- </tr>
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>
419
508
  </tbody>
420
509
  </table>
421
510
  </div>
422
511
  </div>
423
512
  </div>
424
513
  {config.dataDescription && (
425
- <>
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
+ <>
426
533
  <div className="question">
427
- <div className="heading-4 data-question">Are there multiple series represented in your data?</div>
428
- <div>
429
- <button className={config.dataDescription.series === true ? 'btn btn-primary active' : 'btn btn-primary'} style={{marginRight: '.5em'}} onClick={() => {updateDescriptionProp('series', true)}}>Yes</button>
430
- <button className={config.dataDescription.series === false ? 'btn btn-primary active' : 'btn btn-primary'} onClick={() => {updateDescriptionProp('series', false)}}>No</button>
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>
431
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>
432
604
  </div>
433
- {config.dataDescription.horizontal === true && config.dataDescription.series === true && (
434
- <div className="question">
435
- <div className="heading-4 data-question">Which property in the dataset represents which series the row is describing?</div>
436
- <select onChange={(e) => { updateDescriptionProp('seriesKey', e.target.value) }} value={config.dataDescription.seriesKey}>
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)}}>
437
610
  <option value="">Choose an option</option>
438
- {Object.keys(config.data[0]).map(key => <option value={key}>{key}</option>)}
439
- </select>
440
- </div>
441
- )}
442
- {config.dataDescription.horizontal === false && config.dataDescription.series === true && (
443
- <>
444
- <div className="question">
445
- <div className="heading-4 data-question">Are the series values in your data represented in a single row, or across multiple rows?</div>
446
- <div className="table-button-container">
447
- <div className={'table-button' + (config.dataDescription.singleRow === true ? ' active' : '')} onClick={() => {updateDescriptionProp('singleRow', true)}}>
448
- <p>Each row contains the data for an individual series in itself.</p>
449
- <table>
450
- <tbody>
451
- <tr>
452
- <th>Virus</th>
453
- <th>01/01/2020</th>
454
- <th>02/01/2020</th>
455
- <th>...</th>
456
- </tr>
457
- <tr>
458
- <td>Virus 1</td>
459
- <td>100</td>
460
- <td>150</td>
461
- <td>...</td>
462
- </tr>
463
- <tr>
464
- <td>Virus 2</td>
465
- <td>15</td>
466
- <td>20</td>
467
- <td>...</td>
468
- </tr>
469
- </tbody>
470
- </table>
471
- </div>
472
- <div className={'table-button' + (config.dataDescription.singleRow === false ? ' active' : '')} onClick={() => {updateDescriptionProp('singleRow', false)}}>
473
- <p>Each series data is broken out into multiple rows.</p>
474
- <table>
475
- <tbody>
476
- <tr>
477
- <th>Virus</th>
478
- <th>Date</th>
479
- <th>Value</th>
480
- </tr>
481
- <tr>
482
- <td>Virus 1</td>
483
- <td>01/01/2020</td>
484
- <td>100</td>
485
- </tr>
486
- <tr>
487
- <td>Virus 1</td>
488
- <td>02/01/2020</td>
489
- <td>150</td>
490
- </tr>
491
- <tr>
492
- <td>...</td>
493
- <td>...</td>
494
- <td>...</td>
495
- </tr>
496
- <tr>
497
- <td>Virus 2</td>
498
- <td>01/01/2020</td>
499
- <td>15</td>
500
- </tr>
501
- <tr>
502
- <td>Virus 2</td>
503
- <td>02/01/2020</td>
504
- <td>20</td>
505
- </tr>
506
- <tr>
507
- <td>...</td>
508
- <td>...</td>
509
- <td>...</td>
510
- </tr>
511
- </tbody>
512
- </table>
513
- </div>
514
- </div>
515
- </div>
516
- {config.dataDescription.singleRow === false && (
517
- <>
518
- <div className="question">
519
- <div className="heading-4 data-question">Which property in the dataset represents which series the row is describing?</div>
520
- <select onChange={(e) => {updateDescriptionProp('seriesKey', e.target.value)}}>
521
- <option value="">Choose an option</option>
522
- {Object.keys(config.data[0]).map(key => <option value={key}>{key}</option>)}
523
- </select>
524
- </div>
525
- <div className="question">
526
- <div className="heading-4 data-question">Which property in the dataset represents the X-axis, or geography value?</div>
527
- <select onChange={(e) => {updateDescriptionProp('xKey', e.target.value)}}>
528
- <option value="">Choose an option</option>
529
- {Object.keys(config.data[0]).map(key => <option value={key}>{key}</option>)}
530
- </select>
531
- </div>
532
- <div className="question">
533
- <div className="heading-4 data-question">Which property in the dataset represents the numeric value?</div>
534
- <select onChange={(e) => {updateDescriptionProp('valueKey', e.target.value)}}>
535
- <option value="">Choose an option</option>
536
- {Object.keys(config.data[0]).map(key => <option value={key}>{key}</option>)}
537
- </select>
538
- </div>
539
- </>
540
- )}
541
- </>
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
+ </>
542
629
  )}
543
- </>
630
+ </>
631
+ )}
632
+ </>
544
633
  )}
545
634
  {config.formattedData && (
546
- <button className="btn btn-primary" style={{float: 'right', marginBottom: '2em'}} onClick={() => setGlobalActive(1)}>Select your visualization type &raquo;</button>
635
+ <button className="btn btn-primary" style={{ float: 'right', marginBottom: '2em' }}
636
+ onClick={() => setGlobalActive(1)}>Select your visualization type &raquo;</button>
547
637
  )}
548
- </div>
638
+ </div>
549
639
  )}
550
640
  </div>
551
641
  <div className="right-col">
552
- <PreviewDataTable data={config.data} />
642
+ <PreviewDataTable data={config.data}/>
553
643
  </div>
554
644
  </>
555
- );
645
+ )
556
646
  }