@cdc/dashboard 4.26.1 → 4.26.3
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/LICENSE +201 -0
- package/dist/cdcdashboard-8NmHlKRI.es.js +15 -0
- package/dist/cdcdashboard-BPoPzKPz.es.js +6 -0
- package/dist/{cdcdashboard-dgT_1dIT.es.js → cdcdashboard-DQ00cQCm.es.js} +1 -20
- package/dist/cdcdashboard-jiQQPkty.es.js +6 -0
- package/dist/cdcdashboard-vr9HZwRt.es.js +6 -0
- package/dist/cdcdashboard.js +80971 -83096
- package/examples/custom/css/respiratory.css +1 -1
- package/examples/data/data-with-metadata.json +18 -0
- package/examples/default.json +492 -132
- package/examples/nested-dropdown.json +6985 -0
- package/examples/private/abc.json +467 -0
- package/examples/private/dash.json +12696 -0
- package/examples/private/inline-markup.json +775 -0
- package/examples/private/npcr.json +1 -0
- package/examples/private/recent-update.json +1456 -0
- package/examples/private/test.json +125407 -0
- package/examples/private/timeline-data.json +4994 -0
- package/examples/private/timeline.json +1708 -0
- package/examples/private/toggle.json +10137 -0
- package/examples/test-api-filter-reset.json +8 -4
- package/examples/tp5-gauges.json +196 -0
- package/examples/tp5-test.json +266 -0
- package/index.html +1 -29
- package/package.json +38 -40
- package/src/CdcDashboard.tsx +2 -1
- package/src/CdcDashboardComponent.tsx +47 -30
- package/src/_stories/Dashboard.DataSetup.stories.tsx +8 -2
- package/src/_stories/Dashboard.Pages.stories.tsx +22 -0
- package/src/_stories/Dashboard.stories.tsx +4501 -80
- package/src/_stories/_mock/dashboard-line-chart-angles.json +1030 -0
- package/src/_stories/_mock/tab-simple-filter.json +153 -0
- package/src/_stories/_mock/tp5-test.json +267 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +19 -3
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +10 -4
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +1 -1
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +6 -3
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +13 -8
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +8 -8
- package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +1 -1
- package/src/components/DashboardFilters/dashboardfilter.styles.css +3 -3
- package/src/components/DataDesignerModal.tsx +2 -2
- package/src/components/Header/Header.tsx +27 -5
- package/src/components/Header/index.scss +1 -1
- package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +6 -6
- package/src/components/Row.tsx +21 -0
- package/src/components/Toggle/toggle-style.css +7 -7
- package/src/components/VisualizationRow.tsx +42 -29
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +1 -71
- package/src/components/VisualizationsPanel/visualizations-panel-styles.css +2 -2
- package/src/components/Widget/Widget.tsx +2 -2
- package/src/components/Widget/widget.styles.css +12 -12
- package/src/data/initial-state.js +1 -1
- package/src/helpers/addValuesToDashboardFilters.ts +17 -11
- package/src/helpers/addVisualization.ts +71 -0
- package/src/helpers/apiFilterHelpers.ts +28 -32
- package/src/helpers/formatConfigBeforeSave.ts +1 -1
- package/src/helpers/getVizConfig.ts +13 -3
- package/src/helpers/iconHash.tsx +45 -36
- package/src/helpers/processDataLegacy.ts +19 -14
- package/src/helpers/tests/addValuesToDashboardFilters.test.ts +141 -44
- package/src/helpers/tests/addVisualization.test.ts +52 -0
- package/src/helpers/tests/apiFilterHelpers.test.ts +523 -420
- package/src/helpers/tests/formatConfigBeforeSave.test.ts +81 -1
- package/src/scss/editor-panel.scss +1 -1
- package/src/scss/main.scss +169 -41
- package/src/store/dashboard.reducer.ts +1 -1
- package/src/test/CdcDashboard.test.jsx +2 -2
- package/src/test/CdcDashboardComponent.test.tsx +74 -0
- package/src/types/FilterStyles.ts +2 -1
- package/tests/fixtures/dashboard-config-with-metadata.json +89 -0
- package/vite.config.js +7 -1
- package/dist/cdcdashboard-BnB1QM5d.es.js +0 -361528
- package/dist/cdcdashboard-Ct2SB0vL.es.js +0 -231049
- package/dist/cdcdashboard-D6CG2-Hb.es.js +0 -39377
- package/dist/cdcdashboard-MXgURbdZ.es.js +0 -39194
|
@@ -7,7 +7,7 @@ import { FilterBehavior } from '../../helpers/FilterBehavior'
|
|
|
7
7
|
import { getFilteredData } from '../../helpers/getFilteredData'
|
|
8
8
|
import { DashboardFilters } from '../../types/DashboardFilters'
|
|
9
9
|
import { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
|
|
10
|
-
import
|
|
10
|
+
import { VisualizationWrapper, Sidebar, Responsive } from '@cdc/core/components/Layout'
|
|
11
11
|
import DashboardFiltersEditor from './DashboardFiltersEditor'
|
|
12
12
|
import { ViewPort } from '@cdc/core/types/ViewPort'
|
|
13
13
|
import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavior'
|
|
@@ -302,24 +302,24 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
302
302
|
const displayNone = filters?.length ? filters.every(filter => filter.showDropdown === false) : false
|
|
303
303
|
if (displayNone && !isEditor) return <></>
|
|
304
304
|
return (
|
|
305
|
-
<
|
|
305
|
+
<VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
|
|
306
306
|
{isEditor && (
|
|
307
|
-
<
|
|
307
|
+
<Sidebar
|
|
308
308
|
displayPanel={displayPanel}
|
|
309
309
|
isDashboard={true}
|
|
310
310
|
title={'Configure Dashboard Filters'}
|
|
311
311
|
onBackClick={onBackClick}
|
|
312
312
|
>
|
|
313
313
|
<DashboardFiltersEditor updateConfig={updateConfig} vizConfig={visualizationConfig} />
|
|
314
|
-
</
|
|
314
|
+
</Sidebar>
|
|
315
315
|
)}
|
|
316
316
|
|
|
317
317
|
{!displayNone && (
|
|
318
|
-
<
|
|
318
|
+
<Responsive isEditor={isEditor}>
|
|
319
319
|
<div
|
|
320
320
|
className={`${
|
|
321
321
|
isEditor ? ' is-editor' : ''
|
|
322
|
-
} cove-
|
|
322
|
+
} cove-visualization__inner cove-visualization__body col-12 cove-dashboard-filters-container`}
|
|
323
323
|
>
|
|
324
324
|
<Filters
|
|
325
325
|
show={visualizationConfig?.sharedFilterIndexes?.map(Number)}
|
|
@@ -337,9 +337,9 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
337
337
|
}
|
|
338
338
|
/>
|
|
339
339
|
</div>
|
|
340
|
-
</
|
|
340
|
+
</Responsive>
|
|
341
341
|
)}
|
|
342
|
-
</
|
|
342
|
+
</VisualizationWrapper>
|
|
343
343
|
)
|
|
344
344
|
}
|
|
345
345
|
|
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
font-weight: 700;
|
|
5
5
|
}
|
|
6
6
|
.btn {
|
|
7
|
+
align-self: flex-end;
|
|
7
8
|
/* this is the height that is defined for the .form-control class in _forms.scss in bootstrap. */
|
|
8
9
|
height: calc(1.5em + 0.75rem + 2px);
|
|
9
|
-
align-self: flex-end;
|
|
10
10
|
}
|
|
11
11
|
.loading-filter {
|
|
12
12
|
position: relative;
|
|
13
13
|
.spinner-border {
|
|
14
|
+
height: 1.5rem;
|
|
14
15
|
position: absolute;
|
|
15
|
-
top: 55%;
|
|
16
16
|
right: 10%;
|
|
17
|
+
top: 55%;
|
|
17
18
|
width: 1.5rem;
|
|
18
|
-
height: 1.5rem;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
:is(select):disabled {
|
|
@@ -59,8 +59,8 @@ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, ro
|
|
|
59
59
|
if (dataSetChanged || noCachedData) {
|
|
60
60
|
setLoadingAPIData(true)
|
|
61
61
|
try {
|
|
62
|
-
|
|
63
|
-
newData = transform.autoStandardize(
|
|
62
|
+
const result = await fetchRemoteData(dataUrl)
|
|
63
|
+
newData = transform.autoStandardize(result.data)
|
|
64
64
|
} catch (e) {
|
|
65
65
|
setErrorMessage('There was an issue loading the data source. Please check the datasource URL and try again.')
|
|
66
66
|
}
|
|
@@ -176,7 +176,6 @@ const Header = (props: HeaderProps) => {
|
|
|
176
176
|
Show Data Table(s)
|
|
177
177
|
</label>
|
|
178
178
|
<br />
|
|
179
|
-
|
|
180
179
|
<label>
|
|
181
180
|
<input
|
|
182
181
|
type='checkbox'
|
|
@@ -185,7 +184,6 @@ const Header = (props: HeaderProps) => {
|
|
|
185
184
|
/>
|
|
186
185
|
Expanded by Default
|
|
187
186
|
</label>
|
|
188
|
-
<br />
|
|
189
187
|
</div>
|
|
190
188
|
|
|
191
189
|
<div className='wrap'>
|
|
@@ -206,9 +204,6 @@ const Header = (props: HeaderProps) => {
|
|
|
206
204
|
onChange={e => changeConfigValue('table', 'height', e.target.value)}
|
|
207
205
|
/>
|
|
208
206
|
)}
|
|
209
|
-
</div>
|
|
210
|
-
|
|
211
|
-
<div className='wrap'>
|
|
212
207
|
<label>
|
|
213
208
|
<input
|
|
214
209
|
type='checkbox'
|
|
@@ -217,6 +212,17 @@ const Header = (props: HeaderProps) => {
|
|
|
217
212
|
/>
|
|
218
213
|
Show Download CSV Link
|
|
219
214
|
</label>
|
|
215
|
+
{config.table.download && (
|
|
216
|
+
<input
|
|
217
|
+
type='text'
|
|
218
|
+
placeholder='Customize label'
|
|
219
|
+
defaultValue={config.table.downloadDataLabel}
|
|
220
|
+
onChange={e => changeConfigValue('table', 'downloadDataLabel', e.target.value)}
|
|
221
|
+
/>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<div className='wrap'>
|
|
220
226
|
<label>
|
|
221
227
|
<input
|
|
222
228
|
type='checkbox'
|
|
@@ -225,6 +231,22 @@ const Header = (props: HeaderProps) => {
|
|
|
225
231
|
/>
|
|
226
232
|
Show URL to Automatically Updated Data
|
|
227
233
|
</label>
|
|
234
|
+
<label>
|
|
235
|
+
<input
|
|
236
|
+
type='checkbox'
|
|
237
|
+
defaultChecked={config.table.downloadImageButton}
|
|
238
|
+
onChange={e => changeConfigValue('table', 'downloadImageButton', e.target.checked)}
|
|
239
|
+
/>
|
|
240
|
+
Show Download Image Button
|
|
241
|
+
</label>
|
|
242
|
+
{config.table.downloadImageButton && (
|
|
243
|
+
<input
|
|
244
|
+
type='text'
|
|
245
|
+
placeholder='Customize label'
|
|
246
|
+
defaultValue={config.table.downloadImageLabel}
|
|
247
|
+
onChange={e => changeConfigValue('table', 'downloadImageLabel', e.target.value)}
|
|
248
|
+
/>
|
|
249
|
+
)}
|
|
228
250
|
</div>
|
|
229
251
|
</>
|
|
230
252
|
)}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
.multi-config-tabs {
|
|
2
2
|
.nav-link {
|
|
3
|
+
border-color: var(--lightGray);
|
|
3
4
|
border-radius: 6px 6px 0 0;
|
|
4
|
-
|
|
5
|
+
color: var(--primary);
|
|
5
6
|
display: block;
|
|
7
|
+
font-weight: 400;
|
|
6
8
|
padding: 0.5rem 1rem;
|
|
7
9
|
@media (max-width: 480px) {
|
|
8
10
|
padding: 0.5rem 0.8rem;
|
|
9
11
|
}
|
|
10
|
-
color: var(--primary);
|
|
11
|
-
border-color: var(--lightGray);
|
|
12
12
|
:is(button) {
|
|
13
|
-
display: none;
|
|
14
13
|
background: none;
|
|
14
|
+
display: none;
|
|
15
15
|
}
|
|
16
16
|
&:hover {
|
|
17
17
|
:is(button) {
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
font-weight: bold;
|
|
32
32
|
}
|
|
33
33
|
.btn-danger {
|
|
34
|
-
text-decoration: none;
|
|
35
|
-
padding: 0px 5px;
|
|
36
34
|
font-size: inherit;
|
|
35
|
+
padding: 0px 5px;
|
|
36
|
+
text-decoration: none;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
.add {
|
package/src/components/Row.tsx
CHANGED
|
@@ -70,6 +70,12 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
70
70
|
updateConfig({ ...config, rows: newRows })
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
const toggleEqualHeight = () => {
|
|
74
|
+
const newRows = _.cloneDeep(rows)
|
|
75
|
+
newRows[rowIdx].equalHeight = !newRows[rowIdx].equalHeight
|
|
76
|
+
updateConfig({ ...config, rows: newRows })
|
|
77
|
+
}
|
|
78
|
+
|
|
73
79
|
const moveRow = (dir = 'down') => {
|
|
74
80
|
if (rowIdx === rows.length - 1 && dir === 'down') return
|
|
75
81
|
|
|
@@ -179,9 +185,24 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
179
185
|
</li>
|
|
180
186
|
]
|
|
181
187
|
|
|
188
|
+
const isMultiColumn = curr !== '12' && curr !== 'toggle'
|
|
189
|
+
|
|
182
190
|
return (
|
|
183
191
|
<nav className='row-menu'>
|
|
184
192
|
<ul className='row-menu__flyout'>{layoutList}</ul>
|
|
193
|
+
{isMultiColumn && (
|
|
194
|
+
<button
|
|
195
|
+
className={`btn row-menu__btn border-0${row.equalHeight ? ' btn-primary' : ''}`}
|
|
196
|
+
title={row.equalHeight ? 'Disable Equal Height Rows' : 'Enable Equal Height Rows'}
|
|
197
|
+
onClick={toggleEqualHeight}
|
|
198
|
+
>
|
|
199
|
+
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='25' height='20' fill='#fff'>
|
|
200
|
+
<rect x='1' y='2' width='9' height='14' rx='1' />
|
|
201
|
+
<rect x='14' y='2' width='9' height='14' rx='1' />
|
|
202
|
+
<line x1='0' y1='19' x2='24' y2='19' stroke='#fff' strokeWidth='2' strokeDasharray='3 2' />
|
|
203
|
+
</svg>
|
|
204
|
+
</button>
|
|
205
|
+
)}
|
|
185
206
|
<div className='spacer'></div>
|
|
186
207
|
<button
|
|
187
208
|
className={`btn btn-primary row-menu__btn border-0`}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
.
|
|
1
|
+
.cove-visualization {
|
|
2
2
|
--border: 1px solid var(--lightGray);
|
|
3
3
|
.toggle-component {
|
|
4
4
|
display: flex;
|
|
5
5
|
justify-content: right;
|
|
6
|
-
width: 100%;
|
|
7
6
|
margin-bottom: 15px;
|
|
7
|
+
width: 100%;
|
|
8
8
|
:first-child:is(div) {
|
|
9
9
|
border: var(--border);
|
|
10
10
|
border-radius: 5px 0 0 5px;
|
|
@@ -14,18 +14,18 @@
|
|
|
14
14
|
border-radius: 0 5px 5px 0;
|
|
15
15
|
}
|
|
16
16
|
:is(div) {
|
|
17
|
-
|
|
17
|
+
background-color: var(--white);
|
|
18
18
|
border-bottom: var(--border);
|
|
19
|
-
|
|
19
|
+
border-top: var(--border);
|
|
20
|
+
color: var(--primary);
|
|
21
|
+
cursor: pointer;
|
|
20
22
|
display: inline;
|
|
21
23
|
float: right;
|
|
22
|
-
|
|
24
|
+
padding: 7px 15px;
|
|
23
25
|
&.selected {
|
|
24
26
|
background-color: var(--primary);
|
|
25
27
|
color: white;
|
|
26
28
|
}
|
|
27
|
-
background-color: var(--white);
|
|
28
|
-
color: var(--primary);
|
|
29
29
|
:is(svg) {
|
|
30
30
|
height: 25px;
|
|
31
31
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
|
|
2
2
|
import React, { useContext, useEffect, useMemo, useState } from 'react'
|
|
3
3
|
import Toggle from './Toggle'
|
|
4
|
-
import
|
|
4
|
+
import cloneDeep from 'lodash/cloneDeep'
|
|
5
5
|
import { ConfigRow } from '../types/ConfigRow'
|
|
6
6
|
import CdcDataBite from '@cdc/data-bite/src/CdcDataBite'
|
|
7
7
|
import CdcMap from '@cdc/map/src/CdcMapComponent'
|
|
@@ -103,53 +103,65 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
103
103
|
}
|
|
104
104
|
}, [toggledRow, row.toggle])
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (!rowElement) return
|
|
110
|
-
|
|
111
|
-
const tp5Titles = Array.from(rowElement.querySelectorAll('.bite__style--tp5 .cdc-callout__heading'))
|
|
112
|
-
if (tp5Titles.length <= 1) return // No need to equalize if there's only one or none
|
|
106
|
+
const setupTP5MinHeightEqualizer = (rowElement: Element, itemSelector: string) => {
|
|
107
|
+
const items = Array.from(rowElement.querySelectorAll(itemSelector)) as HTMLElement[]
|
|
108
|
+
if (items.length <= 1) return undefined
|
|
113
109
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
title.style.minHeight = ''
|
|
110
|
+
const equalizeHeights = () => {
|
|
111
|
+
items.forEach(item => {
|
|
112
|
+
item.style.minHeight = ''
|
|
118
113
|
})
|
|
119
114
|
|
|
120
|
-
// Calculate max height after reset
|
|
121
115
|
let maxHeight = 0
|
|
122
|
-
|
|
123
|
-
const height =
|
|
116
|
+
items.forEach(item => {
|
|
117
|
+
const height = item.offsetHeight
|
|
124
118
|
if (height > maxHeight) maxHeight = height
|
|
125
119
|
})
|
|
126
120
|
|
|
127
|
-
// Apply max height to all titles
|
|
128
121
|
if (maxHeight > 0) {
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
items.forEach(item => {
|
|
123
|
+
item.style.minHeight = `${maxHeight}px`
|
|
131
124
|
})
|
|
132
125
|
}
|
|
133
126
|
}
|
|
134
127
|
|
|
135
|
-
|
|
136
|
-
equalizeTP5Titles()
|
|
128
|
+
equalizeHeights()
|
|
137
129
|
|
|
138
|
-
// Use ResizeObserver to watch for size changes in any of the titles
|
|
139
130
|
const resizeObserver = new ResizeObserver(() => {
|
|
140
|
-
|
|
131
|
+
equalizeHeights()
|
|
141
132
|
})
|
|
142
133
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
resizeObserver.observe(title as Element)
|
|
134
|
+
items.forEach(item => {
|
|
135
|
+
resizeObserver.observe(item)
|
|
146
136
|
})
|
|
147
137
|
|
|
138
|
+
return () => resizeObserver.disconnect()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Equalize TP5 callout title heights and TP5 gauge message blocks for like visualizations in the same row
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (!row.equalHeight) return
|
|
144
|
+
|
|
145
|
+
const rowElement = document.querySelector(`[data-row-index="${index}"]`)
|
|
146
|
+
if (!rowElement) return
|
|
147
|
+
|
|
148
|
+
const cleanups = [
|
|
149
|
+
setupTP5MinHeightEqualizer(rowElement, '.bite__style--tp5 .cdc-callout__heading'),
|
|
150
|
+
setupTP5MinHeightEqualizer(rowElement, '.waffle__style--tp5 .cdc-callout__heading'),
|
|
151
|
+
setupTP5MinHeightEqualizer(rowElement, '.gauge__style--tp5 .cdc-callout__heading'),
|
|
152
|
+
setupTP5MinHeightEqualizer(rowElement, '.gauge__style--tp5 .cove-gauge-chart__content')
|
|
153
|
+
].filter(Boolean) as Array<() => void>
|
|
154
|
+
|
|
148
155
|
return () => {
|
|
149
|
-
|
|
156
|
+
cleanups.forEach(cleanup => cleanup())
|
|
150
157
|
}
|
|
151
158
|
}, [index, row, config, filteredDataOverride])
|
|
152
159
|
|
|
160
|
+
const isFilterRow = row.columns.some(
|
|
161
|
+
col => col.widget && config.visualizations[col.widget]?.type === 'dashboardFilters'
|
|
162
|
+
)
|
|
163
|
+
const needsEqualHeight = !!row.equalHeight && !isFilterRow
|
|
164
|
+
|
|
153
165
|
const show = useMemo(() => {
|
|
154
166
|
if (row.toggle) {
|
|
155
167
|
return row.columns.map((col, i) => i === toggledRow)
|
|
@@ -196,7 +208,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
196
208
|
)}
|
|
197
209
|
{Object.keys(dataGroups).map(groupName => {
|
|
198
210
|
const dataValue = dataGroups[groupName]
|
|
199
|
-
const _row =
|
|
211
|
+
const _row = cloneDeep(row) // clone the row to avoid mutating the original row
|
|
200
212
|
const originalMultiVizColumn = _row.multiVizColumn // store original value before clearing
|
|
201
213
|
_row.multiVizColumn = undefined // reset the multiVizColumn to avoid passing it to the child components
|
|
202
214
|
_row.originalMultiVizColumn = originalMultiVizColumn // store for footnote filtering
|
|
@@ -224,7 +236,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
224
236
|
|
|
225
237
|
return (
|
|
226
238
|
<div
|
|
227
|
-
className={`row${
|
|
239
|
+
className={`row${needsEqualHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`}
|
|
228
240
|
key={`row__${index}`}
|
|
229
241
|
data-row-index={index}
|
|
230
242
|
>
|
|
@@ -347,6 +359,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
347
359
|
updateChildConfig(col.widget, newConfig)
|
|
348
360
|
}}
|
|
349
361
|
isDashboard={true}
|
|
362
|
+
isEditor={config.editing === true}
|
|
350
363
|
interactionLabel={interactionLabel}
|
|
351
364
|
/>
|
|
352
365
|
)}
|
|
@@ -358,7 +371,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
358
371
|
updateChildConfig(col.widget, newConfig)
|
|
359
372
|
}}
|
|
360
373
|
isDashboard={true}
|
|
361
|
-
interactionLabel={
|
|
374
|
+
interactionLabel={interactionLabel}
|
|
362
375
|
/>
|
|
363
376
|
)}
|
|
364
377
|
{type === 'markup-include' && (
|
|
@@ -1,83 +1,13 @@
|
|
|
1
1
|
import { useContext, useState } from 'react'
|
|
2
|
-
import type { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
3
2
|
import Widget from '../Widget/Widget'
|
|
4
3
|
import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
|
|
5
|
-
import { Table } from '@cdc/core/types/Table'
|
|
6
4
|
import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
|
|
5
|
+
import { addVisualization } from '../../helpers/addVisualization'
|
|
7
6
|
import { mapDataToConfig } from '../../helpers/mapDataToConfig'
|
|
8
7
|
import './visualizations-panel-styles.css'
|
|
9
8
|
import { MultiDashboardConfig } from '../../types/MultiDashboard'
|
|
10
9
|
import { stripConfig } from '../../helpers/formatConfigBeforeSave'
|
|
11
10
|
|
|
12
|
-
const addVisualization = (type, subType) => {
|
|
13
|
-
const modalWillOpen = type !== 'markup-include'
|
|
14
|
-
const newVisualizationConfig: Partial<AnyVisualization> = {
|
|
15
|
-
filters: [],
|
|
16
|
-
filterBehavior: 'Filter Change',
|
|
17
|
-
newViz: type !== 'table',
|
|
18
|
-
openModal: modalWillOpen,
|
|
19
|
-
uid: type + Date.now(),
|
|
20
|
-
type
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
switch (type) {
|
|
24
|
-
case 'chart':
|
|
25
|
-
newVisualizationConfig.visualizationType = subType
|
|
26
|
-
break
|
|
27
|
-
case 'map':
|
|
28
|
-
newVisualizationConfig.general = {}
|
|
29
|
-
newVisualizationConfig.general.geoType = subType
|
|
30
|
-
break
|
|
31
|
-
case 'data-bite' || 'waffle-chart' || 'filtered-text':
|
|
32
|
-
newVisualizationConfig.visualizationType = type
|
|
33
|
-
break
|
|
34
|
-
case 'table':
|
|
35
|
-
const tableConfig: Table = {
|
|
36
|
-
label: 'Data Table',
|
|
37
|
-
show: true,
|
|
38
|
-
showDownloadUrl: false,
|
|
39
|
-
showVertical: true,
|
|
40
|
-
expanded: true,
|
|
41
|
-
collapsible: true
|
|
42
|
-
}
|
|
43
|
-
newVisualizationConfig.table = tableConfig
|
|
44
|
-
newVisualizationConfig.columns = {}
|
|
45
|
-
newVisualizationConfig.dataFormat = {}
|
|
46
|
-
newVisualizationConfig.visualizationType = type
|
|
47
|
-
break
|
|
48
|
-
case 'markup-include':
|
|
49
|
-
newVisualizationConfig.contentEditor = {
|
|
50
|
-
inlineHTML: '<h2>Inline HTML</h2>',
|
|
51
|
-
markupVariables: [],
|
|
52
|
-
showHeader: true,
|
|
53
|
-
srcUrl: '#example',
|
|
54
|
-
useInlineHTML: true
|
|
55
|
-
}
|
|
56
|
-
newVisualizationConfig.theme = 'theme-blue'
|
|
57
|
-
newVisualizationConfig.visual = {
|
|
58
|
-
border: false,
|
|
59
|
-
accent: false,
|
|
60
|
-
background: false,
|
|
61
|
-
hideBackgroundColor: false,
|
|
62
|
-
borderColorTheme: false
|
|
63
|
-
}
|
|
64
|
-
newVisualizationConfig.showEditorPanel = true
|
|
65
|
-
newVisualizationConfig.visualizationType = type
|
|
66
|
-
|
|
67
|
-
break
|
|
68
|
-
case 'dashboardFilters': {
|
|
69
|
-
newVisualizationConfig.sharedFilterIndexes = []
|
|
70
|
-
newVisualizationConfig.visualizationType = type
|
|
71
|
-
break
|
|
72
|
-
}
|
|
73
|
-
default:
|
|
74
|
-
newVisualizationConfig.visualizationType = type
|
|
75
|
-
break
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return newVisualizationConfig
|
|
79
|
-
}
|
|
80
|
-
|
|
81
11
|
const VisualizationsPanel = () => {
|
|
82
12
|
const [advancedEditing, setAdvancedEditing] = useState(false)
|
|
83
13
|
const { config, isEditor } = useContext(DashboardContext)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
.visualizations-panel {
|
|
2
2
|
background-color: #fff;
|
|
3
|
+
border-right: #c7c7c7 1px solid;
|
|
4
|
+
overflow-y: scroll;
|
|
3
5
|
padding: 1em;
|
|
4
6
|
width: var(--editorWidth);
|
|
5
|
-
border-right: #c7c7c7 1px solid;
|
|
6
7
|
z-index: 1;
|
|
7
|
-
overflow-y: scroll;
|
|
8
8
|
|
|
9
9
|
&.advanced-editor {
|
|
10
10
|
width: 50vw;
|
|
@@ -105,11 +105,11 @@ const Widget = ({
|
|
|
105
105
|
const url = changeDataLimit(dataset.dataUrl, 100)
|
|
106
106
|
if (url) {
|
|
107
107
|
fetchRemoteData(url)
|
|
108
|
-
.then(responseData => {
|
|
108
|
+
.then(({ data: responseData }) => {
|
|
109
109
|
// this sample data is temporary.
|
|
110
110
|
// the HEADER component removes the data when you toggle to the main viz panel.
|
|
111
111
|
// data will be cached only when it's loaded via dashboard preview.
|
|
112
|
-
responseData.sample = true
|
|
112
|
+
;(responseData as any).sample = true
|
|
113
113
|
dispatch({ type: 'SET_DATA', payload: { ...data, [dataKey]: responseData } })
|
|
114
114
|
})
|
|
115
115
|
.catch(error => {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
.widget__toggle-title {
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-direction: column;
|
|
4
|
-
justify-content: center;
|
|
5
2
|
align-items: center;
|
|
6
|
-
width: 100%;
|
|
7
|
-
height: 25%;
|
|
8
3
|
background-color: #005eaa;
|
|
9
4
|
color: white;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
height: 25%;
|
|
8
|
+
justify-content: center;
|
|
10
9
|
padding: 5px;
|
|
10
|
+
width: 100%;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
.widget__edit-title-icon {
|
|
@@ -19,37 +19,37 @@
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
.widget--toggle .widget__content .widget-menu + svg {
|
|
22
|
+
flex-grow: unset;
|
|
22
23
|
height: 35px !important;
|
|
23
24
|
width: 35px !important;
|
|
24
|
-
flex-grow: unset;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
.widget-menu {
|
|
28
28
|
position: absolute;
|
|
29
|
-
top: 6px;
|
|
30
29
|
right: 6px;
|
|
30
|
+
top: 6px;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
.widget-menu {
|
|
34
|
-
display: flex;
|
|
35
34
|
align-items: center;
|
|
35
|
+
display: flex;
|
|
36
36
|
justify-content: space-between;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
.btn-configure {
|
|
40
40
|
background: none;
|
|
41
|
-
width: 20px;
|
|
42
41
|
height: 20px;
|
|
43
|
-
padding: 0;
|
|
44
42
|
margin: 0 5px;
|
|
43
|
+
padding: 0;
|
|
44
|
+
width: 20px;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
.widget-menu-item {
|
|
48
|
+
cursor: pointer;
|
|
48
49
|
display: block;
|
|
49
|
-
width: 20px;
|
|
50
50
|
height: 20px;
|
|
51
|
-
cursor: pointer;
|
|
52
51
|
user-select: none;
|
|
52
|
+
width: 20px;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
.widget-menu-item svg {
|
|
@@ -58,8 +58,7 @@ export const addValuesToDashboardFilters = (
|
|
|
58
58
|
const active: string[] = Array.isArray(filterCopy.active) ? filterCopy.active : [filterCopy.active]
|
|
59
59
|
filterCopy.active = active.filter(val => defaultValues.includes(val))
|
|
60
60
|
} else {
|
|
61
|
-
//
|
|
62
|
-
// OR if defaultValue exists, always use it (overrides stale active from saved config)
|
|
61
|
+
// Use defaultValue if set, otherwise keep existing active or use first value
|
|
63
62
|
if (filterCopy.defaultValue) {
|
|
64
63
|
filterCopy.active = filterCopy.defaultValue
|
|
65
64
|
} else if (!filterCopy.active) {
|
|
@@ -82,17 +81,24 @@ export const addValuesToDashboardFilters = (
|
|
|
82
81
|
}
|
|
83
82
|
const queryStringFilterValue = getQueryStringFilterValue(subGroupingFilter)
|
|
84
83
|
const groupActive = groupName || filterCopy.values[0]
|
|
85
|
-
const
|
|
84
|
+
const currentGroupValues = filterCopy.subGrouping.valuesLookup[groupActive as string]?.values || []
|
|
85
|
+
const defaultSubValue = currentGroupValues[0]
|
|
86
86
|
|
|
87
|
-
// Priority order: query string >
|
|
88
|
-
let activeValue
|
|
87
|
+
// Priority order: query string > configured default > existing active > first available value
|
|
88
|
+
let activeValue: string | undefined
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
if (queryStringFilterValue && currentGroupValues.includes(queryStringFilterValue)) {
|
|
91
|
+
// 1. Query string parameter takes highest priority (only if valid for the current group)
|
|
92
|
+
activeValue = queryStringFilterValue
|
|
93
|
+
} else if (
|
|
94
|
+
filterCopy.subGrouping.defaultValue &&
|
|
95
|
+
currentGroupValues.includes(filterCopy.subGrouping.defaultValue)
|
|
96
|
+
) {
|
|
97
|
+
// 2. Use configured defaultValue if it exists and is valid for the current group
|
|
98
|
+
activeValue = filterCopy.subGrouping.defaultValue
|
|
99
|
+
} else if (filterCopy.subGrouping.active && currentGroupValues.includes(filterCopy.subGrouping.active)) {
|
|
100
|
+
// 3. Keep existing active value if it's valid for the current group
|
|
101
|
+
activeValue = filterCopy.subGrouping.active
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
filterCopy.subGrouping.active = activeValue || defaultSubValue
|