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