@cdc/data-bite 1.1.4 → 4.22.11

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.
@@ -1,37 +1,38 @@
1
- import React, { useEffect, useState, useCallback,FC } from 'react';
2
- import EditorPanel from './components/EditorPanel';
3
- import defaults from './data/initial-state';
4
- import Loading from '@cdc/core/components/Loading';
5
- import getViewport from '@cdc/core/helpers/getViewport';
6
- import ResizeObserver from 'resize-observer-polyfill';
7
- import Papa from 'papaparse';
8
- import parse from 'html-react-parser';
9
-
10
- import Context from './context';
11
- // @ts-ignore
12
- import DataTransform from '@cdc/core/components/DataTransform';
13
- import CircleCallout from './components/CircleCallout';
14
- import './scss/main.scss';
15
- import numberFromString from '@cdc/core/helpers/numberFromString';
16
- import { Fragment } from 'react';
1
+ import React, { useEffect, useState, useCallback, FC, memo } from 'react'
2
+ import EditorPanel from './components/EditorPanel'
3
+ import defaults from './data/initial-state'
4
+ import Loading from '@cdc/core/components/Loading'
5
+ import getViewport from '@cdc/core/helpers/getViewport'
6
+ import ResizeObserver from 'resize-observer-polyfill'
7
+ import parse from 'html-react-parser'
8
+
9
+ import Context from './context'
10
+ import { DataTransform } from '@cdc/core/helpers/DataTransform'
11
+ import CircleCallout from './components/CircleCallout'
12
+ import './scss/main.scss'
13
+ import numberFromString from '@cdc/core/helpers/numberFromString'
14
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
15
+ import { Fragment } from 'react'
17
16
 
18
17
  import { publish } from '@cdc/core/helpers/events'
19
-
18
+ import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
19
+ import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
20
20
 
21
21
  type DefaultsType = typeof defaults
22
- interface Props{
23
- configUrl?: string,
22
+ interface Props {
23
+ configUrl?: string
24
24
  config?: any
25
25
  isDashboard?: boolean
26
26
  isEditor?: boolean
27
- setConfig?:any
27
+ setConfig?: any
28
+ link?: any
28
29
  }
29
30
 
30
- const CdcDataBite:FC<Props> = (props) => {
31
- const { configUrl, config: configObj, isDashboard = false, isEditor = false, setConfig: setParentConfig } = props
31
+ const CdcDataBite: FC<Props> = props => {
32
+ const { configUrl, config: configObj, isDashboard = false, isEditor = false, setConfig: setParentConfig, link } = props
32
33
 
33
- const [config, setConfig] = useState<DefaultsType>({...defaults});
34
- const [loading, setLoading] = useState<Boolean>(true);
34
+ const [config, setConfig] = useState<DefaultsType>({ ...defaults })
35
+ const [loading, setLoading] = useState<Boolean>(true)
35
36
 
36
37
  const {
37
38
  title,
@@ -45,198 +46,161 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
45
46
  filters,
46
47
  subtext,
47
48
  general: { isCompactStyle }
48
- } = config;
49
-
49
+ } = config
50
50
 
51
+ const { innerContainerClasses, contentClasses } = useDataVizClasses(config)
51
52
 
52
53
  const transform = new DataTransform()
53
54
 
54
- const [currentViewport, setCurrentViewport] = useState<String>('lg');
55
+ const [currentViewport, setCurrentViewport] = useState<String>('lg')
55
56
 
56
- const [ coveLoadedHasRan, setCoveLoadedHasRan ] = useState(false)
57
+ const [coveLoadedHasRan, setCoveLoadedHasRan] = useState(false)
57
58
 
58
- const [ container, setContainer ] = useState()
59
+ const [container, setContainer] = useState()
59
60
 
60
61
  //Observes changes to outermost container and changes viewport size in state
61
62
  const resizeObserver = new ResizeObserver(entries => {
62
63
  for (let entry of entries) {
63
- let newViewport = getViewport(entry.contentRect.width * 2) // Data bite is usually presented as small, so we scale it up for responsive calculations
64
- setCurrentViewport(newViewport)
64
+ let newViewport = getViewport(entry.contentRect.width * 2) // Data bite is usually presented as small, so we scale it up for responsive calculations
65
+ setCurrentViewport(newViewport)
65
66
  }
66
- });
67
-
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
- }
67
+ })
88
68
 
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
- const updateConfig = (newConfig) => {
69
+ const updateConfig = newConfig => {
107
70
  // Deeper copy
108
71
  Object.keys(defaults).forEach(key => {
109
72
  if (newConfig[key] && 'object' === typeof newConfig[key] && !Array.isArray(newConfig[key])) {
110
73
  newConfig[key] = { ...defaults[key], ...newConfig[key] }
111
74
  }
112
- });
75
+ })
113
76
 
114
77
  //Enforce default values that need to be calculated at runtime
115
- newConfig.runtime = {};
116
- newConfig.runtime.uniqueId = Date.now();
78
+ newConfig.runtime = {}
79
+ newConfig.runtime.uniqueId = Date.now()
117
80
 
118
81
  //Check things that are needed and set error messages if needed
119
- newConfig.runtime.editorErrorMessage = '';
120
- setConfig(newConfig);
82
+ newConfig.runtime.editorErrorMessage = ''
83
+ setConfig(newConfig)
121
84
  }
122
85
 
123
86
  const loadConfig = async () => {
124
- let response = configObj || await (await fetch(configUrl)).json();
125
-
126
- const round = 1000 * 60 * 15;
127
- const date = new Date();
128
- let cacheBustingString = new Date(date.getTime() - (date.getTime() % round)).toISOString();
87
+ let response = configObj || (await (await fetch(configUrl)).json())
129
88
 
130
89
  // If data is included through a URL, fetch that and store
131
90
  let responseData = response.data ?? {}
132
91
 
133
92
  if (response.dataUrl) {
134
- response.dataUrl = `${response.dataUrl}?${cacheBustingString}`;
93
+ response.dataUrl = `${response.dataUrl}?${cacheBustingString()}`
135
94
  let newData = await fetchRemoteData(response.dataUrl)
136
-
95
+
137
96
  if (newData && response.dataDescription) {
138
- newData = transform.autoStandardize(newData);
139
- newData = transform.developerStandardize(newData, response.dataDescription);
97
+ newData = transform.autoStandardize(newData)
98
+ newData = transform.developerStandardize(newData, response.dataDescription)
140
99
  }
141
100
 
142
- if(newData) {
143
- responseData = newData;
101
+ if (newData) {
102
+ responseData = newData
144
103
  }
145
104
  }
146
105
 
147
- response.data = responseData;
106
+ response.data = responseData
148
107
 
149
- updateConfig({ ...defaults, ...response });
108
+ updateConfig({ ...defaults, ...response })
150
109
 
151
- setLoading(false);
110
+ setLoading(false)
152
111
  }
153
112
 
154
- const calculateDataBite = ():string|number => {
155
-
113
+ const calculateDataBite = (includePrefixSuffix: boolean = true): string | number => {
156
114
  //If either the column or function aren't set, do not calculate
157
115
  if (!dataColumn || !dataFunction) {
158
- return '';
116
+ return ''
159
117
  }
160
118
 
161
-
162
- const applyPrecision =(value:number|string):string => {
119
+ const applyPrecision = (value: number | string): string => {
163
120
  // first validation
164
- if(value === undefined || value===null){
165
- console.error('Enter correct value to "applyPrecision()" function ')
166
- return ;
167
- }
168
- // second validation
169
- if(Number.isNaN(value)){
170
- console.error(' Argunment isNaN, "applyPrecision()" function ')
171
- return;
172
- }
173
- let result:number|string = value
174
- let roundToPlace = Number(config.dataFormat.roundToPlace) // default equals to 0
175
- // ROUND FIELD going -1,-2,-3 numbers
176
- if(roundToPlace<0) {
177
- console.error(' ROUND field is below "0", "applyPrecision()" function ')
178
- return;
179
- }
180
- if(typeof roundToPlace ==='number' && roundToPlace > -1 ){
181
- result = Number(result).toFixed(roundToPlace); // returns STRING
182
- }
183
- return String(result)
121
+ if (value === undefined || value === null) {
122
+ console.error('Enter correct value to "applyPrecision()" function ')
123
+ return
124
+ }
125
+ // second validation
126
+ if (Number.isNaN(value)) {
127
+ console.error(' Argunment isNaN, "applyPrecision()" function ')
128
+ return
129
+ }
130
+ let result: number | string = value
131
+ let roundToPlace = Number(config.dataFormat.roundToPlace) // default equals to 0
132
+ // ROUND FIELD going -1,-2,-3 numbers
133
+ if (roundToPlace < 0) {
134
+ console.error(' ROUND field is below "0", "applyPrecision()" function ')
135
+ return
136
+ }
137
+ if (typeof roundToPlace === 'number' && roundToPlace > -1) {
138
+ result = Number(result).toFixed(roundToPlace) // returns STRING
139
+ }
140
+ return String(result)
184
141
  }
185
142
 
186
143
  // filter null and 0 out from count data
187
- const getColumnCount = (arr:(string|number)[]) => {
188
- if(config.dataFormat.ignoreZeros) {
189
- numericalData = numericalData.filter( item => item && item)
144
+ const getColumnCount = (arr: (string | number)[]) => {
145
+ if (config.dataFormat.ignoreZeros) {
146
+ numericalData = numericalData.filter(item => item && item)
190
147
  return numericalData.length
191
148
  } else {
192
149
  return numericalData.length
193
150
  }
194
151
  }
195
152
 
196
- const getColumnSum = (arr:(string|number)[]) => {
153
+ const getColumnSum = (arr: (string | number)[]) => {
197
154
  // first validation
198
- if(arr===undefined || arr===null){
155
+ if (arr === undefined || arr === null) {
199
156
  console.error('Enter valid value for getColumnSum function ')
200
- return;
157
+ return
201
158
  }
202
159
  // second validation
203
- if(arr.length === 0 || !Array.isArray(arr)){
160
+ console.log('arr', arr)
161
+ if (arr.length === 0 || !Array.isArray(arr)) {
204
162
  console.error('Arguments are not valid getColumnSum function ')
205
- return;
163
+ return
206
164
  }
207
- let sum:number = 0
208
- if(arr.length > 1){
165
+ let sum: number = 0
166
+ if (arr.length > 1) {
209
167
  /// first convert each element to number then add using reduce method to escape string concatination.
210
- sum = arr.map(el=>Number(el)).reduce((sum:number, x:number) => sum + x);
211
- }else {
168
+ sum = arr.map(el => Number(el)).reduce((sum: number, x: number) => sum + x)
169
+ } else {
212
170
  sum = Number(arr[0])
213
171
  }
214
- return applyPrecision(sum);
172
+ return applyPrecision(sum)
215
173
  }
216
174
 
217
- const getColumnMean=(arr:(string|number)[]) => { // add default params to escape errors on runtime
175
+ const getColumnMean = (arr: (string | number)[]) => {
176
+ // add default params to escape errors on runtime
218
177
  // first validation
219
- if(arr===undefined || arr===null ||!Array.isArray(arr)){
178
+ if (arr === undefined || arr === null || !Array.isArray(arr)) {
220
179
  console.error('Enter valid parameter getColumnMean function')
221
- return
180
+ return
181
+ }
182
+
183
+ if (config.dataFormat.ignoreZeros) {
184
+ arr = arr.filter(num => num !== 0)
222
185
  }
223
-
224
- let mean:number = 0
225
- if(arr.length > 1){
226
- /// first convert each element to number then add using reduce method to escape string concatination.
227
- mean = arr.map(el=>Number(el)).reduce((a, b) => a + b) / arr.length
228
- }else {
186
+
187
+ let mean: number = 0
188
+ if (arr.length > 1) {
189
+ /// first convert each element to number then add using reduce method to escape string concatination.
190
+ mean = arr.map(el => Number(el)).reduce((a, b) => a + b) / arr.length
191
+ } else {
229
192
  mean = Number(arr[0])
230
193
  }
231
- return applyPrecision(mean);
194
+ return applyPrecision(mean)
232
195
  }
233
196
 
234
- const getMode = (arr:any[]=[]):string[] => { // add default params to escape errors on runtime
235
- // this function accepts any array and returns array of strings
197
+ const getMode = (arr: any[] = []): string[] => {
198
+ // add default params to escape errors on runtime
199
+ // this function accepts any array and returns array of strings
236
200
  let freq = {}
237
201
  let max = -Infinity
238
202
 
239
- for(let i = 0; i < arr.length; i++) {
203
+ for (let i = 0; i < arr.length; i++) {
240
204
  if (freq[arr[i]]) {
241
205
  freq[arr[i]] += 1
242
206
  } else {
@@ -250,152 +214,128 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
250
214
 
251
215
  let res = []
252
216
 
253
- for(let key in freq) {
254
- if(freq[key] === max) res.push(key)
217
+ for (let key in freq) {
218
+ if (freq[key] === max) res.push(key)
255
219
  }
256
220
 
257
221
  return res
258
222
  }
259
223
 
260
224
  const getMedian = arr => {
261
- if(!arr.length) return ;
225
+ if (!arr.length) return
262
226
  const mid = Math.floor(arr.length / 2),
263
- nums = [...arr].sort((a, b) => a - b);
264
- const value = arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
265
- return applyPrecision(value);
266
- };
267
-
268
- const applyLocaleString = (value:string):string=>{
269
- if(value===undefined || value===null) return ;
270
- if(Number.isNaN(value)|| typeof value ==='number') {
271
- value = String(value)
227
+ nums = [...arr].sort((a, b) => a - b)
228
+ const value = arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2
229
+ return applyPrecision(value)
230
+ }
272
231
 
232
+ const applyLocaleString = (value: string): string => {
233
+ if (value === undefined || value === null) return
234
+ if (Number.isNaN(value) || typeof value === 'number') {
235
+ value = String(value)
273
236
  }
274
237
  const language = 'en-US'
275
238
  let formattedValue = parseFloat(value).toLocaleString(language, {
276
239
  useGrouping: true,
277
240
  maximumFractionDigits: 6
278
241
  })
279
- // Add back missing .0 in e.g. 12.0
280
- const match = value.match(/\.\d*?(0*)$/)
242
+ // Add back missing .0 in e.g. 12.0
243
+ const match = value.match(/\.\d*?(0*)$/)
281
244
 
282
- if (match){
283
- formattedValue += (/[1-9]/).test(match[0]) ? match[1] : match[0]
245
+ if (match) {
246
+ formattedValue += /[1-9]/.test(match[0]) ? match[1] : match[0]
284
247
  }
285
248
  return formattedValue
286
249
  }
287
-
288
-
289
250
 
290
-
251
+ let dataBite: string | number = ''
291
252
 
292
- let dataBite:string|number = '';
293
-
294
253
  //Optionally filter the data based on the user's filter
295
- let filteredData = config.data;
254
+ let filteredData = config.data
296
255
 
297
- filters.map((filter) => {
298
- if ( filter.columnName && filter.columnValue ) {
299
- return filteredData = filteredData.filter(function (e) {
300
- return e[filter.columnName] === filter.columnValue;
301
- });
256
+ filters.map(filter => {
257
+ if (filter.columnName && filter.columnValue) {
258
+ return (filteredData = filteredData.filter(function (e) {
259
+ return e[filter.columnName] === filter.columnValue
260
+ }))
302
261
  } else {
303
- return false;
262
+ return false
304
263
  }
305
- });
264
+ })
306
265
 
307
- let numericalData:any[] = []
308
-
309
-
310
- // Get the column's data
311
- if(filteredData.length){
266
+ let numericalData: any[] = []
267
+ // Get the column's data
268
+ if (filteredData.length) {
312
269
  filteredData.forEach(row => {
313
270
  let value = numberFromString(row[dataColumn])
314
- if(typeof value === 'number') numericalData.push(value)
315
- });
316
- }
317
-
318
-
271
+ if (typeof value === 'number') numericalData.push(value)
272
+ })
273
+ } else {
274
+ numericalData = config.data.map(item => Number(item[config.dataColumn]))
275
+ }
319
276
 
320
277
  switch (dataFunction) {
321
278
  case DATA_FUNCTION_COUNT:
322
- dataBite = getColumnCount(numericalData);
323
- break;
279
+ dataBite = getColumnCount(numericalData)
280
+ break
324
281
  case DATA_FUNCTION_SUM:
325
- dataBite = getColumnSum(numericalData);
326
- break;
282
+ dataBite = getColumnSum(numericalData)
283
+ break
327
284
  case DATA_FUNCTION_MEAN:
328
- dataBite = getColumnMean(numericalData);
329
- break;
285
+ dataBite = getColumnMean(numericalData)
286
+ break
330
287
  case DATA_FUNCTION_MEDIAN:
331
- dataBite = getMedian(numericalData);
332
- break;
288
+ dataBite = getMedian(numericalData)
289
+ break
333
290
  case DATA_FUNCTION_MAX:
334
- dataBite = Math.max(...numericalData);
335
- break;
291
+ dataBite = Math.max(...numericalData)
292
+ break
336
293
  case DATA_FUNCTION_MIN:
337
- dataBite =Math.min(...numericalData);
338
- break;
294
+ dataBite = Math.min(...numericalData)
295
+ break
339
296
  case DATA_FUNCTION_MODE:
340
- dataBite = getMode(numericalData).join('');
341
- break;
297
+ dataBite = getMode(numericalData).join('')
298
+ break
342
299
  case DATA_FUNCTION_RANGE:
343
- let rangeMin :number|string = Math.min(...numericalData)
344
- let rangeMax :number|string = Math.max(...numericalData)
345
- rangeMin = applyPrecision(rangeMin)
346
- rangeMax = applyPrecision(rangeMax)
347
- if (config.dataFormat.commas) {
300
+ let rangeMin: number | string = Math.min(...numericalData)
301
+ let rangeMax: number | string = Math.max(...numericalData)
302
+ rangeMin = applyPrecision(rangeMin)
303
+ rangeMax = applyPrecision(rangeMax)
304
+ if (config.dataFormat.commas) {
348
305
  rangeMin = applyLocaleString(rangeMin)
349
306
  rangeMax = applyLocaleString(rangeMax)
350
- }
351
- dataBite = config.dataFormat.prefix + rangeMin + config.dataFormat.suffix + ' - ' + config.dataFormat.prefix + rangeMax+config.dataFormat.suffix;
352
- break;
307
+ }
308
+ dataBite = config.dataFormat.prefix + rangeMin + config.dataFormat.suffix + ' - ' + config.dataFormat.prefix + rangeMax + config.dataFormat.suffix
309
+ break
353
310
  default:
354
- console.warn('Data bite function not recognized: ' + dataFunction);
311
+ console.warn('Data bite function not recognized: ' + dataFunction)
355
312
  }
356
313
 
357
314
  // If not the range, then round and format here
358
315
  if (dataFunction !== DATA_FUNCTION_RANGE) {
359
- dataBite = applyPrecision(dataBite);
360
-
316
+ dataBite = applyPrecision(dataBite)
317
+
361
318
  if (config.dataFormat.commas) {
362
- dataBite = applyLocaleString(dataBite)
319
+ dataBite = applyLocaleString(dataBite)
363
320
  }
364
- // Optional
321
+ // Optional
365
322
  // return config.dataFormat.prefix + dataBite + config.dataFormat.suffix;
366
323
 
367
- return dataFormat.prefix + dataBite + dataFormat.suffix
368
- } else {
324
+ return includePrefixSuffix ? dataFormat.prefix + dataBite + dataFormat.suffix : dataBite
325
+ } else {
369
326
  //Rounding and formatting for ranges happens earlier.
370
327
 
371
- return dataFormat.prefix + dataBite + dataFormat.suffix
328
+ return includePrefixSuffix ? dataFormat.prefix + dataBite + dataFormat.suffix : dataBite
372
329
  }
373
330
  }
374
331
 
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
-
388
- // ! these two will be retired.
389
- config.shadow && innerContainerClasses.push('shadow')
390
- config?.visual?.roundedBorders && innerContainerClasses.push('bite--has-rounded-borders')
391
-
392
332
  // Load data when component first mounts
393
333
  const outerContainerRef = useCallback(node => {
394
- if (node !== null) {
395
- resizeObserver.observe(node);
396
- }
397
- setContainer(node)
398
- },[]);
334
+ if (node !== null) {
335
+ resizeObserver.observe(node)
336
+ }
337
+ setContainer(node)
338
+ }, [])
399
339
 
400
340
  // Initial load
401
341
  useEffect(() => {
@@ -403,34 +343,45 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
403
343
  publish('cove_loaded', { loadConfigHasRun: true })
404
344
  }, [])
405
345
 
406
-
407
346
  useEffect(() => {
408
347
  if (config && !coveLoadedHasRan && container) {
409
348
  publish('cove_loaded', { config: config })
410
349
  setCoveLoadedHasRan(true)
411
350
  }
412
- }, [config, container]);
351
+ }, [config, container])
413
352
 
414
- if(configObj && config && configObj.data !== config.data){
415
- loadConfig();
353
+ if (configObj && config && JSON.stringify(configObj.data) !== JSON.stringify(config.data)) {
354
+ loadConfig()
416
355
  }
417
356
 
418
- let body = (<Loading />)
357
+ let body = <Loading />
419
358
 
420
359
  const DataImage = useCallback(() => {
421
360
  let operators = {
422
- '<': (a, b) => { return a < b },
423
- '>': (a, b) => { return a > b },
424
- '<=': (a, b) => { return a <= b },
425
- '>=': (a, b) => { return a >= b },
426
- '≠': (a, b) => { return a !== b },
427
- '=': (a, b) => { return a === b }
361
+ '<': (a, b) => {
362
+ return a < b
363
+ },
364
+ '>': (a, b) => {
365
+ return a > b
366
+ },
367
+ '<=': (a, b) => {
368
+ return a <= b
369
+ },
370
+ '>=': (a, b) => {
371
+ return a >= b
372
+ },
373
+ '≠': (a, b) => {
374
+ return a !== b
375
+ },
376
+ '=': (a, b) => {
377
+ return a === b
378
+ }
428
379
  }
429
380
  let imageSource = imageData.url
430
381
  let imageAlt = imageData.alt
431
382
 
432
383
  if ('dynamic' === imageData.display && imageData.options && imageData.options?.length > 0) {
433
- let targetVal = Number(calculateDataBite())
384
+ let targetVal = Number(calculateDataBite(false))
434
385
  let argumentActive = false
435
386
 
436
387
  imageData.options.forEach((option, index) => {
@@ -444,13 +395,17 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
444
395
  if (argumentArr[1].operator?.length > 0 && argumentArr[1].threshold?.length > 0) {
445
396
  if (operators[argumentArr[1].operator](targetVal, argumentArr[1].threshold)) {
446
397
  imageSource = source
447
- if (alt !== '' && alt !== undefined) { imageAlt = alt }
398
+ if (alt !== '' && alt !== undefined) {
399
+ imageAlt = alt
400
+ }
448
401
  argumentActive = true
449
402
  }
450
403
  }
451
404
  } else {
452
405
  imageSource = source
453
- if (alt !== '' && alt !== undefined) { imageAlt = alt }
406
+ if (alt !== '' && alt !== undefined) {
407
+ imageAlt = alt
408
+ }
454
409
  argumentActive = true
455
410
  }
456
411
  }
@@ -459,77 +414,90 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
459
414
  })
460
415
  }
461
416
 
462
- return (imageSource.length > 0 && 'graphic' !== biteStyle && 'none' !== imageData.display ? <img alt={imageAlt} src={imageSource} className="bite-image callout" /> : null)
463
- }, [ imageData])
417
+ return imageSource.length > 0 && 'graphic' !== biteStyle && 'none' !== imageData.display ? <img alt={imageAlt} src={imageSource} className='bite-image callout' /> : null
418
+ }, [imageData])
464
419
 
465
- if(false === loading) {
466
- let biteClasses = [];
420
+ if (false === loading) {
421
+ let biteClasses = []
467
422
 
468
423
  let isTop = false
469
424
  let isBottom = false
470
425
 
471
426
  switch (config.bitePosition) {
472
427
  case IMAGE_POSITION_LEFT:
473
- biteClasses.push('bite-left');
428
+ biteClasses.push('bite-left')
474
429
  isTop = true
475
- break;
430
+ break
476
431
  case IMAGE_POSITION_RIGHT:
477
- biteClasses.push('bite-right');
432
+ biteClasses.push('bite-right')
478
433
  isTop = true
479
- break;
434
+ break
480
435
  case IMAGE_POSITION_TOP:
481
- biteClasses.push('bite-top');
436
+ biteClasses.push('bite-top')
482
437
  isTop = true
483
- break;
438
+ break
484
439
  case IMAGE_POSITION_BOTTOM:
485
- biteClasses.push('bite-bottom');
440
+ biteClasses.push('bite-bottom')
486
441
  isBottom = true
487
- break;
442
+ break
488
443
  }
489
444
 
490
- if(config.shadow) biteClasses.push('shadow')
491
-
492
- const showBite = undefined !== dataColumn && undefined !== dataFunction;
445
+ const showBite = undefined !== dataColumn && undefined !== dataFunction
493
446
 
494
447
  body = (
495
448
  <>
496
449
  {isEditor && <EditorPanel />}
497
450
  <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(' ')}`}>
502
- {showBite && 'graphic' === biteStyle && isTop && <CircleCallout theme={config.theme} text={calculateDataBite()} biteFontSize={biteFontSize} dataFormat={dataFormat} /> }
451
+ <div className={innerContainerClasses.join(' ')}>
452
+ {title && <div className={`bite-header cove-component__header component__header ${config.theme}`}>{parse(title)}</div>}
453
+ <div className={`bite ${biteClasses.join(' ')}`}>
454
+ <div className={`bite-content-container ${contentClasses.join(' ')}`}>
455
+ {showBite && 'graphic' === biteStyle && isTop && <CircleCallout theme={config.theme} text={calculateDataBite()} biteFontSize={biteFontSize} dataFormat={dataFormat} />}
503
456
  {isTop && <DataImage />}
504
457
  <div className={`bite-content`}>
505
- {showBite && 'title' === biteStyle && <div className="bite-value cove-component__header" style={{fontSize: biteFontSize + 'px'}}>{calculateDataBite()}</div>}
506
- <Fragment>
507
- <p className="bite-text">
508
- {showBite && 'body' === biteStyle && <span className="bite-value data-bite-body" style={{fontSize: biteFontSize + 'px'}}>{calculateDataBite()}</span>}
458
+ {showBite && 'title' === biteStyle && (
459
+ <div className='bite-value' style={{ fontSize: biteFontSize + 'px' }}>
460
+ {calculateDataBite()}
461
+ </div>
462
+ )}
463
+ {showBite && 'split' === biteStyle && (
464
+ <div className='bite-value' style={{ fontSize: biteFontSize + 'px' }}>
465
+ {calculateDataBite()}
466
+ </div>
467
+ )}
468
+ <Fragment>
469
+ <div className='bite-content__text-wrap'>
470
+ <p className='bite-text'>
471
+ {showBite && 'body' === biteStyle && (
472
+ <span className='bite-value data-bite-body' style={{ fontSize: biteFontSize + 'px' }}>
473
+ {calculateDataBite()}
474
+ </span>
475
+ )}
509
476
  {parse(biteBody)}
510
477
  </p>
511
- {subtext && !isCompactStyle && <p className="bite-subtext">{parse(subtext)}</p>}
512
- </Fragment>
478
+ {showBite && 'end' === biteStyle && (
479
+ <span className='bite-value data-bite-body' style={{ fontSize: biteFontSize + 'px' }}>
480
+ {calculateDataBite()}
481
+ </span>
482
+ )}
483
+ {subtext && !isCompactStyle && <p className='bite-subtext'>{parse(subtext)}</p>}
484
+ </div>
485
+ </Fragment>
513
486
  </div>
514
487
  {isBottom && <DataImage />}
515
- {showBite && 'graphic' === biteStyle && !isTop && <CircleCallout theme={config.theme} text={calculateDataBite()} biteFontSize={biteFontSize} dataFormat={dataFormat} /> }
488
+ {showBite && 'graphic' === biteStyle && !isTop && <CircleCallout theme={config.theme} text={calculateDataBite()} biteFontSize={biteFontSize} dataFormat={dataFormat} />}
516
489
  </div>
517
490
  </div>
518
491
  </div>
492
+ {link && link}
519
493
  </div>
520
494
  </>
521
495
  )
522
496
  }
523
497
 
524
- let classNames = [
525
- 'cdc-open-viz-module',
526
- 'type-data-bite',
527
- currentViewport,
528
- config.theme,
529
- 'font-' + config.fontSize
530
- ];
498
+ let classNames = ['cove', 'cdc-open-viz-module', 'type-data-bite', currentViewport, config.theme, 'font-' + config.fontSize]
531
499
  if (isEditor) {
532
- classNames.push('is-editor');
500
+ classNames.push('is-editor')
533
501
  }
534
502
 
535
503
  return (
@@ -538,50 +506,38 @@ const { configUrl, config: configObj, isDashboard = false, isEditor = false, set
538
506
  {body}
539
507
  </div>
540
508
  </Context.Provider>
541
- );
542
- };
509
+ )
510
+ }
543
511
 
544
- export default CdcDataBite;
512
+ export default CdcDataBite
545
513
 
546
514
  /* Constant */
547
- export const DATA_FUNCTION_MAX = 'Max';
548
- export const DATA_FUNCTION_COUNT = 'Count';
549
- export const DATA_FUNCTION_MEAN = 'Mean (Average)';
550
- export const DATA_FUNCTION_MEDIAN = 'Median';
551
- export const DATA_FUNCTION_MIN = 'Min';
552
- export const DATA_FUNCTION_MODE = 'Mode';
553
- export const DATA_FUNCTION_RANGE = 'Range';
554
- export const DATA_FUNCTION_SUM = 'Sum';
555
- export const DATA_FUNCTIONS = [
556
- DATA_FUNCTION_COUNT,
557
- DATA_FUNCTION_MAX,
558
- DATA_FUNCTION_MEAN,
559
- DATA_FUNCTION_MEDIAN,
560
- DATA_FUNCTION_MIN,
561
- DATA_FUNCTION_MODE,
562
- DATA_FUNCTION_RANGE,
563
- DATA_FUNCTION_SUM
564
- ];
565
-
566
- export const BITE_LOCATION_TITLE = 'title';
567
- export const BITE_LOCATION_BODY = 'body';
568
- export const BITE_LOCATION_GRAPHIC = 'graphic';
515
+ export const DATA_FUNCTION_MAX = 'Max'
516
+ export const DATA_FUNCTION_COUNT = 'Count'
517
+ export const DATA_FUNCTION_MEAN = 'Mean (Average)'
518
+ export const DATA_FUNCTION_MEDIAN = 'Median'
519
+ export const DATA_FUNCTION_MIN = 'Min'
520
+ export const DATA_FUNCTION_MODE = 'Mode'
521
+ export const DATA_FUNCTION_RANGE = 'Range'
522
+ export const DATA_FUNCTION_SUM = 'Sum'
523
+ export const DATA_FUNCTIONS = [DATA_FUNCTION_COUNT, DATA_FUNCTION_MAX, DATA_FUNCTION_MEAN, DATA_FUNCTION_MEDIAN, DATA_FUNCTION_MIN, DATA_FUNCTION_MODE, DATA_FUNCTION_RANGE, DATA_FUNCTION_SUM]
524
+
525
+ export const BITE_LOCATION_TITLE = 'title'
526
+ export const BITE_LOCATION_BODY = 'body'
527
+ export const BITE_LOCATION_GRAPHIC = 'graphic'
569
528
  export const BITE_LOCATIONS = {
570
- 'graphic': 'Graphic',
571
- 'title': 'Value above Message',
572
- 'body': 'Value before Message'
573
- };
574
-
575
- export const IMAGE_POSITION_LEFT = 'Left';
576
- export const IMAGE_POSITION_RIGHT = 'Right';
577
- export const IMAGE_POSITION_TOP = 'Top';
578
- export const IMAGE_POSITION_BOTTOM = 'Bottom';
579
- export const IMAGE_POSITIONS = [
580
- IMAGE_POSITION_LEFT,
581
- IMAGE_POSITION_RIGHT,
582
- IMAGE_POSITION_TOP,
583
- IMAGE_POSITION_BOTTOM,
584
- ];
529
+ graphic: 'Graphic',
530
+ split: 'Split Graphic and Message',
531
+ title: 'Value above Message',
532
+ body: 'Value before Message',
533
+ end: 'Value after Message'
534
+ }
535
+
536
+ export const IMAGE_POSITION_LEFT = 'Left'
537
+ export const IMAGE_POSITION_RIGHT = 'Right'
538
+ export const IMAGE_POSITION_TOP = 'Top'
539
+ export const IMAGE_POSITION_BOTTOM = 'Bottom'
540
+ export const IMAGE_POSITIONS = [IMAGE_POSITION_LEFT, IMAGE_POSITION_RIGHT, IMAGE_POSITION_TOP, IMAGE_POSITION_BOTTOM]
585
541
 
586
542
  export const DATA_OPERATOR_LESS = '<'
587
543
  export const DATA_OPERATOR_GREATER = '>'
@@ -590,11 +546,4 @@ export const DATA_OPERATOR_GREATEREQUAL = '>='
590
546
  export const DATA_OPERATOR_EQUAL = '='
591
547
  export const DATA_OPERATOR_NOTEQUAL = '≠'
592
548
 
593
- export const DATA_OPERATORS = [
594
- DATA_OPERATOR_LESS,
595
- DATA_OPERATOR_GREATER,
596
- DATA_OPERATOR_LESSEQUAL,
597
- DATA_OPERATOR_GREATEREQUAL,
598
- DATA_OPERATOR_EQUAL,
599
- DATA_OPERATOR_NOTEQUAL
600
- ]
549
+ export const DATA_OPERATORS = [DATA_OPERATOR_LESS, DATA_OPERATOR_GREATER, DATA_OPERATOR_LESSEQUAL, DATA_OPERATOR_GREATEREQUAL, DATA_OPERATOR_EQUAL, DATA_OPERATOR_NOTEQUAL]