@cdc/dashboard 4.24.2 → 4.24.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 (52) hide show
  1. package/dist/cdcdashboard.js +128512 -99417
  2. package/examples/chart-data.json +5409 -0
  3. package/examples/full-dash-test.json +14643 -0
  4. package/examples/full-dashboard.json +10036 -0
  5. package/examples/sankey.json +5218 -0
  6. package/index.html +4 -3
  7. package/package.json +11 -10
  8. package/src/CdcDashboard.tsx +129 -124
  9. package/src/CdcDashboardComponent.tsx +316 -441
  10. package/src/DashboardContext.tsx +4 -1
  11. package/src/_stories/Dashboard.stories.tsx +79 -36
  12. package/src/_stories/_mock/api-filter-chart.json +11 -35
  13. package/src/_stories/_mock/api-filter-map.json +17 -31
  14. package/src/_stories/_mock/dashboard-gallery.json +523 -534
  15. package/src/_stories/_mock/multi-viz.json +378 -0
  16. package/src/_stories/_mock/pivot-filter.json +161 -0
  17. package/src/_stories/_mock/standalone-table.json +122 -0
  18. package/src/_stories/_mock/toggle-example.json +4035 -0
  19. package/src/components/DataDesignerModal.tsx +145 -0
  20. package/src/components/EditorWrapper/EditorWrapper.tsx +52 -0
  21. package/src/components/EditorWrapper/editor-wrapper.style.css +13 -0
  22. package/src/components/Filters.tsx +88 -0
  23. package/src/components/Grid.tsx +3 -1
  24. package/src/components/Header/FilterModal.tsx +506 -0
  25. package/src/components/Header/Header.tsx +25 -465
  26. package/src/components/Row.tsx +65 -29
  27. package/src/components/Toggle/Toggle.tsx +36 -0
  28. package/src/components/Toggle/index.tsx +1 -0
  29. package/src/components/Toggle/toggle-style.css +34 -0
  30. package/src/components/VisualizationRow.tsx +174 -0
  31. package/src/components/VisualizationsPanel.tsx +13 -3
  32. package/src/components/Widget.tsx +28 -126
  33. package/src/helpers/filterData.ts +75 -50
  34. package/src/helpers/generateValuesForFilter.ts +2 -12
  35. package/src/helpers/getApiFilterKey.ts +5 -0
  36. package/src/helpers/getFilteredData.ts +39 -0
  37. package/src/helpers/getUpdateConfig.ts +39 -22
  38. package/src/helpers/getVizConfig.ts +31 -0
  39. package/src/helpers/getVizRowColumnLocator.ts +9 -0
  40. package/src/helpers/iconHash.tsx +34 -0
  41. package/src/helpers/tests/filterData.test.ts +149 -0
  42. package/src/images/icon-toggle.svg +1 -0
  43. package/src/scss/grid.scss +10 -3
  44. package/src/scss/main.scss +11 -0
  45. package/src/store/dashboard.actions.ts +35 -3
  46. package/src/store/dashboard.reducer.ts +33 -2
  47. package/src/types/APIFilter.ts +4 -5
  48. package/src/types/ConfigRow.ts +13 -2
  49. package/src/types/DataSet.ts +11 -8
  50. package/src/types/InitialState.ts +2 -1
  51. package/src/types/SharedFilter.ts +6 -3
  52. package/src/types/Tab.ts +1 -0
@@ -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
 
@@ -11,35 +11,55 @@ import TwoColIcon from '../images/icon-col-6.svg'
11
11
  import ThreeColIcon from '../images/icon-col-4.svg'
12
12
  import FourEightColIcon from '../images/icon-col-4-8.svg'
13
13
  import EightFourColIcon from '../images/icon-col-8-4.svg'
14
+ import ToggleIcon from '../images/icon-toggle.svg'
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'
20
+
21
+ type RowMenuProps = {
22
+ rowIdx: number
23
+ }
14
24
 
15
- const RowMenu = ({ rowIdx, row }) => {
25
+ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
16
26
  const { config } = useContext(DashboardContext)
17
- if (!config) return null
18
- const { rows } = config
19
27
  const dispatch = useContext(DashboardDispatchContext)
20
- const updateConfig = config => dispatch({ type: 'UPDATE_CONFIG', payload: [config] })
21
- const getCurr = () => {
22
- let res = [] as Object[]
23
-
24
- for (let i = 0; i < row.length; i++) {
25
- if (row[i].width) res.push(row[i].width)
26
- }
27
-
28
- return res.join('')
29
- }
30
-
31
- const [curr, setCurr] = useState(getCurr())
32
-
33
- const setRowLayout = layout => {
34
- const newRows = [...rows]
35
- const r = newRows[rowIdx]
28
+ const rows = _.cloneDeep(config.rows)
29
+ const row = config.rows[rowIdx]
36
30
 
37
- for (let i = 0; i < r.length; i++) {
38
- r[i].width = layout[i] ?? null
31
+ const updateConfig = config => dispatch({ type: 'UPDATE_CONFIG', payload: [config] })
32
+ const curr = useMemo(() => {
33
+ if (row.toggle) return 'toggle'
34
+ return row.columns.reduce((acc, curr) => {
35
+ if (curr.width) {
36
+ acc += curr.width
37
+ }
38
+ return acc
39
+ }, '')
40
+ }, [row])
41
+
42
+ const setRowLayout = (layout: number[], toggle = undefined) => {
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
+ })
39
60
  }
40
61
 
41
62
  updateConfig({ ...config, rows: newRows })
42
- setCurr(layout.join(''))
43
63
  }
44
64
 
45
65
  const moveRow = (dir = 'down') => {
@@ -104,6 +124,9 @@ const RowMenu = ({ rowIdx, row }) => {
104
124
  </li>,
105
125
  <li className={curr === '84' ? `current row-menu__list--item` : `row-menu__list--item`} onClick={() => setRowLayout([8, 4])} key='84' title='2 Columns'>
106
126
  <EightFourColIcon />
127
+ </li>,
128
+ <li className={curr === 'toggle' ? `current row-menu__list--item` : `row-menu__list--item`} onClick={() => setRowLayout([12, 12, 12], true)} key='toggle' title='Toggle between up to three visualizations'>
129
+ <ToggleIcon />
107
130
  </li>
108
131
  ]
109
132
 
@@ -127,15 +150,28 @@ const RowMenu = ({ rowIdx, row }) => {
127
150
  }
128
151
 
129
152
  const Row = ({ row, idx: rowIdx, uuid }) => {
153
+ const { overlay } = useGlobalContext()
130
154
  return (
131
155
  <div className='builder-row' data-row-id={rowIdx}>
132
- <RowMenu rowIdx={rowIdx} row={row} />
156
+ <RowMenu rowIdx={rowIdx} />
133
157
  <div className='column-container'>
134
- {row
135
- .filter(column => column.width)
136
- .map((column, colIdx) => (
137
- <Column data={column} key={`row-${uuid}-col-${colIdx}`} rowIdx={rowIdx} colIdx={colIdx} />
138
- ))}
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
+ </>
139
175
  </div>
140
176
  </div>
141
177
  )
@@ -0,0 +1,36 @@
1
+ import { useContext } from 'react'
2
+ import { DashboardDispatchContext } from '../../DashboardContext'
3
+ import { ConfigRow } from '../../types/ConfigRow'
4
+ import { Visualization } from '@cdc/core/types/Visualization'
5
+ import { getIcon } from '../../helpers/iconHash'
6
+ import './toggle-style.css'
7
+ import _ from 'lodash'
8
+
9
+ type ToggleProps = {
10
+ active: number
11
+ row: ConfigRow
12
+ visualizations: Record<string, Visualization>
13
+ setToggled: (colIndex: number) => void
14
+ }
15
+ const Toggle: React.FC<ToggleProps> = ({ active, row, visualizations, setToggled }) => {
16
+ const selectItem = (colIndex, e = null) => {
17
+ if (e?.key && e.key !== 'Enter') return
18
+ setToggled(colIndex)
19
+ }
20
+ return (
21
+ <div className='toggle-component'>
22
+ {row.columns.map((col, colIndex) => {
23
+ if (!col.widget) return null
24
+ const type = visualizations[col.widget].type
25
+ const selected = colIndex === active
26
+ return (
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}`}>
28
+ {getIcon(visualizations[col.widget])} <span>{_.capitalize(type)}</span>
29
+ </div>
30
+ )
31
+ })}
32
+ </div>
33
+ )
34
+ }
35
+
36
+ export default Toggle
@@ -0,0 +1 @@
1
+ export { default } from './Toggle'
@@ -0,0 +1,34 @@
1
+ .cdc-open-viz-module {
2
+ --border: 1px solid var(--lightGray);
3
+ .toggle-component {
4
+ display: flex;
5
+ justify-content: right;
6
+ width: 100%;
7
+ margin-bottom: 15px;
8
+ :first-child:is(div) {
9
+ border: var(--border);
10
+ border-radius: 5px 0 0 5px;
11
+ }
12
+ :last-child:is(div) {
13
+ border: var(--border);
14
+ border-radius: 0 5px 5px 0;
15
+ }
16
+ :is(div) {
17
+ border-top: var(--border);
18
+ border-bottom: var(--border);
19
+ padding: 7px 15px;
20
+ display: inline;
21
+ float: right;
22
+ cursor: pointer;
23
+ &.selected {
24
+ background-color: var(--primary);
25
+ color: white;
26
+ }
27
+ background-color: var(--white);
28
+ color: var(--primary);
29
+ :is(svg) {
30
+ height: 25px;
31
+ }
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,174 @@
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 { DashboardConfig } from '../types/DashboardConfig'
6
+ import { ConfigRow } from '../types/ConfigRow'
7
+ import DataTransform from '@cdc/core/helpers/DataTransform'
8
+ import CdcMap from '@cdc/map'
9
+ import CdcChart from '@cdc/chart'
10
+ import CdcDataBite from '@cdc/data-bite'
11
+ import CdcWaffleChart from '@cdc/waffle-chart'
12
+ import CdcMarkupInclude from '@cdc/markup-include'
13
+ import CdcFilteredText from '@cdc/filtered-text'
14
+ import Filters, { APIFilterDropdowns } from './Filters'
15
+ import { FilterBehavior } from './Header/Header'
16
+ import { DashboardContext } from '../DashboardContext'
17
+ import { ViewPort } from '@cdc/core/types/ViewPort'
18
+ import { getVizConfig } from '../helpers/getVizConfig'
19
+
20
+ type VizRowProps = {
21
+ filteredDataOverride?: Object[]
22
+ row: ConfigRow
23
+ rowIndex: number
24
+ setSharedFilter: Function
25
+ updateChildConfig: Function
26
+ applyFilters: MouseEventHandler<HTMLButtonElement>
27
+ apiFilterDropdowns: APIFilterDropdowns
28
+ handleOnChange: Function
29
+ currentViewport: ViewPort
30
+ }
31
+
32
+ const VisualizationRow: React.FC<VizRowProps> = ({ filteredDataOverride, row, rowIndex: index, setSharedFilter, updateChildConfig, applyFilters, apiFilterDropdowns, handleOnChange, currentViewport }) => {
33
+ const { config, filteredData: dashboardFilteredData, data: rawData } = useContext(DashboardContext)
34
+ const [show, setShow] = React.useState(row.columns.map((col, i) => i === 0))
35
+ const setToggled = (colIndex: number) => {
36
+ setShow(show.map((_, i) => i === colIndex))
37
+ }
38
+ const inNoDataState = useMemo(() => {
39
+ const vals = Object.values(rawData)
40
+ if (!vals.length) return true
41
+ return vals.some(val => val === undefined)
42
+ }, [rawData])
43
+ const GoButton = ({ autoLoad }: { autoLoad?: boolean }) => {
44
+ if (config.filterBehavior === FilterBehavior.Apply && !autoLoad) {
45
+ return <button onClick={applyFilters}>GO!</button>
46
+ }
47
+ return null
48
+ }
49
+ return (
50
+ <div className={`dashboard-row ${row.equalHeight ? 'equal-height' : ''} ${row.toggle ? 'toggle' : ''}`} key={`row__${index}`}>
51
+ {row.toggle && <Toggle row={row} visualizations={config.visualizations} active={show.indexOf(true)} setToggled={setToggled} />}
52
+ {row.columns.map((col, colIndex) => {
53
+ if (col.width) {
54
+ if (!col.widget) return <div key={`row__${index}__col__${colIndex}`} className={`dashboard-col dashboard-col-${col.width}`}></div>
55
+
56
+ const visualizationConfig = getVizConfig(col.widget, index, config, rawData, dashboardFilteredData)
57
+ if (filteredDataOverride) {
58
+ visualizationConfig.data = filteredDataOverride
59
+ if (visualizationConfig.formattedData) {
60
+ visualizationConfig.formattedData = filteredDataOverride
61
+ }
62
+ }
63
+
64
+ const setsSharedFilter = config.dashboard.sharedFilters && config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === col.widget).length > 0
65
+ const setSharedFilterValue = setsSharedFilter ? config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === col.widget)[0].active : undefined
66
+ const tableLink = (
67
+ <a href={`#data-table-${visualizationConfig.dataKey}`} className='margin-left-href'>
68
+ {visualizationConfig.dataKey} (Go to Table)
69
+ </a>
70
+ )
71
+ const hideFilter = visualizationConfig.autoLoad && inNoDataState
72
+
73
+ const shouldShow = row.toggle === undefined || (row.toggle && show[colIndex])
74
+ return (
75
+ <React.Fragment key={`vis__${index}__${colIndex}`}>
76
+ <div className={`dashboard-col dashboard-col-${col.width} ${!shouldShow ? 'hidden-toggle' : ''}`}>
77
+ {visualizationConfig.type === 'chart' && (
78
+ <CdcChart
79
+ key={col.widget}
80
+ config={visualizationConfig}
81
+ dashboardConfig={config}
82
+ isEditor={false}
83
+ setConfig={newConfig => {
84
+ updateChildConfig(col.widget, newConfig)
85
+ }}
86
+ setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
87
+ isDashboard={true}
88
+ link={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
89
+ configUrl={undefined}
90
+ setEditing={undefined}
91
+ hostname={undefined}
92
+ setSharedFilterValue={undefined}
93
+ />
94
+ )}
95
+ {visualizationConfig.type === 'map' && (
96
+ <CdcMap
97
+ key={col.widget}
98
+ config={visualizationConfig}
99
+ isEditor={false}
100
+ setConfig={newConfig => {
101
+ updateChildConfig(col.widget, newConfig)
102
+ }}
103
+ showLoader={false}
104
+ setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
105
+ setSharedFilterValue={setSharedFilterValue}
106
+ isDashboard={true}
107
+ link={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
108
+ />
109
+ )}
110
+ {visualizationConfig.type === 'data-bite' && (
111
+ <CdcDataBite
112
+ key={col.widget}
113
+ config={visualizationConfig}
114
+ isEditor={false}
115
+ setConfig={newConfig => {
116
+ updateChildConfig(col.widget, newConfig)
117
+ }}
118
+ isDashboard={true}
119
+ />
120
+ )}
121
+ {visualizationConfig.type === 'waffle-chart' && (
122
+ <CdcWaffleChart
123
+ key={col.widget}
124
+ config={visualizationConfig}
125
+ isEditor={false}
126
+ setConfig={newConfig => {
127
+ updateChildConfig(col.widget, newConfig)
128
+ }}
129
+ isDashboard={true}
130
+ configUrl={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
131
+ />
132
+ )}
133
+ {visualizationConfig.type === 'markup-include' && (
134
+ <CdcMarkupInclude
135
+ key={col.widget}
136
+ config={visualizationConfig}
137
+ isEditor={false}
138
+ setConfig={newConfig => {
139
+ updateChildConfig(col.widget, newConfig)
140
+ }}
141
+ isDashboard={true}
142
+ configUrl={undefined}
143
+ />
144
+ )}
145
+ {visualizationConfig.type === 'filtered-text' && (
146
+ <CdcFilteredText
147
+ key={col.widget}
148
+ config={visualizationConfig}
149
+ isEditor={false}
150
+ setConfig={newConfig => {
151
+ updateChildConfig(col.widget, newConfig)
152
+ }}
153
+ isDashboard={true}
154
+ configUrl={undefined}
155
+ />
156
+ )}
157
+ {visualizationConfig.type === 'filter-dropdowns' && !hideFilter && (
158
+ <React.Fragment key={col.widget}>
159
+ <Filters hide={visualizationConfig.hide} filters={config.dashboard.sharedFilters} apiFilterDropdowns={apiFilterDropdowns} handleOnChange={handleOnChange} />
160
+ <GoButton autoLoad={visualizationConfig.autoLoad} />
161
+ </React.Fragment>
162
+ )}
163
+ {visualizationConfig.type === 'table' && <DataTableStandAlone key={col.widget} visualizationKey={col.widget} config={visualizationConfig} viewport={currentViewport} />}
164
+ </div>
165
+ </React.Fragment>
166
+ )
167
+ }
168
+ return <React.Fragment key={`vis__${index}__${colIndex}`}></React.Fragment>
169
+ })}
170
+ </div>
171
+ )
172
+ }
173
+
174
+ export default VisualizationRow
@@ -2,11 +2,12 @@ import React from 'react'
2
2
  import type { Visualization } from '@cdc/core/types/Visualization'
3
3
  import Widget from './Widget'
4
4
  import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
5
+ import { Table } from '@cdc/core/types/Table'
5
6
 
6
7
  const addVisualization = (type, subType) => {
7
- let modalWillOpen = type !== 'markup-include'
8
- let newVisualizationConfig: Partial<Visualization> = {
9
- newViz: true,
8
+ const modalWillOpen = type !== 'markup-include'
9
+ const newVisualizationConfig: Partial<Visualization> = {
10
+ newViz: type !== 'table',
10
11
  openModal: modalWillOpen,
11
12
  uid: type + Date.now(),
12
13
  type
@@ -23,6 +24,13 @@ const addVisualization = (type, subType) => {
23
24
  case 'data-bite' || 'waffle-chart' || 'markup-include' || 'filtered-text':
24
25
  newVisualizationConfig.visualizationType = type
25
26
  break
27
+ case 'table':
28
+ const tableConfig: Table = { label: 'Data Table', show: true, showDownloadUrl: false, showVertical: true, expanded: true }
29
+ newVisualizationConfig.table = tableConfig
30
+ newVisualizationConfig.columns = {}
31
+ newVisualizationConfig.dataFormat = {}
32
+ newVisualizationConfig.visualizationType = type
33
+ break
26
34
  default:
27
35
  newVisualizationConfig.visualizationType = type
28
36
  break
@@ -39,6 +47,7 @@ const VisualizationsPanel = ({ loadConfig, config }) => (
39
47
  <Widget addVisualization={() => addVisualization('chart', 'Bar')} type='Bar' />
40
48
  <Widget addVisualization={() => addVisualization('chart', 'Line')} type='Line' />
41
49
  <Widget addVisualization={() => addVisualization('chart', 'Pie')} type='Pie' />
50
+ <Widget addVisualization={() => addVisualization('chart', 'Sankey')} type='Sankey' />
42
51
  </div>
43
52
  <span className='subheading-3'>Map</span>
44
53
  <div className='drag-grid'>
@@ -53,6 +62,7 @@ const VisualizationsPanel = ({ loadConfig, config }) => (
53
62
  <Widget addVisualization={() => addVisualization('markup-include', '')} type='markup-include' />
54
63
  <Widget addVisualization={() => addVisualization('filtered-text', '')} type='filtered-text' />
55
64
  <Widget addVisualization={() => addVisualization('filter-dropdowns', '')} type='filter-dropdowns' />
65
+ <Widget addVisualization={() => addVisualization('table', '')} type='table' />
56
66
  </div>
57
67
  <span className='subheading-3'>Advanced</span>
58
68
  <AdvancedEditor loadConfig={loadConfig} state={config} convertStateToConfig={undefined} />
@@ -1,34 +1,16 @@
1
- import React, { useContext, useRef, useEffect } from 'react'
1
+ import { useContext } from 'react'
2
2
  import { useDrag } from 'react-dnd'
3
3
 
4
4
  import { useGlobalContext } from '@cdc/core/components/GlobalContext'
5
5
  import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
6
6
 
7
7
  import { DataTransform } from '@cdc/core/helpers/DataTransform'
8
- import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
9
-
10
- import DataDesigner from '@cdc/core/components/managers/DataDesigner'
11
8
  import Icon from '@cdc/core/components/ui/Icon'
12
9
  import Modal from '@cdc/core/components/ui/Modal'
13
10
  import { Visualization } from '@cdc/core/types/Visualization'
14
-
15
- const iconHash = {
16
- 'data-bite': <Icon display='databite' base />,
17
- Bar: <Icon display='chartBar' base />,
18
- 'Spark Line': <Icon display='chartLine' />,
19
- 'waffle-chart': <Icon display='grid' base />,
20
- 'markup-include': <Icon display='code' base />,
21
- Line: <Icon display='chartLine' base />,
22
- Pie: <Icon display='chartPie' base />,
23
- us: <Icon display='mapUsa' base />,
24
- 'us-county': <Icon display='mapUsa' base />,
25
- world: <Icon display='mapWorld' base />,
26
- 'single-state': <Icon display='mapAl' base />,
27
- gear: <Icon display='gear' base />,
28
- tools: <Icon display='tools' base />,
29
- 'filtered-text': <Icon display='filtered-text' base />,
30
- 'filter-dropdowns': <Icon display='filter-dropdowns' base />
31
- }
11
+ import { iconHash } from '../helpers/iconHash'
12
+ import _ from 'lodash'
13
+ import { DataDesignerModal } from './DataDesignerModal'
32
14
 
33
15
  const labelHash = {
34
16
  'data-bite': 'Data Bite',
@@ -43,7 +25,9 @@ const labelHash = {
43
25
  world: 'World',
44
26
  'single-state': 'U.S. State',
45
27
  'filtered-text': 'Filtered Text',
46
- 'filter-dropdowns': 'Filter Dropdowns'
28
+ 'filter-dropdowns': 'Filter Dropdowns',
29
+ Sankey: 'Sankey Chart',
30
+ table: 'Table'
47
31
  }
48
32
 
49
33
  type WidgetData = Visualization & { rowIdx: number; colIdx: number }
@@ -56,13 +40,10 @@ type WidgetProps = {
56
40
  const Widget = ({ data, addVisualization, type }: WidgetProps) => {
57
41
  const { overlay } = useGlobalContext()
58
42
  const { config } = useContext(DashboardContext)
59
- if (!config) return null
60
43
  const rows = config.rows
61
44
  const visualizations = config.visualizations
62
45
  const dispatch = useContext(DashboardDispatchContext)
63
46
  const updateConfig = config => dispatch({ type: 'UPDATE_CONFIG', payload: [config] })
64
- const dataRef = useRef<WidgetData>()
65
- dataRef.current = data
66
47
 
67
48
  const transform = new DataTransform()
68
49
 
@@ -74,14 +55,14 @@ const Widget = ({ data, addVisualization, type }: WidgetProps) => {
74
55
  const { rowIdx, colIdx } = result
75
56
 
76
57
  if (undefined !== data?.rowIdx) {
77
- rows[data.rowIdx][data.colIdx].widget = null // Wipe from old position
58
+ rows[data.rowIdx].columns[data.colIdx].widget = null // Wipe from old position
78
59
 
79
- rows[rowIdx][colIdx].widget = data.uid // Add to new row and col
60
+ rows[rowIdx].columns[colIdx].widget = data.uid // Add to new row and col
80
61
  } else if (!!addVisualization) {
81
62
  // Item does not exist, instantiate a new one
82
63
  const newViz = addVisualization()
83
64
  visualizations[newViz.uid] = newViz // Add to widgets collection
84
- rows[rowIdx][colIdx].widget = newViz.uid // Store reference in rows collection under the specific column
65
+ rows[rowIdx].columns[colIdx].widget = newViz.uid // Store reference in rows collection under the specific column
85
66
  }
86
67
 
87
68
  updateConfig({ ...config, rows, visualizations })
@@ -100,7 +81,7 @@ const Widget = ({ data, addVisualization, type }: WidgetProps) => {
100
81
 
101
82
  const deleteWidget = () => {
102
83
  if (!data) return
103
- rows[data.rowIdx][data.colIdx].widget = null
84
+ rows[data.rowIdx].columns[data.colIdx].widget = null
104
85
 
105
86
  delete visualizations[data.uid]
106
87
 
@@ -122,78 +103,6 @@ const Widget = ({ data, addVisualization, type }: WidgetProps) => {
122
103
  updateConfig({ ...config, visualizations })
123
104
  }
124
105
 
125
- const changeDataset = (uid, value) => {
126
- visualizations[uid].dataDescription = {}
127
- visualizations[uid].formattedData = undefined
128
-
129
- visualizations[uid].dataKey = value
130
-
131
- updateConfig({ ...config, visualizations })
132
- }
133
-
134
- const updateDescriptionProp = async (visualizationKey, datasetKey, key, value) => {
135
- let dataDescription = { ...(dataRef.current?.dataDescription as Object), [key]: value }
136
-
137
- let newData
138
- if (!config.datasets[datasetKey].data && config.datasets[datasetKey].dataUrl) {
139
- newData = await fetchRemoteData(config.datasets[datasetKey].dataUrl)
140
- newData = transform.autoStandardize(newData)
141
- } else {
142
- newData = config.datasets[datasetKey].data
143
- }
144
-
145
- let formattedData = transform.developerStandardize(newData, dataDescription)
146
-
147
- let newVisualizations = { ...config.visualizations }
148
- newVisualizations[visualizationKey] = { ...newVisualizations[visualizationKey], data: newData, dataDescription, formattedData }
149
-
150
- updateConfig({ ...config, visualizations: newVisualizations })
151
-
152
- overlay?.actions.openOverlay(dataDesignerModal(newVisualizations[visualizationKey]))
153
- }
154
-
155
- const dataDesignerModal = (configureData, dataKeyOverride?) => {
156
- const dataKey = !dataKeyOverride && dataKeyOverride !== '' ? data?.dataKey || dataRef.current?.dataKey : dataKeyOverride
157
-
158
- overlay?.actions.toggleOverlay()
159
-
160
- return (
161
- <Modal>
162
- <Modal.Content>
163
- <div className='dataset-selector-container'>
164
- Select a dataset:&nbsp;
165
- <select
166
- className='dataset-selector'
167
- defaultValue={dataKey}
168
- onChange={e => {
169
- changeDataset(data?.uid, e.target.value)
170
- overlay?.actions.openOverlay(dataDesignerModal(data, e.target.value || ''))
171
- }}
172
- >
173
- <option value=''>Select a dataset</option>
174
- {config.datasets && Object.keys(config.datasets).map(datasetKey => <option key={datasetKey}>{datasetKey}</option>)}
175
- </select>
176
- </div>
177
- {dataKey && (
178
- <DataDesigner
179
- {...{
180
- configureData,
181
- visualizationKey: data?.uid,
182
- dataKey: dataKey,
183
- updateDescriptionProp
184
- }}
185
- />
186
- )}
187
- {configureData.formattedData && (
188
- <button style={{ margin: '1em' }} className='cove-button' onClick={() => overlay?.actions.toggleOverlay()}>
189
- Continue
190
- </button>
191
- )}
192
- </Modal.Content>
193
- </Modal>
194
- )
195
- }
196
-
197
106
  const FilterHideModal = configureData => {
198
107
  const currentVizKey = Object.keys(visualizations).find(vizKey => vizKey === configureData.uid) || ''
199
108
  const currentViz = config.visualizations && config.visualizations[currentVizKey]
@@ -224,8 +133,6 @@ const Widget = ({ data, addVisualization, type }: WidgetProps) => {
224
133
  }
225
134
  }
226
135
 
227
- overlay?.actions.toggleOverlay()
228
-
229
136
  const showAutoLoadCheckbox = !vizWithAutoLoad || vizWithAutoLoad === currentVizKey
230
137
  return (
231
138
  <Modal>
@@ -259,29 +166,24 @@ const Widget = ({ data, addVisualization, type }: WidgetProps) => {
259
166
  )
260
167
  }
261
168
 
262
- useEffect(() => {
263
- if (data?.openModal) {
264
- overlay?.actions.openOverlay(type === 'filter-dropdowns' ? FilterHideModal(dataRef.current) : dataDesignerModal(dataRef.current))
265
-
266
- visualizations[data.uid].openModal = false
267
-
268
- updateConfig({ ...config, visualizations })
269
- }
270
- }, [data?.openModal])
271
-
272
- let isConfigurationReady = false;
273
- if(type === 'markup-include' || type === 'filter-dropdowns'){
274
- isConfigurationReady = true;
275
- } else if(data && data.formattedData) {
276
- isConfigurationReady = true;
277
- } else if(data && data.dataKey && data.dataDescription && config.datasets[data.dataKey]){
278
- let formattedDataAttempt = transform.autoStandardize(config.datasets[data.dataKey].data);
279
- formattedDataAttempt = transform.developerStandardize(formattedDataAttempt, data.dataDescription);
280
- if(formattedDataAttempt){
281
- isConfigurationReady = true;
169
+ let isConfigurationReady = false
170
+ const dataConfiguredForRow = !!rows[data?.rowIdx]?.dataKey
171
+ if (dataConfiguredForRow || ['markup-include', 'filter-dropdowns'].includes(type)) {
172
+ isConfigurationReady = true
173
+ } else {
174
+ if (data?.formattedData) {
175
+ isConfigurationReady = true
176
+ } else if (data?.dataKey && data?.dataDescription && config.datasets[data.dataKey]) {
177
+ const formattedDataAttempt = transform.autoStandardize(config.datasets[data.dataKey].data)
178
+ const canFormatData = !!transform.developerStandardize(formattedDataAttempt, data.dataDescription)
179
+ if (canFormatData) {
180
+ isConfigurationReady = true
181
+ }
282
182
  }
283
183
  }
284
184
 
185
+ const needsDataConfiguration = !dataConfiguredForRow && type !== 'markup-include'
186
+
285
187
  return (
286
188
  <>
287
189
  <div className='widget' ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }} {...collected}>
@@ -294,12 +196,12 @@ const Widget = ({ data, addVisualization, type }: WidgetProps) => {
294
196
  {iconHash['tools']}
295
197
  </button>
296
198
  )}
297
- {type !== 'markup-include' && (
199
+ {needsDataConfiguration && (
298
200
  <button
299
201
  title='Configure Data'
300
202
  className='btn btn-configure'
301
203
  onClick={() => {
302
- overlay?.actions.openOverlay(type === 'filter-dropdowns' ? FilterHideModal(data) : dataDesignerModal(data))
204
+ overlay?.actions.openOverlay(type === 'filter-dropdowns' ? FilterHideModal(data) : <DataDesignerModal rowIndex={data.rowIdx} vizKey={data.uid} />)
303
205
  }}
304
206
  >
305
207
  {iconHash['gear']}