@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.
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": "1.1.3",
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": "^1.1.3",
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": "ff89a7aea74c533413c62ef8859cc011e6b3cbfa"
42
+ "gitHead": "90faf22c91ca0062432607e4599598f9e67c848a"
43
43
  }
@@ -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
- // @ts-ignore
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
- if(config.shadow) biteClasses.push('shadow')
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="cdc-data-bite-inner-container">
499
- {title && <div className={`bite-header component__header ${config.theme}`}>{parse(title)}</div>}
500
- <div className={`bite ${biteClasses.join(' ')} ${contentClasses.join(' ')}`}>
501
- <div className={`bite-content-container ${innerContainerClasses.join(' ')}`}>
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 cove-component__header" style={{fontSize: biteFontSize + 'px'}}>{calculateDataBite()}</div>}
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';