@cdc/dashboard 1.1.4 → 4.22.10-alpha.1

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