@cdc/chart 4.23.4 → 4.23.5
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 +52384 -50875
- package/examples/feature/__data__/planet-example-data.json +2 -19
- 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/planet-chart-logaritmic-config.json +170 -0
- package/examples/feature/boxplot/valid-boxplot.csv +17 -0
- package/examples/feature/filters/filter-testing.json +37 -3
- package/examples/feature/forecasting/random_data.csv +366 -0
- package/examples/feature/line/line-chart.json +2 -2
- 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/index.html +8 -8
- package/package.json +2 -2
- package/src/CdcChart.jsx +294 -14
- package/src/components/AreaChart.jsx +27 -20
- package/src/components/BarChart.jsx +85 -25
- package/src/components/DeviationBar.jsx +32 -32
- package/src/components/EditorPanel.jsx +1105 -184
- package/src/components/Legend.jsx +39 -3
- package/src/components/LineChart.jsx +1 -8
- package/src/components/LinearChart.jsx +121 -270
- package/src/data/initial-state.js +18 -3
- package/src/hooks/useHighlightedBars.js +154 -0
- package/src/hooks/useMinMax.js +92 -0
- package/src/hooks/useReduceData.js +31 -57
- package/src/hooks/useScales.js +202 -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,7 +27,6 @@ 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'
|
|
@@ -42,6 +41,36 @@ import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
|
|
|
42
41
|
import isNumber from '@cdc/core/helpers/isNumber'
|
|
43
42
|
|
|
44
43
|
import './scss/main.scss'
|
|
44
|
+
// load both then config below determines which to use
|
|
45
|
+
import DataTable_horiz from './components/DataTable'
|
|
46
|
+
import DataTable_vert from '@cdc/core/components/DataTable'
|
|
47
|
+
|
|
48
|
+
const generateColorsArray = (color = '#000000', special = false) => {
|
|
49
|
+
let colorObj = chroma(color)
|
|
50
|
+
let hoverColor = special ? colorObj.brighten(0.5).hex() : colorObj.saturate(1.3).hex()
|
|
51
|
+
|
|
52
|
+
return [color, hoverColor, colorObj.darken(0.3).hex()]
|
|
53
|
+
}
|
|
54
|
+
const hashObj = row => {
|
|
55
|
+
try {
|
|
56
|
+
if (!row) throw new Error('No row supplied to hashObj')
|
|
57
|
+
|
|
58
|
+
let str = JSON.stringify(row)
|
|
59
|
+
let hash = 0
|
|
60
|
+
|
|
61
|
+
if (str.length === 0) return hash
|
|
62
|
+
|
|
63
|
+
for (let i = 0; i < str.length; i++) {
|
|
64
|
+
let char = str.charCodeAt(i)
|
|
65
|
+
hash = (hash << 5) - hash + char
|
|
66
|
+
hash = hash & hash
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return hash
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.error('COVE: ', e) // eslint-disable-line
|
|
72
|
+
}
|
|
73
|
+
}
|
|
45
74
|
|
|
46
75
|
export default function CdcChart({ configUrl, config: configObj, isEditor = false, isDebug = false, isDashboard = false, setConfig: setParentConfig, setEditing, hostname, link }) {
|
|
47
76
|
const transform = new DataTransform()
|
|
@@ -60,6 +89,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
60
89
|
const [dynamicLegendItems, setDynamicLegendItems] = useState([])
|
|
61
90
|
const [imageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`)
|
|
62
91
|
|
|
92
|
+
let legendMemo = useRef(new Map()) // map collection
|
|
93
|
+
let innerContainerRef = useRef()
|
|
94
|
+
|
|
95
|
+
if (isDebug) console.log('Chart config', config)
|
|
96
|
+
|
|
97
|
+
const DataTable = config?.table?.showVertical ? DataTable_vert : DataTable_horiz
|
|
98
|
+
|
|
63
99
|
// Destructure items from config for more readable JSX
|
|
64
100
|
let { legend, title, description, visualizationType } = config
|
|
65
101
|
|
|
@@ -102,26 +138,121 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
102
138
|
}
|
|
103
139
|
}
|
|
104
140
|
|
|
141
|
+
const reloadURLData = async () => {
|
|
142
|
+
if (config.dataUrl) {
|
|
143
|
+
const dataUrl = new URL(config.runtimeDataUrl || config.dataUrl)
|
|
144
|
+
let qsParams = Object.fromEntries(new URLSearchParams(dataUrl.search))
|
|
145
|
+
|
|
146
|
+
let isUpdateNeeded = false
|
|
147
|
+
config.filters.forEach(filter => {
|
|
148
|
+
if (filter.type === 'url' && qsParams[filter.queryParameter] !== decodeURIComponent(filter.active)) {
|
|
149
|
+
qsParams[filter.queryParameter] = filter.active
|
|
150
|
+
isUpdateNeeded = true
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
if ((!config.formattedData || config.formattedData.urlFiltered) && !isUpdateNeeded) return
|
|
155
|
+
|
|
156
|
+
let dataUrlFinal = `${dataUrl.origin}${dataUrl.pathname}${Object.keys(qsParams)
|
|
157
|
+
.map((param, i) => {
|
|
158
|
+
let qs = i === 0 ? '?' : '&'
|
|
159
|
+
qs += param + '='
|
|
160
|
+
qs += qsParams[param]
|
|
161
|
+
return qs
|
|
162
|
+
})
|
|
163
|
+
.join('')}`
|
|
164
|
+
|
|
165
|
+
let data
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const regex = /(?:\.([^.]+))?$/
|
|
169
|
+
|
|
170
|
+
const ext = regex.exec(dataUrl.pathname)[1]
|
|
171
|
+
if ('csv' === ext) {
|
|
172
|
+
data = await fetch(dataUrlFinal)
|
|
173
|
+
.then(response => response.text())
|
|
174
|
+
.then(responseText => {
|
|
175
|
+
const parsedCsv = Papa.parse(responseText, {
|
|
176
|
+
header: true,
|
|
177
|
+
dynamicTyping: true,
|
|
178
|
+
skipEmptyLines: true
|
|
179
|
+
})
|
|
180
|
+
return parsedCsv.data
|
|
181
|
+
})
|
|
182
|
+
} else if ('json' === ext) {
|
|
183
|
+
data = await fetch(dataUrlFinal).then(response => response.json())
|
|
184
|
+
} else {
|
|
185
|
+
data = []
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
console.error(`Cannot parse URL: ${dataUrlFinal}`)
|
|
189
|
+
data = []
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (config.dataDescription) {
|
|
193
|
+
data = transform.autoStandardize(data)
|
|
194
|
+
data = transform.developerStandardize(data, config.dataDescription)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
Object.assign(data, { urlFiltered: true })
|
|
198
|
+
|
|
199
|
+
updateConfig({ ...config, runtimeDataUrl: dataUrlFinal, data, formattedData: data })
|
|
200
|
+
|
|
201
|
+
if (data) {
|
|
202
|
+
setStateData(data)
|
|
203
|
+
setExcludedData(data)
|
|
204
|
+
setFilteredData(filterData(config.filters, data))
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
105
209
|
const handleLineType = lineType => {
|
|
106
210
|
switch (lineType) {
|
|
107
211
|
case 'dashed-sm':
|
|
108
212
|
return '5 5'
|
|
213
|
+
case 'Dashed Small':
|
|
214
|
+
return '5 5'
|
|
109
215
|
case 'dashed-md':
|
|
110
216
|
return '10 5'
|
|
217
|
+
case 'Dashed Medium':
|
|
218
|
+
return '10 5'
|
|
111
219
|
case 'dashed-lg':
|
|
112
220
|
return '15 5'
|
|
221
|
+
case 'Dashed Large':
|
|
222
|
+
return '15 5'
|
|
113
223
|
default:
|
|
114
224
|
return 0
|
|
115
225
|
}
|
|
116
226
|
}
|
|
117
227
|
|
|
228
|
+
const lineOptions = [
|
|
229
|
+
{
|
|
230
|
+
value: 'Dashed Small',
|
|
231
|
+
key: 'dashed-sm'
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
value: 'Dashed Medium',
|
|
235
|
+
key: 'dashed-md'
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
value: 'Dashed Large',
|
|
239
|
+
key: 'dashed-lg'
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
value: 'Solid Line',
|
|
243
|
+
key: 'solid-line'
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
|
|
118
247
|
const loadConfig = async () => {
|
|
119
248
|
let response = configObj || (await (await fetch(configUrl)).json())
|
|
120
249
|
|
|
121
250
|
// If data is included through a URL, fetch that and store
|
|
122
251
|
let data = response.formattedData || response.data || {}
|
|
123
252
|
|
|
124
|
-
|
|
253
|
+
const urlFilters = response.filters ? (response.filters.filter(filter => filter.type === 'url').length > 0 ? true : false) : false
|
|
254
|
+
|
|
255
|
+
if (response.dataUrl && !urlFilters) {
|
|
125
256
|
try {
|
|
126
257
|
const regex = /(?:\.([^.]+))?$/
|
|
127
258
|
|
|
@@ -158,6 +289,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
158
289
|
setExcludedData(data)
|
|
159
290
|
}
|
|
160
291
|
|
|
292
|
+
// force showVertical for data tables false if it does not exist
|
|
293
|
+
if (response !== undefined && response.table !== undefined) {
|
|
294
|
+
if (!response.table || !response.table.showVertical) {
|
|
295
|
+
response.table = response.table || {}
|
|
296
|
+
response.table.showVertical = false
|
|
297
|
+
}
|
|
298
|
+
}
|
|
161
299
|
let newConfig = { ...defaults, ...response }
|
|
162
300
|
if (newConfig.visualizationType === 'Box Plot') {
|
|
163
301
|
newConfig.legend.hide = true
|
|
@@ -219,7 +357,8 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
219
357
|
|
|
220
358
|
newConfig.filters[index].values = filterValues
|
|
221
359
|
// Initial filter should be active
|
|
222
|
-
|
|
360
|
+
|
|
361
|
+
newConfig.filters[index].active = newConfig.filters[index].active || filterValues[0]
|
|
223
362
|
newConfig.filters[index].filterStyle = newConfig.filters[index].filterStyle ? newConfig.filters[index].filterStyle : 'dropdown'
|
|
224
363
|
})
|
|
225
364
|
currentData = filterData(newConfig.filters, newExcludedData)
|
|
@@ -416,14 +555,17 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
416
555
|
|
|
417
556
|
data.forEach(row => {
|
|
418
557
|
let add = true
|
|
419
|
-
filters
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
558
|
+
filters
|
|
559
|
+
.filter(filter => filter.type !== 'url')
|
|
560
|
+
.forEach(filter => {
|
|
561
|
+
if (row[filter.columnName] != filter.active) {
|
|
562
|
+
add = false
|
|
563
|
+
}
|
|
564
|
+
})
|
|
424
565
|
|
|
425
566
|
if (add) filteredData.push(row)
|
|
426
567
|
})
|
|
568
|
+
|
|
427
569
|
return filteredData
|
|
428
570
|
}
|
|
429
571
|
|
|
@@ -498,6 +640,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
498
640
|
loadConfig()
|
|
499
641
|
}, []) // eslint-disable-line
|
|
500
642
|
|
|
643
|
+
useEffect(() => {
|
|
644
|
+
reloadURLData()
|
|
645
|
+
}, [JSON.stringify(config.filters)])
|
|
646
|
+
|
|
501
647
|
/**
|
|
502
648
|
* When cove has a config and container ref publish the cove_loaded event.
|
|
503
649
|
*/
|
|
@@ -632,10 +778,12 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
632
778
|
|
|
633
779
|
const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
|
|
634
780
|
|
|
635
|
-
const parseDate = dateString => {
|
|
781
|
+
const parseDate = (dateString, showError = true) => {
|
|
636
782
|
let date = timeParse(config.runtime[section].dateParseFormat)(dateString)
|
|
637
783
|
if (!date) {
|
|
638
|
-
|
|
784
|
+
if (showError) {
|
|
785
|
+
config.runtime.editorErrorMessage = `Error parsing date "${dateString}". Try reviewing your data and date parse settings in the X Axis section.`
|
|
786
|
+
}
|
|
639
787
|
return new Date()
|
|
640
788
|
} else {
|
|
641
789
|
return date
|
|
@@ -822,10 +970,113 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
822
970
|
return false
|
|
823
971
|
}
|
|
824
972
|
|
|
973
|
+
// used for Additional Column
|
|
974
|
+
const displayDataAsText = (value, columnName) => {
|
|
975
|
+
if (value === null || value === '' || value === undefined) {
|
|
976
|
+
return ''
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
if (typeof value === 'string' && value.length > 0 && config.legend.type === 'equalnumber') {
|
|
980
|
+
return value
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
let formattedValue = value
|
|
984
|
+
|
|
985
|
+
let columnObj //= config.columns[columnName]
|
|
986
|
+
// config.columns not an array but a hash of objects
|
|
987
|
+
if (Object.keys(config.columns).length > 0) {
|
|
988
|
+
Object.keys(config.columns).forEach(function (key) {
|
|
989
|
+
var column = config.columns[key]
|
|
990
|
+
// add if not the index AND it is enabled to be added to data table
|
|
991
|
+
if (column.name === columnName) {
|
|
992
|
+
columnObj = column
|
|
993
|
+
}
|
|
994
|
+
})
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (columnObj === undefined) {
|
|
998
|
+
// then use left axis config
|
|
999
|
+
columnObj = config.type === 'chart' ? config.dataFormat : config.primary
|
|
1000
|
+
// NOTE: Left Value Axis uses different names
|
|
1001
|
+
// so map them below so the code below works
|
|
1002
|
+
// - copy commas to useCommas to work below
|
|
1003
|
+
columnObj['useCommas'] = columnObj.commas
|
|
1004
|
+
// - copy roundTo to roundToPlace to work below
|
|
1005
|
+
columnObj['roundToPlace'] = columnObj.roundTo ? columnObj.roundTo : ''
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (columnObj) {
|
|
1009
|
+
// If value is a number, apply specific formattings
|
|
1010
|
+
let hasDecimal = false
|
|
1011
|
+
let decimalPoint = 0
|
|
1012
|
+
if (Number(value)) {
|
|
1013
|
+
if (columnObj.roundToPlace >= 0) {
|
|
1014
|
+
hasDecimal = columnObj.roundToPlace ? columnObj.roundToPlace !== '' || columnObj.roundToPlace !== null : false
|
|
1015
|
+
decimalPoint = columnObj.roundToPlace ? Number(columnObj.roundToPlace) : 0
|
|
1016
|
+
|
|
1017
|
+
// Rounding
|
|
1018
|
+
if (columnObj.hasOwnProperty('roundToPlace') && hasDecimal) {
|
|
1019
|
+
formattedValue = Number(value).toFixed(decimalPoint)
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
if (columnObj.hasOwnProperty('useCommas') && columnObj.useCommas === true) {
|
|
1024
|
+
// Formats number to string with commas - allows up to 5 decimal places, if rounding is not defined.
|
|
1025
|
+
// Otherwise, uses the rounding value set at 'columnObj.roundToPlace'.
|
|
1026
|
+
formattedValue = Number(value).toLocaleString('en-US', {
|
|
1027
|
+
style: 'decimal',
|
|
1028
|
+
minimumFractionDigits: hasDecimal ? decimalPoint : 0,
|
|
1029
|
+
maximumFractionDigits: hasDecimal ? decimalPoint : 5
|
|
1030
|
+
})
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// add prefix and suffix if set
|
|
1035
|
+
formattedValue = (columnObj.prefix || '') + formattedValue + (columnObj.suffix || '')
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
return formattedValue
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// this is passed DOWN into the various components
|
|
1042
|
+
// then they do a lookup based on the bin number as index into here (TT)
|
|
1043
|
+
const applyLegendToRow = rowObj => {
|
|
1044
|
+
try {
|
|
1045
|
+
if (!rowObj) throw new Error('COVE: No rowObj in applyLegendToRow')
|
|
1046
|
+
// Navigation map
|
|
1047
|
+
if ('navigation' === config.type) {
|
|
1048
|
+
let mapColorPalette = colorPalettes[config.color] || colorPalettes['bluegreenreverse']
|
|
1049
|
+
return generateColorsArray(mapColorPalette[3])
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
let hash = hashObj(rowObj)
|
|
1053
|
+
|
|
1054
|
+
if (legendMemo.current.has(hash)) {
|
|
1055
|
+
let idx = legendMemo.current.get(hash)
|
|
1056
|
+
if (runtimeLegend[idx]?.disabled) return false
|
|
1057
|
+
|
|
1058
|
+
// DEV-784 changed to use bin prop to get color instead of idx
|
|
1059
|
+
// bc we re-order legend when showSpecialClassesLast is checked
|
|
1060
|
+
let legendBinColor = runtimeLegend.find(o => o.bin === idx)?.color
|
|
1061
|
+
return generateColorsArray(legendBinColor, runtimeLegend[idx]?.special)
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Fail state
|
|
1065
|
+
return generateColorsArray()
|
|
1066
|
+
} catch (e) {
|
|
1067
|
+
console.error('COVE: ', e) // eslint-disable-line
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
825
1071
|
const clean = data => {
|
|
826
1072
|
return config?.xAxis?.dataKey ? transform.cleanData(data, config.xAxis.dataKey) : data
|
|
827
1073
|
}
|
|
828
1074
|
|
|
1075
|
+
// required for DataTable
|
|
1076
|
+
const displayGeoName = key => {
|
|
1077
|
+
return key
|
|
1078
|
+
}
|
|
1079
|
+
|
|
829
1080
|
// Prevent render if loading
|
|
830
1081
|
let body = <Loading />
|
|
831
1082
|
|
|
@@ -841,7 +1092,6 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
841
1092
|
{!missingRequiredSections() && !config.newViz && (
|
|
842
1093
|
<div className='cdc-chart-inner-container'>
|
|
843
1094
|
{/* Title */}
|
|
844
|
-
|
|
845
1095
|
{title && config.showTitle && (
|
|
846
1096
|
<div role='heading' className={`chart-title ${config.theme} cove-component__header`} aria-level={2}>
|
|
847
1097
|
{config && <sup className='superTitle'>{parse(config.superTitle || '')}</sup>}
|
|
@@ -892,7 +1142,36 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
892
1142
|
</CoveMediaControls.Section>
|
|
893
1143
|
|
|
894
1144
|
{/* Data Table */}
|
|
895
|
-
{config.xAxis.dataKey && config.table.show && config.visualizationType !== 'Spark Line' &&
|
|
1145
|
+
{config.xAxis.dataKey && config.table.show && config.visualizationType !== 'Spark Line' && (
|
|
1146
|
+
<DataTable
|
|
1147
|
+
config={config}
|
|
1148
|
+
rawData={config.data}
|
|
1149
|
+
runtimeData={filteredData || excludedData}
|
|
1150
|
+
//navigationHandler={navigationHandler}
|
|
1151
|
+
expandDataTable={config.table.expanded}
|
|
1152
|
+
//headerColor={general.headerColor}
|
|
1153
|
+
columns={config.columns}
|
|
1154
|
+
showDownloadButton={config.general.showDownloadButton}
|
|
1155
|
+
runtimeLegend={dynamicLegendItems}
|
|
1156
|
+
displayDataAsText={displayDataAsText}
|
|
1157
|
+
displayGeoName={displayGeoName}
|
|
1158
|
+
applyLegendToRow={applyLegendToRow}
|
|
1159
|
+
tableTitle={config.table.label}
|
|
1160
|
+
indexTitle={config.table.indexLabel}
|
|
1161
|
+
vizTitle={title}
|
|
1162
|
+
viewport={currentViewport}
|
|
1163
|
+
parseDate={parseDate}
|
|
1164
|
+
formatDate={formatDate}
|
|
1165
|
+
formatNumber={formatNumber}
|
|
1166
|
+
tabbingId={handleChartTabbing}
|
|
1167
|
+
showDownloadImgButton={config.showDownloadImgButton}
|
|
1168
|
+
showDownloadPdfButton={config.showDownloadPdfButton}
|
|
1169
|
+
innerContainerRef={innerContainerRef}
|
|
1170
|
+
outerContainerRef={outerContainerRef}
|
|
1171
|
+
imageRef={imageId}
|
|
1172
|
+
isDebug={isDebug}
|
|
1173
|
+
/>
|
|
1174
|
+
)}
|
|
896
1175
|
{config?.footnotes && <section className='footnotes'>{parse(config.footnotes)}</section>}
|
|
897
1176
|
{/* show pdf or image button */}
|
|
898
1177
|
</div>
|
|
@@ -939,6 +1218,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
939
1218
|
filterData,
|
|
940
1219
|
imageId,
|
|
941
1220
|
handleLineType,
|
|
1221
|
+
lineOptions,
|
|
942
1222
|
isNumber,
|
|
943
1223
|
getTextWidth,
|
|
944
1224
|
twoColorPalette,
|
|
@@ -40,23 +40,21 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
40
40
|
// Draw transparent bars over the chart to get tooltip data
|
|
41
41
|
// Turn DEBUG on for additional context.
|
|
42
42
|
if (!data) return
|
|
43
|
-
let barThickness = xMax / data
|
|
43
|
+
let barThickness = xMax / data.length
|
|
44
44
|
let barThicknessAdjusted = barThickness * (config.barThickness || 0.8)
|
|
45
45
|
let offset = (barThickness * (1 - (config.barThickness || 0.8))) / 2
|
|
46
46
|
|
|
47
47
|
// Tooltip helper for getting data to the closest date/category hovered.
|
|
48
48
|
const getXValueFromCoordinate = x => {
|
|
49
|
-
if (config.xAxis.type === 'categorical') {
|
|
49
|
+
if (config.xAxis.type === 'categorical' || config.visualizationType === 'Combo') {
|
|
50
50
|
let eachBand = xScale.step()
|
|
51
51
|
let numerator = x
|
|
52
52
|
const index = Math.floor(Number(numerator) / eachBand)
|
|
53
53
|
return xScale.domain()[index - 1] // fixes off by 1 error
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
if (config.xAxis.type === 'date') {
|
|
56
|
+
if (config.xAxis.type === 'date' && config.visualizationType !== 'Combo') {
|
|
57
57
|
const bisectDate = bisector(d => parseDate(d[config.xAxis.dataKey])).left
|
|
58
|
-
if (!x) return
|
|
59
|
-
if (!xScale) return
|
|
60
58
|
const x0 = xScale.invert(x)
|
|
61
59
|
const index = bisectDate(config.data, x0, 1)
|
|
62
60
|
const val = parseDate(config.data[index - 1][config.xAxis.dataKey])
|
|
@@ -123,8 +121,8 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
123
121
|
return config.xAxis.type === 'date' ? xScale(parseDate(d[config.xAxis.dataKey])) : xScale(d[config.xAxis.dataKey])
|
|
124
122
|
}
|
|
125
123
|
|
|
126
|
-
const handleY = (d, index) => {
|
|
127
|
-
return yScale(d[
|
|
124
|
+
const handleY = (d, index, s = undefined) => {
|
|
125
|
+
return yScale(d[s.dataKey])
|
|
128
126
|
}
|
|
129
127
|
|
|
130
128
|
return (
|
|
@@ -132,21 +130,29 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
132
130
|
<ErrorBoundary component='AreaChart'>
|
|
133
131
|
<Group className='area-chart' key='area-wrapper' left={Number(config.yAxis.size)}>
|
|
134
132
|
{(config.runtime.areaSeriesKeys || config.runtime.seriesKeys).map((s, index) => {
|
|
135
|
-
let
|
|
133
|
+
let seriesData = data.map(d => {
|
|
134
|
+
return {
|
|
135
|
+
[config.xAxis.dataKey]: d[config.xAxis.dataKey],
|
|
136
|
+
[s.dataKey]: d[s.dataKey]
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
136
140
|
let curveType = allCurves[s.lineType]
|
|
137
141
|
let transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(s.dataKey) === -1
|
|
138
142
|
let displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(s.dataKey) !== -1
|
|
139
143
|
|
|
140
|
-
|
|
144
|
+
if (config.xAxis.type === 'date') {
|
|
145
|
+
data.map(d => xScale(parseDate(d[config.xAxis.dataKey])))
|
|
146
|
+
}
|
|
141
147
|
|
|
142
148
|
return (
|
|
143
149
|
<React.Fragment key={index}>
|
|
144
150
|
{/* prettier-ignore */}
|
|
145
151
|
<LinePath
|
|
146
|
-
data={
|
|
152
|
+
data={seriesData}
|
|
147
153
|
x={d => handleX(d)}
|
|
148
|
-
y={d =>
|
|
149
|
-
stroke={displayArea ?
|
|
154
|
+
y={d => handleY(d, index, s)}
|
|
155
|
+
stroke={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
|
|
150
156
|
strokeWidth={2}
|
|
151
157
|
strokeOpacity={1}
|
|
152
158
|
shapeRendering='geometricPrecision'
|
|
@@ -159,11 +165,12 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
159
165
|
key={'area-chart'}
|
|
160
166
|
fill={ displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
|
|
161
167
|
fillOpacity={transparentArea ? 0.25 : 0.5}
|
|
162
|
-
data={
|
|
163
|
-
|
|
168
|
+
data={seriesData}
|
|
169
|
+
x={d => handleX(d)}
|
|
170
|
+
y={d => handleY(d, index, s)}
|
|
164
171
|
yScale={yScale}
|
|
165
172
|
curve={curveType}
|
|
166
|
-
strokeDasharray={s.type ? handleLineType(s.
|
|
173
|
+
strokeDasharray={s.type ? handleLineType(s.type) : 0}
|
|
167
174
|
/>
|
|
168
175
|
|
|
169
176
|
{/* Transparent bar for tooltips */}
|
|
@@ -178,7 +185,7 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
178
185
|
/>
|
|
179
186
|
|
|
180
187
|
{/* circles that appear on hover */}
|
|
181
|
-
{tooltipData && (
|
|
188
|
+
{tooltipData && Object.entries(tooltipData.data).length > 0 && (
|
|
182
189
|
<circle
|
|
183
190
|
cx={config.xAxis.type === 'categorical' ? xScale(tooltipData.data[config.xAxis.dataKey]) : xScale(parseDate(tooltipData.data[config.xAxis.dataKey]))}
|
|
184
191
|
cy={yScale(tooltipData.data[s.dataKey])}
|
|
@@ -196,12 +203,12 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
196
203
|
return (
|
|
197
204
|
<Bar
|
|
198
205
|
className='bar-here'
|
|
199
|
-
x={Number(barThickness * index
|
|
206
|
+
x={Number(barThickness * index)}
|
|
200
207
|
y={d => Number(yScale(d[config.series[index].dataKey]))}
|
|
201
208
|
yScale={yScale}
|
|
202
|
-
width={
|
|
209
|
+
width={Number(barThickness)}
|
|
203
210
|
height={yMax}
|
|
204
|
-
fill={'transparent'}
|
|
211
|
+
fill={DEBUG ? 'red' : 'transparent'}
|
|
205
212
|
fillOpacity={1}
|
|
206
213
|
style={{ stroke: 'black', strokeWidth: 2 }}
|
|
207
214
|
onMouseMove={e => handleMouseOver(e, data)}
|
|
@@ -209,7 +216,7 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
209
216
|
)
|
|
210
217
|
})}
|
|
211
218
|
|
|
212
|
-
{tooltipData && (
|
|
219
|
+
{tooltipData && Object.entries(tooltipData.data).length > 0 && (
|
|
213
220
|
<TooltipInPortal key={Math.random()} top={tooltipData.dataYPosition + chartPosition?.top} left={tooltipData.dataXPosition + chartPosition?.left} style={defaultStyles}>
|
|
214
221
|
<ul style={{ listStyle: 'none', paddingLeft: 'unset', fontFamily: 'sans-serif', margin: 'auto', lineHeight: '1rem' }} data-tooltip-id={tooltip_id}>
|
|
215
222
|
{typeof tooltipData === 'object' &&
|