@cdc/chart 4.23.4 → 4.23.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +54845 -51755
- package/examples/feature/__data__/planet-example-data.json +14 -32
- package/examples/feature/__data__/planet-logaritmic-data.json +56 -0
- package/examples/feature/area/area-chart-category.json +240 -0
- package/examples/feature/bar/example-bar-chart.json +544 -22
- package/examples/feature/bar/new.json +561 -0
- package/examples/feature/bar/planet-chart-logaritmic-config.json +170 -0
- package/examples/feature/boxplot/valid-boxplot.csv +17 -0
- package/examples/feature/combo/right-issues.json +190 -0
- package/examples/feature/filters/filter-testing.json +37 -3
- package/examples/feature/forecasting/combo-forecasting.json +245 -0
- package/examples/feature/forecasting/forecasting.json +5325 -0
- package/examples/feature/forecasting/index.json +203 -0
- package/examples/feature/forecasting/random_data.csv +366 -0
- package/examples/feature/line/line-chart.json +3 -3
- package/examples/feature/test-highlight/test-highlight-2.json +789 -0
- package/examples/feature/test-highlight/test-highlight-vertical.json +561 -0
- package/examples/feature/test-highlight/test-highlight.json +100 -0
- package/examples/feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json +1 -2
- package/examples/gallery/bar-chart-horizontal/horizontal-highlight.json +345 -0
- package/examples/gallery/line/line.json +173 -1
- package/index.html +14 -8
- package/package.json +2 -2
- package/src/CdcChart.jsx +342 -25
- package/src/components/AreaChart.jsx +32 -40
- package/src/components/BarChart.jsx +147 -25
- package/src/components/DataTable.jsx +30 -12
- package/src/components/DeviationBar.jsx +32 -32
- package/src/components/EditorPanel.jsx +1902 -1126
- package/src/components/Forecasting.jsx +147 -0
- package/src/components/Legend.jsx +193 -243
- package/src/components/LineChart.jsx +4 -9
- package/src/components/LinearChart.jsx +263 -285
- package/src/components/Series.jsx +518 -0
- package/src/components/SparkLine.jsx +3 -3
- package/src/data/initial-state.js +24 -5
- package/src/hooks/useHighlightedBars.js +154 -0
- package/src/hooks/useMinMax.js +128 -0
- package/src/hooks/useReduceData.js +31 -57
- package/src/hooks/useRightAxis.js +8 -2
- package/src/hooks/useScales.js +196 -0
- /package/examples/feature/area/{area-chart.json → area-chart-date.json} +0 -0
package/src/CdcChart.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from 'react'
|
|
1
|
+
import React, { useState, useEffect, useCallback, useRef } from 'react'
|
|
2
2
|
|
|
3
3
|
// IE11
|
|
4
4
|
import ResizeObserver from 'resize-observer-polyfill'
|
|
@@ -9,10 +9,10 @@ import * as d3 from 'd3-array'
|
|
|
9
9
|
import { scaleOrdinal } from '@visx/scale'
|
|
10
10
|
import ParentSize from '@visx/responsive/lib/components/ParentSize'
|
|
11
11
|
import { timeParse, timeFormat } from 'd3-time-format'
|
|
12
|
-
import { format } from 'd3-format'
|
|
13
12
|
import Papa from 'papaparse'
|
|
14
13
|
import parse from 'html-react-parser'
|
|
15
14
|
import 'react-tooltip/dist/react-tooltip.css'
|
|
15
|
+
import chroma from 'chroma-js'
|
|
16
16
|
|
|
17
17
|
// Primary Components
|
|
18
18
|
import ConfigContext from './ConfigContext'
|
|
@@ -27,12 +27,11 @@ import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
|
|
|
27
27
|
|
|
28
28
|
import SparkLine from './components/SparkLine'
|
|
29
29
|
import Legend from './components/Legend'
|
|
30
|
-
import DataTable from './components/DataTable'
|
|
31
30
|
import defaults from './data/initial-state'
|
|
32
31
|
import EditorPanel from './components/EditorPanel'
|
|
33
32
|
import Loading from '@cdc/core/components/Loading'
|
|
34
33
|
import Filters from '@cdc/core/components/Filters'
|
|
35
|
-
import
|
|
34
|
+
import MediaControls from '@cdc/core/components/MediaControls'
|
|
36
35
|
|
|
37
36
|
// Helpers
|
|
38
37
|
import numberFromString from '@cdc/core/helpers/numberFromString'
|
|
@@ -40,10 +39,44 @@ import getViewport from '@cdc/core/helpers/getViewport'
|
|
|
40
39
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
41
40
|
import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
|
|
42
41
|
import isNumber from '@cdc/core/helpers/isNumber'
|
|
42
|
+
import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
|
|
43
43
|
|
|
44
44
|
import './scss/main.scss'
|
|
45
|
+
// load both then config below determines which to use
|
|
46
|
+
import DataTable_horiz from './components/DataTable'
|
|
47
|
+
import DataTable_vert from '@cdc/core/components/DataTable'
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
const generateColorsArray = (color = '#000000', special = false) => {
|
|
50
|
+
let colorObj = chroma(color)
|
|
51
|
+
let hoverColor = special ? colorObj.brighten(0.5).hex() : colorObj.saturate(1.3).hex()
|
|
52
|
+
|
|
53
|
+
return [color, hoverColor, colorObj.darken(0.3).hex()]
|
|
54
|
+
}
|
|
55
|
+
const hashObj = row => {
|
|
56
|
+
try {
|
|
57
|
+
if (!row) throw new Error('No row supplied to hashObj')
|
|
58
|
+
|
|
59
|
+
let str = JSON.stringify(row)
|
|
60
|
+
let hash = 0
|
|
61
|
+
|
|
62
|
+
if (str.length === 0) return hash
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < str.length; i++) {
|
|
65
|
+
let char = str.charCodeAt(i)
|
|
66
|
+
hash = (hash << 5) - hash + char
|
|
67
|
+
hash = hash & hash
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return hash
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.error('COVE: ', e) // eslint-disable-line
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// * FILE REVIEW
|
|
77
|
+
// TODO: @tturnerswdev33 - remove/fix mentions of runtimeLegend that were added
|
|
78
|
+
|
|
79
|
+
export default function CdcChart({ configUrl, config: configObj, isEditor = false, isDebug = false, isDashboard = false, setConfig: setParentConfig, setEditing, hostname, link, setSharedFilter, setSharedFilterValue, dashboardConfig }) {
|
|
47
80
|
const transform = new DataTransform()
|
|
48
81
|
const [loading, setLoading] = useState(true)
|
|
49
82
|
const [colorScale, setColorScale] = useState(null)
|
|
@@ -60,6 +93,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
60
93
|
const [dynamicLegendItems, setDynamicLegendItems] = useState([])
|
|
61
94
|
const [imageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`)
|
|
62
95
|
|
|
96
|
+
let legendMemo = useRef(new Map()) // map collection
|
|
97
|
+
let innerContainerRef = useRef()
|
|
98
|
+
|
|
99
|
+
if (isDebug) console.log('Chart config', config)
|
|
100
|
+
|
|
101
|
+
const DataTable = config?.table?.showVertical ? DataTable_vert : DataTable_horiz
|
|
102
|
+
|
|
63
103
|
// Destructure items from config for more readable JSX
|
|
64
104
|
let { legend, title, description, visualizationType } = config
|
|
65
105
|
|
|
@@ -102,26 +142,121 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
102
142
|
}
|
|
103
143
|
}
|
|
104
144
|
|
|
145
|
+
const reloadURLData = async () => {
|
|
146
|
+
if (config.dataUrl) {
|
|
147
|
+
const dataUrl = new URL(config.runtimeDataUrl || config.dataUrl)
|
|
148
|
+
let qsParams = Object.fromEntries(new URLSearchParams(dataUrl.search))
|
|
149
|
+
|
|
150
|
+
let isUpdateNeeded = false
|
|
151
|
+
config.filters.forEach(filter => {
|
|
152
|
+
if (filter.type === 'url' && qsParams[filter.queryParameter] !== decodeURIComponent(filter.active)) {
|
|
153
|
+
qsParams[filter.queryParameter] = filter.active
|
|
154
|
+
isUpdateNeeded = true
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
if ((!config.formattedData || config.formattedData.urlFiltered) && !isUpdateNeeded) return
|
|
159
|
+
|
|
160
|
+
let dataUrlFinal = `${dataUrl.origin}${dataUrl.pathname}${Object.keys(qsParams)
|
|
161
|
+
.map((param, i) => {
|
|
162
|
+
let qs = i === 0 ? '?' : '&'
|
|
163
|
+
qs += param + '='
|
|
164
|
+
qs += qsParams[param]
|
|
165
|
+
return qs
|
|
166
|
+
})
|
|
167
|
+
.join('')}`
|
|
168
|
+
|
|
169
|
+
let data
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const regex = /(?:\.([^.]+))?$/
|
|
173
|
+
|
|
174
|
+
const ext = regex.exec(dataUrl.pathname)[1]
|
|
175
|
+
if ('csv' === ext) {
|
|
176
|
+
data = await fetch(dataUrlFinal)
|
|
177
|
+
.then(response => response.text())
|
|
178
|
+
.then(responseText => {
|
|
179
|
+
const parsedCsv = Papa.parse(responseText, {
|
|
180
|
+
header: true,
|
|
181
|
+
dynamicTyping: true,
|
|
182
|
+
skipEmptyLines: true
|
|
183
|
+
})
|
|
184
|
+
return parsedCsv.data
|
|
185
|
+
})
|
|
186
|
+
} else if ('json' === ext) {
|
|
187
|
+
data = await fetch(dataUrlFinal).then(response => response.json())
|
|
188
|
+
} else {
|
|
189
|
+
data = []
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
console.error(`Cannot parse URL: ${dataUrlFinal}`)
|
|
193
|
+
data = []
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (config.dataDescription) {
|
|
197
|
+
data = transform.autoStandardize(data)
|
|
198
|
+
data = transform.developerStandardize(data, config.dataDescription)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
Object.assign(data, { urlFiltered: true })
|
|
202
|
+
|
|
203
|
+
updateConfig({ ...config, runtimeDataUrl: dataUrlFinal, data, formattedData: data })
|
|
204
|
+
|
|
205
|
+
if (data) {
|
|
206
|
+
setStateData(data)
|
|
207
|
+
setExcludedData(data)
|
|
208
|
+
setFilteredData(filterData(config.filters, data))
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
105
213
|
const handleLineType = lineType => {
|
|
106
214
|
switch (lineType) {
|
|
107
215
|
case 'dashed-sm':
|
|
108
216
|
return '5 5'
|
|
217
|
+
case 'Dashed Small':
|
|
218
|
+
return '5 5'
|
|
109
219
|
case 'dashed-md':
|
|
110
220
|
return '10 5'
|
|
221
|
+
case 'Dashed Medium':
|
|
222
|
+
return '10 5'
|
|
111
223
|
case 'dashed-lg':
|
|
112
224
|
return '15 5'
|
|
225
|
+
case 'Dashed Large':
|
|
226
|
+
return '15 5'
|
|
113
227
|
default:
|
|
114
228
|
return 0
|
|
115
229
|
}
|
|
116
230
|
}
|
|
117
231
|
|
|
232
|
+
const lineOptions = [
|
|
233
|
+
{
|
|
234
|
+
value: 'Dashed Small',
|
|
235
|
+
key: 'dashed-sm'
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
value: 'Dashed Medium',
|
|
239
|
+
key: 'dashed-md'
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
value: 'Dashed Large',
|
|
243
|
+
key: 'dashed-lg'
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
value: 'Solid Line',
|
|
247
|
+
key: 'solid-line'
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
|
|
118
251
|
const loadConfig = async () => {
|
|
119
252
|
let response = configObj || (await (await fetch(configUrl)).json())
|
|
120
253
|
|
|
121
254
|
// If data is included through a URL, fetch that and store
|
|
122
255
|
let data = response.formattedData || response.data || {}
|
|
123
256
|
|
|
124
|
-
|
|
257
|
+
const urlFilters = response.filters ? (response.filters.filter(filter => filter.type === 'url').length > 0 ? true : false) : false
|
|
258
|
+
|
|
259
|
+
if (response.dataUrl && !urlFilters) {
|
|
125
260
|
try {
|
|
126
261
|
const regex = /(?:\.([^.]+))?$/
|
|
127
262
|
|
|
@@ -130,10 +265,20 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
130
265
|
data = await fetch(response.dataUrl + `?v=${cacheBustingString()}`)
|
|
131
266
|
.then(response => response.text())
|
|
132
267
|
.then(responseText => {
|
|
268
|
+
// for every comma NOT inside quotes, replace with a pipe delimiter
|
|
269
|
+
// - this will let commas inside the quotes not be parsed as a new column
|
|
270
|
+
// - Limitation: if a delimiter other than comma is used in the csv this will break
|
|
271
|
+
// Examples of other delimiters that would break: tab
|
|
272
|
+
responseText = responseText.replace(/(".*?")|,/g, (...m) => m[1] || '|')
|
|
273
|
+
// now strip the double quotes
|
|
274
|
+
responseText = responseText.replace(/["]+/g, '')
|
|
133
275
|
const parsedCsv = Papa.parse(responseText, {
|
|
276
|
+
//quotes: "true", // dont need these
|
|
277
|
+
//quoteChar: "'", // has no effect that I can tell
|
|
134
278
|
header: true,
|
|
135
279
|
dynamicTyping: true,
|
|
136
|
-
skipEmptyLines: true
|
|
280
|
+
skipEmptyLines: true,
|
|
281
|
+
delimiter: '|' // we are using pipe symbol as delimiter so setting this explicitly for Papa.parse
|
|
137
282
|
})
|
|
138
283
|
return parsedCsv.data
|
|
139
284
|
})
|
|
@@ -158,13 +303,22 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
158
303
|
setExcludedData(data)
|
|
159
304
|
}
|
|
160
305
|
|
|
306
|
+
// force showVertical for data tables false if it does not exist
|
|
307
|
+
if (response !== undefined && response.table !== undefined) {
|
|
308
|
+
if (!response.table || !response.table.showVertical) {
|
|
309
|
+
response.table = response.table || {}
|
|
310
|
+
response.table.showVertical = false
|
|
311
|
+
}
|
|
312
|
+
}
|
|
161
313
|
let newConfig = { ...defaults, ...response }
|
|
162
314
|
if (newConfig.visualizationType === 'Box Plot') {
|
|
163
315
|
newConfig.legend.hide = true
|
|
164
316
|
}
|
|
165
317
|
if (undefined === newConfig.table.show) newConfig.table.show = !isDashboard
|
|
166
318
|
|
|
167
|
-
|
|
319
|
+
const processedConfig = { ...(await coveUpdateWorker(newConfig)) }
|
|
320
|
+
|
|
321
|
+
updateConfig(processedConfig, data)
|
|
168
322
|
}
|
|
169
323
|
|
|
170
324
|
const updateConfig = (newConfig, dataOverride = undefined) => {
|
|
@@ -177,7 +331,6 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
177
331
|
}
|
|
178
332
|
})
|
|
179
333
|
|
|
180
|
-
// Loop through and set initial data with exclusions - this should persist through any following data transformations (ie. filters)
|
|
181
334
|
let newExcludedData
|
|
182
335
|
|
|
183
336
|
if (newConfig.exclusions && newConfig.exclusions.active) {
|
|
@@ -219,7 +372,8 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
219
372
|
|
|
220
373
|
newConfig.filters[index].values = filterValues
|
|
221
374
|
// Initial filter should be active
|
|
222
|
-
|
|
375
|
+
|
|
376
|
+
newConfig.filters[index].active = newConfig.filters[index].active || filterValues[0]
|
|
223
377
|
newConfig.filters[index].filterStyle = newConfig.filters[index].filterStyle ? newConfig.filters[index].filterStyle : 'dropdown'
|
|
224
378
|
})
|
|
225
379
|
currentData = filterData(newConfig.filters, newExcludedData)
|
|
@@ -373,11 +527,15 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
373
527
|
newConfig.runtime.barSeriesKeys = []
|
|
374
528
|
newConfig.runtime.lineSeriesKeys = []
|
|
375
529
|
newConfig.runtime.areaSeriesKeys = []
|
|
530
|
+
newConfig.runtime.forecastingSeriesKeys = []
|
|
376
531
|
|
|
377
532
|
newConfig.series.forEach(series => {
|
|
378
533
|
if (series.type === 'Area Chart') {
|
|
379
534
|
newConfig.runtime.areaSeriesKeys.push(series)
|
|
380
535
|
}
|
|
536
|
+
if (series.type === 'Forecasting') {
|
|
537
|
+
newConfig.runtime.forecastingSeriesKeys.push(series)
|
|
538
|
+
}
|
|
381
539
|
if (series.type === 'Bar') {
|
|
382
540
|
newConfig.runtime.barSeriesKeys.push(series.dataKey)
|
|
383
541
|
}
|
|
@@ -386,6 +544,17 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
386
544
|
}
|
|
387
545
|
})
|
|
388
546
|
}
|
|
547
|
+
|
|
548
|
+
if (newConfig.visualizationType === 'Forecasting' && newConfig.series) {
|
|
549
|
+
newConfig.runtime.forecastingSeriesKeys = []
|
|
550
|
+
|
|
551
|
+
newConfig.series.forEach(series => {
|
|
552
|
+
if (series.type === 'Forecasting') {
|
|
553
|
+
newConfig.runtime.forecastingSeriesKeys.push(series)
|
|
554
|
+
}
|
|
555
|
+
})
|
|
556
|
+
}
|
|
557
|
+
|
|
389
558
|
if (newConfig.visualizationType === 'Area Chart' && newConfig.series) {
|
|
390
559
|
newConfig.runtime.areaSeriesKeys = []
|
|
391
560
|
|
|
@@ -416,14 +585,17 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
416
585
|
|
|
417
586
|
data.forEach(row => {
|
|
418
587
|
let add = true
|
|
419
|
-
filters
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
588
|
+
filters
|
|
589
|
+
.filter(filter => filter.type !== 'url')
|
|
590
|
+
.forEach(filter => {
|
|
591
|
+
if (row[filter.columnName] != filter.active) {
|
|
592
|
+
add = false
|
|
593
|
+
}
|
|
594
|
+
})
|
|
424
595
|
|
|
425
596
|
if (add) filteredData.push(row)
|
|
426
597
|
})
|
|
598
|
+
|
|
427
599
|
return filteredData
|
|
428
600
|
}
|
|
429
601
|
|
|
@@ -498,6 +670,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
498
670
|
loadConfig()
|
|
499
671
|
}, []) // eslint-disable-line
|
|
500
672
|
|
|
673
|
+
useEffect(() => {
|
|
674
|
+
reloadURLData()
|
|
675
|
+
}, [JSON.stringify(config.filters)])
|
|
676
|
+
|
|
501
677
|
/**
|
|
502
678
|
* When cove has a config and container ref publish the cove_loaded event.
|
|
503
679
|
*/
|
|
@@ -594,7 +770,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
594
770
|
const newSeriesHighlight = []
|
|
595
771
|
|
|
596
772
|
// If we're highlighting all the series, reset them
|
|
597
|
-
if (seriesHighlight.length + 1 === config.runtime.seriesKeys.length && !config.legend.dynamicLegend) {
|
|
773
|
+
if (seriesHighlight.length + 1 === config.runtime.seriesKeys.length && !config.legend.dynamicLegend && config.visualizationType !== 'Forecasting') {
|
|
598
774
|
highlightReset()
|
|
599
775
|
return
|
|
600
776
|
}
|
|
@@ -632,10 +808,12 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
632
808
|
|
|
633
809
|
const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
|
|
634
810
|
|
|
635
|
-
const parseDate = dateString => {
|
|
811
|
+
const parseDate = (dateString, showError = true) => {
|
|
636
812
|
let date = timeParse(config.runtime[section].dateParseFormat)(dateString)
|
|
637
813
|
if (!date) {
|
|
638
|
-
|
|
814
|
+
if (showError) {
|
|
815
|
+
config.runtime.editorErrorMessage = `Error parsing date "${dateString}". Try reviewing your data and date parse settings in the X Axis section.`
|
|
816
|
+
}
|
|
639
817
|
return new Date()
|
|
640
818
|
} else {
|
|
641
819
|
return date
|
|
@@ -794,6 +972,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
794
972
|
// Select appropriate chart type
|
|
795
973
|
const chartComponents = {
|
|
796
974
|
'Paired Bar': <LinearChart />,
|
|
975
|
+
Forecasting: <LinearChart />,
|
|
797
976
|
Bar: <LinearChart />,
|
|
798
977
|
Line: <LinearChart />,
|
|
799
978
|
Combo: <LinearChart />,
|
|
@@ -805,6 +984,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
805
984
|
}
|
|
806
985
|
|
|
807
986
|
const missingRequiredSections = () => {
|
|
987
|
+
if (config.visualizationType === 'Forecasting') return false // skip required checks for now.
|
|
808
988
|
if (config.visualizationType === 'Pie') {
|
|
809
989
|
if (undefined === config?.yAxis.dataKey) {
|
|
810
990
|
return true
|
|
@@ -822,10 +1002,115 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
822
1002
|
return false
|
|
823
1003
|
}
|
|
824
1004
|
|
|
1005
|
+
// used for Additional Column
|
|
1006
|
+
const displayDataAsText = (value, columnName) => {
|
|
1007
|
+
if (value === null || value === '' || value === undefined) {
|
|
1008
|
+
return ''
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (typeof value === 'string' && value.length > 0 && config.legend.type === 'equalnumber') {
|
|
1012
|
+
return value
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
let formattedValue = value
|
|
1016
|
+
|
|
1017
|
+
let columnObj //= config.columns[columnName]
|
|
1018
|
+
// config.columns not an array but a hash of objects
|
|
1019
|
+
if (Object.keys(config.columns).length > 0) {
|
|
1020
|
+
Object.keys(config.columns).forEach(function (key) {
|
|
1021
|
+
var column = config.columns[key]
|
|
1022
|
+
// add if not the index AND it is enabled to be added to data table
|
|
1023
|
+
if (column.name === columnName) {
|
|
1024
|
+
columnObj = column
|
|
1025
|
+
}
|
|
1026
|
+
})
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
if (columnObj === undefined) {
|
|
1030
|
+
// then use left axis config
|
|
1031
|
+
columnObj = config.type === 'chart' ? config.dataFormat : config.primary
|
|
1032
|
+
// NOTE: Left Value Axis uses different names
|
|
1033
|
+
// so map them below so the code below works
|
|
1034
|
+
// - copy commas to useCommas to work below
|
|
1035
|
+
columnObj['useCommas'] = columnObj.commas
|
|
1036
|
+
// - copy roundTo to roundToPlace to work below
|
|
1037
|
+
columnObj['roundToPlace'] = columnObj.roundTo ? columnObj.roundTo : ''
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
if (columnObj) {
|
|
1041
|
+
// If value is a number, apply specific formattings
|
|
1042
|
+
let hasDecimal = false
|
|
1043
|
+
let decimalPoint = 0
|
|
1044
|
+
if (Number(value)) {
|
|
1045
|
+
if (columnObj.roundToPlace >= 0) {
|
|
1046
|
+
hasDecimal = columnObj.roundToPlace ? columnObj.roundToPlace !== '' || columnObj.roundToPlace !== null : false
|
|
1047
|
+
decimalPoint = columnObj.roundToPlace ? Number(columnObj.roundToPlace) : 0
|
|
1048
|
+
|
|
1049
|
+
// Rounding
|
|
1050
|
+
if (columnObj.hasOwnProperty('roundToPlace') && hasDecimal) {
|
|
1051
|
+
formattedValue = Number(value).toFixed(decimalPoint)
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if (columnObj.hasOwnProperty('useCommas') && columnObj.useCommas === true) {
|
|
1056
|
+
// Formats number to string with commas - allows up to 5 decimal places, if rounding is not defined.
|
|
1057
|
+
// Otherwise, uses the rounding value set at 'columnObj.roundToPlace'.
|
|
1058
|
+
formattedValue = Number(value).toLocaleString('en-US', {
|
|
1059
|
+
style: 'decimal',
|
|
1060
|
+
minimumFractionDigits: hasDecimal ? decimalPoint : 0,
|
|
1061
|
+
maximumFractionDigits: hasDecimal ? decimalPoint : 5
|
|
1062
|
+
})
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// add prefix and suffix if set
|
|
1067
|
+
formattedValue = (columnObj.prefix || '') + formattedValue + (columnObj.suffix || '')
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
return formattedValue
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// this is passed DOWN into the various components
|
|
1074
|
+
// then they do a lookup based on the bin number as index into here (TT)
|
|
1075
|
+
const applyLegendToRow = rowObj => {
|
|
1076
|
+
try {
|
|
1077
|
+
if (!rowObj) throw new Error('COVE: No rowObj in applyLegendToRow')
|
|
1078
|
+
// Navigation map
|
|
1079
|
+
if ('navigation' === config.type) {
|
|
1080
|
+
let mapColorPalette = colorPalettes[config.color] || colorPalettes['bluegreenreverse']
|
|
1081
|
+
return generateColorsArray(mapColorPalette[3])
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
let hash = hashObj(rowObj)
|
|
1085
|
+
|
|
1086
|
+
if (legendMemo.current.has(hash)) {
|
|
1087
|
+
let idx = legendMemo.current.get(hash)
|
|
1088
|
+
if (runtimeLegend[idx]?.disabled) return false
|
|
1089
|
+
|
|
1090
|
+
// DEV-784 changed to use bin prop to get color instead of idx
|
|
1091
|
+
// bc we re-order legend when showSpecialClassesLast is checked
|
|
1092
|
+
let legendBinColor = runtimeLegend.find(o => o.bin === idx)?.color
|
|
1093
|
+
return generateColorsArray(legendBinColor, runtimeLegend[idx]?.special)
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// Fail state
|
|
1097
|
+
return generateColorsArray()
|
|
1098
|
+
} catch (e) {
|
|
1099
|
+
console.error('COVE: ', e) // eslint-disable-line
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
825
1103
|
const clean = data => {
|
|
1104
|
+
// cleaning is deleting data we need in forecasting charts.
|
|
1105
|
+
if (config.visualizationType === 'Forecasting') return data
|
|
826
1106
|
return config?.xAxis?.dataKey ? transform.cleanData(data, config.xAxis.dataKey) : data
|
|
827
1107
|
}
|
|
828
1108
|
|
|
1109
|
+
// required for DataTable
|
|
1110
|
+
const displayGeoName = key => {
|
|
1111
|
+
return key
|
|
1112
|
+
}
|
|
1113
|
+
|
|
829
1114
|
// Prevent render if loading
|
|
830
1115
|
let body = <Loading />
|
|
831
1116
|
|
|
@@ -841,7 +1126,6 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
841
1126
|
{!missingRequiredSections() && !config.newViz && (
|
|
842
1127
|
<div className='cdc-chart-inner-container'>
|
|
843
1128
|
{/* Title */}
|
|
844
|
-
|
|
845
1129
|
{title && config.showTitle && (
|
|
846
1130
|
<div role='heading' className={`chart-title ${config.theme} cove-component__header`} aria-level={2}>
|
|
847
1131
|
{config && <sup className='superTitle'>{parse(config.superTitle || '')}</sup>}
|
|
@@ -886,13 +1170,42 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
886
1170
|
{description && config.visualizationType !== 'Spark Line' && <div className='subtext'>{parse(description)}</div>}
|
|
887
1171
|
|
|
888
1172
|
{/* buttons */}
|
|
889
|
-
<
|
|
890
|
-
{config.table.showDownloadImgButton && <
|
|
891
|
-
{config.table.showDownloadPdfButton && <
|
|
892
|
-
</
|
|
1173
|
+
<MediaControls.Section classes={['download-buttons']}>
|
|
1174
|
+
{config.table.showDownloadImgButton && <MediaControls.Button text='Download Image' title='Download Chart as Image' type='image' state={config} elementToCapture={imageId} />}
|
|
1175
|
+
{config.table.showDownloadPdfButton && <MediaControls.Button text='Download PDF' title='Download Chart as PDF' type='pdf' state={config} elementToCapture={imageId} />}
|
|
1176
|
+
</MediaControls.Section>
|
|
893
1177
|
|
|
894
1178
|
{/* Data Table */}
|
|
895
|
-
{config.xAxis.dataKey && config.table.show && config.visualizationType !== 'Spark Line' &&
|
|
1179
|
+
{config.xAxis.dataKey && config.table.show && config.visualizationType !== 'Spark Line' && (
|
|
1180
|
+
<DataTable
|
|
1181
|
+
config={config}
|
|
1182
|
+
rawData={config.data}
|
|
1183
|
+
runtimeData={filteredData || excludedData}
|
|
1184
|
+
//navigationHandler={navigationHandler}
|
|
1185
|
+
expandDataTable={config.table.expanded}
|
|
1186
|
+
//headerColor={general.headerColor}
|
|
1187
|
+
columns={config.columns}
|
|
1188
|
+
showDownloadButton={config.general.showDownloadButton}
|
|
1189
|
+
runtimeLegend={dynamicLegendItems}
|
|
1190
|
+
displayDataAsText={displayDataAsText}
|
|
1191
|
+
displayGeoName={displayGeoName}
|
|
1192
|
+
applyLegendToRow={applyLegendToRow}
|
|
1193
|
+
tableTitle={config.table.label}
|
|
1194
|
+
indexTitle={config.table.indexLabel}
|
|
1195
|
+
vizTitle={title}
|
|
1196
|
+
viewport={currentViewport}
|
|
1197
|
+
parseDate={parseDate}
|
|
1198
|
+
formatDate={formatDate}
|
|
1199
|
+
formatNumber={formatNumber}
|
|
1200
|
+
tabbingId={handleChartTabbing}
|
|
1201
|
+
showDownloadImgButton={config.showDownloadImgButton}
|
|
1202
|
+
showDownloadPdfButton={config.showDownloadPdfButton}
|
|
1203
|
+
innerContainerRef={innerContainerRef}
|
|
1204
|
+
outerContainerRef={outerContainerRef}
|
|
1205
|
+
imageRef={imageId}
|
|
1206
|
+
isDebug={isDebug}
|
|
1207
|
+
/>
|
|
1208
|
+
)}
|
|
896
1209
|
{config?.footnotes && <section className='footnotes'>{parse(config.footnotes)}</section>}
|
|
897
1210
|
{/* show pdf or image button */}
|
|
898
1211
|
</div>
|
|
@@ -939,10 +1252,14 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
939
1252
|
filterData,
|
|
940
1253
|
imageId,
|
|
941
1254
|
handleLineType,
|
|
1255
|
+
lineOptions,
|
|
942
1256
|
isNumber,
|
|
943
1257
|
getTextWidth,
|
|
944
1258
|
twoColorPalette,
|
|
945
|
-
isDebug
|
|
1259
|
+
isDebug,
|
|
1260
|
+
setSharedFilter,
|
|
1261
|
+
setSharedFilterValue,
|
|
1262
|
+
dashboardConfig
|
|
946
1263
|
}
|
|
947
1264
|
|
|
948
1265
|
const classes = ['cdc-open-viz-module', 'type-chart', `${currentViewport}`, `font-${config.fontSize}`, `${config.theme}`]
|