@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.
Files changed (109) hide show
  1. package/CONFIG.md +172 -0
  2. package/README.md +60 -20
  3. package/dist/cdcdashboard-CY9IcPSi.es.js +6 -0
  4. package/dist/cdcdashboard-DlpiY3fQ.es.js +4 -0
  5. package/dist/cdcdashboard.js +56686 -50281
  6. package/examples/__data__/data-2.json +6 -0
  7. package/examples/__data__/data-with-metadata.json +18 -0
  8. package/examples/__data__/data.json +6 -0
  9. package/examples/default.json +7 -36
  10. package/examples/legend-issue.json +1 -1
  11. package/examples/minimal-example.json +34 -0
  12. package/examples/private/dengue.json +4640 -0
  13. package/examples/private/inline-markup.json +775 -0
  14. package/examples/private/link_to_file.json +16662 -0
  15. package/examples/private/recent-update.json +1456 -0
  16. package/examples/private/toggle.json +10137 -0
  17. package/examples/sankey.json +3 -3
  18. package/examples/test-api-filter-reset.json +4 -4
  19. package/examples/tp5-test.json +86 -4
  20. package/package.json +9 -9
  21. package/src/CdcDashboard.tsx +2 -1
  22. package/src/CdcDashboardComponent.tsx +48 -28
  23. package/src/_stories/Dashboard.DataSetup.stories.tsx +6 -1
  24. package/src/_stories/Dashboard.Pages.smoke.stories.tsx +22 -0
  25. package/src/_stories/Dashboard.smoke.stories.tsx +33 -0
  26. package/src/_stories/Dashboard.stories.tsx +4523 -83
  27. package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
  28. package/src/_stories/_mock/tab-simple-filter.json +153 -0
  29. package/src/_stories/_mock/tp5-test.json +86 -5
  30. package/src/components/DashboardEditors.tsx +15 -0
  31. package/src/components/DashboardFilters/DashboardFilters.test.tsx +129 -0
  32. package/src/components/DashboardFilters/DashboardFilters.tsx +29 -10
  33. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +12 -8
  34. package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +6 -4
  35. package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +59 -58
  36. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +127 -0
  37. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +29 -6
  38. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +10 -9
  39. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +8 -8
  40. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +1 -1
  41. package/src/components/DashboardFilters/dashboardfilter.styles.css +3 -3
  42. package/src/components/DataDesignerModal.tsx +2 -2
  43. package/src/components/ExpandCollapseButtons.tsx +6 -4
  44. package/src/components/Grid.tsx +4 -3
  45. package/src/components/Header/Header.tsx +27 -5
  46. package/src/components/Header/index.scss +1 -1
  47. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
  48. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +6 -6
  49. package/src/components/Row.tsx +30 -8
  50. package/src/components/Toggle/toggle-style.css +7 -7
  51. package/src/components/VisualizationRow.tsx +81 -22
  52. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +2 -55
  53. package/src/components/VisualizationsPanel/visualizations-panel-styles.css +2 -2
  54. package/src/components/Widget/Widget.tsx +7 -6
  55. package/src/components/Widget/widget.styles.css +48 -17
  56. package/src/data/initial-state.js +2 -1
  57. package/src/helpers/addVisualization.ts +73 -0
  58. package/src/helpers/formatConfigBeforeSave.ts +1 -1
  59. package/src/helpers/getVizConfig.ts +13 -3
  60. package/src/helpers/iconHash.tsx +45 -36
  61. package/src/helpers/processDataLegacy.ts +19 -14
  62. package/src/helpers/tests/addVisualization.test.ts +52 -0
  63. package/src/helpers/tests/formatConfigBeforeSave.test.ts +81 -1
  64. package/src/scss/editor-panel.scss +1 -1
  65. package/src/scss/grid.scss +38 -8
  66. package/src/scss/main.scss +237 -40
  67. package/src/store/dashboard.reducer.ts +2 -1
  68. package/src/test/CdcDashboard.test.jsx +26 -2
  69. package/src/test/CdcDashboardComponent.test.tsx +74 -0
  70. package/src/types/FilterStyles.ts +2 -1
  71. package/src/types/SharedFilter.ts +1 -0
  72. package/tests/fixtures/dashboard-config-with-metadata.json +89 -0
  73. package/vite.config.js +2 -2
  74. package/dist/cdcdashboard-Cf9_fbQf.es.js +0 -6
  75. package/examples/DEV-6574.json +0 -2224
  76. package/examples/api-dashboard-data.json +0 -272
  77. package/examples/api-dashboard-years.json +0 -11
  78. package/examples/api-geographies-data.json +0 -11
  79. package/examples/chart-data.json +0 -5409
  80. package/examples/custom/css/respiratory.css +0 -236
  81. package/examples/custom/js/respiratory.js +0 -242
  82. package/examples/default-data.json +0 -368
  83. package/examples/default-filter-control.json +0 -209
  84. package/examples/default-multi-dataset-shared-filter.json +0 -1729
  85. package/examples/default-multi-dataset.json +0 -506
  86. package/examples/ed-visits-county-file.json +0 -402
  87. package/examples/filters/Alabama.json +0 -72
  88. package/examples/filters/Alaska.json +0 -1737
  89. package/examples/filters/Arkansas.json +0 -4713
  90. package/examples/filters/California.json +0 -212
  91. package/examples/filters/Colorado.json +0 -1500
  92. package/examples/filters/Connecticut.json +0 -559
  93. package/examples/filters/Delaware.json +0 -63
  94. package/examples/filters/DistrictofColumbia.json +0 -63
  95. package/examples/filters/Florida.json +0 -4217
  96. package/examples/filters/States.json +0 -146
  97. package/examples/state-level.json +0 -90136
  98. package/examples/state-points.json +0 -10474
  99. package/examples/temp-example-data.json +0 -130
  100. package/examples/test-dashboard-simple.json +0 -503
  101. package/examples/test-example.json +0 -752
  102. package/examples/test-file.json +0 -147
  103. package/examples/test.json +0 -752
  104. package/examples/testing.json +0 -94456
  105. /package/examples/{legend-issue-data.json → __data__/legend-issue-data.json} +0 -0
  106. /package/examples/api-test/{categories.json → __data__/categories.json} +0 -0
  107. /package/examples/api-test/{chart-data.json → __data__/chart-data.json} +0 -0
  108. /package/examples/api-test/{topics.json → __data__/topics.json} +0 -0
  109. /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 _ from 'lodash'
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 setupTP5MinHeightEqualizer = (rowElement: Element, itemSelector: string) => {
107
- const items = Array.from(rowElement.querySelectorAll(itemSelector)) as HTMLElement[]
108
- if (items.length <= 1) return undefined
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
- equalizeHeights()
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
- const resizeObserver = new ResizeObserver(() => {
149
+ items = nextItems
131
150
  equalizeHeights()
132
- })
151
+ }
133
152
 
134
- items.forEach(item => {
135
- resizeObserver.observe(item)
153
+ const mutationObserver = new MutationObserver(() => {
154
+ refreshItems()
136
155
  })
137
156
 
138
- return () => resizeObserver.disconnect()
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
- // Equalize TP5 callout title heights and TP5 gauge message blocks for like visualizations in the same row
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
- const rowElement = document.querySelector(`[data-row-index="${index}"]`)
186
+ if (!shouldEqualizeRow) return
187
+
188
+ const rowElement = rowRef.current
144
189
  if (!rowElement) return
145
190
 
146
191
  const cleanups = [
147
- setupTP5MinHeightEqualizer(rowElement, '.bite__style--tp5 .cdc-callout__heading'),
148
- setupTP5MinHeightEqualizer(rowElement, '.waffle__style--tp5 .cdc-callout__heading'),
149
- setupTP5MinHeightEqualizer(rowElement, '.gauge__style--tp5 .cdc-callout__heading'),
150
- setupTP5MinHeightEqualizer(rowElement, '.gauge__style--tp5 .cove-gauge-chart__content')
151
- ].filter(Boolean) as Array<() => void>
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
- }, [index, row, config, filteredDataOverride])
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 = _.cloneDeep(row) // clone the row to avoid mutating the original 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
- className={`row${row.equalHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`}
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={link}
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
- <button title='Configure Visualization' className='btn btn-configure' onClick={editWidget}>
171
+ <Button title='Configure Visualization' className='btn btn-configure' onClick={editWidget}>
171
172
  {iconHash['tools']}
172
- </button>
173
+ </Button>
173
174
  )}
174
175
  {needsDataConfiguration && (
175
- <button
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
- </button>
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
- justify-content: space-between;
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
- background: none;
41
- width: 20px;
42
- height: 20px;
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
- display: block;
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
- const dataKey = visualizationConfig.dataKey || 'backwards-compatibility'
101
- // Markup-includes need data even when shared filters exist (for markup variables)
102
- const shouldClearData = sharedFilterColumns.length && visualizationConfig.type !== 'markup-include'
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 =
@@ -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
- export const getIcon = (visualization: AnyVisualization) => {
27
- const { type, visualizationType, general } = visualization
28
- if (visualizationType) return iconHash[visualizationType]
29
- if (general?.geoType) {
30
- // for visualizations, mismatching state and state icon is not desired
31
- // so instead of showing alabama as the default state icon we show the US icon.
32
- if (general.geoType === 'single-state') return iconHash['us']
33
- return iconHash[general.geoType]
34
- }
35
- return iconHash[type]
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
+ }