@cdc/dashboard 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcdashboard.js +147 -48
- package/examples/default.json +36 -348
- package/examples/test-example.json +1 -0
- package/package.json +13 -8
- package/src/CdcDashboard.js +267 -156
- package/src/components/DataTable.tsx +52 -48
- package/src/components/EditorPanel.js +16 -10
- package/src/components/Header.js +1 -1
- package/src/components/Widget.js +20 -5
- package/src/images/icon-code.svg +3 -0
- package/src/images/icon-edit.svg +3 -1
- package/src/images/icon-grid.svg +4 -0
- package/src/index.html +14 -12
- package/src/scss/editor-panel.scss +53 -48
- package/src/scss/main.scss +6 -3
- package/LICENSE +0 -201
package/src/CdcDashboard.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from 'react'
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react'
|
|
2
2
|
|
|
3
3
|
// IE11
|
|
4
4
|
import 'core-js/stable'
|
|
@@ -8,44 +8,56 @@ import ResizeObserver from 'resize-observer-polyfill'
|
|
|
8
8
|
import { DndProvider } from 'react-dnd'
|
|
9
9
|
import { HTML5Backend } from 'react-dnd-html5-backend'
|
|
10
10
|
|
|
11
|
-
import parse from 'html-react-parser'
|
|
11
|
+
import parse from 'html-react-parser'
|
|
12
12
|
|
|
13
|
-
import Loading from '@cdc/core/components/Loading'
|
|
14
|
-
import DataTransform from '@cdc/core/components/DataTransform'
|
|
15
|
-
import getViewport from '@cdc/core/helpers/getViewport'
|
|
13
|
+
import Loading from '@cdc/core/components/Loading'
|
|
14
|
+
import DataTransform from '@cdc/core/components/DataTransform'
|
|
15
|
+
import getViewport from '@cdc/core/helpers/getViewport'
|
|
16
16
|
|
|
17
|
-
import CdcMap from '@cdc/map'
|
|
18
|
-
import CdcChart from '@cdc/chart'
|
|
19
|
-
import CdcDataBite from '@cdc/data-bite'
|
|
17
|
+
import CdcMap from '@cdc/map'
|
|
18
|
+
import CdcChart from '@cdc/chart'
|
|
19
|
+
import CdcDataBite from '@cdc/data-bite'
|
|
20
|
+
import CdcWaffleChart from '@cdc/waffle-chart'
|
|
21
|
+
import CdcMarkupInclude from '@cdc/markup-include'
|
|
20
22
|
|
|
21
|
-
import EditorPanel from './components/EditorPanel'
|
|
22
|
-
import Grid from './components/Grid'
|
|
23
|
-
import Header from './components/Header'
|
|
24
|
-
import Context from './context'
|
|
25
|
-
import defaults from './data/initial-state'
|
|
26
|
-
import Widget from './components/Widget'
|
|
27
|
-
import DataTable from './components/DataTable'
|
|
23
|
+
import EditorPanel from './components/EditorPanel'
|
|
24
|
+
import Grid from './components/Grid'
|
|
25
|
+
import Header from './components/Header'
|
|
26
|
+
import Context from './context'
|
|
27
|
+
import defaults from './data/initial-state'
|
|
28
|
+
import Widget from './components/Widget'
|
|
29
|
+
import DataTable from './components/DataTable'
|
|
28
30
|
|
|
29
|
-
import '
|
|
31
|
+
import Papa from 'papaparse'
|
|
32
|
+
|
|
33
|
+
import './scss/main.scss'
|
|
34
|
+
|
|
35
|
+
import { publish } from '@cdc/core/helpers/events'
|
|
30
36
|
|
|
31
37
|
const addVisualization = (type, subType) => {
|
|
32
38
|
let newVisualizationConfig = {
|
|
33
39
|
newViz: true,
|
|
34
40
|
uid: type + Date.now(),
|
|
35
41
|
type
|
|
36
|
-
}
|
|
42
|
+
}
|
|
37
43
|
|
|
38
|
-
switch(type) {
|
|
44
|
+
switch (type) {
|
|
39
45
|
case 'chart':
|
|
40
|
-
newVisualizationConfig.visualizationType = subType
|
|
41
|
-
break
|
|
46
|
+
newVisualizationConfig.visualizationType = subType
|
|
47
|
+
break
|
|
42
48
|
case 'map':
|
|
43
|
-
newVisualizationConfig.general = {}
|
|
44
|
-
newVisualizationConfig.general.geoType = subType
|
|
45
|
-
break
|
|
49
|
+
newVisualizationConfig.general = {}
|
|
50
|
+
newVisualizationConfig.general.geoType = subType
|
|
51
|
+
break
|
|
46
52
|
case 'data-bite':
|
|
47
|
-
newVisualizationConfig.visualizationType = type
|
|
48
|
-
break
|
|
53
|
+
newVisualizationConfig.visualizationType = type
|
|
54
|
+
break
|
|
55
|
+
case 'waffle-chart':
|
|
56
|
+
newVisualizationConfig.visualizationType = type
|
|
57
|
+
break
|
|
58
|
+
case 'markup-include':
|
|
59
|
+
newVisualizationConfig.visualizationType = type
|
|
60
|
+
break
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
return newVisualizationConfig
|
|
@@ -53,176 +65,255 @@ const addVisualization = (type, subType) => {
|
|
|
53
65
|
|
|
54
66
|
const VisualizationsPanel = () => (
|
|
55
67
|
<div className="visualizations-panel">
|
|
56
|
-
<p style={{fontSize: '14px'}}>Click and drag an item onto the grid to add it to your dashboard.</p>
|
|
68
|
+
<p style={{ fontSize: '14px' }}>Click and drag an item onto the grid to add it to your dashboard.</p>
|
|
57
69
|
<span className="subheading-3">Chart</span>
|
|
58
70
|
<div className="drag-grid">
|
|
59
|
-
<Widget addVisualization={() => addVisualization('chart', 'Bar')} type="Bar"
|
|
60
|
-
<Widget addVisualization={() => addVisualization('chart', 'Line')} type="Line"
|
|
61
|
-
<Widget addVisualization={() => addVisualization('chart', 'Pie')} type="Pie"
|
|
71
|
+
<Widget addVisualization={() => addVisualization('chart', 'Bar')} type="Bar"/>
|
|
72
|
+
<Widget addVisualization={() => addVisualization('chart', 'Line')} type="Line"/>
|
|
73
|
+
<Widget addVisualization={() => addVisualization('chart', 'Pie')} type="Pie"/>
|
|
62
74
|
</div>
|
|
63
75
|
<span className="subheading-3">Map</span>
|
|
64
76
|
<div className="drag-grid">
|
|
65
|
-
<Widget addVisualization={() => addVisualization('map', 'us')} type="us"
|
|
66
|
-
<Widget addVisualization={() => addVisualization('map', 'world')} type="world"
|
|
77
|
+
<Widget addVisualization={() => addVisualization('map', 'us')} type="us"/>
|
|
78
|
+
<Widget addVisualization={() => addVisualization('map', 'world')} type="world"/>
|
|
79
|
+
<Widget addVisualization={() => addVisualization('map', 'single-state')} type="single-state"/>
|
|
67
80
|
</div>
|
|
68
81
|
<span className="subheading-3">Misc.</span>
|
|
69
82
|
<div className="drag-grid">
|
|
70
|
-
<Widget addVisualization={() => addVisualization('data-bite', '')} type="data-bite"
|
|
83
|
+
<Widget addVisualization={() => addVisualization('data-bite', '')} type="data-bite"/>
|
|
84
|
+
<Widget addVisualization={() => addVisualization('waffle-chart', '')} type="waffle-chart"/>
|
|
85
|
+
<Widget addVisualization={() => addVisualization('markup-include', '')} type="markup-include"/>
|
|
71
86
|
</div>
|
|
72
87
|
</div>
|
|
73
88
|
)
|
|
74
89
|
|
|
75
90
|
export default function CdcDashboard(
|
|
76
|
-
{ configUrl = '', config: configObj = undefined, isEditor = false, setConfig: setParentConfig }
|
|
91
|
+
{ configUrl = '', config: configObj = undefined, isEditor = false, setConfig: setParentConfig, hostname }
|
|
77
92
|
) {
|
|
78
93
|
|
|
79
|
-
const transform = new DataTransform()
|
|
94
|
+
const transform = new DataTransform()
|
|
80
95
|
|
|
81
|
-
const [config, setConfig] = useState(configObj
|
|
96
|
+
const [ config, setConfig ] = useState(configObj)
|
|
82
97
|
|
|
83
|
-
const [data, setData] = useState([])
|
|
98
|
+
const [ data, setData ] = useState([])
|
|
84
99
|
|
|
85
|
-
const [filteredData, setFilteredData] = useState()
|
|
100
|
+
const [ filteredData, setFilteredData ] = useState()
|
|
86
101
|
|
|
87
|
-
const [loading, setLoading] = useState(true)
|
|
102
|
+
const [ loading, setLoading ] = useState(true)
|
|
88
103
|
|
|
89
|
-
const [preview, setPreview] = useState(false)
|
|
104
|
+
const [ preview, setPreview ] = useState(false)
|
|
90
105
|
|
|
91
|
-
const [currentViewport, setCurrentViewport] = useState('lg')
|
|
106
|
+
const [ currentViewport, setCurrentViewport ] = useState('lg')
|
|
92
107
|
|
|
93
|
-
const
|
|
108
|
+
const [ coveLoadedHasRan, setCoveLoadedHasRan ] = useState(false)
|
|
94
109
|
|
|
95
|
-
const
|
|
96
|
-
let response = configObj || await (await fetch(configUrl)).json();
|
|
110
|
+
const [ container, setContainer ] = useState()
|
|
97
111
|
|
|
98
|
-
|
|
99
|
-
|
|
112
|
+
const { title, description } = config ? (config.dashboard || config) : {}
|
|
113
|
+
|
|
114
|
+
// Supports JSON or CSV
|
|
115
|
+
const fetchRemoteData = async (url) => {
|
|
116
|
+
try {
|
|
117
|
+
const urlObj = new URL(url)
|
|
118
|
+
const regex = /(?:\.([^.]+))?$/
|
|
100
119
|
|
|
101
|
-
|
|
102
|
-
const dataString = await fetch(response.dataUrl);
|
|
120
|
+
let data = []
|
|
103
121
|
|
|
104
|
-
|
|
122
|
+
const ext = (regex.exec(urlObj.pathname)[1])
|
|
123
|
+
if ('csv' === ext) {
|
|
124
|
+
data = await fetch(url)
|
|
125
|
+
.then(response => response.text())
|
|
126
|
+
.then(responseText => {
|
|
127
|
+
const parsedCsv = Papa.parse(responseText, {
|
|
128
|
+
header: true,
|
|
129
|
+
dynamicTyping: true,
|
|
130
|
+
skipEmptyLines: true
|
|
131
|
+
})
|
|
132
|
+
return parsedCsv.data
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if ('json' === ext) {
|
|
137
|
+
data = await fetch(url)
|
|
138
|
+
.then(response => response.json())
|
|
139
|
+
}
|
|
105
140
|
|
|
141
|
+
return data
|
|
142
|
+
} catch {
|
|
143
|
+
// If we can't parse it, still attempt to fetch it
|
|
106
144
|
try {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
} catch
|
|
110
|
-
|
|
145
|
+
let response = await (await fetch(configUrl)).json()
|
|
146
|
+
return response
|
|
147
|
+
} catch {
|
|
148
|
+
console.error(`Cannot parse URL: ${url}`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const cacheBustingString = () => {
|
|
154
|
+
const round = 1000 * 60 * 15;
|
|
155
|
+
const date = new Date();
|
|
156
|
+
return new Date(date.getTime() - (date.getTime() % round)).toISOString();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const loadConfig = async (configObj) => {
|
|
160
|
+
// Set loading flag
|
|
161
|
+
if (!loading) setLoading(true)
|
|
162
|
+
|
|
163
|
+
let newState = configObj || await (await fetch(configUrl)).json()
|
|
164
|
+
|
|
165
|
+
// If a dataUrl property exists, always pull from that.
|
|
166
|
+
if (newState.dataUrl) {
|
|
167
|
+
|
|
168
|
+
if (newState.dataUrl[0] === '/') {
|
|
169
|
+
newState.dataUrl = 'https://' + hostname + newState.dataUrl
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let newData = await fetchRemoteData(newState.dataUrl + `?v=${cacheBustingString()}`)
|
|
173
|
+
|
|
174
|
+
if (newData && newState.dataDescription) {
|
|
175
|
+
newData = transform.autoStandardize(newData)
|
|
176
|
+
newData = transform.developerStandardize(newData, newState.dataDescription)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (newData) {
|
|
180
|
+
newState.data = newData
|
|
111
181
|
}
|
|
112
182
|
}
|
|
113
183
|
|
|
114
|
-
|
|
184
|
+
// If data is included through a URL, fetch that and store
|
|
185
|
+
let data = newState.formattedData || newState.data || {}
|
|
186
|
+
|
|
187
|
+
setData(data)
|
|
115
188
|
|
|
116
|
-
let newConfig = {...defaults, ...
|
|
189
|
+
let newConfig = { ...defaults, ...newState }
|
|
117
190
|
|
|
118
|
-
updateConfig(newConfig, data)
|
|
191
|
+
updateConfig(newConfig, data)
|
|
119
192
|
|
|
120
|
-
setLoading(false)
|
|
193
|
+
setLoading(false)
|
|
121
194
|
}
|
|
122
195
|
|
|
123
196
|
const filterData = (filters, data) => {
|
|
124
|
-
let filteredData = []
|
|
197
|
+
let filteredData = []
|
|
125
198
|
|
|
126
199
|
data.forEach((row) => {
|
|
127
|
-
let add = true
|
|
200
|
+
let add = true
|
|
128
201
|
|
|
129
202
|
filters.forEach((filter) => {
|
|
130
|
-
if(row[filter.columnName] !== filter.active) {
|
|
131
|
-
add = false
|
|
203
|
+
if (row[filter.columnName] !== filter.active) {
|
|
204
|
+
add = false
|
|
132
205
|
}
|
|
133
|
-
})
|
|
206
|
+
})
|
|
134
207
|
|
|
135
|
-
if(add) filteredData.push(row)
|
|
136
|
-
})
|
|
208
|
+
if (add) filteredData.push(row)
|
|
209
|
+
})
|
|
137
210
|
|
|
138
|
-
return filteredData
|
|
211
|
+
return filteredData
|
|
139
212
|
}
|
|
140
213
|
|
|
141
214
|
// Gets filer values from dataset
|
|
142
215
|
const generateValuesForFilter = (columnName, data = this.state.data) => {
|
|
143
|
-
const values = []
|
|
216
|
+
const values = []
|
|
144
217
|
|
|
145
|
-
data.forEach(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
})
|
|
218
|
+
data.forEach((row) => {
|
|
219
|
+
const value = row[columnName]
|
|
220
|
+
if (value && false === values.includes(value)) {
|
|
221
|
+
values.push(value)
|
|
222
|
+
}
|
|
223
|
+
})
|
|
151
224
|
|
|
152
|
-
return values
|
|
225
|
+
return values
|
|
153
226
|
}
|
|
154
227
|
|
|
155
|
-
|
|
228
|
+
function isEmpty(obj) {
|
|
229
|
+
return Object.keys(obj).length === 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const updateConfig = (newConfig, dataOverride = null) => {
|
|
156
233
|
// After data is grabbed, loop through and generate filter column values if there are any
|
|
157
234
|
if (newConfig.dashboard.filters) {
|
|
158
|
-
const filterList = []
|
|
235
|
+
const filterList = []
|
|
159
236
|
|
|
160
237
|
newConfig.dashboard.filters.forEach((filter) => {
|
|
161
|
-
|
|
162
|
-
})
|
|
238
|
+
filterList.push(filter.columnName)
|
|
239
|
+
})
|
|
163
240
|
|
|
164
241
|
filterList.forEach((filter, index) => {
|
|
165
|
-
|
|
242
|
+
const filterValues = generateValuesForFilter(filter, (dataOverride || data))
|
|
243
|
+
|
|
244
|
+
if (newConfig.dashboard.filters[index].order === 'asc') {
|
|
245
|
+
filterValues.sort()
|
|
246
|
+
}
|
|
247
|
+
if (newConfig.dashboard.filters[index].order === 'desc') {
|
|
248
|
+
filterValues.sort().reverse()
|
|
249
|
+
}
|
|
166
250
|
|
|
167
|
-
|
|
251
|
+
newConfig.dashboard.filters[index].values = filterValues
|
|
168
252
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
})
|
|
253
|
+
// Initial filter should be active
|
|
254
|
+
newConfig.dashboard.filters[index].active = filterValues[0]
|
|
255
|
+
})
|
|
172
256
|
|
|
173
|
-
setFilteredData(filterData(newConfig.dashboard.filters, (dataOverride || data)))
|
|
257
|
+
setFilteredData(filterData(newConfig.dashboard.filters, (dataOverride || data)))
|
|
174
258
|
}
|
|
175
259
|
|
|
176
260
|
//Enforce default values that need to be calculated at runtime
|
|
177
|
-
newConfig.runtime = {}
|
|
261
|
+
newConfig.runtime = {}
|
|
178
262
|
|
|
179
|
-
setConfig(newConfig)
|
|
180
|
-
}
|
|
263
|
+
setConfig(newConfig)
|
|
264
|
+
}
|
|
181
265
|
|
|
182
266
|
// Load data when component first mounts
|
|
183
267
|
useEffect(() => {
|
|
184
|
-
loadConfig()
|
|
185
|
-
}, [])
|
|
268
|
+
loadConfig(config)
|
|
269
|
+
}, [])
|
|
186
270
|
|
|
187
271
|
// Pass up to <CdcEditor /> if it exists when config state changes
|
|
188
272
|
useEffect(() => {
|
|
189
|
-
if(setParentConfig && isEditor) {
|
|
190
|
-
setParentConfig(config)
|
|
273
|
+
if (setParentConfig && isEditor) {
|
|
274
|
+
setParentConfig(config)
|
|
191
275
|
}
|
|
192
|
-
}, [config])
|
|
276
|
+
}, [ config ])
|
|
277
|
+
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
if (config && !coveLoadedHasRan && container) {
|
|
280
|
+
publish('cove_loaded', { config: config })
|
|
281
|
+
setCoveLoadedHasRan(true)
|
|
282
|
+
}
|
|
283
|
+
}, [config, container]);
|
|
193
284
|
|
|
194
285
|
const updateChildConfig = (visualizationKey, newConfig) => {
|
|
195
|
-
let updatedConfig = {...config}
|
|
286
|
+
let updatedConfig = { ...config }
|
|
196
287
|
|
|
197
|
-
updatedConfig.visualizations[visualizationKey] = newConfig
|
|
198
|
-
setConfig(updatedConfig)
|
|
199
|
-
}
|
|
288
|
+
updatedConfig.visualizations[visualizationKey] = newConfig
|
|
289
|
+
setConfig(updatedConfig)
|
|
290
|
+
}
|
|
200
291
|
|
|
201
292
|
const Filters = () => {
|
|
202
293
|
const changeFilterActive = (index, value) => {
|
|
203
|
-
let dashboardConfig = {...config.dashboard}
|
|
294
|
+
let dashboardConfig = { ...config.dashboard }
|
|
204
295
|
|
|
205
|
-
dashboardConfig.filters[index].active = value
|
|
296
|
+
dashboardConfig.filters[index].active = value
|
|
206
297
|
|
|
207
|
-
setConfig({...config, dashboard: dashboardConfig})
|
|
298
|
+
setConfig({ ...config, dashboard: dashboardConfig })
|
|
208
299
|
|
|
209
|
-
setFilteredData(filterData(dashboardConfig.filters, data))
|
|
210
|
-
}
|
|
300
|
+
setFilteredData(filterData(dashboardConfig.filters, data))
|
|
301
|
+
}
|
|
211
302
|
|
|
212
303
|
const announceChange = (text) => {
|
|
213
304
|
|
|
214
|
-
}
|
|
305
|
+
}
|
|
215
306
|
|
|
216
307
|
return config.dashboard.filters.map((singleFilter, index) => {
|
|
217
|
-
const values = []
|
|
308
|
+
const values = []
|
|
218
309
|
|
|
219
310
|
singleFilter.values.forEach((filterOption, index) => {
|
|
220
311
|
values.push(<option
|
|
221
312
|
key={index}
|
|
222
313
|
value={filterOption}
|
|
223
314
|
>{filterOption}
|
|
224
|
-
</option>)
|
|
225
|
-
})
|
|
315
|
+
</option>)
|
|
316
|
+
})
|
|
226
317
|
|
|
227
318
|
return (
|
|
228
319
|
<section className="dashboard-filters-section" key={index}>
|
|
@@ -233,50 +324,52 @@ export default function CdcDashboard(
|
|
|
233
324
|
data-index="0"
|
|
234
325
|
value={singleFilter.active}
|
|
235
326
|
onChange={(val) => {
|
|
236
|
-
changeFilterActive(index, val.target.value)
|
|
237
|
-
announceChange(`Filter ${singleFilter.label} value has been changed to ${val.target.value}, please reference the data table to see updated values.`)
|
|
327
|
+
changeFilterActive(index, val.target.value)
|
|
328
|
+
announceChange(`Filter ${singleFilter.label} value has been changed to ${val.target.value}, please reference the data table to see updated values.`)
|
|
238
329
|
}}
|
|
239
330
|
>
|
|
240
331
|
{values}
|
|
241
332
|
</select>
|
|
242
333
|
</section>
|
|
243
|
-
)
|
|
244
|
-
})
|
|
334
|
+
)
|
|
335
|
+
})
|
|
336
|
+
|
|
245
337
|
}
|
|
246
338
|
|
|
247
339
|
const resizeObserver = new ResizeObserver(entries => {
|
|
248
340
|
for (let entry of entries) {
|
|
249
|
-
|
|
341
|
+
let newViewport = getViewport(entry.contentRect.width)
|
|
250
342
|
|
|
251
|
-
|
|
343
|
+
setCurrentViewport(newViewport)
|
|
252
344
|
}
|
|
253
|
-
})
|
|
345
|
+
})
|
|
254
346
|
|
|
255
347
|
const outerContainerRef = useCallback(node => {
|
|
256
348
|
if (node !== null) {
|
|
257
|
-
|
|
349
|
+
resizeObserver.observe(node)
|
|
258
350
|
}
|
|
259
|
-
|
|
351
|
+
setContainer(node)
|
|
352
|
+
}, [])
|
|
260
353
|
|
|
261
354
|
// Prevent render if loading
|
|
262
|
-
if(loading) return <Loading
|
|
355
|
+
if (loading) return <Loading/>
|
|
263
356
|
|
|
264
357
|
let body = null
|
|
265
358
|
|
|
266
359
|
// Editor mode
|
|
267
|
-
if(isEditor && !preview) {
|
|
268
|
-
let subVisualizationEditing = false
|
|
360
|
+
if (isEditor && !preview) {
|
|
361
|
+
let subVisualizationEditing = false
|
|
269
362
|
|
|
270
363
|
Object.keys(config.visualizations).forEach(visualizationKey => {
|
|
271
|
-
let visualizationConfig = config.visualizations[visualizationKey]
|
|
364
|
+
let visualizationConfig = config.visualizations[visualizationKey]
|
|
272
365
|
|
|
273
|
-
visualizationConfig.data = filteredData || data
|
|
366
|
+
visualizationConfig.data = filteredData || data
|
|
274
367
|
|
|
275
|
-
|
|
276
|
-
subVisualizationEditing = true
|
|
368
|
+
if (visualizationConfig.editing) {
|
|
369
|
+
subVisualizationEditing = true
|
|
277
370
|
|
|
278
371
|
const back = () => {
|
|
279
|
-
const newConfig = {...config}
|
|
372
|
+
const newConfig = { ...config }
|
|
280
373
|
|
|
281
374
|
delete newConfig.visualizations[visualizationKey].editing
|
|
282
375
|
|
|
@@ -285,28 +378,34 @@ export default function CdcDashboard(
|
|
|
285
378
|
|
|
286
379
|
const updateConfig = (newConfig) => updateChildConfig(visualizationKey, newConfig)
|
|
287
380
|
|
|
288
|
-
switch(visualizationConfig.type){
|
|
381
|
+
switch (visualizationConfig.type) {
|
|
289
382
|
case 'chart':
|
|
290
|
-
body = <><Header back={back} subEditor="Chart"
|
|
291
|
-
break
|
|
292
|
-
case 'map':
|
|
293
|
-
body = <><Header back={back} subEditor="Map"
|
|
294
|
-
break
|
|
383
|
+
body = <><Header back={back} subEditor="Chart"/><CdcChart key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
|
|
384
|
+
break
|
|
385
|
+
case 'map':
|
|
386
|
+
body = <><Header back={back} subEditor="Map"/><CdcMap key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
|
|
387
|
+
break
|
|
295
388
|
case 'data-bite':
|
|
296
|
-
visualizationConfig = {...visualizationConfig, newViz: true}
|
|
297
|
-
body = <><Header back={back} subEditor="Data Bite"
|
|
298
|
-
break
|
|
389
|
+
visualizationConfig = { ...visualizationConfig, newViz: true }
|
|
390
|
+
body = <><Header back={back} subEditor="Data Bite"/><CdcDataBite key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
|
|
391
|
+
break
|
|
392
|
+
case 'waffle-chart':
|
|
393
|
+
body = <><Header back={back} subEditor="Waffle Chart"/><CdcWaffleChart key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
|
|
394
|
+
break
|
|
395
|
+
case 'markup-include':
|
|
396
|
+
body = <><Header back={back} subEditor="Markup Include"/><CdcMarkupInclude key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
|
|
397
|
+
break
|
|
299
398
|
}
|
|
300
399
|
}
|
|
301
|
-
})
|
|
400
|
+
})
|
|
302
401
|
|
|
303
|
-
if(!subVisualizationEditing){
|
|
402
|
+
if (!subVisualizationEditing) {
|
|
304
403
|
body = (
|
|
305
404
|
<DndProvider backend={HTML5Backend}>
|
|
306
|
-
<Header preview={preview} setPreview={setPreview}
|
|
405
|
+
<Header preview={preview} setPreview={setPreview}/>
|
|
307
406
|
<div className="layout-container">
|
|
308
|
-
<VisualizationsPanel
|
|
309
|
-
<Grid
|
|
407
|
+
<VisualizationsPanel/>
|
|
408
|
+
<Grid/>
|
|
310
409
|
</div>
|
|
311
410
|
</DndProvider>
|
|
312
411
|
)
|
|
@@ -314,42 +413,54 @@ export default function CdcDashboard(
|
|
|
314
413
|
} else {
|
|
315
414
|
body = (
|
|
316
415
|
<>
|
|
317
|
-
{isEditor && <Header preview={preview} setPreview={setPreview}
|
|
318
|
-
{isEditor && <EditorPanel
|
|
416
|
+
{isEditor && <Header preview={preview} setPreview={setPreview}/>}
|
|
417
|
+
{isEditor && <EditorPanel/>}
|
|
319
418
|
<div className="cdc-dashboard-inner-container">
|
|
320
419
|
{/* Title */}
|
|
321
420
|
{title && <div role="heading" className={`dashboard-title ${config.dashboard.theme ?? 'theme-blue'}`}>{title}</div>}
|
|
322
421
|
|
|
323
422
|
{/* Filters */}
|
|
324
|
-
{config.dashboard.filters && <Filters
|
|
423
|
+
{config.dashboard.filters && <Filters/>}
|
|
325
424
|
|
|
326
425
|
{/* Visualizations */}
|
|
327
|
-
{config.rows && config.rows.map(row => {
|
|
426
|
+
{config.rows && config.rows.map((row, index) => {
|
|
328
427
|
// Empty check
|
|
329
|
-
if(row.filter(col => col.widget).length === 0) return null
|
|
428
|
+
if (row.filter(col => col.widget).length === 0) return null
|
|
330
429
|
|
|
331
430
|
return (
|
|
332
|
-
<div className="dashboard-row">
|
|
333
|
-
{row.map(col => {
|
|
334
|
-
if(col.width) {
|
|
335
|
-
if(!col.widget) return <div className={`dashboard-col dashboard-col-${col.width}`}></div>
|
|
336
|
-
|
|
337
|
-
let visualizationConfig = config.visualizations[col.widget]
|
|
338
|
-
|
|
339
|
-
visualizationConfig.data = filteredData || data
|
|
340
|
-
|
|
341
|
-
return <div className={`dashboard-col dashboard-col-${col.width}`}>
|
|
342
|
-
{visualizationConfig.type === 'chart' && <CdcChart key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
343
|
-
|
|
344
|
-
|
|
431
|
+
<div className="dashboard-row" key={`row__${index}`}>
|
|
432
|
+
{row.map((col, index) => {
|
|
433
|
+
if (col.width) {
|
|
434
|
+
if (!col.widget) return <div className={`dashboard-col dashboard-col-${col.width}`}></div>
|
|
435
|
+
|
|
436
|
+
let visualizationConfig = config.visualizations[col.widget]
|
|
437
|
+
|
|
438
|
+
visualizationConfig.data = filteredData || data
|
|
439
|
+
|
|
440
|
+
return <div className={`dashboard-col dashboard-col-${col.width}`} key={`vis__${index}`}>
|
|
441
|
+
{visualizationConfig.type === 'chart' && <CdcChart key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
442
|
+
updateChildConfig(col.widget, newConfig)
|
|
443
|
+
}} isDashboard={true}/>}
|
|
444
|
+
{visualizationConfig.type === 'map' && <CdcMap key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
445
|
+
updateChildConfig(col.widget, newConfig)
|
|
446
|
+
}} isDashboard={true}/>}
|
|
447
|
+
{visualizationConfig.type === 'data-bite' && <CdcDataBite key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
448
|
+
updateChildConfig(col.widget, newConfig)
|
|
449
|
+
}} isDashboard={true}/>}
|
|
450
|
+
{visualizationConfig.type === 'waffle-chart' && <CdcWaffleChart key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
451
|
+
updateChildConfig(col.widget, newConfig)
|
|
452
|
+
}} isDashboard={true}/>}
|
|
453
|
+
{visualizationConfig.type === 'markup-include' && <CdcMarkupInclude key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
454
|
+
updateChildConfig(col.widget, newConfig)
|
|
455
|
+
}} isDashboard={true}/>}
|
|
345
456
|
</div>
|
|
346
457
|
}
|
|
347
458
|
})}
|
|
348
|
-
</div>)
|
|
459
|
+
</div>)
|
|
349
460
|
})}
|
|
350
461
|
|
|
351
462
|
{/* Data Table */}
|
|
352
|
-
{config.table.show && <DataTable
|
|
463
|
+
{config.table.show && <DataTable/>}
|
|
353
464
|
|
|
354
465
|
{/* Description */}
|
|
355
466
|
{description && <div className="subtext">{parse(description)}</div>}
|
|
@@ -369,12 +480,12 @@ export default function CdcDashboard(
|
|
|
369
480
|
setParentConfig,
|
|
370
481
|
setPreview
|
|
371
482
|
}
|
|
372
|
-
|
|
483
|
+
|
|
373
484
|
return (
|
|
374
485
|
<Context.Provider value={contextValues}>
|
|
375
486
|
<div className={`cdc-open-viz-module type-dashboard ${currentViewport}`} ref={outerContainerRef}>
|
|
376
487
|
{body}
|
|
377
488
|
</div>
|
|
378
489
|
</Context.Provider>
|
|
379
|
-
)
|
|
490
|
+
)
|
|
380
491
|
}
|