@cdc/data-bite 1.1.4 → 4.22.10

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.4",
3
+ "version": "4.22.10",
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.4",
23
+ "@cdc/core": "^4.22.10",
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": "a7eb551a98c7363d3be58cb81dfc8bbc00522804"
43
43
  }
@@ -1,21 +1,22 @@
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'
18
+ import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses';
19
+ import cacheBustingString from '@cdc/core/helpers/cacheBustingString';
19
20
 
20
21
 
21
22
  type DefaultsType = typeof defaults
@@ -25,10 +26,11 @@ interface Props{
25
26
  isDashboard?: boolean
26
27
  isEditor?: boolean
27
28
  setConfig?:any
29
+ link?:any
28
30
  }
29
31
 
30
32
  const CdcDataBite:FC<Props> = (props) => {
31
- const { configUrl, config: configObj, isDashboard = false, isEditor = false, setConfig: setParentConfig } = props
33
+ const { configUrl, config: configObj, isDashboard = false, isEditor = false, setConfig: setParentConfig,link } = props
32
34
 
33
35
  const [config, setConfig] = useState<DefaultsType>({...defaults});
34
36
  const [loading, setLoading] = useState<Boolean>(true);
@@ -47,6 +49,7 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
47
49
  general: { isCompactStyle }
48
50
  } = config;
49
51
 
52
+ const { innerContainerClasses, contentClasses } = useDataVizClasses(config);
50
53
 
51
54
 
52
55
  const transform = new DataTransform()
@@ -65,44 +68,6 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
65
68
  }
66
69
  });
67
70
 
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
71
  const updateConfig = (newConfig) => {
107
72
  // Deeper copy
108
73
  Object.keys(defaults).forEach(key => {
@@ -123,15 +88,14 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
123
88
  const loadConfig = async () => {
124
89
  let response = configObj || await (await fetch(configUrl)).json();
125
90
 
126
- const round = 1000 * 60 * 15;
127
- const date = new Date();
128
- let cacheBustingString = new Date(date.getTime() - (date.getTime() % round)).toISOString();
91
+
129
92
 
130
93
  // If data is included through a URL, fetch that and store
131
94
  let responseData = response.data ?? {}
132
95
 
133
96
  if (response.dataUrl) {
134
- response.dataUrl = `${response.dataUrl}?${cacheBustingString}`;
97
+
98
+ response.dataUrl = `${response.dataUrl}?${cacheBustingString()}`;
135
99
  let newData = await fetchRemoteData(response.dataUrl)
136
100
 
137
101
  if (newData && response.dataDescription) {
@@ -151,11 +115,11 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
151
115
  setLoading(false);
152
116
  }
153
117
 
154
- const calculateDataBite = ():string|number => {
155
-
118
+ const calculateDataBite = (includePrefixSuffix:boolean = true):string|number => {
119
+
156
120
  //If either the column or function aren't set, do not calculate
157
121
  if (!dataColumn || !dataFunction) {
158
- return '';
122
+ return '';
159
123
  }
160
124
 
161
125
 
@@ -164,8 +128,8 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
164
128
  if(value === undefined || value===null){
165
129
  console.error('Enter correct value to "applyPrecision()" function ')
166
130
  return ;
167
- }
168
- // second validation
131
+ }
132
+ // second validation
169
133
  if(Number.isNaN(value)){
170
134
  console.error(' Argunment isNaN, "applyPrecision()" function ')
171
135
  return;
@@ -200,6 +164,7 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
200
164
  return;
201
165
  }
202
166
  // second validation
167
+ console.log('arr', arr)
203
168
  if(arr.length === 0 || !Array.isArray(arr)){
204
169
  console.error('Arguments are not valid getColumnSum function ')
205
170
  return;
@@ -218,9 +183,13 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
218
183
  // first validation
219
184
  if(arr===undefined || arr===null ||!Array.isArray(arr)){
220
185
  console.error('Enter valid parameter getColumnMean function')
221
- return
186
+ return
187
+ }
188
+
189
+ if(config.dataFormat.ignoreZeros) {
190
+ arr = arr.filter( num => num !== 0 )
222
191
  }
223
-
192
+
224
193
  let mean:number = 0
225
194
  if(arr.length > 1){
226
195
  /// first convert each element to number then add using reduce method to escape string concatination.
@@ -284,13 +253,9 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
284
253
  }
285
254
  return formattedValue
286
255
  }
287
-
288
-
289
-
290
-
291
256
 
292
257
  let dataBite:string|number = '';
293
-
258
+
294
259
  //Optionally filter the data based on the user's filter
295
260
  let filteredData = config.data;
296
261
 
@@ -304,18 +269,17 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
304
269
  }
305
270
  });
306
271
 
307
- let numericalData:any[] = []
308
-
272
+ let numericalData:any[] = [];
273
+ // Get the column's data
274
+ if(filteredData.length){
275
+ filteredData.forEach(row => {
276
+ let value = numberFromString(row[dataColumn])
277
+ if(typeof value === 'number') numericalData.push(value)
278
+ });
279
+ } else {
280
+ numericalData = config.data.map( item => Number( item[config.dataColumn] ));
281
+ }
309
282
 
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
283
 
320
284
  switch (dataFunction) {
321
285
  case DATA_FUNCTION_COUNT:
@@ -348,7 +312,7 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
348
312
  rangeMin = applyLocaleString(rangeMin)
349
313
  rangeMax = applyLocaleString(rangeMax)
350
314
  }
351
- dataBite = config.dataFormat.prefix + rangeMin + config.dataFormat.suffix + ' - ' + config.dataFormat.prefix + rangeMax+config.dataFormat.suffix;
315
+ dataBite = config.dataFormat.prefix + rangeMin + config.dataFormat.suffix + ' - ' + config.dataFormat.prefix + rangeMax+config.dataFormat.suffix;
352
316
  break;
353
317
  default:
354
318
  console.warn('Data bite function not recognized: ' + dataFunction);
@@ -357,37 +321,22 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
357
321
  // If not the range, then round and format here
358
322
  if (dataFunction !== DATA_FUNCTION_RANGE) {
359
323
  dataBite = applyPrecision(dataBite);
360
-
324
+
361
325
  if (config.dataFormat.commas) {
362
326
  dataBite = applyLocaleString(dataBite)
363
327
  }
364
- // Optional
328
+ // Optional
365
329
  // return config.dataFormat.prefix + dataBite + config.dataFormat.suffix;
366
330
 
367
- return dataFormat.prefix + dataBite + dataFormat.suffix
331
+ return includePrefixSuffix ? (dataFormat.prefix + dataBite + dataFormat.suffix) : dataBite
368
332
  } else {
369
333
  //Rounding and formatting for ranges happens earlier.
370
334
 
371
- return dataFormat.prefix + dataBite + dataFormat.suffix
335
+ return includePrefixSuffix ? (dataFormat.prefix + dataBite + dataFormat.suffix) : dataBite
372
336
  }
373
337
  }
374
338
 
375
- let innerContainerClasses = ['cove-component__inner']
376
- config.title && innerContainerClasses.push('component--has-title')
377
- config.subtext && innerContainerClasses.push('component--has-subtext')
378
- config.biteStyle && innerContainerClasses.push(`bite__style--${config.biteStyle}`)
379
- config.general?.isCompactStyle && innerContainerClasses.push(`component--isCompactStyle`)
380
-
381
- let contentClasses = ['cove-component__content'];
382
- !config.visual?.border && contentClasses.push('no-borders');
383
- config.visual?.borderColorTheme && contentClasses.push('component--has-borderColorTheme');
384
- config.visual?.accent && contentClasses.push('component--has-accent');
385
- config.visual?.background && contentClasses.push('component--has-background');
386
- config.visual?.hideBackgroundColor && contentClasses.push('component--hideBackgroundColor');
387
339
 
388
- // ! these two will be retired.
389
- config.shadow && innerContainerClasses.push('shadow')
390
- config?.visual?.roundedBorders && innerContainerClasses.push('bite--has-rounded-borders')
391
340
 
392
341
  // Load data when component first mounts
393
342
  const outerContainerRef = useCallback(node => {
@@ -411,7 +360,7 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
411
360
  }
412
361
  }, [config, container]);
413
362
 
414
- if(configObj && config && configObj.data !== config.data){
363
+ if(configObj && config && JSON.stringify(configObj.data) !== JSON.stringify(config.data)){
415
364
  loadConfig();
416
365
  }
417
366
 
@@ -430,7 +379,7 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
430
379
  let imageAlt = imageData.alt
431
380
 
432
381
  if ('dynamic' === imageData.display && imageData.options && imageData.options?.length > 0) {
433
- let targetVal = Number(calculateDataBite())
382
+ let targetVal = Number(calculateDataBite(false))
434
383
  let argumentActive = false
435
384
 
436
385
  imageData.options.forEach((option, index) => {
@@ -487,28 +436,30 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
487
436
  break;
488
437
  }
489
438
 
490
- if(config.shadow) biteClasses.push('shadow')
491
-
492
439
  const showBite = undefined !== dataColumn && undefined !== dataFunction;
493
440
 
494
441
  body = (
495
442
  <>
496
443
  {isEditor && <EditorPanel />}
497
444
  <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(' ')}`}>
445
+ <div className={innerContainerClasses.join(' ')}>
446
+ {title && <div className={`bite-header cove-component__header component__header ${config.theme}`}>{parse(title)}</div>}
447
+ <div className={`bite ${biteClasses.join(' ')}`}>
448
+ <div className={`bite-content-container ${contentClasses.join(' ')}` }>
502
449
  {showBite && 'graphic' === biteStyle && isTop && <CircleCallout theme={config.theme} text={calculateDataBite()} biteFontSize={biteFontSize} dataFormat={dataFormat} /> }
503
450
  {isTop && <DataImage />}
504
451
  <div className={`bite-content`}>
505
- {showBite && 'title' === biteStyle && <div className="bite-value cove-component__header" style={{fontSize: biteFontSize + 'px'}}>{calculateDataBite()}</div>}
452
+ {showBite && 'title' === biteStyle && <div className="bite-value" style={{fontSize: biteFontSize + 'px'}}>{calculateDataBite()}</div>}
453
+ {showBite && 'split' === biteStyle && <div className="bite-value" style={{fontSize: biteFontSize + 'px'}}>{calculateDataBite()}</div>}
506
454
  <Fragment>
455
+ <div className="bite-content__text-wrap">
507
456
  <p className="bite-text">
508
457
  {showBite && 'body' === biteStyle && <span className="bite-value data-bite-body" style={{fontSize: biteFontSize + 'px'}}>{calculateDataBite()}</span>}
509
458
  {parse(biteBody)}
510
459
  </p>
460
+ {showBite && 'end' === biteStyle && <span className="bite-value data-bite-body" style={{fontSize: biteFontSize + 'px'}}>{calculateDataBite()}</span>}
511
461
  {subtext && !isCompactStyle && <p className="bite-subtext">{parse(subtext)}</p>}
462
+ </div>
512
463
  </Fragment>
513
464
  </div>
514
465
  {isBottom && <DataImage />}
@@ -516,12 +467,14 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
516
467
  </div>
517
468
  </div>
518
469
  </div>
470
+ {link && link}
519
471
  </div>
520
472
  </>
521
473
  )
522
474
  }
523
475
 
524
476
  let classNames = [
477
+ 'cove',
525
478
  'cdc-open-viz-module',
526
479
  'type-data-bite',
527
480
  currentViewport,
@@ -568,8 +521,10 @@ export const BITE_LOCATION_BODY = 'body';
568
521
  export const BITE_LOCATION_GRAPHIC = 'graphic';
569
522
  export const BITE_LOCATIONS = {
570
523
  'graphic': 'Graphic',
524
+ 'split': 'Split Graphic and Message',
571
525
  'title': 'Value above Message',
572
- 'body': 'Value before Message'
526
+ 'body': 'Value before Message',
527
+ 'end': 'Value after Message'
573
528
  };
574
529
 
575
530
  export const IMAGE_POSITION_LEFT = 'Left';