@cdc/dashboard 1.1.2 → 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.
Files changed (36) hide show
  1. package/dist/cdcdashboard.js +159 -48
  2. package/examples/default-filter-control.json +175 -0
  3. package/examples/default-multi-dataset.json +498 -0
  4. package/examples/default.json +36 -348
  5. package/examples/private/chart-issue.json +3467 -0
  6. package/examples/private/no-issue.json +3467 -0
  7. package/examples/private/totals-two.json +104 -0
  8. package/examples/private/totals.json +103 -0
  9. package/examples/temp-example-data.json +130 -0
  10. package/examples/test-example.json +1 -0
  11. package/package.json +14 -8
  12. package/src/CdcDashboard.js +282 -156
  13. package/src/CdcDashboard.jsx +668 -0
  14. package/src/{context.tsx → ConfigContext.js} +0 -0
  15. package/src/components/{Column.js → Column.jsx} +9 -7
  16. package/src/components/DataTable.tsx +55 -54
  17. package/src/components/EditorPanel.js +207 -45
  18. package/src/components/{Grid.js → Grid.jsx} +5 -4
  19. package/src/components/Header.jsx +242 -0
  20. package/src/components/Row.js +1 -0
  21. package/src/components/Row.jsx +181 -0
  22. package/src/components/Row.jsx~HEAD +212 -0
  23. package/src/components/Widget.js +24 -5
  24. package/src/components/Widget.jsx +191 -0
  25. package/src/index.html +14 -11
  26. package/src/scss/editor-panel.scss +53 -49
  27. package/src/scss/grid.scss +61 -14
  28. package/src/scss/main.scss +73 -9
  29. package/LICENSE +0 -201
  30. package/src/components/Header.js +0 -15
  31. package/src/images/icon-close.svg +0 -1
  32. package/src/images/icon-down.svg +0 -1
  33. package/src/images/icon-edit.svg +0 -1
  34. package/src/images/icon-move.svg +0 -8
  35. package/src/images/icon-up.svg +0 -1
  36. package/src/images/warning.svg +0 -1
@@ -0,0 +1,668 @@
1
+ import React, { useState, useEffect, useCallback } from 'react'
2
+
3
+ // IE11
4
+ import 'core-js/stable'
5
+ import 'whatwg-fetch'
6
+ import ResizeObserver from 'resize-observer-polyfill'
7
+
8
+ import { DndProvider } from 'react-dnd'
9
+ import { HTML5Backend } from 'react-dnd-html5-backend'
10
+
11
+ import parse from 'html-react-parser'
12
+
13
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
14
+ import { GlobalContextProvider } from '@cdc/core/components/GlobalContext'
15
+ import ConfigContext from './ConfigContext'
16
+
17
+ import OverlayFrame from '@cdc/core/components/ui/OverlayFrame'
18
+ import Loading from '@cdc/core/components/Loading'
19
+ import { DataTransform } from '@cdc/core/helpers/DataTransform'
20
+ import getViewport from '@cdc/core/helpers/getViewport'
21
+
22
+ import CdcMap from '@cdc/map'
23
+ import CdcChart from '@cdc/chart'
24
+ import CdcDataBite from '@cdc/data-bite'
25
+ import CdcWaffleChart from '@cdc/waffle-chart'
26
+ import CdcMarkupInclude from '@cdc/markup-include'
27
+
28
+ import Grid from './components/Grid'
29
+ import Header from './components/Header'
30
+ import defaults from './data/initial-state'
31
+ import Widget from './components/Widget'
32
+ import DataTable from './components/DataTable'
33
+
34
+ import './scss/main.scss'
35
+ import '@cdc/core/styles/v2/main.scss'
36
+
37
+ const addVisualization = (type, subType) => {
38
+ let newVisualizationConfig = {
39
+ newViz: true,
40
+ openModal: true,
41
+ uid: type + Date.now(),
42
+ type
43
+ }
44
+
45
+ switch (type) {
46
+ case 'chart':
47
+ newVisualizationConfig.visualizationType = subType
48
+ break
49
+ case 'map':
50
+ newVisualizationConfig.general = {}
51
+ newVisualizationConfig.general.geoType = subType
52
+ break
53
+ case 'data-bite':
54
+ newVisualizationConfig.visualizationType = type
55
+ break
56
+ case 'waffle-chart':
57
+ newVisualizationConfig.visualizationType = type
58
+ break
59
+ case 'markup-include':
60
+ newVisualizationConfig.visualizationType = type
61
+ break
62
+ default:
63
+ newVisualizationConfig.visualizationType = type
64
+ break
65
+ }
66
+
67
+ return newVisualizationConfig
68
+ }
69
+
70
+ const VisualizationsPanel = () => (
71
+ <div className="visualizations-panel">
72
+ <p style={{ fontSize: '14px' }}>Click and drag an item onto the grid to add it to your dashboard.</p>
73
+ <span className="subheading-3">Chart</span>
74
+ <div className="drag-grid">
75
+ <Widget addVisualization={() => addVisualization('chart', 'Bar')} type="Bar"/>
76
+ <Widget addVisualization={() => addVisualization('chart', 'Line')} type="Line"/>
77
+ <Widget addVisualization={() => addVisualization('chart', 'Pie')} type="Pie"/>
78
+ </div>
79
+ <span className="subheading-3">Map</span>
80
+ <div className="drag-grid">
81
+ <Widget addVisualization={() => addVisualization('map', 'us')} type="us"/>
82
+ <Widget addVisualization={() => addVisualization('map', 'world')} type="world"/>
83
+ <Widget addVisualization={() => addVisualization('map', 'single-state')} type="single-state"/>
84
+ </div>
85
+ <span className="subheading-3">Misc.</span>
86
+ <div className="drag-grid">
87
+ <Widget addVisualization={() => addVisualization('data-bite', '')} type="data-bite"/>
88
+ <Widget addVisualization={() => addVisualization('waffle-chart', '')} type="waffle-chart"/>
89
+ <Widget addVisualization={() => addVisualization('markup-include', '')} type="markup-include"/>
90
+ </div>
91
+ </div>
92
+ )
93
+
94
+ export default function CdcDashboard({ configUrl = '', config: configObj = undefined, isEditor = false, setConfig: setParentConfig }) {
95
+ const [ config, setConfig ] = useState(configObj ?? {})
96
+ const [ data, setData ] = useState([])
97
+ const [ filteredData, setFilteredData ] = useState()
98
+ const [ loading, setLoading ] = useState(true)
99
+ const [ preview, setPreview ] = useState(false)
100
+ const [ tabSelected, setTabSelected ] = useState(0);
101
+ const [ currentViewport, setCurrentViewport ] = useState('lg')
102
+
103
+ const { title, description } = config.dashboard || config
104
+
105
+ const transform = new DataTransform()
106
+
107
+ const processData = async (config) => {
108
+ let dataset = config.formattedData || config.data
109
+
110
+ if (config.dataUrl) {
111
+ dataset = await fetchRemoteData(config.dataUrl)
112
+
113
+ if (dataset && config.dataDescription) {
114
+ try {
115
+ dataset = transform.autoStandardize(data)
116
+ dataset = transform.developerStandardize(data, config.dataDescription)
117
+ } catch (e) {
118
+ //Data not able to be standardized, leave as is
119
+ }
120
+ }
121
+ }
122
+
123
+ return dataset
124
+ }
125
+
126
+ const loadConfig = async () => {
127
+ let response = configObj || await (await fetch(configUrl)).json()
128
+ let newConfig = { ...defaults, ...response }
129
+ let datasets = {}
130
+
131
+ if (response.datasets) {
132
+ await Promise.all(Object.keys(response.datasets).map(async (key) => {
133
+ datasets[key] = await processData(response.datasets[key])
134
+ }))
135
+ } else {
136
+ let dataKey = newConfig.dataFileName || 'backwards-compatibility'
137
+ datasets[dataKey] = await processData(response)
138
+
139
+ let datasetsFull = {};
140
+ datasetsFull[dataKey] = {
141
+ data: datasets[dataKey],
142
+ dataDescription: newConfig.dataDescription
143
+ }
144
+ newConfig.datasets = datasetsFull;
145
+
146
+ Object.keys(newConfig.visualizations).forEach(vizKey => {
147
+ newConfig.visualizations[vizKey].dataKey = dataKey
148
+ newConfig.visualizations[vizKey].dataDescription = newConfig.dataDescription
149
+ newConfig.visualizations[vizKey].formattedData = newConfig.formattedData
150
+ })
151
+
152
+ delete newConfig.data
153
+ delete newConfig.dataUrl
154
+ delete newConfig.dataFileName
155
+ delete newConfig.dataFileSourceType
156
+ delete newConfig.dataDescription
157
+ delete newConfig.formattedData
158
+
159
+ if (newConfig.dashboard && newConfig.dashboard.filters) {
160
+ newConfig.dashboard.sharedFilters = newConfig.dashboard.sharedFilters || []
161
+ newConfig.dashboard.filters.forEach(filter => {
162
+ newConfig.dashboard.sharedFilters.push({ ...filter, key: filter.label, showDropdown: true, usedBy: Object.keys(newConfig.visualizations) })
163
+ })
164
+
165
+ delete newConfig.dashboard.filters
166
+ }
167
+ }
168
+
169
+ setData(datasets)
170
+
171
+ updateConfig(newConfig, datasets)
172
+ setLoading(false)
173
+ }
174
+
175
+ const filterData = (filters, data) => {
176
+ let filteredData = []
177
+
178
+ if(data){
179
+ data.forEach((row) => {
180
+ let add = true
181
+
182
+ filters.forEach((filter) => {
183
+ if (row[filter.columnName] !== filter.active) {
184
+ add = false
185
+ }
186
+ })
187
+
188
+ if (add) filteredData.push(row)
189
+ })
190
+
191
+ return filteredData
192
+ }
193
+ }
194
+
195
+ const setSharedFilter = (key, datum) => {
196
+ let newConfig = { ...config }
197
+ let newFilteredData = { ...filteredData }
198
+
199
+ for (let i = 0; i < newConfig.dashboard.sharedFilters.length; i++) {
200
+ if (newConfig.dashboard.sharedFilters[i].setBy === key) {
201
+ newConfig.dashboard.sharedFilters[i].active = datum[newConfig.dashboard.sharedFilters[i].columnName]
202
+ break
203
+ }
204
+ }
205
+
206
+ Object.keys(newConfig.visualizations).forEach(visualizationKey => {
207
+ let applicableFilters = newConfig.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) !== -1);
208
+
209
+ if (applicableFilters.length > 0) {
210
+ const visualization = newConfig.visualizations[visualizationKey]
211
+
212
+ newFilteredData[visualizationKey] = filterData(applicableFilters, visualization.formattedData || data[visualization.dataKey])
213
+ }
214
+ })
215
+
216
+ setFilteredData(newFilteredData)
217
+ setConfig(newConfig)
218
+ }
219
+
220
+ // Gets filer values from dataset
221
+ const generateValuesForFilter = (columnName, data = this.state.data) => {
222
+ const values = []
223
+
224
+ Object.keys(data).forEach(key => {
225
+ data[key].forEach((row) => {
226
+ const value = row[columnName]
227
+ if (value && false === values.includes(value)) {
228
+ values.push(value)
229
+ }
230
+ })
231
+ })
232
+
233
+ return values
234
+ }
235
+
236
+ const updateConfig = (newConfig, dataOverride = null) => {
237
+ let newFilteredData = {}
238
+ let visualizationKeys = Object.keys(newConfig.visualizations)
239
+
240
+ if (newConfig.dashboard.sharedFilters) {
241
+ newConfig.dashboard.sharedFilters.forEach((filter, i) => {
242
+ for (let j = 0; j < visualizationKeys.length; j++) {
243
+ if (visualizationKeys[j] === filter.setBy) {
244
+ const filterValues = generateValuesForFilter(filter.columnName, (dataOverride || data))
245
+
246
+ if (newConfig.dashboard.sharedFilters[i].order === 'asc') {
247
+ filterValues.sort()
248
+ }
249
+ if (newConfig.dashboard.sharedFilters[i].order === 'desc') {
250
+ filterValues.sort().reverse()
251
+ }
252
+
253
+ newConfig.dashboard.sharedFilters[i].values = filterValues
254
+ if(filterValues.length > 0){
255
+ newConfig.dashboard.sharedFilters[i].active = newConfig.dashboard.sharedFilters[i].active || newConfig.dashboard.sharedFilters[i].values[0];
256
+ }
257
+ break
258
+ }
259
+ }
260
+
261
+ if ((!newConfig.dashboard.sharedFilters[i].values || newConfig.dashboard.sharedFilters[i].values.length === 0) && newConfig.dashboard.sharedFilters[i].showDropdown) {
262
+ newConfig.dashboard.sharedFilters[i].values = generateValuesForFilter(filter.columnName, (dataOverride || data))
263
+ if(newConfig.dashboard.sharedFilters[i].values.length > 0){
264
+ newConfig.dashboard.sharedFilters[i].active = newConfig.dashboard.sharedFilters[i].active || newConfig.dashboard.sharedFilters[i].values[0];
265
+ }
266
+ }
267
+ })
268
+
269
+ visualizationKeys.forEach(visualizationKey => {
270
+ let applicableFilters = newConfig.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) !== -1);
271
+
272
+ if (applicableFilters.length > 0) {
273
+ newFilteredData[visualizationKey] = filterData(applicableFilters, newConfig.visualizations[visualizationKey].formattedData || newConfig.visualizations[visualizationKey].data || (dataOverride || data)[newConfig.visualizations[visualizationKey].dataKey])
274
+ }
275
+ })
276
+ }
277
+
278
+ setFilteredData(newFilteredData)
279
+
280
+ //Enforce default values that need to be calculated at runtime
281
+ newConfig.runtime = {}
282
+ setConfig(newConfig)
283
+ }
284
+
285
+ // Load data when component first mounts
286
+ useEffect(() => {
287
+ loadConfig()
288
+ }, [])
289
+
290
+ // Pass up to <CdcEditor /> if it exists when config state changes
291
+ useEffect(() => {
292
+ if (setParentConfig && isEditor) {
293
+ setParentConfig(config)
294
+ }
295
+ }, [ config ])
296
+
297
+ const updateChildConfig = (visualizationKey, newConfig) => {
298
+ let updatedConfig = { ...config }
299
+
300
+ updatedConfig.visualizations[visualizationKey] = newConfig
301
+ updatedConfig.visualizations[visualizationKey].formattedData = config.visualizations[visualizationKey].formattedData
302
+
303
+ setConfig(updatedConfig)
304
+ }
305
+
306
+ const Filters = () => {
307
+ const changeFilterActive = (index, value) => {
308
+ let dashboardConfig = { ...config.dashboard }
309
+
310
+ dashboardConfig.sharedFilters[index].active = value
311
+
312
+ setConfig({ ...config, dashboard: dashboardConfig })
313
+
314
+ let newFilteredData = {}
315
+ Object.keys(config.visualizations).forEach(key => {
316
+ let applicableFilters = dashboardConfig.sharedFilters.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(key) !== -1)
317
+ if(applicableFilters.length > 0){
318
+ newFilteredData[key] = filterData(applicableFilters, config.visualizations[key].formattedData || data[config.visualizations[key].dataKey])
319
+ }
320
+ })
321
+
322
+ setFilteredData(newFilteredData)
323
+ }
324
+
325
+ const announceChange = (text) => {}
326
+
327
+ return config.dashboard.sharedFilters.map((singleFilter, index) => {
328
+ if (!singleFilter.showDropdown) return
329
+
330
+ const values = []
331
+
332
+ singleFilter.values.forEach((filterOption, index) => {
333
+ values.push(<option
334
+ key={`${singleFilter.key}-option-${index}`}
335
+ value={filterOption}
336
+ >{filterOption}
337
+ </option>)
338
+ })
339
+
340
+ return (
341
+ <section className="dashboard-filters-section" key={`${singleFilter.key}-filtersection-${index}`}>
342
+ <label htmlFor={`filter-${index}`}>{singleFilter.key}</label>
343
+ <select
344
+ id={`filter-${index}`}
345
+ className="filter-select"
346
+ data-index="0"
347
+ value={singleFilter.active}
348
+ onChange={(val) => {
349
+ changeFilterActive(index, val.target.value)
350
+ announceChange(`Filter ${singleFilter.key} value has been changed to ${val.target.value}, please reference the data table to see updated values.`)
351
+ }}
352
+ >
353
+ {values}
354
+ </select>
355
+ </section>
356
+ )
357
+ })
358
+ }
359
+
360
+ const resizeObserver = new ResizeObserver(entries => {
361
+ for (let entry of entries) {
362
+ let newViewport = getViewport(entry.contentRect.width)
363
+
364
+ setCurrentViewport(newViewport)
365
+ }
366
+ })
367
+
368
+ const outerContainerRef = useCallback(node => {
369
+ if (node !== null) {
370
+ resizeObserver.observe(node)
371
+ }
372
+ }, [])
373
+
374
+ // Prevent render if loading
375
+ if (loading) return <Loading/>
376
+
377
+ let body = null
378
+
379
+ // Editor mode
380
+ if (isEditor && !preview) {
381
+ let subVisualizationEditing = false
382
+
383
+ Object.keys(config.visualizations).forEach(visualizationKey => {
384
+ let visualizationConfig = { ...config.visualizations[visualizationKey] }
385
+
386
+ const dataKey = visualizationConfig.dataKey || 'backwards-compatibility'
387
+
388
+ visualizationConfig.data = filteredData && filteredData[visualizationKey] ? filteredData[visualizationKey] : data[dataKey]
389
+ if (visualizationConfig.formattedData) {
390
+ visualizationConfig.originalFormattedData = visualizationConfig.formattedData;
391
+ visualizationConfig.formattedData = visualizationConfig.data
392
+ }
393
+
394
+ const setsSharedFilter = config.dashboard.sharedFilters && config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === visualizationKey).length > 0
395
+ const setSharedFilterValue = setsSharedFilter ? config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === visualizationKey)[0].active : undefined
396
+
397
+ if (visualizationConfig.editing) {
398
+ subVisualizationEditing = true
399
+
400
+ const back = () => {
401
+ const newConfig = { ...config }
402
+
403
+ delete newConfig.visualizations[visualizationKey].editing
404
+
405
+ setConfig(newConfig)
406
+ }
407
+
408
+ const updateConfig = (newConfig) => {
409
+ let dataCorrectedConfig = visualizationConfig.originalFormattedData ? {...newConfig, formattedData: visualizationConfig.originalFormattedData} : newConfig;
410
+ updateChildConfig(visualizationKey, dataCorrectedConfig)
411
+ }
412
+
413
+ switch (visualizationConfig.type) {
414
+ case 'chart':
415
+ body = <><Header tabSelected={tabSelected} setTabSelected={setTabSelected} back={back} subEditor="Chart"/>
416
+ <CdcChart
417
+ key={visualizationKey}
418
+ config={visualizationConfig}
419
+ isEditor={true}
420
+ setConfig={updateConfig}
421
+ setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
422
+ isDashboard={true}
423
+ />
424
+ </>
425
+ break
426
+ case 'map':
427
+ body = <><Header tabSelected={tabSelected} setTabSelected={setTabSelected} back={back} subEditor="Map"/>
428
+ <CdcMap
429
+ key={visualizationKey}
430
+ config={visualizationConfig}
431
+ isEditor={true}
432
+ setConfig={updateConfig}
433
+ setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
434
+ setSharedFilterValue={setSharedFilterValue}
435
+ isDashboard={true}
436
+ />
437
+ </>
438
+ break
439
+ case 'data-bite':
440
+ visualizationConfig = { ...visualizationConfig, newViz: true }
441
+ body = <><Header tabSelected={tabSelected} setTabSelected={setTabSelected} back={back} subEditor="Data Bite"/>
442
+ <CdcDataBite
443
+ key={visualizationKey}
444
+ config={visualizationConfig}
445
+ isEditor={true}
446
+ setConfig={updateConfig}
447
+ isDashboard={true}
448
+ />
449
+ </>
450
+ break
451
+ case 'waffle-chart':
452
+ body = <><Header tabSelected={tabSelected} setTabSelected={setTabSelected} back={back} subEditor="Waffle Chart"/>
453
+ <CdcWaffleChart
454
+ key={visualizationKey}
455
+ config={visualizationConfig}
456
+ isEditor={true}
457
+ setConfig={updateConfig}
458
+ isDashboard={true}
459
+ />
460
+ </>
461
+ break
462
+ case 'markup-include':
463
+ body = <><Header tabSelected={tabSelected} setTabSelected={setTabSelected} back={back} subEditor="Markup Include"/>
464
+ <CdcMarkupInclude
465
+ key={visualizationKey}
466
+ config={visualizationConfig}
467
+ isEditor={true}
468
+ setConfig={updateConfig}
469
+ isDashboard={true}
470
+ />
471
+ </>
472
+ break
473
+ default:
474
+ body = <></>
475
+ break
476
+ }
477
+ }
478
+ })
479
+
480
+ if (!subVisualizationEditing) {
481
+ body = (
482
+ <DndProvider backend={HTML5Backend}>
483
+ <Header tabSelected={tabSelected} setTabSelected={setTabSelected} preview={preview} setPreview={setPreview}/>
484
+ <div className="layout-container">
485
+ <VisualizationsPanel/>
486
+ <Grid/>
487
+ </div>
488
+ </DndProvider>
489
+ )
490
+ }
491
+ } else {
492
+ body = (
493
+ <>
494
+
495
+ {isEditor && <Header tabSelected={tabSelected} setTabSelected={setTabSelected} preview={preview} setPreview={setPreview}/>}
496
+ <div className={`cdc-dashboard-inner-container${isEditor ? ' is-editor' : ''}`}>
497
+ {/* Title */}
498
+ {title &&
499
+ <div role="heading" aria-level="3" className={`dashboard-title ${config.dashboard.theme ?? 'theme-blue'}`}>{title}</div>}
500
+ {/* Description */}
501
+ {description && <div className="subtext">{parse(description)}</div>}
502
+ {/* Filters */}
503
+ {config.dashboard.sharedFilters && <Filters/>}
504
+
505
+ {/* Visualizations */}
506
+ {config.rows && config.rows.filter(row => row.filter(col => col.widget).length !== 0).map((row, index) => {
507
+
508
+ return (
509
+
510
+ <div className={`dashboard-row ${row.equalHeight ? 'equal-height' : ''}`} key={`row__${index}`}>
511
+ {row.map((col, colIndex) => {
512
+ if (col.width) {
513
+ if (!col.widget) return <div key={`row__${index}__col__${colIndex}`} className={`dashboard-col dashboard-col-${col.width}`}></div>
514
+
515
+ let visualizationConfig = { ...config.visualizations[col.widget] }
516
+
517
+ const dataKey = visualizationConfig.dataKey || 'backwards-compatibility'
518
+
519
+ visualizationConfig.data = filteredData && filteredData[col.widget] ? filteredData[col.widget] : data[dataKey]
520
+ if (visualizationConfig.formattedData) {
521
+ visualizationConfig.formattedData = visualizationConfig.data
522
+ }
523
+
524
+ const setsSharedFilter = config.dashboard.sharedFilters && config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === col.widget).length > 0
525
+ const setSharedFilterValue = setsSharedFilter ? config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === col.widget)[0].active : undefined
526
+ const tableLink = <a href={`#data-table-${visualizationConfig.dataKey}`}>{visualizationConfig.dataKey} (Go to Table)</a>;
527
+
528
+ return (
529
+ <React.Fragment key={`vis__${index}__${colIndex}`}>
530
+ <div className={`dashboard-col dashboard-col-${col.width}`}>
531
+ {visualizationConfig.type === 'chart' && (
532
+ <CdcChart
533
+ key={col.widget}
534
+ config={visualizationConfig}
535
+ isEditor={false}
536
+ setConfig={(newConfig) => {
537
+ updateChildConfig(col.widget, newConfig)
538
+ }}
539
+ setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
540
+ isDashboard={true}
541
+ link = { config.table && config.table.show && config.datasets ? tableLink : undefined }
542
+ />
543
+ )}
544
+ {visualizationConfig.type === 'map' && (
545
+ <CdcMap
546
+ key={col.widget}
547
+ config={visualizationConfig}
548
+ isEditor={false}
549
+ setConfig={(newConfig) => {
550
+ updateChildConfig(col.widget, newConfig)
551
+ }}
552
+ setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
553
+ setSharedFilterValue={setSharedFilterValue}
554
+ isDashboard={true}
555
+ link = { config.table && config.table.show && config.datasets ? tableLink : undefined }
556
+ />
557
+ )}
558
+ {visualizationConfig.type === 'data-bite' && (
559
+ <CdcDataBite
560
+ key={col.widget}
561
+ config={visualizationConfig}
562
+ isEditor={false}
563
+ setConfig={(newConfig) => {
564
+ updateChildConfig(col.widget, newConfig)
565
+ }}
566
+ isDashboard={true}
567
+ />
568
+ )}
569
+ {visualizationConfig.type === 'waffle-chart' && (
570
+ <CdcWaffleChart
571
+ key={col.widget}
572
+ config={visualizationConfig}
573
+ isEditor={false}
574
+ setConfig={(newConfig) => {
575
+ updateChildConfig(col.widget, newConfig)
576
+ }}
577
+ isDashboard={true}
578
+ link = { config.table && config.table.show && config.datasets ? tableLink : undefined }
579
+ />
580
+ )}
581
+ {visualizationConfig.type === 'markup-include' && (
582
+ <CdcMarkupInclude
583
+ key={col.widget}
584
+ config={visualizationConfig}
585
+ isEditor={false}
586
+ setConfig={(newConfig) => {
587
+ updateChildConfig(col.widget, newConfig)
588
+ }}
589
+ isDashboard={true}
590
+ />
591
+ )}
592
+ </div>
593
+ </React.Fragment>
594
+ )
595
+ }
596
+ return <React.Fragment key={`vis__${index}__${colIndex}`}></React.Fragment>
597
+ })}
598
+ </div>)
599
+ })}
600
+
601
+ {/* Data Table */}
602
+ {config.table && config.table.show && config.data && <DataTable data={config.data} config={config}/>}
603
+ {config.table && config.table.show && config.datasets && Object.keys(config.datasets).map(datasetKey => {
604
+
605
+ //For each dataset, find any shared filters that apply to all visualizations using the dataset
606
+ //Apply these filters to the table
607
+ let filteredTableData;
608
+ if(config.dashboard.sharedFilters && config.dashboard.sharedFilters.length > 0){
609
+ //Gets list of visuailzations using the dataset
610
+ let vizKeysUsingDataset = [];
611
+ Object.keys(config.visualizations).forEach(visualizationKey => {
612
+ if(config.visualizations[visualizationKey].dataKey === datasetKey){
613
+ vizKeysUsingDataset.push(visualizationKey);
614
+ }
615
+ });
616
+
617
+ //Checks shared filters against list to see if all visualizations are represented
618
+ let applicableFilters = [];
619
+ config.dashboard.sharedFilters.forEach(sharedFilter => {
620
+ let allMatch = true;
621
+ vizKeysUsingDataset.forEach(visualizationKey => {
622
+ if(sharedFilter.usedBy.indexOf(visualizationKey) === -1){
623
+ allMatch = false;
624
+ }
625
+ });
626
+ if(allMatch){
627
+ applicableFilters.push(sharedFilter);
628
+ }
629
+ });
630
+
631
+ //Applys any applicable filters
632
+ if(applicableFilters.length > 0){
633
+ filteredTableData = filterData(applicableFilters, config.datasets[datasetKey].data)
634
+ }
635
+ }
636
+
637
+ return (<div className="multi-table-container" id={`data-table-${datasetKey}`} key={`data-table-${datasetKey}`}>
638
+ <DataTable data={filteredTableData || config.datasets[datasetKey].data} datasetKey={datasetKey} config={config}></DataTable>
639
+ </div>)
640
+ })}
641
+ </div>
642
+ </>
643
+ )
644
+ }
645
+
646
+ const contextValues = {
647
+ config,
648
+ rawData: data,
649
+ data: filteredData ?? data,
650
+ visualizations: config.visualizations,
651
+ rows: config.rows,
652
+ loading,
653
+ updateConfig,
654
+ setParentConfig,
655
+ setPreview
656
+ }
657
+
658
+ return (
659
+ <GlobalContextProvider>
660
+ <ConfigContext.Provider value={contextValues}>
661
+ <div className={`cdc-open-viz-module type-dashboard ${currentViewport}`} ref={outerContainerRef}>
662
+ {body}
663
+ </div>
664
+ <OverlayFrame/>
665
+ </ConfigContext.Provider>
666
+ </GlobalContextProvider>
667
+ )
668
+ }
File without changes