@cdc/dashboard 4.26.2 → 4.26.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/CONFIG.md +172 -0
- package/README.md +60 -20
- package/dist/cdcdashboard-CY9IcPSi.es.js +6 -0
- package/dist/cdcdashboard-DlpiY3fQ.es.js +4 -0
- package/dist/cdcdashboard.js +56686 -50281
- package/examples/__data__/data-2.json +6 -0
- package/examples/__data__/data-with-metadata.json +18 -0
- package/examples/__data__/data.json +6 -0
- package/examples/default.json +7 -36
- package/examples/legend-issue.json +1 -1
- package/examples/minimal-example.json +34 -0
- package/examples/private/dengue.json +4640 -0
- package/examples/private/inline-markup.json +775 -0
- package/examples/private/link_to_file.json +16662 -0
- package/examples/private/recent-update.json +1456 -0
- package/examples/private/toggle.json +10137 -0
- package/examples/sankey.json +3 -3
- package/examples/test-api-filter-reset.json +4 -4
- package/examples/tp5-test.json +86 -4
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +2 -1
- package/src/CdcDashboardComponent.tsx +48 -28
- package/src/_stories/Dashboard.DataSetup.stories.tsx +6 -1
- package/src/_stories/Dashboard.Pages.smoke.stories.tsx +22 -0
- package/src/_stories/Dashboard.smoke.stories.tsx +33 -0
- package/src/_stories/Dashboard.stories.tsx +4523 -83
- package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
- package/src/_stories/_mock/tab-simple-filter.json +153 -0
- package/src/_stories/_mock/tp5-test.json +86 -5
- package/src/components/DashboardEditors.tsx +15 -0
- package/src/components/DashboardFilters/DashboardFilters.test.tsx +129 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +29 -10
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +12 -8
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +6 -4
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +59 -58
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +127 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +29 -6
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +10 -9
- 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/ExpandCollapseButtons.tsx +6 -4
- package/src/components/Grid.tsx +4 -3
- package/src/components/Header/Header.tsx +27 -5
- package/src/components/Header/index.scss +1 -1
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
- package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +6 -6
- package/src/components/Row.tsx +30 -8
- package/src/components/Toggle/toggle-style.css +7 -7
- package/src/components/VisualizationRow.tsx +81 -22
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +2 -55
- package/src/components/VisualizationsPanel/visualizations-panel-styles.css +2 -2
- package/src/components/Widget/Widget.tsx +7 -6
- package/src/components/Widget/widget.styles.css +48 -17
- package/src/data/initial-state.js +2 -1
- package/src/helpers/addVisualization.ts +73 -0
- 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/addVisualization.test.ts +52 -0
- package/src/helpers/tests/formatConfigBeforeSave.test.ts +81 -1
- package/src/scss/editor-panel.scss +1 -1
- package/src/scss/grid.scss +38 -8
- package/src/scss/main.scss +237 -40
- package/src/store/dashboard.reducer.ts +2 -1
- package/src/test/CdcDashboard.test.jsx +26 -2
- package/src/test/CdcDashboardComponent.test.tsx +74 -0
- package/src/types/FilterStyles.ts +2 -1
- package/src/types/SharedFilter.ts +1 -0
- package/tests/fixtures/dashboard-config-with-metadata.json +89 -0
- package/vite.config.js +2 -2
- package/dist/cdcdashboard-Cf9_fbQf.es.js +0 -6
- package/examples/DEV-6574.json +0 -2224
- package/examples/api-dashboard-data.json +0 -272
- package/examples/api-dashboard-years.json +0 -11
- package/examples/api-geographies-data.json +0 -11
- package/examples/chart-data.json +0 -5409
- package/examples/custom/css/respiratory.css +0 -236
- package/examples/custom/js/respiratory.js +0 -242
- package/examples/default-data.json +0 -368
- package/examples/default-filter-control.json +0 -209
- package/examples/default-multi-dataset-shared-filter.json +0 -1729
- package/examples/default-multi-dataset.json +0 -506
- package/examples/ed-visits-county-file.json +0 -402
- package/examples/filters/Alabama.json +0 -72
- package/examples/filters/Alaska.json +0 -1737
- package/examples/filters/Arkansas.json +0 -4713
- package/examples/filters/California.json +0 -212
- package/examples/filters/Colorado.json +0 -1500
- package/examples/filters/Connecticut.json +0 -559
- package/examples/filters/Delaware.json +0 -63
- package/examples/filters/DistrictofColumbia.json +0 -63
- package/examples/filters/Florida.json +0 -4217
- package/examples/filters/States.json +0 -146
- package/examples/state-level.json +0 -90136
- package/examples/state-points.json +0 -10474
- package/examples/temp-example-data.json +0 -130
- package/examples/test-dashboard-simple.json +0 -503
- package/examples/test-example.json +0 -752
- package/examples/test-file.json +0 -147
- package/examples/test.json +0 -752
- package/examples/testing.json +0 -94456
- /package/examples/{legend-issue-data.json → __data__/legend-issue-data.json} +0 -0
- /package/examples/api-test/{categories.json → __data__/categories.json} +0 -0
- /package/examples/api-test/{chart-data.json → __data__/chart-data.json} +0 -0
- /package/examples/api-test/{topics.json → __data__/topics.json} +0 -0
- /package/examples/api-test/{years.json → __data__/years.json} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
|
|
2
|
-
import React, { useContext, useEffect, useMemo, useState } from 'react'
|
|
2
|
+
import React, { useContext, useEffect, useMemo, useRef, 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'
|
|
@@ -88,6 +88,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
88
88
|
}) => {
|
|
89
89
|
const { config, filteredData: dashboardFilteredData, data: rawData } = useContext(DashboardContext)
|
|
90
90
|
const [toggledRow, setToggled] = React.useState<number>(0)
|
|
91
|
+
const rowRef = useRef<HTMLDivElement>(null)
|
|
91
92
|
|
|
92
93
|
useEffect(() => {
|
|
93
94
|
if (row.toggle) setToggled(0)
|
|
@@ -103,15 +104,18 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
103
104
|
}
|
|
104
105
|
}, [toggledRow, row.toggle])
|
|
105
106
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
const setupMinHeightEqualizer = (rowElement: HTMLElement, selectors: string[]) => {
|
|
108
|
+
let items: HTMLElement[] = []
|
|
109
|
+
const selector = selectors.join(', ')
|
|
110
|
+
const resizeObserver = new ResizeObserver(() => equalizeHeights())
|
|
109
111
|
|
|
110
112
|
const equalizeHeights = () => {
|
|
111
113
|
items.forEach(item => {
|
|
112
114
|
item.style.minHeight = ''
|
|
113
115
|
})
|
|
114
116
|
|
|
117
|
+
if (items.length <= 1) return
|
|
118
|
+
|
|
115
119
|
let maxHeight = 0
|
|
116
120
|
items.forEach(item => {
|
|
117
121
|
const height = item.offsetHeight
|
|
@@ -125,35 +129,86 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
|
|
132
|
+
const refreshItems = () => {
|
|
133
|
+
const nextItems = Array.from(rowElement.querySelectorAll(selector)) as HTMLElement[]
|
|
134
|
+
const nextSet = new Set(nextItems)
|
|
135
|
+
|
|
136
|
+
items.forEach(item => {
|
|
137
|
+
if (!nextSet.has(item)) {
|
|
138
|
+
resizeObserver.unobserve(item)
|
|
139
|
+
item.style.minHeight = ''
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
nextItems.forEach(item => {
|
|
144
|
+
if (!items.includes(item)) {
|
|
145
|
+
resizeObserver.observe(item)
|
|
146
|
+
}
|
|
147
|
+
})
|
|
129
148
|
|
|
130
|
-
|
|
149
|
+
items = nextItems
|
|
131
150
|
equalizeHeights()
|
|
132
|
-
}
|
|
151
|
+
}
|
|
133
152
|
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
const mutationObserver = new MutationObserver(() => {
|
|
154
|
+
refreshItems()
|
|
136
155
|
})
|
|
137
156
|
|
|
138
|
-
|
|
157
|
+
mutationObserver.observe(rowElement, { childList: true, subtree: true })
|
|
158
|
+
refreshItems()
|
|
159
|
+
|
|
160
|
+
return () => {
|
|
161
|
+
mutationObserver.disconnect()
|
|
162
|
+
resizeObserver.disconnect()
|
|
163
|
+
items.forEach(item => {
|
|
164
|
+
item.style.minHeight = ''
|
|
165
|
+
})
|
|
166
|
+
}
|
|
139
167
|
}
|
|
140
168
|
|
|
141
|
-
|
|
169
|
+
const tp5CountInRow = row.columns.reduce((count, col) => {
|
|
170
|
+
if (!col.widget) return count
|
|
171
|
+
const viz = config.visualizations[col.widget]
|
|
172
|
+
if (!viz) return count
|
|
173
|
+
|
|
174
|
+
const isTp5DataBite = viz.type === 'data-bite' && (viz as any).biteStyle === 'tp5'
|
|
175
|
+
const isTp5WaffleOrGauge =
|
|
176
|
+
viz.type === 'waffle-chart' && (viz.visualizationType === 'TP5 Waffle' || viz.visualizationType === 'TP5 Gauge')
|
|
177
|
+
const isTp5MarkupInclude = viz.type === 'markup-include' && (viz as any).contentEditor?.style === 'tp5'
|
|
178
|
+
|
|
179
|
+
return isTp5DataBite || isTp5WaffleOrGauge || isTp5MarkupInclude ? count + 1 : count
|
|
180
|
+
}, 0)
|
|
181
|
+
const needsTP5AutoEqualization = tp5CountInRow > 1
|
|
182
|
+
const shouldEqualizeRow = !!row.equalHeight || needsTP5AutoEqualization
|
|
183
|
+
|
|
184
|
+
// Layer TP5 equalization for row-level title consistency and same-type internals.
|
|
142
185
|
useEffect(() => {
|
|
143
|
-
|
|
186
|
+
if (!shouldEqualizeRow) return
|
|
187
|
+
|
|
188
|
+
const rowElement = rowRef.current
|
|
144
189
|
if (!rowElement) return
|
|
145
190
|
|
|
146
191
|
const cleanups = [
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
192
|
+
// Cross-type TP5 title alignment in the row.
|
|
193
|
+
setupMinHeightEqualizer(rowElement, [
|
|
194
|
+
'.bite__style--tp5 .cdc-callout__heading',
|
|
195
|
+
'.waffle__style--tp5 .cdc-callout__heading',
|
|
196
|
+
'.gauge__style--tp5 .cdc-callout__heading',
|
|
197
|
+
'.markup-include__style--tp5 .cdc-callout__heading'
|
|
198
|
+
]),
|
|
199
|
+
// Same-type gauge internals alignment so gauge meters line up despite content variance.
|
|
200
|
+
setupMinHeightEqualizer(rowElement, ['.gauge__style--tp5 .cove-gauge-chart__content'])
|
|
201
|
+
]
|
|
152
202
|
|
|
153
203
|
return () => {
|
|
154
204
|
cleanups.forEach(cleanup => cleanup())
|
|
155
205
|
}
|
|
156
|
-
}, [
|
|
206
|
+
}, [shouldEqualizeRow, row.columns, config.activeDashboard, filteredDataOverride, dashboardFilteredData[index]])
|
|
207
|
+
|
|
208
|
+
const isFilterRow = row.columns.some(
|
|
209
|
+
col => col.widget && config.visualizations[col.widget]?.type === 'dashboardFilters'
|
|
210
|
+
)
|
|
211
|
+
const needsEqualHeight = shouldEqualizeRow && !isFilterRow
|
|
157
212
|
|
|
158
213
|
const show = useMemo(() => {
|
|
159
214
|
if (row.toggle) {
|
|
@@ -201,7 +256,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
201
256
|
)}
|
|
202
257
|
{Object.keys(dataGroups).map(groupName => {
|
|
203
258
|
const dataValue = dataGroups[groupName]
|
|
204
|
-
const _row =
|
|
259
|
+
const _row = cloneDeep(row) // clone the row to avoid mutating the original row
|
|
205
260
|
const originalMultiVizColumn = _row.multiVizColumn // store original value before clearing
|
|
206
261
|
_row.multiVizColumn = undefined // reset the multiVizColumn to avoid passing it to the child components
|
|
207
262
|
_row.originalMultiVizColumn = originalMultiVizColumn // store for footnote filtering
|
|
@@ -229,7 +284,8 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
229
284
|
|
|
230
285
|
return (
|
|
231
286
|
<div
|
|
232
|
-
|
|
287
|
+
ref={rowRef}
|
|
288
|
+
className={`row${needsEqualHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`}
|
|
233
289
|
key={`row__${index}`}
|
|
234
290
|
data-row-index={index}
|
|
235
291
|
>
|
|
@@ -348,10 +404,12 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
348
404
|
<CdcDataBite
|
|
349
405
|
key={col.widget}
|
|
350
406
|
config={visualizationConfig}
|
|
407
|
+
rawData={rawData?.[visualizationConfig.dataKey] || []}
|
|
351
408
|
setConfig={newConfig => {
|
|
352
409
|
updateChildConfig(col.widget, newConfig)
|
|
353
410
|
}}
|
|
354
411
|
isDashboard={true}
|
|
412
|
+
isEditor={config.editing === true}
|
|
355
413
|
interactionLabel={interactionLabel}
|
|
356
414
|
/>
|
|
357
415
|
)}
|
|
@@ -359,11 +417,12 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
359
417
|
<CdcWaffleChart
|
|
360
418
|
key={col.widget}
|
|
361
419
|
config={visualizationConfig}
|
|
420
|
+
rawData={rawData?.[visualizationConfig.dataKey] || []}
|
|
362
421
|
setConfig={newConfig => {
|
|
363
422
|
updateChildConfig(col.widget, newConfig)
|
|
364
423
|
}}
|
|
365
424
|
isDashboard={true}
|
|
366
|
-
interactionLabel={
|
|
425
|
+
interactionLabel={interactionLabel}
|
|
367
426
|
/>
|
|
368
427
|
)}
|
|
369
428
|
{type === 'markup-include' && (
|
|
@@ -1,66 +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.visualizationType = type
|
|
50
|
-
break
|
|
51
|
-
case 'dashboardFilters': {
|
|
52
|
-
newVisualizationConfig.sharedFilterIndexes = []
|
|
53
|
-
newVisualizationConfig.visualizationType = type
|
|
54
|
-
break
|
|
55
|
-
}
|
|
56
|
-
default:
|
|
57
|
-
newVisualizationConfig.visualizationType = type
|
|
58
|
-
break
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return newVisualizationConfig
|
|
62
|
-
}
|
|
63
|
-
|
|
64
11
|
const VisualizationsPanel = () => {
|
|
65
12
|
const [advancedEditing, setAdvancedEditing] = useState(false)
|
|
66
13
|
const { config, isEditor } = useContext(DashboardContext)
|
|
@@ -96,7 +43,7 @@ const VisualizationsPanel = () => {
|
|
|
96
43
|
<span className='subheading-3'>Misc.</span>
|
|
97
44
|
<div className='drag-grid'>
|
|
98
45
|
<Widget addVisualization={() => addVisualization('data-bite', '')} type='data-bite' />
|
|
99
|
-
<Widget addVisualization={() => addVisualization('waffle-chart', '')} type='waffle-chart' />
|
|
46
|
+
<Widget addVisualization={() => addVisualization('waffle-chart', 'Waffle')} type='waffle-chart' />
|
|
100
47
|
<Widget addVisualization={() => addVisualization('markup-include', '')} type='markup-include' />
|
|
101
48
|
<Widget addVisualization={() => addVisualization('filtered-text', '')} type='filtered-text' />
|
|
102
49
|
<Widget addVisualization={() => addVisualization('dashboardFilters', '')} type='dashboardFilters' />
|
|
@@ -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;
|
|
@@ -5,6 +5,7 @@ import { DashboardContext, DashboardDispatchContext } from '../../DashboardConte
|
|
|
5
5
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
6
6
|
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
7
7
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
8
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
8
9
|
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
9
10
|
import { iconHash } from '../../helpers/iconHash'
|
|
10
11
|
import _ from 'lodash'
|
|
@@ -105,11 +106,11 @@ const Widget = ({
|
|
|
105
106
|
const url = changeDataLimit(dataset.dataUrl, 100)
|
|
106
107
|
if (url) {
|
|
107
108
|
fetchRemoteData(url)
|
|
108
|
-
.then(responseData => {
|
|
109
|
+
.then(({ data: responseData }) => {
|
|
109
110
|
// this sample data is temporary.
|
|
110
111
|
// the HEADER component removes the data when you toggle to the main viz panel.
|
|
111
112
|
// data will be cached only when it's loaded via dashboard preview.
|
|
112
|
-
responseData.sample = true
|
|
113
|
+
;(responseData as any).sample = true
|
|
113
114
|
dispatch({ type: 'SET_DATA', payload: { ...data, [dataKey]: responseData } })
|
|
114
115
|
})
|
|
115
116
|
.catch(error => {
|
|
@@ -167,12 +168,12 @@ const Widget = ({
|
|
|
167
168
|
{widgetConfig?.rowIdx !== undefined && (
|
|
168
169
|
<div className='widget-menu'>
|
|
169
170
|
{isConfigurationReady && (
|
|
170
|
-
<
|
|
171
|
+
<Button title='Configure Visualization' className='btn btn-configure' onClick={editWidget}>
|
|
171
172
|
{iconHash['tools']}
|
|
172
|
-
</
|
|
173
|
+
</Button>
|
|
173
174
|
)}
|
|
174
175
|
{needsDataConfiguration && (
|
|
175
|
-
<
|
|
176
|
+
<Button
|
|
176
177
|
title='Configure Data'
|
|
177
178
|
className='btn btn-configure'
|
|
178
179
|
onClick={() => {
|
|
@@ -182,7 +183,7 @@ const Widget = ({
|
|
|
182
183
|
}}
|
|
183
184
|
>
|
|
184
185
|
{iconHash['gear']}
|
|
185
|
-
</
|
|
186
|
+
</Button>
|
|
186
187
|
)}
|
|
187
188
|
<div className='widget-menu-item' onClick={deleteWidget}>
|
|
188
189
|
<Icon display='close' base />
|
|
@@ -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,39 +19,70 @@
|
|
|
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
|
+
align-items: center;
|
|
34
35
|
display: flex;
|
|
36
|
+
gap: 6px;
|
|
37
|
+
justify-content: flex-end;
|
|
38
|
+
padding: 2px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.widget-menu .cove-button.btn-configure {
|
|
35
42
|
align-items: center;
|
|
36
|
-
|
|
43
|
+
background: transparent !important;
|
|
44
|
+
border: 0;
|
|
45
|
+
border-radius: 0;
|
|
46
|
+
box-shadow: none;
|
|
47
|
+
color: var(--mediumGray);
|
|
48
|
+
display: inline-flex;
|
|
49
|
+
height: 28px;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
margin: 0;
|
|
52
|
+
min-height: 28px;
|
|
53
|
+
padding: 4px;
|
|
54
|
+
transform: none;
|
|
55
|
+
width: 28px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.widget-menu .cove-button.btn-configure:hover:not(:disabled),
|
|
59
|
+
.widget-menu .cove-button.btn-configure:active:not(:disabled) {
|
|
60
|
+
background: transparent !important;
|
|
61
|
+
box-shadow: none;
|
|
62
|
+
transform: none;
|
|
37
63
|
}
|
|
38
64
|
|
|
39
|
-
.btn-configure {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
padding: 0;
|
|
44
|
-
margin: 0 5px;
|
|
65
|
+
.widget-menu .cove-button.btn-configure svg {
|
|
66
|
+
margin-bottom: 0;
|
|
67
|
+
top: 0;
|
|
68
|
+
vertical-align: middle;
|
|
45
69
|
}
|
|
46
70
|
|
|
47
71
|
.widget-menu-item {
|
|
48
|
-
|
|
49
|
-
width: 20px;
|
|
50
|
-
height: 20px;
|
|
72
|
+
align-items: center;
|
|
51
73
|
cursor: pointer;
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
height: 28px;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
line-height: 1;
|
|
78
|
+
padding: 4px;
|
|
52
79
|
user-select: none;
|
|
80
|
+
width: 28px;
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
.widget-menu-item svg {
|
|
56
84
|
fill: var(--mediumGray);
|
|
85
|
+
margin-bottom: 0;
|
|
86
|
+
top: 0;
|
|
87
|
+
vertical-align: middle;
|
|
57
88
|
}
|
|
@@ -4,12 +4,13 @@ export default {
|
|
|
4
4
|
titleStyle: 'small',
|
|
5
5
|
sharedFilters: []
|
|
6
6
|
},
|
|
7
|
-
rows: [[{ width: 12 }
|
|
7
|
+
rows: [{ columns: [{ width: 12 }] }],
|
|
8
8
|
visualizations: {},
|
|
9
9
|
table: {
|
|
10
10
|
label: 'Data Table',
|
|
11
11
|
show: true,
|
|
12
12
|
showDownloadUrl: false,
|
|
13
|
+
downloadUrlLabel: '',
|
|
13
14
|
showDownloadLinkBelow: true,
|
|
14
15
|
showVertical: true
|
|
15
16
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
2
|
+
import type { Table } from '@cdc/core/types/Table'
|
|
3
|
+
|
|
4
|
+
export const addVisualization = (type, subType) => {
|
|
5
|
+
const modalWillOpen = type !== 'markup-include'
|
|
6
|
+
const newVisualizationConfig: Partial<AnyVisualization> = {
|
|
7
|
+
filters: [],
|
|
8
|
+
filterBehavior: 'Filter Change',
|
|
9
|
+
newViz: type !== 'table',
|
|
10
|
+
openModal: modalWillOpen,
|
|
11
|
+
uid: type + Date.now(),
|
|
12
|
+
type
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
switch (type) {
|
|
16
|
+
case 'chart':
|
|
17
|
+
newVisualizationConfig.visual = {
|
|
18
|
+
border: false,
|
|
19
|
+
borderColorTheme: false,
|
|
20
|
+
accent: false,
|
|
21
|
+
background: false,
|
|
22
|
+
hideBackgroundColor: false
|
|
23
|
+
}
|
|
24
|
+
newVisualizationConfig.visualizationType = subType
|
|
25
|
+
break
|
|
26
|
+
case 'map':
|
|
27
|
+
newVisualizationConfig.general = {}
|
|
28
|
+
newVisualizationConfig.general.geoType = subType
|
|
29
|
+
newVisualizationConfig.visual = {
|
|
30
|
+
border: false,
|
|
31
|
+
borderColorTheme: false,
|
|
32
|
+
accent: false,
|
|
33
|
+
background: false,
|
|
34
|
+
hideBackgroundColor: false
|
|
35
|
+
}
|
|
36
|
+
break
|
|
37
|
+
case 'data-bite':
|
|
38
|
+
case 'filtered-text':
|
|
39
|
+
newVisualizationConfig.visualizationType = type
|
|
40
|
+
break
|
|
41
|
+
case 'waffle-chart':
|
|
42
|
+
newVisualizationConfig.visualizationType = subType
|
|
43
|
+
break
|
|
44
|
+
case 'table': {
|
|
45
|
+
const tableConfig: Table = {
|
|
46
|
+
label: 'Data Table',
|
|
47
|
+
show: true,
|
|
48
|
+
showDownloadUrl: false,
|
|
49
|
+
showVertical: true,
|
|
50
|
+
expanded: true,
|
|
51
|
+
collapsible: true
|
|
52
|
+
}
|
|
53
|
+
newVisualizationConfig.table = tableConfig
|
|
54
|
+
newVisualizationConfig.columns = {}
|
|
55
|
+
newVisualizationConfig.dataFormat = {}
|
|
56
|
+
newVisualizationConfig.visualizationType = type
|
|
57
|
+
break
|
|
58
|
+
}
|
|
59
|
+
case 'markup-include':
|
|
60
|
+
newVisualizationConfig.visualizationType = type
|
|
61
|
+
break
|
|
62
|
+
case 'dashboardFilters': {
|
|
63
|
+
newVisualizationConfig.sharedFilterIndexes = []
|
|
64
|
+
newVisualizationConfig.visualizationType = type
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
newVisualizationConfig.visualizationType = type
|
|
69
|
+
break
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return newVisualizationConfig
|
|
73
|
+
}
|
|
@@ -129,7 +129,7 @@ export const stripConfig = (configToStrip, isEditor = false) => {
|
|
|
129
129
|
} else {
|
|
130
130
|
delete strippedConfig.runtime
|
|
131
131
|
delete strippedConfig.formattedData
|
|
132
|
-
if (strippedConfig.dataUrl) {
|
|
132
|
+
if (strippedConfig.dataUrl && !isEditor) {
|
|
133
133
|
delete strippedConfig.data
|
|
134
134
|
}
|
|
135
135
|
}
|
|
@@ -90,6 +90,9 @@ export const getVizConfig = (
|
|
|
90
90
|
|
|
91
91
|
if (visualizationConfig.formattedData) visualizationConfig.originalFormattedData = visualizationConfig.formattedData
|
|
92
92
|
const filteredVizData = filteredData?.[rowNumber] ?? filteredData?.[visualizationKey]
|
|
93
|
+
const dataKey = visualizationConfig.dataKey || 'backwards-compatibility'
|
|
94
|
+
|
|
95
|
+
visualizationConfig.dataMetadata = config.datasets[dataKey]?.dataMetadata || {}
|
|
93
96
|
|
|
94
97
|
if (filteredVizData) {
|
|
95
98
|
visualizationConfig.data = filteredVizData || []
|
|
@@ -97,9 +100,16 @@ export const getVizConfig = (
|
|
|
97
100
|
visualizationConfig.formattedData = visualizationConfig.data
|
|
98
101
|
}
|
|
99
102
|
} else {
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
+
// Clear data for charts/maps when shared filters exist but filtered data
|
|
104
|
+
// hasn't arrived yet — prevents rendering the full unfiltered dataset as DOM.
|
|
105
|
+
// Lighter types (data-bite, waffle-chart, filtered-text, markup-include) are
|
|
106
|
+
// excluded: they only compute scalars or single elements, and their editor
|
|
107
|
+
// panels need data to populate column dropdowns. Ideally data filters would
|
|
108
|
+
// apply synchronously before render, but they currently go through the same
|
|
109
|
+
// async loadAPIFilters pipeline as API filters, so filtered data isn't
|
|
110
|
+
// available on first render.
|
|
111
|
+
const heavyVizTypes = ['chart', 'map']
|
|
112
|
+
const shouldClearData = sharedFilterColumns.length && heavyVizTypes.includes(visualizationConfig.type)
|
|
103
113
|
visualizationConfig.data = shouldClearData ? [] : data[dataKey] || []
|
|
104
114
|
if (visualizationConfig.formattedData) {
|
|
105
115
|
visualizationConfig.formattedData =
|
package/src/helpers/iconHash.tsx
CHANGED
|
@@ -1,36 +1,45 @@
|
|
|
1
|
-
import Icon from '@cdc/core/components/ui/Icon'
|
|
2
|
-
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
3
|
-
|
|
4
|
-
export const iconHash = {
|
|
5
|
-
'data-bite': <Icon display='databite' base />,
|
|
6
|
-
Bar: <Icon display='chartBar' base />,
|
|
7
|
-
'Spark Line': <Icon display='chartLine' />,
|
|
8
|
-
'Bump Chart': <Icon display='chartLine' />,
|
|
9
|
-
'waffle-chart': <Icon display='grid' base />,
|
|
10
|
-
'markup-include': <Icon display='code' base />,
|
|
11
|
-
Line: <Icon display='chartLine' base />,
|
|
12
|
-
Pie: <Icon display='chartPie' base />,
|
|
13
|
-
us: <Icon display='mapUsa' base />,
|
|
14
|
-
'us-county': <Icon display='mapUsa' base />,
|
|
15
|
-
world: <Icon display='mapWorld' base />,
|
|
16
|
-
'single-state': <Icon display='mapAl' base />,
|
|
17
|
-
gear: <Icon display='gear' base />,
|
|
18
|
-
gearMulti: <Icon display='gearMulti' base />,
|
|
19
|
-
tools: <Icon display='tools' base />,
|
|
20
|
-
'filtered-text': <Icon display='filtered-text' base />,
|
|
21
|
-
dashboardFilters: <Icon display='dashboardFilters' base />,
|
|
22
|
-
table: <Icon display='table' base />,
|
|
23
|
-
Sankey: <Icon display='sankey' base
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
1
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
2
|
+
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
3
|
+
|
|
4
|
+
export const iconHash = {
|
|
5
|
+
'data-bite': <Icon display='databite' base />,
|
|
6
|
+
Bar: <Icon display='chartBar' base />,
|
|
7
|
+
'Spark Line': <Icon display='chartLine' />,
|
|
8
|
+
'Bump Chart': <Icon display='chartLine' />,
|
|
9
|
+
'waffle-chart': <Icon display='grid' base />,
|
|
10
|
+
'markup-include': <Icon display='code' base />,
|
|
11
|
+
Line: <Icon display='chartLine' base />,
|
|
12
|
+
Pie: <Icon display='chartPie' base />,
|
|
13
|
+
us: <Icon display='mapUsa' base />,
|
|
14
|
+
'us-county': <Icon display='mapUsa' base />,
|
|
15
|
+
world: <Icon display='mapWorld' base />,
|
|
16
|
+
'single-state': <Icon display='mapAl' base />,
|
|
17
|
+
gear: <Icon display='gear' base />,
|
|
18
|
+
gearMulti: <Icon display='gearMulti' base />,
|
|
19
|
+
tools: <Icon display='tools' base />,
|
|
20
|
+
'filtered-text': <Icon display='filtered-text' base />,
|
|
21
|
+
dashboardFilters: <Icon display='dashboardFilters' base />,
|
|
22
|
+
table: <Icon display='table' base />,
|
|
23
|
+
Sankey: <Icon display='sankey' base />,
|
|
24
|
+
Combo: <Icon display='chartBar' base />,
|
|
25
|
+
'Scatter Plot': <Icon display='chartBar' base />,
|
|
26
|
+
'Area Chart': <Icon display='chartLine' base />,
|
|
27
|
+
'Deviation Bar': <Icon display='chartBar' base />,
|
|
28
|
+
'Paired Bar': <Icon display='chartBar' base />,
|
|
29
|
+
'Box Plot': <Icon display='chartBar' base />,
|
|
30
|
+
'Forest Plot': <Icon display='chartBar' base />,
|
|
31
|
+
Forecasting: <Icon display='chartLine' base />,
|
|
32
|
+
'Warming Stripes': <Icon display='chartBar' base />
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const getIcon = (visualization: AnyVisualization) => {
|
|
36
|
+
const { type, visualizationType, general } = visualization
|
|
37
|
+
if (visualizationType) return iconHash[visualizationType]
|
|
38
|
+
if (general?.geoType) {
|
|
39
|
+
// for visualizations, mismatching state and state icon is not desired
|
|
40
|
+
// so instead of showing alabama as the default state icon we show the US icon.
|
|
41
|
+
if (general.geoType === 'single-state') return iconHash['us']
|
|
42
|
+
return iconHash[general.geoType]
|
|
43
|
+
}
|
|
44
|
+
return iconHash[type]
|
|
45
|
+
}
|