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