@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.
- package/dist/cdcdashboard.js +159 -48
- package/examples/default-filter-control.json +175 -0
- package/examples/default-multi-dataset.json +498 -0
- package/examples/default.json +36 -348
- package/examples/private/chart-issue.json +3467 -0
- package/examples/private/no-issue.json +3467 -0
- package/examples/private/totals-two.json +104 -0
- package/examples/private/totals.json +103 -0
- package/examples/temp-example-data.json +130 -0
- package/examples/test-example.json +1 -0
- package/package.json +14 -8
- package/src/CdcDashboard.js +282 -156
- package/src/CdcDashboard.jsx +668 -0
- package/src/{context.tsx → ConfigContext.js} +0 -0
- package/src/components/{Column.js → Column.jsx} +9 -7
- package/src/components/DataTable.tsx +55 -54
- package/src/components/EditorPanel.js +207 -45
- package/src/components/{Grid.js → Grid.jsx} +5 -4
- package/src/components/Header.jsx +242 -0
- package/src/components/Row.js +1 -0
- package/src/components/Row.jsx +181 -0
- package/src/components/Row.jsx~HEAD +212 -0
- package/src/components/Widget.js +24 -5
- package/src/components/Widget.jsx +191 -0
- package/src/index.html +14 -11
- package/src/scss/editor-panel.scss +53 -49
- package/src/scss/grid.scss +61 -14
- package/src/scss/main.scss +73 -9
- package/LICENSE +0 -201
- package/src/components/Header.js +0 -15
- package/src/images/icon-close.svg +0 -1
- package/src/images/icon-down.svg +0 -1
- package/src/images/icon-edit.svg +0 -1
- package/src/images/icon-move.svg +0 -8
- package/src/images/icon-up.svg +0 -1
- 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
|