@cdc/dashboard 4.24.3 → 4.24.5
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 +148123 -105018
- package/examples/chart-data.json +5409 -0
- package/examples/full-dash-test.json +14643 -0
- package/examples/full-dashboard.json +10036 -0
- package/index.html +2 -2
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +8 -3
- package/src/CdcDashboardComponent.tsx +141 -290
- package/src/_stories/Dashboard.stories.tsx +52 -7
- package/src/_stories/_mock/markup-include.json +78 -0
- package/src/_stories/_mock/multi-dashboards.json +914 -0
- package/src/_stories/_mock/multi-viz.json +378 -0
- package/src/_stories/_mock/pivot-filter.json +2 -2
- package/src/_stories/_mock/standalone-table.json +2 -0
- package/src/components/DataDesignerModal.tsx +145 -0
- package/src/components/Grid.tsx +3 -1
- package/src/components/Header/FilterModal.tsx +63 -33
- package/src/components/MultiConfigTabs/MultiTabs.tsx +3 -2
- package/src/components/Row.tsx +50 -25
- package/src/components/Toggle/Toggle.tsx +6 -7
- package/src/components/VisualizationRow.tsx +183 -0
- package/src/components/VisualizationsPanel.tsx +26 -3
- package/src/components/Widget.tsx +21 -103
- package/src/helpers/filterData.ts +1 -1
- package/src/helpers/getFilteredData.ts +39 -0
- package/src/helpers/getUpdateConfig.ts +15 -0
- package/src/helpers/getVizConfig.ts +31 -0
- package/src/helpers/getVizRowColumnLocator.ts +9 -0
- package/src/scss/grid.scss +9 -2
- package/src/scss/main.scss +5 -0
- package/src/store/dashboard.actions.ts +6 -0
- package/src/store/dashboard.reducer.ts +33 -8
- package/src/types/ConfigRow.ts +12 -3
- package/src/types/DataSet.ts +11 -8
- package/src/types/SharedFilter.ts +1 -1
- package/src/components/EditorWrapper/EditorWrapper.tsx +0 -52
- package/src/components/EditorWrapper/editor-wrapper.style.css +0 -13
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useContext, useEffect, useState } from 'react'
|
|
1
|
+
import { useContext, useEffect, useMemo, useState } from 'react'
|
|
2
2
|
import { MultiDashboardConfig } from '../../types/MultiDashboard'
|
|
3
3
|
import { SharedFilter } from '../../types/SharedFilter'
|
|
4
4
|
import { DashboardDispatchContext } from '../../DashboardContext'
|
|
@@ -11,6 +11,8 @@ import Icon from '@cdc/core/components/ui/Icon'
|
|
|
11
11
|
import Button from '@cdc/core/components/elements/Button'
|
|
12
12
|
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
13
13
|
import DataTransform from '@cdc/core/helpers/DataTransform'
|
|
14
|
+
import { getVizRowColumnLocator } from '../../helpers/getVizRowColumnLocator'
|
|
15
|
+
import _ from 'lodash'
|
|
14
16
|
|
|
15
17
|
type ModalProps = {
|
|
16
18
|
config: MultiDashboardConfig
|
|
@@ -26,9 +28,39 @@ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeF
|
|
|
26
28
|
const [columns, setColumns] = useState<string[]>([])
|
|
27
29
|
const transform = new DataTransform()
|
|
28
30
|
|
|
31
|
+
const vizRowColumnLocator = getVizRowColumnLocator(config.rows)
|
|
32
|
+
|
|
33
|
+
const [usedByNameLookup, usedByOptions] = useMemo(() => {
|
|
34
|
+
const nameLookup = {}
|
|
35
|
+
const vizOptions = Object.keys(config.visualizations)
|
|
36
|
+
.filter(vizKey => {
|
|
37
|
+
const notAdded = !filter.usedBy || filter.usedBy.indexOf(vizKey) === -1
|
|
38
|
+
const usesSharedFilter = config.visualizations[vizKey].usesSharedFilter
|
|
39
|
+
const row = vizRowColumnLocator[vizKey].row
|
|
40
|
+
const dataConfiguredOnRow = config.rows[row].dataKey
|
|
41
|
+
return filter.setBy !== vizKey && notAdded && !usesSharedFilter && !dataConfiguredOnRow
|
|
42
|
+
})
|
|
43
|
+
.map(vizKey => {
|
|
44
|
+
const viz = config.visualizations[vizKey]
|
|
45
|
+
const vizName = viz.general?.title || viz.title || vizKey
|
|
46
|
+
nameLookup[vizKey] = vizName
|
|
47
|
+
return vizKey
|
|
48
|
+
})
|
|
49
|
+
const rowOptions: number[] = []
|
|
50
|
+
|
|
51
|
+
config.rows.forEach((row, rowIndex) => {
|
|
52
|
+
if (!!row.multiVizColumn) {
|
|
53
|
+
nameLookup[rowIndex] = `Row ${rowIndex + 1}`
|
|
54
|
+
rowOptions.push(rowIndex)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const rowsNotSelected = rowOptions.filter(row => !filter.usedBy || filter.usedBy.indexOf(row.toString()) === -1)
|
|
59
|
+
return [nameLookup, [...vizOptions, ...rowsNotSelected]]
|
|
60
|
+
}, [config.visualizations, filter.usedBy, filter.setBy, vizRowColumnLocator])
|
|
61
|
+
|
|
29
62
|
useEffect(() => {
|
|
30
63
|
const runSetColumns = async () => {
|
|
31
|
-
if (config.filterBehavior === FilterBehavior.Apply) return
|
|
32
64
|
let columns = {}
|
|
33
65
|
let dataKeys = Object.keys(config.datasets)
|
|
34
66
|
|
|
@@ -71,18 +103,13 @@ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeF
|
|
|
71
103
|
}
|
|
72
104
|
|
|
73
105
|
const updateFilterProp = (name, value) => {
|
|
74
|
-
|
|
75
|
-
// it's unsafe to directly set objects w/o guardrails
|
|
76
|
-
let newFilter = { ...filter }
|
|
77
|
-
|
|
78
|
-
newFilter[name] = value
|
|
79
|
-
|
|
80
|
-
console.log('newFilter', newFilter)
|
|
106
|
+
const newFilter = { ..._.cloneDeep(filter), [name]: value }
|
|
81
107
|
|
|
82
108
|
setFilter(newFilter)
|
|
83
109
|
}
|
|
84
110
|
|
|
85
111
|
const addFilterUsedBy = (filter, value) => {
|
|
112
|
+
if (value === '') return
|
|
86
113
|
if (!filter.usedBy) filter.usedBy = []
|
|
87
114
|
filter.usedBy.push(value)
|
|
88
115
|
updateFilterProp('usedBy', filter.usedBy)
|
|
@@ -97,9 +124,10 @@ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeF
|
|
|
97
124
|
}
|
|
98
125
|
|
|
99
126
|
const updateAPIFilter = (key: keyof APIFilter, value: string | boolean) => {
|
|
100
|
-
const
|
|
127
|
+
const filterClone = _.cloneDeep(filter)
|
|
128
|
+
const _filter = filterClone.apiFilter || { apiEndpoint: '', valueSelector: '', textSelector: '' }
|
|
101
129
|
const newAPIFilter: APIFilter = { ..._filter, [key]: value }
|
|
102
|
-
setFilter({ ...
|
|
130
|
+
setFilter({ ...filterClone, apiFilter: newAPIFilter })
|
|
103
131
|
}
|
|
104
132
|
|
|
105
133
|
return (
|
|
@@ -341,6 +369,7 @@ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeF
|
|
|
341
369
|
value={filter.pivot}
|
|
342
370
|
onChange={e => {
|
|
343
371
|
updateFilterProp('pivot', e.target.value)
|
|
372
|
+
updateFilterProp('showDropdown', true)
|
|
344
373
|
}}
|
|
345
374
|
>
|
|
346
375
|
<option value=''>- Select Option -</option>
|
|
@@ -363,16 +392,19 @@ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeF
|
|
|
363
392
|
}}
|
|
364
393
|
/>
|
|
365
394
|
</label>
|
|
366
|
-
|
|
367
|
-
<
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
395
|
+
{!filter.pivot && (
|
|
396
|
+
<label>
|
|
397
|
+
<span className='edit-label column-heading'>Show Dropdown</span>
|
|
398
|
+
<input
|
|
399
|
+
type='checkbox'
|
|
400
|
+
defaultChecked={filter.showDropdown === true}
|
|
401
|
+
onChange={e => {
|
|
402
|
+
updateFilterProp('showDropdown', !filter.showDropdown)
|
|
403
|
+
}}
|
|
404
|
+
/>
|
|
405
|
+
</label>
|
|
406
|
+
)}
|
|
407
|
+
|
|
376
408
|
<label>
|
|
377
409
|
<span className='edit-label column-heading'>Set By: </span>
|
|
378
410
|
<select value={filter.setBy} onChange={e => updateFilterProp('setBy', e.target.value)}>
|
|
@@ -388,13 +420,13 @@ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeF
|
|
|
388
420
|
<span className='edit-label column-heading'>Used By: </span>
|
|
389
421
|
<ul>
|
|
390
422
|
{filter.usedBy &&
|
|
391
|
-
filter.usedBy.map(
|
|
392
|
-
<li key={`used-by-list-item-${
|
|
393
|
-
<span>{
|
|
423
|
+
filter.usedBy.map(opt => (
|
|
424
|
+
<li key={`used-by-list-item-${opt}`}>
|
|
425
|
+
<span>{usedByNameLookup[opt] || opt}</span>{' '}
|
|
394
426
|
<button
|
|
395
427
|
onClick={e => {
|
|
396
428
|
e.preventDefault()
|
|
397
|
-
removeFilterUsedBy(filter,
|
|
429
|
+
removeFilterUsedBy(filter, opt)
|
|
398
430
|
}}
|
|
399
431
|
>
|
|
400
432
|
X
|
|
@@ -402,15 +434,13 @@ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeF
|
|
|
402
434
|
</li>
|
|
403
435
|
))}
|
|
404
436
|
</ul>
|
|
405
|
-
<select onChange={e => addFilterUsedBy(filter, e.target.value)}>
|
|
437
|
+
<select value='' onChange={e => addFilterUsedBy(filter, e.target.value)}>
|
|
406
438
|
<option value=''>- Select Option -</option>
|
|
407
|
-
{
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
</option>
|
|
413
|
-
))}
|
|
439
|
+
{usedByOptions.map(opt => (
|
|
440
|
+
<option value={opt} key={`used-by-select-item-${opt}`}>
|
|
441
|
+
{usedByNameLookup[opt] || opt}
|
|
442
|
+
</option>
|
|
443
|
+
))}
|
|
414
444
|
</select>
|
|
415
445
|
</label>
|
|
416
446
|
<label>
|
|
@@ -9,7 +9,8 @@ const MultiTabs = () => {
|
|
|
9
9
|
const tabs = useMemo<string[]>(() => (config.multiDashboards || []).map(({ label }) => label), [config.multiDashboards])
|
|
10
10
|
const activeTab = useMemo<number>(() => config.activeDashboard, [config.activeDashboard])
|
|
11
11
|
|
|
12
|
-
const load = (indexToSwitchTo: number) => {
|
|
12
|
+
const load = (indexToSwitchTo: number, e) => {
|
|
13
|
+
e.preventDefault() // some form wrapper is causing this to act as a submit button
|
|
13
14
|
dispatch({ type: 'SWITCH_CONFIG', payload: indexToSwitchTo })
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -18,7 +19,7 @@ const MultiTabs = () => {
|
|
|
18
19
|
<ul className='nav nav-tabs multi-config-tabs'>
|
|
19
20
|
{tabs.map((tab, index) => (
|
|
20
21
|
<li className='nav-item'>
|
|
21
|
-
<a className={`nav-link${activeTab === index ? ' active' : ''}`} aria-current={activeTab === index ? 'page' : null} href='#' onClick={
|
|
22
|
+
<a className={`nav-link${activeTab === index ? ' active' : ''}`} aria-current={activeTab === index ? 'page' : null} href='#' onClick={e => load(index, e)}>
|
|
22
23
|
{tab}
|
|
23
24
|
</a>
|
|
24
25
|
</li>
|
package/src/components/Row.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext, useState } from 'react'
|
|
1
|
+
import React, { useContext, useMemo, useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
|
|
4
4
|
|
|
@@ -13,41 +13,53 @@ import FourEightColIcon from '../images/icon-col-4-8.svg'
|
|
|
13
13
|
import EightFourColIcon from '../images/icon-col-8-4.svg'
|
|
14
14
|
import ToggleIcon from '../images/icon-toggle.svg'
|
|
15
15
|
import { ConfigRow } from '../types/ConfigRow'
|
|
16
|
+
import { DataDesignerModal } from './DataDesignerModal'
|
|
17
|
+
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
18
|
+
import { iconHash } from '../helpers/iconHash'
|
|
19
|
+
import _ from 'lodash'
|
|
16
20
|
|
|
17
21
|
type RowMenuProps = {
|
|
18
22
|
rowIdx: number
|
|
19
|
-
row: ConfigRow
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
const RowMenu: React.FC<RowMenuProps> = ({ rowIdx
|
|
25
|
+
const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
23
26
|
const { config } = useContext(DashboardContext)
|
|
24
27
|
const dispatch = useContext(DashboardDispatchContext)
|
|
25
|
-
const
|
|
28
|
+
const rows = _.cloneDeep(config.rows)
|
|
29
|
+
const row = config.rows[rowIdx]
|
|
26
30
|
|
|
27
31
|
const updateConfig = config => dispatch({ type: 'UPDATE_CONFIG', payload: [config] })
|
|
28
|
-
const
|
|
29
|
-
if (row
|
|
30
|
-
return row.reduce((acc, curr) => {
|
|
32
|
+
const curr = useMemo(() => {
|
|
33
|
+
if (row.toggle) return 'toggle'
|
|
34
|
+
return row.columns.reduce((acc, curr) => {
|
|
31
35
|
if (curr.width) {
|
|
32
36
|
acc += curr.width
|
|
33
37
|
}
|
|
34
38
|
return acc
|
|
35
39
|
}, '')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const [curr, setCurr] = useState(getCurr())
|
|
40
|
+
}, [row])
|
|
39
41
|
|
|
40
42
|
const setRowLayout = (layout: number[], toggle = undefined) => {
|
|
41
|
-
const newRows =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
const newRows = _.cloneDeep(rows)
|
|
44
|
+
newRows[rowIdx].toggle = toggle
|
|
45
|
+
const rowColumns = newRows[rowIdx].columns
|
|
46
|
+
const columnsWithWidgets = rowColumns.filter(c => c.widget)
|
|
47
|
+
|
|
48
|
+
const totalWidgets = columnsWithWidgets.length
|
|
49
|
+
if (totalWidgets > layout.length) {
|
|
50
|
+
// don't let them change to a smaller layout and lose viz config work
|
|
51
|
+
return
|
|
52
|
+
} else {
|
|
53
|
+
// a 3 column becoming a 2 column with only a VizConfig in the second column will maintain order
|
|
54
|
+
// a 2 column becoming a 1 column with only a VizConfig in the second column will move the VizConfig to the first column
|
|
55
|
+
const mapRow = rowColumns.length > layout.length ? columnsWithWidgets : rowColumns
|
|
56
|
+
newRows[rowIdx].columns = layout.map((width, colIndex) => {
|
|
57
|
+
const col = mapRow[colIndex]
|
|
58
|
+
return col ? { ...col, width } : { width }
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
49
62
|
updateConfig({ ...config, rows: newRows })
|
|
50
|
-
setCurr(toggle ? 'toggle' : layout.join(''))
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
const moveRow = (dir = 'down') => {
|
|
@@ -138,15 +150,28 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx, row }) => {
|
|
|
138
150
|
}
|
|
139
151
|
|
|
140
152
|
const Row = ({ row, idx: rowIdx, uuid }) => {
|
|
153
|
+
const { overlay } = useGlobalContext()
|
|
141
154
|
return (
|
|
142
155
|
<div className='builder-row' data-row-id={rowIdx}>
|
|
143
|
-
<RowMenu rowIdx={rowIdx}
|
|
156
|
+
<RowMenu rowIdx={rowIdx} />
|
|
144
157
|
<div className='column-container'>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
158
|
+
<>
|
|
159
|
+
<button
|
|
160
|
+
title='Configure Data'
|
|
161
|
+
className='btn btn-configure-row'
|
|
162
|
+
onClick={() => {
|
|
163
|
+
overlay?.actions.openOverlay(<DataDesignerModal rowIndex={rowIdx} />)
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
{iconHash['gear']}
|
|
167
|
+
</button>
|
|
168
|
+
|
|
169
|
+
{row.columns
|
|
170
|
+
.filter(column => column.width)
|
|
171
|
+
.map((column, colIdx) => (
|
|
172
|
+
<Column data={column} key={`row-${uuid}-col-${colIdx}`} rowIdx={rowIdx} colIdx={colIdx} />
|
|
173
|
+
))}
|
|
174
|
+
</>
|
|
150
175
|
</div>
|
|
151
176
|
</div>
|
|
152
177
|
)
|
|
@@ -7,23 +7,22 @@ import './toggle-style.css'
|
|
|
7
7
|
import _ from 'lodash'
|
|
8
8
|
|
|
9
9
|
type ToggleProps = {
|
|
10
|
+
active: number
|
|
10
11
|
row: ConfigRow
|
|
11
|
-
rowIndex: number
|
|
12
12
|
visualizations: Record<string, Visualization>
|
|
13
|
+
setToggled: (colIndex: number) => void
|
|
13
14
|
}
|
|
14
|
-
const Toggle: React.FC<ToggleProps> = ({
|
|
15
|
-
const dispatch = useContext(DashboardDispatchContext)
|
|
16
|
-
|
|
15
|
+
const Toggle: React.FC<ToggleProps> = ({ active, row, visualizations, setToggled }) => {
|
|
17
16
|
const selectItem = (colIndex, e = null) => {
|
|
18
17
|
if (e?.key && e.key !== 'Enter') return
|
|
19
|
-
|
|
18
|
+
setToggled(colIndex)
|
|
20
19
|
}
|
|
21
20
|
return (
|
|
22
21
|
<div className='toggle-component'>
|
|
23
|
-
{row.map((col, colIndex) => {
|
|
22
|
+
{row.columns.map((col, colIndex) => {
|
|
24
23
|
if (!col.widget) return null
|
|
25
24
|
const type = visualizations[col.widget].type
|
|
26
|
-
const selected =
|
|
25
|
+
const selected = colIndex === active
|
|
27
26
|
return (
|
|
28
27
|
<div role='radio' className={selected ? 'selected' : ''} key={colIndex} onClick={() => selectItem(colIndex)} onKeyUp={e => selectItem(colIndex, e)} aria-checked={selected} tabIndex={0} aria-label={`Toggle ${type}`}>
|
|
29
28
|
{getIcon(visualizations[col.widget])} <span>{_.capitalize(type)}</span>
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
|
|
2
|
+
import React, { MouseEventHandler, useContext, useMemo } from 'react'
|
|
3
|
+
import Toggle from './Toggle'
|
|
4
|
+
import _ from 'lodash'
|
|
5
|
+
import { ConfigRow } from '../types/ConfigRow'
|
|
6
|
+
import CdcMap from '@cdc/map'
|
|
7
|
+
import CdcChart from '@cdc/chart'
|
|
8
|
+
import CdcDataBite from '@cdc/data-bite'
|
|
9
|
+
import CdcWaffleChart from '@cdc/waffle-chart'
|
|
10
|
+
import CdcMarkupInclude from '@cdc/markup-include'
|
|
11
|
+
import CdcFilteredText from '@cdc/filtered-text'
|
|
12
|
+
import Filters, { APIFilterDropdowns } from './Filters'
|
|
13
|
+
import { FilterBehavior } from './Header/Header'
|
|
14
|
+
import { DashboardContext } from '../DashboardContext'
|
|
15
|
+
import { ViewPort } from '@cdc/core/types/ViewPort'
|
|
16
|
+
import { getVizConfig } from '../helpers/getVizConfig'
|
|
17
|
+
import { TableConfig } from '@cdc/core/components/DataTable/types/TableConfig'
|
|
18
|
+
|
|
19
|
+
type VizRowProps = {
|
|
20
|
+
filteredDataOverride?: Object[]
|
|
21
|
+
row: ConfigRow
|
|
22
|
+
rowIndex: number
|
|
23
|
+
setSharedFilter: Function
|
|
24
|
+
updateChildConfig: Function
|
|
25
|
+
applyFilters: MouseEventHandler<HTMLButtonElement>
|
|
26
|
+
apiFilterDropdowns: APIFilterDropdowns
|
|
27
|
+
handleOnChange: Function
|
|
28
|
+
currentViewport: ViewPort
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const VisualizationRow: React.FC<VizRowProps> = ({ filteredDataOverride, row, rowIndex: index, setSharedFilter, updateChildConfig, applyFilters, apiFilterDropdowns, handleOnChange, currentViewport }) => {
|
|
32
|
+
const { config, filteredData: dashboardFilteredData, data: rawData } = useContext(DashboardContext)
|
|
33
|
+
const [show, setShow] = React.useState(row.columns.map((col, i) => i === 0))
|
|
34
|
+
const setToggled = (colIndex: number) => {
|
|
35
|
+
setShow(show.map((_, i) => i === colIndex))
|
|
36
|
+
}
|
|
37
|
+
const inNoDataState = useMemo(() => {
|
|
38
|
+
const vals = Object.values(rawData)
|
|
39
|
+
if (!vals.length) return true
|
|
40
|
+
return vals.some(val => val === undefined)
|
|
41
|
+
}, [rawData])
|
|
42
|
+
const GoButton = ({ autoLoad }: { autoLoad?: boolean }) => {
|
|
43
|
+
if (config.filterBehavior === FilterBehavior.Apply && !autoLoad) {
|
|
44
|
+
return <button onClick={applyFilters}>GO!</button>
|
|
45
|
+
}
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
return (
|
|
49
|
+
<div className={`dashboard-row ${row.equalHeight ? 'equal-height' : ''} ${row.toggle ? 'toggle' : ''}`} key={`row__${index}`}>
|
|
50
|
+
{row.toggle && <Toggle row={row} visualizations={config.visualizations} active={show.indexOf(true)} setToggled={setToggled} />}
|
|
51
|
+
{row.columns.map((col, colIndex) => {
|
|
52
|
+
if (col.width) {
|
|
53
|
+
if (!col.widget) return <div key={`row__${index}__col__${colIndex}`} className={`dashboard-col dashboard-col-${col.width}`}></div>
|
|
54
|
+
|
|
55
|
+
const visualizationConfig = getVizConfig(col.widget, index, config, rawData, dashboardFilteredData)
|
|
56
|
+
if (filteredDataOverride) {
|
|
57
|
+
visualizationConfig.data = filteredDataOverride
|
|
58
|
+
if (visualizationConfig.formattedData) {
|
|
59
|
+
visualizationConfig.formattedData = filteredDataOverride
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const setsSharedFilter = config.dashboard.sharedFilters && config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === col.widget).length > 0
|
|
64
|
+
const setSharedFilterValue = setsSharedFilter ? config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === col.widget)[0].active : undefined
|
|
65
|
+
const tableLink = (
|
|
66
|
+
<a href={`#data-table-${visualizationConfig.dataKey}`} className='margin-left-href'>
|
|
67
|
+
{visualizationConfig.dataKey} (Go to Table)
|
|
68
|
+
</a>
|
|
69
|
+
)
|
|
70
|
+
const hideFilter = visualizationConfig.autoLoad && inNoDataState
|
|
71
|
+
|
|
72
|
+
const shouldShow = row.toggle === undefined || (row.toggle && show[colIndex])
|
|
73
|
+
return (
|
|
74
|
+
<React.Fragment key={`vis__${index}__${colIndex}`}>
|
|
75
|
+
<div className={`dashboard-col dashboard-col-${col.width} ${!shouldShow ? 'hidden-toggle' : ''}`}>
|
|
76
|
+
{visualizationConfig.type === 'chart' && (
|
|
77
|
+
<CdcChart
|
|
78
|
+
key={col.widget}
|
|
79
|
+
config={visualizationConfig}
|
|
80
|
+
dashboardConfig={config}
|
|
81
|
+
isEditor={false}
|
|
82
|
+
setConfig={newConfig => {
|
|
83
|
+
updateChildConfig(col.widget, newConfig)
|
|
84
|
+
}}
|
|
85
|
+
setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
|
|
86
|
+
isDashboard={true}
|
|
87
|
+
link={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
|
|
88
|
+
configUrl={undefined}
|
|
89
|
+
setEditing={undefined}
|
|
90
|
+
hostname={undefined}
|
|
91
|
+
setSharedFilterValue={undefined}
|
|
92
|
+
/>
|
|
93
|
+
)}
|
|
94
|
+
{visualizationConfig.type === 'map' && (
|
|
95
|
+
<CdcMap
|
|
96
|
+
key={col.widget}
|
|
97
|
+
config={visualizationConfig}
|
|
98
|
+
isEditor={false}
|
|
99
|
+
setConfig={newConfig => {
|
|
100
|
+
updateChildConfig(col.widget, newConfig)
|
|
101
|
+
}}
|
|
102
|
+
showLoader={false}
|
|
103
|
+
setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
|
|
104
|
+
setSharedFilterValue={setSharedFilterValue}
|
|
105
|
+
isDashboard={true}
|
|
106
|
+
link={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
|
|
107
|
+
/>
|
|
108
|
+
)}
|
|
109
|
+
{visualizationConfig.type === 'data-bite' && (
|
|
110
|
+
<CdcDataBite
|
|
111
|
+
key={col.widget}
|
|
112
|
+
config={visualizationConfig}
|
|
113
|
+
isEditor={false}
|
|
114
|
+
setConfig={newConfig => {
|
|
115
|
+
updateChildConfig(col.widget, newConfig)
|
|
116
|
+
}}
|
|
117
|
+
isDashboard={true}
|
|
118
|
+
/>
|
|
119
|
+
)}
|
|
120
|
+
{visualizationConfig.type === 'waffle-chart' && (
|
|
121
|
+
<CdcWaffleChart
|
|
122
|
+
key={col.widget}
|
|
123
|
+
config={visualizationConfig}
|
|
124
|
+
isEditor={false}
|
|
125
|
+
setConfig={newConfig => {
|
|
126
|
+
updateChildConfig(col.widget, newConfig)
|
|
127
|
+
}}
|
|
128
|
+
isDashboard={true}
|
|
129
|
+
configUrl={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
|
|
130
|
+
/>
|
|
131
|
+
)}
|
|
132
|
+
{visualizationConfig.type === 'markup-include' && (
|
|
133
|
+
<CdcMarkupInclude
|
|
134
|
+
key={col.widget}
|
|
135
|
+
config={visualizationConfig}
|
|
136
|
+
configUrl={undefined}
|
|
137
|
+
isDashboard={true}
|
|
138
|
+
isEditor={false}
|
|
139
|
+
setConfig={newConfig => {
|
|
140
|
+
updateChildConfig(col.widget, newConfig)
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
)}
|
|
144
|
+
{visualizationConfig.type === 'filtered-text' && (
|
|
145
|
+
<CdcFilteredText
|
|
146
|
+
key={col.widget}
|
|
147
|
+
config={visualizationConfig}
|
|
148
|
+
isEditor={false}
|
|
149
|
+
setConfig={newConfig => {
|
|
150
|
+
updateChildConfig(col.widget, newConfig)
|
|
151
|
+
}}
|
|
152
|
+
isDashboard={true}
|
|
153
|
+
configUrl={undefined}
|
|
154
|
+
/>
|
|
155
|
+
)}
|
|
156
|
+
{visualizationConfig.type === 'filter-dropdowns' && !hideFilter && (
|
|
157
|
+
<React.Fragment key={col.widget}>
|
|
158
|
+
<Filters hide={visualizationConfig.hide} filters={config.dashboard.sharedFilters} apiFilterDropdowns={apiFilterDropdowns} handleOnChange={handleOnChange} />
|
|
159
|
+
<GoButton autoLoad={visualizationConfig.autoLoad} />
|
|
160
|
+
</React.Fragment>
|
|
161
|
+
)}
|
|
162
|
+
{visualizationConfig.type === 'table' && (
|
|
163
|
+
<DataTableStandAlone
|
|
164
|
+
key={col.widget}
|
|
165
|
+
updateConfig={newConfig => {
|
|
166
|
+
updateChildConfig(col.widget, newConfig)
|
|
167
|
+
}}
|
|
168
|
+
visualizationKey={col.widget}
|
|
169
|
+
config={visualizationConfig as TableConfig}
|
|
170
|
+
viewport={currentViewport}
|
|
171
|
+
/>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
</React.Fragment>
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
return <React.Fragment key={`vis__${index}__${colIndex}`}></React.Fragment>
|
|
178
|
+
})}
|
|
179
|
+
</div>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export default VisualizationRow
|
|
@@ -7,6 +7,8 @@ import { Table } from '@cdc/core/types/Table'
|
|
|
7
7
|
const addVisualization = (type, subType) => {
|
|
8
8
|
const modalWillOpen = type !== 'markup-include'
|
|
9
9
|
const newVisualizationConfig: Partial<Visualization> = {
|
|
10
|
+
filters: [],
|
|
11
|
+
filterBehavior: 'Filter Change',
|
|
10
12
|
newViz: type !== 'table',
|
|
11
13
|
openModal: modalWillOpen,
|
|
12
14
|
uid: type + Date.now(),
|
|
@@ -21,15 +23,36 @@ const addVisualization = (type, subType) => {
|
|
|
21
23
|
newVisualizationConfig.general = {}
|
|
22
24
|
newVisualizationConfig.general.geoType = subType
|
|
23
25
|
break
|
|
24
|
-
case 'data-bite' || 'waffle-chart' || '
|
|
26
|
+
case 'data-bite' || 'waffle-chart' || 'filtered-text':
|
|
25
27
|
newVisualizationConfig.visualizationType = type
|
|
26
28
|
break
|
|
27
29
|
case 'table':
|
|
28
|
-
const tableConfig: Table = { label: 'Data Table', show: true, showDownloadUrl: false, showVertical: true, expanded: true }
|
|
30
|
+
const tableConfig: Table = { label: 'Data Table', show: true, showDownloadUrl: false, showVertical: true, expanded: true, collapsible: true }
|
|
29
31
|
newVisualizationConfig.table = tableConfig
|
|
30
32
|
newVisualizationConfig.columns = {}
|
|
31
33
|
newVisualizationConfig.dataFormat = {}
|
|
32
34
|
newVisualizationConfig.visualizationType = type
|
|
35
|
+
break
|
|
36
|
+
case 'markup-include':
|
|
37
|
+
newVisualizationConfig.contentEditor = {
|
|
38
|
+
inlineHTML: '<h2>Inline HTML</h2>',
|
|
39
|
+
markupVariables: [],
|
|
40
|
+
showHeader: true,
|
|
41
|
+
srcUrl: '#example',
|
|
42
|
+
title: 'Markup Include',
|
|
43
|
+
useInlineHTML: true
|
|
44
|
+
}
|
|
45
|
+
newVisualizationConfig.theme = 'theme-blue'
|
|
46
|
+
newVisualizationConfig.visual = {
|
|
47
|
+
border: false,
|
|
48
|
+
accent: false,
|
|
49
|
+
background: false,
|
|
50
|
+
hideBackgroundColor: false,
|
|
51
|
+
borderColorTheme: false
|
|
52
|
+
}
|
|
53
|
+
newVisualizationConfig.showEditorPanel = true
|
|
54
|
+
newVisualizationConfig.visualizationType = type
|
|
55
|
+
|
|
33
56
|
break
|
|
34
57
|
default:
|
|
35
58
|
newVisualizationConfig.visualizationType = type
|
|
@@ -65,7 +88,7 @@ const VisualizationsPanel = ({ loadConfig, config }) => (
|
|
|
65
88
|
<Widget addVisualization={() => addVisualization('table', '')} type='table' />
|
|
66
89
|
</div>
|
|
67
90
|
<span className='subheading-3'>Advanced</span>
|
|
68
|
-
<AdvancedEditor loadConfig={loadConfig} state={config} convertStateToConfig={undefined} />
|
|
91
|
+
<AdvancedEditor loadConfig={loadConfig} state={config} convertStateToConfig={() => undefined} />
|
|
69
92
|
</div>
|
|
70
93
|
)
|
|
71
94
|
|