@cdc/dashboard 4.22.11 → 4.23.1

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.
@@ -73,13 +73,29 @@
73
73
  "key": "sharedFilter1",
74
74
  "columnName": "state",
75
75
  "setBy": "map1",
76
- "usedBy": ["chart1"]
76
+ "usedBy": [
77
+ "chart1"
78
+ ]
77
79
  }
78
80
  ]
79
81
  },
80
82
  "rows": [
81
- [{ "width": 12, "widget": "map1" }, {}, {}],
82
- [{ "width": 12, "widget": "chart1" }, {}, {}]
83
+ [
84
+ {
85
+ "width": 12,
86
+ "widget": "map1"
87
+ },
88
+ {},
89
+ {}
90
+ ],
91
+ [
92
+ {
93
+ "width": 12,
94
+ "widget": "chart1"
95
+ },
96
+ {},
97
+ {}
98
+ ]
83
99
  ],
84
100
  "visualizations": {
85
101
  "map1": {
@@ -128,7 +144,9 @@
128
144
  "title": "Legend Title",
129
145
  "description": "Legend Text",
130
146
  "type": "equalnumber",
131
- "specialClasses": ["N/A"]
147
+ "specialClasses": [
148
+ "N/A"
149
+ ]
132
150
  }
133
151
  },
134
152
  "chart1": {
@@ -138,7 +156,12 @@
138
156
  "title": "Test",
139
157
  "description": "<p>Test</p>",
140
158
  "visualizationType": "Line",
141
- "series": [{ "dataKey": "Insured Rate", "label": "Insured Rate" }],
159
+ "series": [
160
+ {
161
+ "dataKey": "Insured Rate",
162
+ "label": "Insured Rate"
163
+ }
164
+ ],
142
165
  "fontSize": "large",
143
166
  "dataFormat": {
144
167
  "commas": false,
@@ -173,5 +196,11 @@
173
196
  "download": true
174
197
  }
175
198
  }
199
+ },
200
+ "table": {
201
+ "downloadImageButton": true,
202
+ "downloadPdfButton": true,
203
+ "download": true,
204
+ "show": true
176
205
  }
177
206
  }
@@ -86,7 +86,7 @@
86
86
  "newViz": true,
87
87
  "uid": "data-bite1661201913029",
88
88
  "visualizationType": "data-bite",
89
- "editing": true
89
+ "editing": false
90
90
  }
91
91
  },
92
92
  "table": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/dashboard",
3
- "version": "4.22.11",
3
+ "version": "4.23.1",
4
4
  "description": "React component for combining multiple visualizations into a single dashboard",
5
5
  "main": "dist/cdcdashboard",
6
6
  "scripts": {
@@ -20,12 +20,12 @@
20
20
  "license": "Apache-2.0",
21
21
  "dependencies": {
22
22
  "@cdc/chart": "^9.22.9",
23
- "@cdc/core": "^4.22.11",
24
- "@cdc/data-bite": "^4.22.11",
23
+ "@cdc/core": "^4.23.1",
24
+ "@cdc/data-bite": "^4.23.1",
25
25
  "@cdc/filtered-text": "^0.0.1",
26
- "@cdc/map": "^4.22.11",
27
- "@cdc/markup-include": "^4.22.11",
28
- "@cdc/waffle-chart": "^4.22.11",
26
+ "@cdc/map": "^4.23.1",
27
+ "@cdc/markup-include": "^4.23.1",
28
+ "@cdc/waffle-chart": "^4.23.1",
29
29
  "html-react-parser": "1.4.9",
30
30
  "js-base64": "^2.5.2",
31
31
  "papaparse": "^5.3.0",
@@ -44,5 +44,5 @@
44
44
  "resolutions": {
45
45
  "@types/react": "17.x"
46
46
  },
47
- "gitHead": "9768d1ea0e2383044977d988e33531bcdfe33ea6"
47
+ "gitHead": "58163844cc9ce2c379235413b19f49679eab4bef"
48
48
  }
@@ -32,6 +32,8 @@ import Header from './components/Header'
32
32
  import defaults from './data/initial-state'
33
33
  import Widget from './components/Widget'
34
34
  import DataTable from './components/DataTable'
35
+ import CoveMediaControls from '@cdc/core/helpers/CoveMediaControls'
36
+
35
37
 
36
38
  import './scss/main.scss'
37
39
  import '@cdc/core/styles/v2/main.scss'
@@ -106,6 +108,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
106
108
  const [preview, setPreview] = useState(false)
107
109
  const [tabSelected, setTabSelected] = useState(0)
108
110
  const [currentViewport, setCurrentViewport] = useState('lg')
111
+ const [imageId, setImageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`)
109
112
 
110
113
  const { title, description } = config.dashboard || config
111
114
 
@@ -331,7 +334,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
331
334
  setFilteredData(newFilteredData)
332
335
  }
333
336
 
334
- const announceChange = text => {}
337
+ const announceChange = text => { }
335
338
 
336
339
  return config.dashboard.sharedFilters.map((singleFilter, index) => {
337
340
  if (!singleFilter.showDropdown) return
@@ -617,10 +620,15 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
617
620
  )
618
621
  })}
619
622
 
623
+ {/* Image or PDF Inserts */}
624
+ <section className='download-buttons'>
625
+ {config.table.downloadImageButton && <CoveMediaControls.Button title='Download Dashboard as Image' type='image' state={config} text='Download Dashboard Image' elementToCapture={imageId} />}
626
+ {config.table.downloadPdfButton && <CoveMediaControls.Button title='Download Dashboard as PDF' type='pdf' state={config} text='Download Dashboard PDF' elementToCapture={imageId} />}
627
+ </section>
628
+
620
629
  {/* Data Table */}
621
- {config.table && config.table.show && config.data && <DataTable data={config.data} config={config} />}
630
+ {config.table && config.data && <DataTable data={config.data} config={config} imageRef={imageId} />}
622
631
  {config.table &&
623
- config.table.show &&
624
632
  config.datasets &&
625
633
  Object.keys(config.datasets).map(datasetKey => {
626
634
  //For each dataset, find any shared filters that apply to all visualizations using the dataset
@@ -655,9 +663,12 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
655
663
  }
656
664
  }
657
665
 
666
+
667
+ let dataFileSourceType = config.datasets[datasetKey]?.dataFileSourceType
668
+
658
669
  return (
659
670
  <div className='multi-table-container' id={`data-table-${datasetKey}`} key={`data-table-${datasetKey}`}>
660
- <DataTable data={filteredTableData || config.datasets[datasetKey].data} datasetKey={datasetKey} config={config}></DataTable>
671
+ <DataTable data={filteredTableData || config.datasets[datasetKey].data} dataFileSourceType={dataFileSourceType} datasetKey={datasetKey} config={config} imageRef={imageId}></DataTable>
661
672
  </div>
662
673
  )
663
674
  })}
@@ -675,13 +686,21 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
675
686
  loading,
676
687
  updateConfig,
677
688
  setParentConfig,
678
- setPreview
689
+ setPreview,
690
+ outerContainerRef
679
691
  }
680
692
 
693
+
694
+ const dashboardContainerClasses = [
695
+ 'cdc-open-viz-module',
696
+ 'type-dashboard',
697
+ `${currentViewport}`
698
+ ]
699
+
681
700
  return (
682
701
  <GlobalContextProvider>
683
702
  <ConfigContext.Provider value={contextValues}>
684
- <div className={`cdc-open-viz-module type-dashboard ${currentViewport}`} ref={outerContainerRef}>
703
+ <div className={dashboardContainerClasses.join(' ')} ref={outerContainerRef} data-download-id={imageId} >
685
704
  {body}
686
705
  </div>
687
706
  <OverlayFrame />
@@ -2,12 +2,12 @@ import React, { useContext, useEffect, useState, useMemo, memo } from 'react'
2
2
  import { useTable, useSortBy, useResizeColumns, useBlockLayout } from 'react-table'
3
3
  import Papa from 'papaparse'
4
4
  import { Base64 } from 'js-base64'
5
+ import CoveMediaControls from '@cdc/core/helpers/CoveMediaControls'
5
6
 
6
7
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
7
8
 
8
9
  export default function DataTable(props) {
9
- const { data, datasetKey, config } = props
10
-
10
+ const { data, datasetKey, config, imageRef, dataFileSourceType } = props
11
11
  const [tableExpanded, setTableExpanded] = useState<boolean>(config.table ? config.table.expanded : false)
12
12
  const [accessibilityLabel, setAccessibilityLabel] = useState('')
13
13
 
@@ -26,7 +26,7 @@ export default function DataTable(props) {
26
26
  }
27
27
 
28
28
  return (
29
- <a download={fileName} onClick={saveBlob} href={`data:text/csv;base64,${Base64.encode(csvData)}`} aria-label='Download this data in a CSV file format.' className={`btn btn-download no-border`}>
29
+ <a download={fileName} onClick={saveBlob} href={`data:text/csv;base64,${Base64.encode(csvData)}`} aria-label='Download this data in a CSV file format.' className={`no-border dashboard-download-link`}>
30
30
  Download {datasetKey ? `"${datasetKey}"` : 'Data'} (CSV)
31
31
  </a>
32
32
  )
@@ -91,60 +91,69 @@ export default function DataTable(props) {
91
91
 
92
92
  return (
93
93
  <ErrorBoundary component='DataTable'>
94
- <section className={`data-table-container`} aria-label={accessibilityLabel}>
95
- <div
96
- role='button'
97
- className={tableExpanded ? 'data-table-heading' : 'collapsed data-table-heading'}
98
- onClick={() => {
99
- setTableExpanded(!tableExpanded)
100
- }}
101
- tabIndex={0}
102
- onKeyDown={e => {
103
- if (e.keyCode === 13) {
94
+ <CoveMediaControls.Section classes={['download-links']}>
95
+ {config.table.showDownloadUrl && dataFileSourceType === 'url' && (
96
+ <a className='dashboard-download-link' href={config.datasets[datasetKey].dataFileName} title='Link to View Dataset' target='_blank'>
97
+ Link to View Dataset
98
+ </a>
99
+ )}
100
+ {config.table.download && <DownloadButton data={data} />}
101
+ </CoveMediaControls.Section>
102
+ {config.table.show && (
103
+ <section className={`data-table-container`} aria-label={accessibilityLabel}>
104
+ <div
105
+ role='button'
106
+ className={tableExpanded ? 'data-table-heading' : 'collapsed data-table-heading'}
107
+ onClick={() => {
104
108
  setTableExpanded(!tableExpanded)
105
- }
106
- }}
107
- >
108
- {config.table.label}
109
- {datasetKey ? `: ${datasetKey}` : ''}
110
- </div>
111
- <div className='table-container' style={{ maxHeight: config.table && config.table.limitHeight && `${config.table.height}px`, overflowY: 'scroll' }}>
112
- <table className={tableExpanded ? 'data-table' : 'data-table cdcdataviz-sr-only'} hidden={!tableExpanded} {...getTableProps()}>
113
- <caption className='visually-hidden'>{config.table.label}</caption>
114
- <thead>
115
- {headerGroups &&
116
- headerGroups.map(headerGroup => (
117
- <tr {...headerGroup.getHeaderGroupProps()}>
118
- {headerGroup.headers.map(column => (
119
- <th tabIndex='0' {...column.getHeaderProps(column.getSortByToggleProps())} className={column.isSorted ? (column.isSortedDesc ? 'sort sort-desc' : 'sort sort-asc') : 'sort'} title={column.Header}>
120
- {column.render('Header')}
121
- <div {...column.getResizerProps()} className='resizer' />
122
- </th>
123
- ))}
124
- </tr>
125
- ))}
126
- </thead>
127
- {rows && (
128
- <tbody {...getTableBodyProps()}>
129
- {rows.map(row => {
130
- prepareRow(row)
131
- return (
132
- <tr {...row.getRowProps()}>
133
- {row.cells &&
134
- row.cells.map(cell => (
135
- <td tabIndex='0' {...cell.getCellProps()}>
136
- {cell.render('Cell')}
137
- </td>
138
- ))}
109
+ }}
110
+ tabIndex={0}
111
+ onKeyDown={e => {
112
+ if (e.keyCode === 13) {
113
+ setTableExpanded(!tableExpanded)
114
+ }
115
+ }}
116
+ >
117
+ {config.table.label}
118
+ {datasetKey ? `: ${datasetKey}` : ''}
119
+ </div>
120
+ <div className='table-container' style={{ maxHeight: config.table && config.table.limitHeight && `${config.table.height}px`, overflowY: 'scroll' }}>
121
+ <table className={tableExpanded ? 'data-table' : 'data-table cdcdataviz-sr-only'} hidden={!tableExpanded} {...getTableProps()}>
122
+ <caption className='visually-hidden'>{config.table.label}</caption>
123
+ <thead>
124
+ {headerGroups &&
125
+ headerGroups.map(headerGroup => (
126
+ <tr {...headerGroup.getHeaderGroupProps()}>
127
+ {headerGroup.headers.map(column => (
128
+ <th tabIndex='0' {...column.getHeaderProps(column.getSortByToggleProps())} className={column.isSorted ? (column.isSortedDesc ? 'sort sort-desc' : 'sort sort-asc') : 'sort'} title={column.Header}>
129
+ {column.render('Header')}
130
+ <div {...column.getResizerProps()} className='resizer' />
131
+ </th>
132
+ ))}
139
133
  </tr>
140
- )
141
- })}
142
- </tbody>
143
- )}
144
- </table>
145
- </div>
146
- {config.table.download && <DownloadButton data={data} />}
147
- </section>
134
+ ))}
135
+ </thead>
136
+ {rows && (
137
+ <tbody {...getTableBodyProps()}>
138
+ {rows.map(row => {
139
+ prepareRow(row)
140
+ return (
141
+ <tr {...row.getRowProps()}>
142
+ {row.cells &&
143
+ row.cells.map(cell => (
144
+ <td tabIndex='0' {...cell.getCellProps()}>
145
+ {cell.render('Cell')}
146
+ </td>
147
+ ))}
148
+ </tr>
149
+ )
150
+ })}
151
+ </tbody>
152
+ )}
153
+ </table>
154
+ </div>
155
+ </section>
156
+ )}
148
157
  </ErrorBoundary>
149
158
  )
150
159
  }
@@ -257,7 +257,7 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
257
257
  </div>
258
258
  )}
259
259
  {!subEditor && (
260
- <div>
260
+ <div className="toggle-bar__wrapper">
261
261
  <ul className='toggle-bar'>
262
262
  <li
263
263
  className={tabSelected === 0 ? 'active' : 'inactive'}
@@ -316,21 +316,55 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
316
316
  )}
317
317
  {tabSelected === 2 && (
318
318
  <>
319
- <label>Show Table</label>
320
- <input type='checkbox' defaultChecked={config.table.show} onChange={e => changeConfigValue('table', 'show', e.target.checked)} />
321
- <label>Expanded by Default</label>
322
- <input type='checkbox' defaultChecked={config.table.expanded} onChange={e => changeConfigValue('table', 'expanded', e.target.checked)} />
323
- <label>Display Download Button</label>
324
- <input type='checkbox' defaultChecked={config.table.download} onChange={e => changeConfigValue('table', 'download', e.target.checked)} />
325
- <label>Limit Table Height</label>
326
- <input type='checkbox' defaultChecked={config.table.limitHeight} onChange={e => changeConfigValue('table', 'limitHeight', e.target.checked)} />
327
- {config.table.limitHeight && <input class='table-height-input' type='text' placeholder='Height (px)' defaultValue={config.table.height} onChange={e => changeConfigValue('table', 'height', e.target.value)} />}
319
+
320
+ <div className="wrap">
321
+ <label>
322
+ <input type='checkbox' defaultChecked={config.table.show} onChange={e => changeConfigValue('table', 'show', e.target.checked)} />
323
+ Show Table
324
+ </label><br />
325
+
326
+ <label>
327
+ <input type='checkbox' defaultChecked={config.table.expanded} onChange={e => changeConfigValue('table', 'expanded', e.target.checked)} />
328
+ Expanded by Default
329
+ </label><br />
330
+ </div>
331
+
332
+ {/* <div className="wrap">
333
+ <label>
334
+ <input type='checkbox' defaultChecked={config.table.downloadPdfButton} onChange={e => changeConfigValue('table', 'downloadPdfButton', e.target.checked)} />
335
+ Show PDF Button
336
+ </label>
337
+ <label>
338
+ <input type='checkbox' defaultChecked={config.table.downloadImageButton} onChange={e => changeConfigValue('table', 'downloadImageButton', e.target.checked)} />
339
+ Show Image Button
340
+ </label>
341
+ </div> */}
342
+
343
+ <div className="wrap">
344
+ <label>
345
+ <input type='checkbox' defaultChecked={config.table.limitHeight} onChange={e => changeConfigValue('table', 'limitHeight', e.target.checked)} />
346
+ Limit Table Height
347
+ </label>
348
+ {config.table.limitHeight && <input class='table-height-input' type='text' placeholder='Height (px)' defaultValue={config.table.height} onChange={e => changeConfigValue('table', 'height', e.target.value)} />}
349
+ </div>
350
+
351
+ <div className="wrap">
352
+ <label>
353
+ <input type='checkbox' defaultChecked={config.table.download} onChange={e => changeConfigValue('table', 'download', e.target.checked)} />
354
+ Show CSV Button
355
+ </label>
356
+ <label>
357
+ <input type='checkbox' defaultChecked={config.table.showDownloadUrl} onChange={e => changeConfigValue('table', 'showDownloadUrl', e.target.checked)} />
358
+ Show Link to Dataset
359
+ </label>
360
+ </div>
328
361
  </>
329
362
  )}
330
363
  </div>
331
- </div>
332
- )}
333
- </div>
364
+ </div >
365
+ )
366
+ }
367
+ </div >
334
368
  )
335
369
  }
336
370
 
@@ -6,6 +6,7 @@ export default {
6
6
  visualizations: {},
7
7
  table: {
8
8
  label: 'Data Table',
9
- show: true
9
+ show: true,
10
+ showDownloadUrl: false
10
11
  }
11
12
  }
package/src/index.html CHANGED
@@ -1,29 +1,31 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
6
- <style>
7
- body {
8
- margin: 0 auto !important;
9
- display: flex;
10
- flex-direction: column;
11
- justify-content: center;
12
- min-height: unset !important;
13
- }
14
3
 
15
- .react-container + .react-container {
16
- margin-top: 3rem;
17
- }
18
- </style>
19
- </head>
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
7
+ <style>
8
+ body {
9
+ margin: 0 auto !important;
10
+ display: flex;
11
+ flex-direction: column;
12
+ justify-content: center;
13
+ min-height: unset !important;
14
+ }
15
+
16
+ .react-container+.react-container {
17
+ margin-top: 3rem;
18
+ }
19
+ </style>
20
+ </head>
21
+
22
+ <body>
23
+ <!-- <div class="react-container" data-config="/examples/default.json"></div> -->
24
+ <!-- <div class="react-container" data-config="/examples/default-multi-dataset.json"></div> -->
25
+ <div class="react-container" data-config="/examples/default-filter-control.json"></div>
26
+ <!-- <div class="react-container" data-config="/examples/private/totals.json"></div> -->
27
+ <!-- <div class="react-container" data-config="/examples/private/totals-two.json"></div> -->
28
+ <noscript>You need to enable JavaScript to run this app.</noscript>
29
+ </body>
20
30
 
21
- <body>
22
- <div class="react-container" data-config="/examples/default.json"></div>
23
- <!--<div class="react-container" data-config="/examples/default-multi-dataset.json"></div>-->
24
- <!-- <div class="react-container" data-config="/examples/default-filter-control.json"></div> -->
25
- <!-- <div class="react-container" data-config="/examples/private/totals.json"></div> -->
26
- <!-- <div class="react-container" data-config="/examples/private/totals-two.json"></div> -->
27
- <noscript>You need to enable JavaScript to run this app.</noscript>
28
- </body>
29
31
  </html>
@@ -24,6 +24,7 @@
24
24
  height: 3em;
25
25
  }
26
26
  .heading-1 {
27
+ min-width: 335px;
27
28
  font-size: 1.2em;
28
29
  padding-right: 1em;
29
30
  margin: 0.5em 1em 0.5em 0;
@@ -35,13 +36,40 @@
35
36
  padding-right: 0.3em;
36
37
  }
37
38
  }
39
+ input[type='text'] {
40
+ width: 100%;
41
+ margin-top: 10px;
42
+ }
43
+ }
44
+
45
+ .heading-body {
46
+ display: flex;
47
+
48
+ .wrap {
49
+ display: inline-flex;
50
+ flex-wrap: wrap;
51
+ width: 20%;
52
+
53
+ .table-height-input {
54
+ width: 100%;
55
+ height: 25px;
56
+ }
57
+
58
+ label {
59
+ display: block;
60
+ width: 100%;
61
+ }
62
+ }
63
+ }
64
+
65
+ .heading-2 {
38
66
  }
39
67
 
40
68
  .heading-body {
41
69
  padding: 0.5em;
42
70
 
43
71
  input[type='checkbox'] {
44
- margin-right: 2em;
72
+ margin-right: 5px;
45
73
  margin-left: 0.5em;
46
74
  }
47
75
 
@@ -57,17 +85,18 @@
57
85
  align-self: stretch;
58
86
  position: relative;
59
87
  bottom: -1px;
88
+ border-bottom: #c7c7c7 1px solid;
60
89
  li {
61
90
  padding: 0.3em 1.5em;
62
91
  color: $mediumGray;
63
- border-bottom: #c7c7c7 1px solid;
64
92
  cursor: pointer;
65
93
  white-space: nowrap;
66
94
  text-overflow: ellipsis;
67
95
  overflow: hidden;
68
96
  &.active {
69
- border-bottom-width: 5px;
70
97
  border-bottom-color: $mediumGray;
98
+ border-bottom-style: inherit;
99
+ border-bottom-width: 5px;
71
100
  color: $darkGray;
72
101
  }
73
102
  &:hover {
@@ -75,6 +104,10 @@
75
104
  color: $darkGray;
76
105
  }
77
106
  }
107
+
108
+ &__wrapper {
109
+ width: 100%;
110
+ }
78
111
  }
79
112
 
80
113
  .description-input {
@@ -137,6 +170,17 @@
137
170
  }
138
171
  }
139
172
 
173
+ .multi-table-container {
174
+ margin: 15px 0 0;
175
+ }
176
+
177
+ .data-table-container {
178
+ margin: 0;
179
+ @include breakpointClass(md) {
180
+ margin: 15px 0;
181
+ }
182
+ }
183
+
140
184
  div.dashboard-title {
141
185
  color: white;
142
186
  padding: 0.6em 0.8em;
@@ -160,6 +204,10 @@
160
204
  }
161
205
  }
162
206
 
207
+ .dashboard-download-link {
208
+ font-size: 14px;
209
+ }
210
+
163
211
  .dashboard-description {
164
212
  margin-bottom: 20px;
165
213
  }