@cdc/data-bite 1.1.3 → 9.22.9
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/dist/cdcdatabite.js +8 -8
- package/examples/gallery/calculated-average.json +3167 -0
- package/examples/gallery/calculated-with-pic.json +3173 -0
- package/examples/gallery/max-value.json +3167 -0
- package/examples/gallery/sum-of-data.json +3161 -0
- package/examples/private/double.json +0 -0
- package/examples/private/totals.json +1 -0
- package/package.json +3 -3
- package/src/CdcDataBite.tsx +66 -81
- package/src/components/EditorPanel.js +547 -166
- package/src/index.html +11 -2
- package/src/scss/bite.scss +99 -37
- package/src/scss/editor-panel.scss +0 -1
- package/src/scss/main.scss +2 -2
- package/LICENSE +0 -201
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"dashboard":{"theme":"theme-blue","title":"Total Vaccine Doses Administered","description":"Total vaccine doses administered data are updated every Wednesday as soon as they are reviewed and verified.\n<br>\nInformation about the number of vials shipped is posted on <a href=\"https://aspr.hhs.gov/SNS/Pages/JYNNEOS-Distribution.aspx\">https://aspr.hhs.gov/SNS/Pages/JYNNEOS-Distribution.aspx</a>, and is updated every Monday, Wednesday, and Friday.\n"},"rows":[[{"width":12,"widget":"data-bite1661201913029"},{},{}]],"visualizations":{"data-bite1661201911110":{"type":"data-bite","data":[{"Vaccines Distributed":null,"Vaccines Administered":803596,"Doses Shipped Date":null,"AsOf":" Data as of September 27 2022 4:00 AM EDT"}],"dataBite":"","dataFunction":"Sum","dataColumn":"Vaccines Distributed","bitePosition":"Left","biteFontSize":24,"fontSize":"small","biteBody":"<br><span class=\"h5\">Vials Shipped to all U.S. Jurisdictions</span>","imageData":{"display":"none","url":"","alt":"","options":[]},"dataFormat":{"roundToPlace":0,"commas":true,"prefix":"","suffix":""},"biteStyle":"body","filters":[],"subtext":"","title":"","theme":"theme-blue","shadow":false,"newViz":true,"uid":"data-bite1661201911110","visualizationType":"data-bite"},"data-bite1661201913029":{"type":"data-bite","data":[{"Vaccines Distributed":null,"Vaccines Administered":803596,"Doses Shipped Date":null,"AsOf":" Data as of September 27 2022 4:00 AM EDT"}],"dataBite":"","dataFunction":"Sum","dataColumn":"Vaccines Administered","bitePosition":"Left","biteFontSize":24,"fontSize":"small","biteBody":"<br><span class=\"h5\">Doses Administered in the 54 U.S. Jurisdictions Reporting Data<span id=\"administered_date\"> as of October 4, 2022.","imageData":{"display":"none","url":"","alt":"","options":[]},"dataFormat":{"roundToPlace":0,"commas":true,"prefix":"","suffix":""},"biteStyle":"body","filters":[],"subtext":"","title":"","theme":"theme-blue","shadow":false,"visual":{"border":false,"accent":false,"background":false,"hideBackgroundColor":false,"borderColorTheme":false},"general":{"isCompactStyle":false},"newViz":true,"uid":"data-bite1661201913029","visualizationType":"data-bite","editing":true}},"table":{"label":"Data Table","show":false},"data":[{"Vaccines Distributed":null,"Vaccines Administered":803596,"Doses Shipped Date":null,"AsOf":" Data as of September 27 2022 4:00 AM EDT"}],"dataFileName":"https://www.cdc.gov/wcms/vizdata/poxvirus/monkeypox/data/vaccines/mpx_vaccine_databites.csv","dataFileSourceType":"url","dataUrl":"https://www.cdc.gov/wcms/vizdata/poxvirus/monkeypox/data/vaccines/mpx_vaccine_databites.csv","type":"dashboard","orientation":null,"visualizationSubType":null,"runtime":{},"uuid":1661454522321}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/data-bite",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.22.9",
|
|
4
4
|
"description": "React component for displaying a single piece of data in a card module",
|
|
5
5
|
"main": "dist/cdcdatabite",
|
|
6
6
|
"scripts": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"license": "Apache-2.0",
|
|
21
21
|
"homepage": "https://github.com/CDCgov/cdc-open-viz#readme",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@cdc/core": "^
|
|
23
|
+
"@cdc/core": "^9.22.9",
|
|
24
24
|
"chroma": "0.0.1",
|
|
25
25
|
"chroma-js": "^2.1.0",
|
|
26
26
|
"html-react-parser": "1.4.9",
|
|
@@ -39,5 +39,5 @@
|
|
|
39
39
|
"resolutions": {
|
|
40
40
|
"@types/react": "17.x"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "90faf22c91ca0062432607e4599598f9e67c848a"
|
|
43
43
|
}
|
package/src/CdcDataBite.tsx
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import React, { useEffect, useState, useCallback,FC } from 'react';
|
|
1
|
+
import React, { useEffect, useState, useCallback,FC, memo } from 'react';
|
|
2
2
|
import EditorPanel from './components/EditorPanel';
|
|
3
3
|
import defaults from './data/initial-state';
|
|
4
4
|
import Loading from '@cdc/core/components/Loading';
|
|
5
5
|
import getViewport from '@cdc/core/helpers/getViewport';
|
|
6
6
|
import ResizeObserver from 'resize-observer-polyfill';
|
|
7
|
-
import Papa from 'papaparse';
|
|
8
7
|
import parse from 'html-react-parser';
|
|
9
8
|
|
|
10
9
|
import Context from './context';
|
|
11
|
-
|
|
12
|
-
import DataTransform from '@cdc/core/components/DataTransform';
|
|
10
|
+
import { DataTransform } from '@cdc/core/helpers/DataTransform';
|
|
13
11
|
import CircleCallout from './components/CircleCallout';
|
|
14
12
|
import './scss/main.scss';
|
|
15
13
|
import numberFromString from '@cdc/core/helpers/numberFromString';
|
|
14
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData';
|
|
16
15
|
import { Fragment } from 'react';
|
|
17
16
|
|
|
18
17
|
import { publish } from '@cdc/core/helpers/events'
|
|
@@ -25,10 +24,11 @@ interface Props{
|
|
|
25
24
|
isDashboard?: boolean
|
|
26
25
|
isEditor?: boolean
|
|
27
26
|
setConfig?:any
|
|
27
|
+
link?:any
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const CdcDataBite:FC<Props> = (props) => {
|
|
31
|
-
const { configUrl, config: configObj, isDashboard = false, isEditor = false, setConfig: setParentConfig } = props
|
|
31
|
+
const { configUrl, config: configObj, isDashboard = false, isEditor = false, setConfig: setParentConfig,link } = props
|
|
32
32
|
|
|
33
33
|
const [config, setConfig] = useState<DefaultsType>({...defaults});
|
|
34
34
|
const [loading, setLoading] = useState<Boolean>(true);
|
|
@@ -48,7 +48,6 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
48
48
|
} = config;
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
|
|
52
51
|
const transform = new DataTransform()
|
|
53
52
|
|
|
54
53
|
const [currentViewport, setCurrentViewport] = useState<String>('lg');
|
|
@@ -65,44 +64,6 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
65
64
|
}
|
|
66
65
|
});
|
|
67
66
|
|
|
68
|
-
const fetchRemoteData = async (url) => {
|
|
69
|
-
try {
|
|
70
|
-
const urlObj = new URL(url);
|
|
71
|
-
const regex = /(?:\.([^.]+))?$/
|
|
72
|
-
|
|
73
|
-
let data = []
|
|
74
|
-
|
|
75
|
-
const ext = (regex.exec(urlObj.pathname)[1])
|
|
76
|
-
if ('csv' === ext) {
|
|
77
|
-
data = await fetch(url)
|
|
78
|
-
.then(response => response.text())
|
|
79
|
-
.then(responseText => {
|
|
80
|
-
const parsedCsv = Papa.parse(responseText, {
|
|
81
|
-
header: true,
|
|
82
|
-
dynamicTyping: true,
|
|
83
|
-
skipEmptyLines: true
|
|
84
|
-
})
|
|
85
|
-
return parsedCsv.data
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if ('json' === ext) {
|
|
90
|
-
data = await fetch(url)
|
|
91
|
-
.then(response => response.json())
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return data;
|
|
95
|
-
} catch {
|
|
96
|
-
// If we can't parse it, still attempt to fetch it
|
|
97
|
-
try {
|
|
98
|
-
let response = await (await fetch(configUrl)).json()
|
|
99
|
-
return response
|
|
100
|
-
} catch {
|
|
101
|
-
console.error(`Cannot parse URL: ${url}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
67
|
const updateConfig = (newConfig) => {
|
|
107
68
|
// Deeper copy
|
|
108
69
|
Object.keys(defaults).forEach(key => {
|
|
@@ -133,6 +94,7 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
133
94
|
if (response.dataUrl) {
|
|
134
95
|
response.dataUrl = `${response.dataUrl}?${cacheBustingString}`;
|
|
135
96
|
let newData = await fetchRemoteData(response.dataUrl)
|
|
97
|
+
|
|
136
98
|
|
|
137
99
|
if (newData && response.dataDescription) {
|
|
138
100
|
newData = transform.autoStandardize(newData);
|
|
@@ -151,11 +113,11 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
151
113
|
setLoading(false);
|
|
152
114
|
}
|
|
153
115
|
|
|
154
|
-
const calculateDataBite = ():string|number => {
|
|
155
|
-
|
|
116
|
+
const calculateDataBite = (includePrefixSuffix:boolean = true):string|number => {
|
|
117
|
+
|
|
156
118
|
//If either the column or function aren't set, do not calculate
|
|
157
119
|
if (!dataColumn || !dataFunction) {
|
|
158
|
-
return '';
|
|
120
|
+
return '';
|
|
159
121
|
}
|
|
160
122
|
|
|
161
123
|
|
|
@@ -164,8 +126,8 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
164
126
|
if(value === undefined || value===null){
|
|
165
127
|
console.error('Enter correct value to "applyPrecision()" function ')
|
|
166
128
|
return ;
|
|
167
|
-
}
|
|
168
|
-
// second validation
|
|
129
|
+
}
|
|
130
|
+
// second validation
|
|
169
131
|
if(Number.isNaN(value)){
|
|
170
132
|
console.error(' Argunment isNaN, "applyPrecision()" function ')
|
|
171
133
|
return;
|
|
@@ -218,9 +180,13 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
218
180
|
// first validation
|
|
219
181
|
if(arr===undefined || arr===null ||!Array.isArray(arr)){
|
|
220
182
|
console.error('Enter valid parameter getColumnMean function')
|
|
221
|
-
return
|
|
183
|
+
return
|
|
222
184
|
}
|
|
223
|
-
|
|
185
|
+
|
|
186
|
+
if(config.dataFormat.ignoreZeros) {
|
|
187
|
+
arr = arr.filter( num => num !== 0 )
|
|
188
|
+
}
|
|
189
|
+
|
|
224
190
|
let mean:number = 0
|
|
225
191
|
if(arr.length > 1){
|
|
226
192
|
/// first convert each element to number then add using reduce method to escape string concatination.
|
|
@@ -284,13 +250,9 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
284
250
|
}
|
|
285
251
|
return formattedValue
|
|
286
252
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
253
|
|
|
292
254
|
let dataBite:string|number = '';
|
|
293
|
-
|
|
255
|
+
|
|
294
256
|
//Optionally filter the data based on the user's filter
|
|
295
257
|
let filteredData = config.data;
|
|
296
258
|
|
|
@@ -304,18 +266,17 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
304
266
|
}
|
|
305
267
|
});
|
|
306
268
|
|
|
307
|
-
let numericalData:any[] = []
|
|
308
|
-
|
|
269
|
+
let numericalData:any[] = [];
|
|
270
|
+
// Get the column's data
|
|
271
|
+
if(filteredData.length){
|
|
272
|
+
filteredData.forEach(row => {
|
|
273
|
+
let value = numberFromString(row[dataColumn])
|
|
274
|
+
if(typeof value === 'number') numericalData.push(value)
|
|
275
|
+
});
|
|
276
|
+
} else {
|
|
277
|
+
numericalData = config.data.map( item => Number( item[config.dataColumn] ));
|
|
278
|
+
}
|
|
309
279
|
|
|
310
|
-
// Get the column's data
|
|
311
|
-
if(filteredData.length){
|
|
312
|
-
filteredData.forEach(row => {
|
|
313
|
-
let value = numberFromString(row[dataColumn])
|
|
314
|
-
if(typeof value === 'number') numericalData.push(value)
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
|
|
319
280
|
|
|
320
281
|
switch (dataFunction) {
|
|
321
282
|
case DATA_FUNCTION_COUNT:
|
|
@@ -348,7 +309,7 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
348
309
|
rangeMin = applyLocaleString(rangeMin)
|
|
349
310
|
rangeMax = applyLocaleString(rangeMax)
|
|
350
311
|
}
|
|
351
|
-
dataBite = config.dataFormat.prefix + rangeMin + config.dataFormat.suffix + ' - ' + config.dataFormat.prefix + rangeMax+config.dataFormat.suffix;
|
|
312
|
+
dataBite = config.dataFormat.prefix + rangeMin + config.dataFormat.suffix + ' - ' + config.dataFormat.prefix + rangeMax+config.dataFormat.suffix;
|
|
352
313
|
break;
|
|
353
314
|
default:
|
|
354
315
|
console.warn('Data bite function not recognized: ' + dataFunction);
|
|
@@ -357,18 +318,18 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
357
318
|
// If not the range, then round and format here
|
|
358
319
|
if (dataFunction !== DATA_FUNCTION_RANGE) {
|
|
359
320
|
dataBite = applyPrecision(dataBite);
|
|
360
|
-
|
|
321
|
+
|
|
361
322
|
if (config.dataFormat.commas) {
|
|
362
323
|
dataBite = applyLocaleString(dataBite)
|
|
363
324
|
}
|
|
364
|
-
// Optional
|
|
325
|
+
// Optional
|
|
365
326
|
// return config.dataFormat.prefix + dataBite + config.dataFormat.suffix;
|
|
366
327
|
|
|
367
|
-
return dataFormat.prefix + dataBite + dataFormat.suffix
|
|
328
|
+
return includePrefixSuffix ? (dataFormat.prefix + dataBite + dataFormat.suffix) : dataBite
|
|
368
329
|
} else {
|
|
369
330
|
//Rounding and formatting for ranges happens earlier.
|
|
370
331
|
|
|
371
|
-
return dataFormat.prefix + dataBite + dataFormat.suffix
|
|
332
|
+
return includePrefixSuffix ? (dataFormat.prefix + dataBite + dataFormat.suffix) : dataBite
|
|
372
333
|
}
|
|
373
334
|
}
|
|
374
335
|
|
|
@@ -411,7 +372,7 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
411
372
|
}
|
|
412
373
|
}, [config, container]);
|
|
413
374
|
|
|
414
|
-
if(configObj && config && configObj.data !== config.data){
|
|
375
|
+
if(configObj && config && JSON.stringify(configObj.data) !== JSON.stringify(config.data)){
|
|
415
376
|
loadConfig();
|
|
416
377
|
}
|
|
417
378
|
|
|
@@ -430,7 +391,7 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
430
391
|
let imageAlt = imageData.alt
|
|
431
392
|
|
|
432
393
|
if ('dynamic' === imageData.display && imageData.options && imageData.options?.length > 0) {
|
|
433
|
-
let targetVal = Number(calculateDataBite())
|
|
394
|
+
let targetVal = Number(calculateDataBite(false))
|
|
434
395
|
let argumentActive = false
|
|
435
396
|
|
|
436
397
|
imageData.options.forEach((option, index) => {
|
|
@@ -487,7 +448,23 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
487
448
|
break;
|
|
488
449
|
}
|
|
489
450
|
|
|
490
|
-
|
|
451
|
+
|
|
452
|
+
let innerContainerClasses = ['cove-component__inner']
|
|
453
|
+
config.title && innerContainerClasses.push('component--has-title')
|
|
454
|
+
config.subtext && innerContainerClasses.push('component--has-subtext')
|
|
455
|
+
config.biteStyle && innerContainerClasses.push(`bite__style--${config.biteStyle}`)
|
|
456
|
+
config.general.isCompactStyle && innerContainerClasses.push(`component--isCompactStyle`)
|
|
457
|
+
|
|
458
|
+
let contentClasses = ['cove-component__content'];
|
|
459
|
+
!config.visual.border && contentClasses.push('no-borders');
|
|
460
|
+
config.visual.borderColorTheme && contentClasses.push('component--has-borderColorTheme');
|
|
461
|
+
config.visual.accent && contentClasses.push('component--has-accent');
|
|
462
|
+
config.visual.background && contentClasses.push('component--has-background');
|
|
463
|
+
config.visual.hideBackgroundColor && contentClasses.push('component--hideBackgroundColor');
|
|
464
|
+
|
|
465
|
+
// ! these two will be retired.
|
|
466
|
+
config.shadow && innerContainerClasses.push('shadow')
|
|
467
|
+
config?.visual?.roundedBorders && innerContainerClasses.push('bite--has-rounded-borders')
|
|
491
468
|
|
|
492
469
|
const showBite = undefined !== dataColumn && undefined !== dataFunction;
|
|
493
470
|
|
|
@@ -495,20 +472,24 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
495
472
|
<>
|
|
496
473
|
{isEditor && <EditorPanel />}
|
|
497
474
|
<div className={isEditor ? 'spacing-wrapper' : ''}>
|
|
498
|
-
<div className=
|
|
499
|
-
{title && <div className={`bite-header component__header ${config.theme}`}>{parse(title)}</div>}
|
|
500
|
-
<div className={`bite ${biteClasses.join(' ')}
|
|
501
|
-
<div className={`bite-content-container ${
|
|
475
|
+
<div className={innerContainerClasses.join(' ')}>
|
|
476
|
+
{title && <div className={`bite-header cove-component__header component__header ${config.theme}`}>{parse(title)}</div>}
|
|
477
|
+
<div className={`bite ${biteClasses.join(' ')}`}>
|
|
478
|
+
<div className={`bite-content-container ${contentClasses.join(' ')}` }>
|
|
502
479
|
{showBite && 'graphic' === biteStyle && isTop && <CircleCallout theme={config.theme} text={calculateDataBite()} biteFontSize={biteFontSize} dataFormat={dataFormat} /> }
|
|
503
480
|
{isTop && <DataImage />}
|
|
504
481
|
<div className={`bite-content`}>
|
|
505
|
-
{showBite && 'title' === biteStyle && <div className="bite-value
|
|
482
|
+
{showBite && 'title' === biteStyle && <div className="bite-value" style={{fontSize: biteFontSize + 'px'}}>{calculateDataBite()}</div>}
|
|
483
|
+
{showBite && 'split' === biteStyle && <div className="bite-value" style={{fontSize: biteFontSize + 'px'}}>{calculateDataBite()}</div>}
|
|
506
484
|
<Fragment>
|
|
485
|
+
<div className="bite-content__text-wrap">
|
|
507
486
|
<p className="bite-text">
|
|
508
487
|
{showBite && 'body' === biteStyle && <span className="bite-value data-bite-body" style={{fontSize: biteFontSize + 'px'}}>{calculateDataBite()}</span>}
|
|
509
488
|
{parse(biteBody)}
|
|
510
489
|
</p>
|
|
490
|
+
{showBite && 'end' === biteStyle && <span className="bite-value data-bite-body" style={{fontSize: biteFontSize + 'px'}}>{calculateDataBite()}</span>}
|
|
511
491
|
{subtext && !isCompactStyle && <p className="bite-subtext">{parse(subtext)}</p>}
|
|
492
|
+
</div>
|
|
512
493
|
</Fragment>
|
|
513
494
|
</div>
|
|
514
495
|
{isBottom && <DataImage />}
|
|
@@ -516,12 +497,14 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
|
|
|
516
497
|
</div>
|
|
517
498
|
</div>
|
|
518
499
|
</div>
|
|
500
|
+
{link && link}
|
|
519
501
|
</div>
|
|
520
502
|
</>
|
|
521
503
|
)
|
|
522
504
|
}
|
|
523
505
|
|
|
524
506
|
let classNames = [
|
|
507
|
+
'cove',
|
|
525
508
|
'cdc-open-viz-module',
|
|
526
509
|
'type-data-bite',
|
|
527
510
|
currentViewport,
|
|
@@ -568,8 +551,10 @@ export const BITE_LOCATION_BODY = 'body';
|
|
|
568
551
|
export const BITE_LOCATION_GRAPHIC = 'graphic';
|
|
569
552
|
export const BITE_LOCATIONS = {
|
|
570
553
|
'graphic': 'Graphic',
|
|
554
|
+
'split': 'Split Graphic and Message',
|
|
571
555
|
'title': 'Value above Message',
|
|
572
|
-
'body': 'Value before Message'
|
|
556
|
+
'body': 'Value before Message',
|
|
557
|
+
'end': 'Value after Message'
|
|
573
558
|
};
|
|
574
559
|
|
|
575
560
|
export const IMAGE_POSITION_LEFT = 'Left';
|