@cdc/editor 4.23.4 → 4.23.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/editor",
3
- "version": "4.23.4",
3
+ "version": "4.23.6",
4
4
  "description": "React component for generating a new component entry",
5
5
  "moduleName": "CdcEditor",
6
6
  "main": "dist/cdceditor",
@@ -24,13 +24,13 @@
24
24
  },
25
25
  "license": "Apache-2.0",
26
26
  "dependencies": {
27
- "@cdc/chart": "^4.23.4",
28
- "@cdc/core": "^4.23.4",
29
- "@cdc/dashboard": "^4.23.4",
30
- "@cdc/data-bite": "^4.23.4",
31
- "@cdc/map": "^4.23.4",
32
- "@cdc/markup-include": "^4.23.4",
33
- "@cdc/waffle-chart": "^4.23.4",
27
+ "@cdc/chart": "^4.23.6",
28
+ "@cdc/core": "^4.23.6",
29
+ "@cdc/dashboard": "^4.23.6",
30
+ "@cdc/data-bite": "^4.23.6",
31
+ "@cdc/map": "^4.23.6",
32
+ "@cdc/markup-include": "^4.23.6",
33
+ "@cdc/waffle-chart": "^4.23.6",
34
34
  "axios": "^0.21.1",
35
35
  "d3": "^7.0.0",
36
36
  "html-react-parser": "^3.0.8",
@@ -43,5 +43,5 @@
43
43
  "react": "^18.2.0",
44
44
  "react-dom": "^18.2.0"
45
45
  },
46
- "gitHead": "dcd395d76f70b2d113f2b4c6fe50a52522655cd1"
46
+ "gitHead": "aaed0388b487adfeb3e7e278b4ce74df09cbaade"
47
47
  }
@@ -19,6 +19,8 @@ import HorizontalStackIcon from '@cdc/core/assets/icon-chart-bar-stacked.svg'
19
19
  import ScatterPlotIcon from '@cdc/core/assets/icon-chart-scatterplot.svg'
20
20
  import BoxPlotIcon from '@cdc/core/assets/icon-chart-box-whisker.svg'
21
21
  import AreaChartIcon from '@cdc/core/assets/icon-area-chart.svg'
22
+ import GaugeChartIcon from '@cdc/core/assets/icon-linear-gauge.svg'
23
+ import InfoIcon from '@cdc/core/assets/icon-info.svg'
22
24
 
23
25
  export default function ChooseTab() {
24
26
  const { config, setConfig, setGlobalActive, tempConfig, setTempConfig } = useContext(ConfigContext)
@@ -37,18 +39,16 @@ export default function ChooseTab() {
37
39
  let isSubType = false
38
40
  let isHorizontalStackedChart = false
39
41
  let classNames
40
-
41
42
  if (type === 'map' && config.general) {
42
43
  let geoType = config.general.geoType
43
44
  isSubType = subType === geoType
44
45
  }
45
-
46
46
  if (type === 'chart') {
47
47
  isSubType = subType === config.visualizationType
48
48
  isHorizontalStackedChart = orientation === config.orientation && stacked === true
49
49
  }
50
50
 
51
- if (type === 'dashboard' || type === 'data-bite' || type === 'waffle-chart' || type === 'markup-include') isSubType = true
51
+ if (type === 'dashboard' || type === 'data-bite' || type === 'markup-include') isSubType = true
52
52
 
53
53
  // TODO: sorry, we should refactor this at some point.
54
54
  // trying to get this out for 4.22.5 - this is so stacked horizontal and bar charts aren't highlighted at the same time.
@@ -137,11 +137,19 @@ export default function ChooseTab() {
137
137
  <li>
138
138
  <Tooltip>
139
139
  <Tooltip.Target>
140
- <IconButton label='Waffle Chart' type='waffle-chart' icon={<WaffleChartIcon />} />
140
+ <IconButton label='Waffle Chart' type='chart' subType='Waffle' icon={<WaffleChartIcon />} />
141
141
  </Tooltip.Target>
142
142
  <Tooltip.Content>Highlight a piece of data in relationship to a data set.</Tooltip.Content>
143
143
  </Tooltip>
144
144
  </li>
145
+ {/* <li>
146
+ <Tooltip>
147
+ <Tooltip.Target>
148
+ <IconButton label='Gauge Chart' type='chart' subType='Gauge' icon={<GaugeChartIcon />} />
149
+ </Tooltip.Target>
150
+ <Tooltip.Content>Specify the calculation of a single data point (such as a percentage value) and present it on a horizontal scale.</Tooltip.Content>
151
+ </Tooltip>
152
+ </li> */}
145
153
  </ul>
146
154
 
147
155
  <div className='heading-2'>Charts</div>
@@ -206,13 +214,21 @@ export default function ChooseTab() {
206
214
  <Tooltip.Content>Display a scatter plot</Tooltip.Content>
207
215
  </Tooltip>
208
216
  </li>
209
- {/* <li>
217
+ <li>
210
218
  <Tooltip>
211
219
  <Tooltip.Target>
212
220
  <IconButton label='Area Chart' type='chart' subType='Area Chart' orientation='vertical' icon={<AreaChartIcon />} />
213
221
  </Tooltip.Target>
214
222
  <Tooltip.Content>Display an area chart</Tooltip.Content>
215
223
  </Tooltip>
224
+ </li>
225
+ {/* <li>
226
+ <Tooltip>
227
+ <Tooltip.Target>
228
+ <IconButton label='Forecast Chart' type='chart' subType='Forecasting' orientation='vertical' icon={<InfoIcon />} />
229
+ </Tooltip.Target>
230
+ <Tooltip.Content>Display a forecasting chart</Tooltip.Content>
231
+ </Tooltip>
216
232
  </li> */}
217
233
  </ul>
218
234
 
@@ -25,11 +25,19 @@ export default function ConfigureTab({ containerEl }) {
25
25
  </ErrorBoundary>
26
26
  )
27
27
  case 'chart':
28
- return (
29
- <ErrorBoundary component='CdcChart'>
30
- <CdcChart isEditor={true} isDebug={isDebug} config={config} setConfig={setTempConfig} />
31
- </ErrorBoundary>
32
- )
28
+ if (config.visualizationType === 'Waffle' || config.visualizationType === 'Gauge') {
29
+ return (
30
+ <ErrorBoundary component='CdcWaffleChart'>
31
+ <CdcWaffleChart isEditor={true} isDebug={isDebug} config={config} setConfig={setTempConfig} />
32
+ </ErrorBoundary>
33
+ )
34
+ } else {
35
+ return (
36
+ <ErrorBoundary component='CdcChart'>
37
+ <CdcChart isEditor={true} isDebug={isDebug} config={config} setConfig={setTempConfig} />
38
+ </ErrorBoundary>
39
+ )
40
+ }
33
41
  case 'dashboard':
34
42
  return (
35
43
  <ErrorBoundary component='CdcDashboard'>
@@ -42,12 +50,6 @@ export default function ConfigureTab({ containerEl }) {
42
50
  <CdcDataBite isEditor={true} isDebug={isDebug} config={config} setConfig={setTempConfig} />
43
51
  </ErrorBoundary>
44
52
  )
45
- case 'waffle-chart':
46
- return (
47
- <ErrorBoundary component='CdcDashboard'>
48
- <CdcWaffleChart isEditor={true} isDebug={isDebug} config={config} setConfig={setTempConfig} />
49
- </ErrorBoundary>
50
- )
51
53
  case 'markup-include':
52
54
  return (
53
55
  <ErrorBoundary component='CdcDashboard'>
@@ -17,7 +17,10 @@ import SampleData from './SampleData'
17
17
  import FileUploadIcon from '../assets/icons/file-upload-solid.svg'
18
18
  import CloseIcon from '@cdc/core/assets/icon-close.svg'
19
19
 
20
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
20
21
  import DataDesigner from '@cdc/core/components/managers/DataDesigner'
22
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
23
+ import Icon from '@cdc/core/components/ui/Icon'
21
24
 
22
25
  import '../scss/data-import.scss'
23
26
 
@@ -36,6 +39,8 @@ export default function DataImport() {
36
39
 
37
40
  const [editingDataset, setEditingDataset] = useState()
38
41
 
42
+ const [asyncPreviewData, setAsyncPreviewData] = useState()
43
+
39
44
  const supportedDataTypes = {
40
45
  '.csv': 'text/csv',
41
46
  '.json': 'application/json'
@@ -204,7 +209,6 @@ export default function DataImport() {
204
209
  // Validate parsed data and set if no issues.
205
210
  try {
206
211
  text = transform.autoStandardize(text)
207
-
208
212
  if (config.data && config.series) {
209
213
  if (dataExists(text, config.series, config?.xAxis.dataKey)) {
210
214
  if (config.type === 'dashboard') {
@@ -257,7 +261,6 @@ export default function DataImport() {
257
261
  } else {
258
262
  if (config.type === 'dashboard') {
259
263
  let newDatasets = { ...config.datasets }
260
-
261
264
  Object.keys(newDatasets).forEach(datasetKey => (newDatasets[datasetKey].preview = false))
262
265
 
263
266
  newDatasets[editingDatasetKey || fileSource] = {
@@ -294,8 +297,6 @@ export default function DataImport() {
294
297
  setEditingDataset(undefined)
295
298
  }
296
299
  setAddingDataset(false)
297
- setExternalURL('')
298
- setKeepURL(false)
299
300
  } catch (err) {
300
301
  setErrors(err)
301
302
  }
@@ -320,6 +321,34 @@ export default function DataImport() {
320
321
  setConfig(newConfig)
321
322
  }, []) // eslint-disable-line
322
323
 
324
+ useEffect(() => {
325
+ const asyncWrapper = async () => {
326
+ if (config.type === 'dashboard') {
327
+ Object.keys(config.datasets).forEach(async datasetKey => {
328
+ if (config.datasets[datasetKey].preview) {
329
+ if (config.datasets[datasetKey].dataUrl) {
330
+ const remoteData = await fetchRemoteData(config.datasets[datasetKey].dataUrl)
331
+ if (Array.isArray(remoteData)) {
332
+ setAsyncPreviewData(remoteData)
333
+ }
334
+ } else if (Array.isArray(config.datasets[datasetKey].data)) {
335
+ setAsyncPreviewData(config.datasets[datasetKey].data)
336
+ }
337
+ }
338
+ })
339
+ } else {
340
+ if (config.dataUrl) {
341
+ const remoteData = await fetchRemoteData(config.dataUrl)
342
+ if (Array.isArray(remoteData)) {
343
+ setAsyncPreviewData(remoteData)
344
+ }
345
+ }
346
+ }
347
+ }
348
+
349
+ asyncWrapper()
350
+ }, [config.datasets]) // eslint-disable-line
351
+
323
352
  const updateDescriptionProp = (visualizationKey, datasetKey, key, value) => {
324
353
  if (config.type === 'dashboard') {
325
354
  let dataDescription = { ...config.datasets[datasetKey].dataDescription, [key]: value }
@@ -395,9 +424,9 @@ export default function DataImport() {
395
424
  return (
396
425
  //todo convert to modal
397
426
  <>
398
- <button className='btn danger' onClick={() => resetEditor({ type: config.type, visualizationType: config.visualizationType }, 'Resetting will remove your data and settings. Do you want to continue?')}>
399
- Clear
400
- <CloseIcon />
427
+ <button className='btn danger' onClick={() => resetEditor({ type: config.type, visualizationType: config.visualizationType }, 'Resetting will remove your data and settings. Do you want to continue?')}>
428
+ Clear
429
+ <CloseIcon />
401
430
  </button>
402
431
  {/* DEV-851 link to replace file should pop file dialog */}
403
432
  {config.dataFileSourceType === 'file' && (
@@ -406,7 +435,8 @@ export default function DataImport() {
406
435
  <p>
407
436
  <span>or replace file</span>
408
437
  </p>
409
- </div>)}
438
+ </div>
439
+ )}
410
440
  </>
411
441
  )
412
442
  }
@@ -475,7 +505,7 @@ export default function DataImport() {
475
505
  if (config.type === 'dashboard') {
476
506
  readyToConfigure = Object.keys(config.datasets).length > 0
477
507
  Object.keys(config.datasets).forEach(datasetKey => {
478
- if (config.datasets[datasetKey].preview) {
508
+ if (config.datasets[datasetKey].preview && Array.isArray(config.datasets[datasetKey].data)) {
479
509
  previewData = config.datasets[datasetKey].data
480
510
  }
481
511
  })
@@ -491,6 +521,139 @@ export default function DataImport() {
491
521
  readyToConfigure = true
492
522
  }
493
523
 
524
+ const urlFilters = (
525
+ <>
526
+ {config.filters &&
527
+ config.filters.map((filter, i) =>
528
+ filter.type !== 'url' ? (
529
+ <></>
530
+ ) : (
531
+ <fieldset key={filter.key} className='edit-block url-filters-block'>
532
+ <button
533
+ onClick={e => {
534
+ let newFilters = [...config.filters]
535
+ newFilters.splice(i, 1)
536
+ setConfig({ ...config, filters: newFilters, runtimeDataUrl: undefined })
537
+ }}
538
+ >
539
+ Remove
540
+ </button>
541
+ <label>
542
+ <span class='edit-label column-heading'>
543
+ Label
544
+ <Tooltip style={{ textTransform: 'none' }}>
545
+ <Tooltip.Target>
546
+ <Icon display='question' />
547
+ </Tooltip.Target>
548
+ <Tooltip.Content>
549
+ <p style={{ padding: '0.5rem' }}>The label that will appear above the dropdown filter.</p>
550
+ </Tooltip.Content>
551
+ </Tooltip>
552
+ </span>{' '}
553
+ <input
554
+ type='text'
555
+ defaultValue={filter.label}
556
+ onChange={e => {
557
+ let newFilters = [...config.filters]
558
+ newFilters[i].label = e.target.value
559
+ setConfig({ ...config, filters: newFilters })
560
+ }}
561
+ />
562
+ </label>
563
+ <label>
564
+ <span class='edit-label column-heading'>
565
+ Query string parameter
566
+ <Tooltip style={{ textTransform: 'none' }}>
567
+ <Tooltip.Target>
568
+ <Icon display='question' />
569
+ </Tooltip.Target>
570
+ <Tooltip.Content>
571
+ <p style={{ padding: '0.5rem' }}>Name of the query string parameter that will be appended to the URL above with the values provided below.</p>
572
+ </Tooltip.Content>
573
+ </Tooltip>
574
+ </span>{' '}
575
+ <input
576
+ type='text'
577
+ defaultValue={filter.queryParameter}
578
+ onChange={e => {
579
+ let newFilters = [...config.filters]
580
+ newFilters[i].queryParameter = e.target.value
581
+ setConfig({ ...config, filters: newFilters })
582
+ }}
583
+ />
584
+ </label>
585
+ <label>
586
+ <span class='edit-label column-heading'>Values</span>{' '}
587
+ </label>
588
+ <ul className='value-list'>
589
+ {filter.orderedValues &&
590
+ filter.orderedValues.map((value, valueIndex) => (
591
+ <li>
592
+ {value}
593
+ <input
594
+ type='text'
595
+ placeholder='Enter value display name here'
596
+ value={filter.labels ? filter.labels[value] : undefined}
597
+ className='url-value-label'
598
+ onChange={e => {
599
+ let newFilters = [...config.filters]
600
+
601
+ newFilters[i].labels = newFilters[i].labels || {}
602
+ newFilters[i].labels[value] = e.target.value
603
+
604
+ setConfig({ ...config, filters: newFilters })
605
+ }}
606
+ />
607
+ <button
608
+ onClick={() => {
609
+ let newFilters = [...config.filters]
610
+
611
+ if (newFilters[i].labels) {
612
+ delete newFilters[i].labels[newFilters[i].orderedValues[valueIndex]]
613
+ }
614
+
615
+ newFilters[i].orderedValues.splice(valueIndex, 1)
616
+ setConfig({ ...config, filters: newFilters })
617
+ }}
618
+ >
619
+ X
620
+ </button>
621
+ </li>
622
+ ))}
623
+ </ul>
624
+ <form
625
+ onSubmit={e => {
626
+ e.preventDefault()
627
+ if (!config.filters[i].orderedValues || config.filters[i].orderedValues.indexOf(e.target[0].value) === -1) {
628
+ let newFilters = [...config.filters]
629
+ newFilters[i].orderedValues = newFilters[i].orderedValues || []
630
+ newFilters[i].orderedValues.push(e.target[0].value)
631
+ newFilters[i].values = newFilters[i].orderedValues
632
+ if (!newFilters[i].active) newFilters[i].active = e.target[0].value
633
+ e.target[0].value = ''
634
+ setConfig({ ...config, filters: newFilters })
635
+ }
636
+ }}
637
+ >
638
+ <input type='text' placeholder='Enter new value name here' />{' '}
639
+ <button type='submit' style={{ marginTop: '1em' }}>
640
+ Add New Value
641
+ </button>
642
+ </form>
643
+ </fieldset>
644
+ )
645
+ )}
646
+ <button
647
+ className='btn full-width'
648
+ onClick={() => {
649
+ setConfig({ ...config, filters: config.filters ? [...config.filters, { type: 'url', key: Date.now() }] : [{ type: 'url', key: Date.now() }] })
650
+ }}
651
+ >
652
+ Add New URL Filter
653
+ </button>
654
+ </>
655
+ )
656
+
494
657
  const showDataDesigner = config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Scatter Plot'
495
658
 
496
659
  return (
@@ -560,7 +723,7 @@ export default function DataImport() {
560
723
  <>
561
724
  <div className='heading-3'>Data Source</div>
562
725
  <div className='file-loaded-area'>
563
- {config.dataFileSourceType === 'file' && (
726
+ {(config.dataFileSourceType === 'file' || !config.dataFileSourceType) && (
564
727
  <div className='data-source-options'>
565
728
  <div className={isDragActive2 ? 'drag-active cdcdataviz-file-selector loaded-file' : 'cdcdataviz-file-selector loaded-file'} {...getRootProps2()}>
566
729
  <input {...getInputProps2()} />
@@ -577,10 +740,13 @@ export default function DataImport() {
577
740
  )}
578
741
 
579
742
  {config.dataFileSourceType === 'url' && (
580
- <div className='url-source-options'>
581
- <div>{loadFileFromUrl(externalURL)}</div>
582
- <div>{resetButton()}</div>
583
- </div>
743
+ <>
744
+ <div className='url-source-options'>
745
+ <div>{loadFileFromUrl(externalURL)}</div>
746
+ <div>{resetButton()}</div>
747
+ </div>
748
+ {config.dataUrl && (config.type === 'chart' || config.type === 'map') && urlFilters}
749
+ </>
584
750
  )}
585
751
  </div>
586
752
  </>
@@ -614,10 +780,10 @@ export default function DataImport() {
614
780
  {errors &&
615
781
  (errors.map
616
782
  ? errors.map((message, index) => (
617
- <div className='error-box slim mt-2' key={`error-${message}`}>
618
- <span>{message}</span> <CloseIcon className='inline-icon dismiss-error' onClick={() => setErrors(errors.filter((val, i) => i !== index))} />
619
- </div>
620
- ))
783
+ <div className='error-box slim mt-2' key={`error-${message}`}>
784
+ <span>{message}</span> <CloseIcon className='inline-icon dismiss-error' onClick={() => setErrors(errors.filter((val, i) => i !== index))} />
785
+ </div>
786
+ ))
621
787
  : errors.message)}
622
788
  <p className='footnote'>
623
789
  Supported file types: {Object.keys(supportedDataTypes).join(', ')}. Maximum file size {maxFileSize}MB.
@@ -627,29 +793,24 @@ export default function DataImport() {
627
793
  <SampleDataContext.Provider value={{ loadData, editingDataset, config }}>
628
794
  <SampleData.Buttons />
629
795
  </SampleDataContext.Provider>
630
- </div >
631
- )
632
- }
796
+ </div>
797
+ )}
633
798
 
634
- {
635
- config.type === 'dashboard' && !addingDataset && (
636
- <p>
637
- <button className='btn btn-primary' onClick={() => setAddingDataset(true)}>
638
- + Add More Files
639
- </button>
640
- </p>
641
- )
642
- }
799
+ {config.type === 'dashboard' && !addingDataset && (
800
+ <p>
801
+ <button className='btn btn-primary' onClick={() => setAddingDataset(true)}>
802
+ + Add More Files
803
+ </button>
804
+ </p>
805
+ )}
643
806
 
644
- {
645
- readyToConfigure && (
646
- <p>
647
- <button className='btn btn-primary' onClick={() => setGlobalActive(2)}>
648
- Configure your visualization
649
- </button>
650
- </p>
651
- )
652
- }
807
+ {readyToConfigure && (
808
+ <p>
809
+ <button className='btn btn-primary' onClick={() => setGlobalActive(2)}>
810
+ Configure your visualization
811
+ </button>
812
+ </p>
813
+ )}
653
814
 
654
815
  <a href='https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/data-map.html' target='_blank' rel='noopener noreferrer' className='guidance-link'>
655
816
  <div>
@@ -657,9 +818,9 @@ export default function DataImport() {
657
818
  <p>Documentation and examples on formatting data and configuring visualizations.</p>
658
819
  </div>
659
820
  </a>
660
- </div >
821
+ </div>
661
822
  <div className='right-col'>
662
- <PreviewDataTable data={previewData} />
823
+ <PreviewDataTable data={asyncPreviewData || previewData} />
663
824
  </div>
664
825
  </>
665
826
  )
@@ -10,6 +10,7 @@ import validScatterPlot from './../samples/valid-scatterplot.csv?raw'
10
10
  import validBoxPlotData from './../samples/valid-boxplot.csv?raw'
11
11
  import validAreaChart from './../samples/valid-area-chart.json?raw'
12
12
  import validWorldGeocodeData from './../samples/valid-world-geocode.json?raw'
13
+ import validForecastData from './../samples/valid-forecast-data.csv?raw'
13
14
 
14
15
  // Add additional data to samples
15
16
  const sampleData = {
@@ -28,11 +29,16 @@ const sampleData = {
28
29
  text: 'Scatter Plot Sample Data',
29
30
  fileName: 'valid-scatterplot.csv',
30
31
  data: validScatterPlot
32
+ },
33
+ {
34
+ text: 'Area Chart Sample Data',
35
+ fileName: 'valid-area-chart.json',
36
+ data: validAreaChart
31
37
  }
32
38
  // {
33
- // text: 'Area Chart Sample Data',
34
- // fileName: 'valid-area-chart.json',
35
- // data: validAreaChart
39
+ // text: 'Forecast Chart Data',
40
+ // fileName: 'valid-forecast-data.csv',
41
+ // data: validForecastData
36
42
  // }
37
43
  ],
38
44
  maps: [