@cdc/chart 4.22.10 → 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.
- package/README.md +5 -5
- package/dist/cdcchart.js +4 -4
- package/examples/age-adjusted-rates.json +1486 -1218
- package/examples/case-rate-example-config.json +1 -1
- package/examples/covid-confidence-example-config.json +33 -33
- package/examples/covid-example-config.json +34 -34
- package/examples/covid-example-data-confidence.json +30 -30
- package/examples/covid-example-data.json +20 -20
- package/examples/cutoff-example-config.json +36 -36
- package/examples/cutoff-example-data.json +36 -36
- package/examples/date-exclusions-config.json +1 -1
- package/examples/dynamic-legends.json +124 -124
- package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart-with-numbers-on-bar.json +191 -197
- package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +230 -240
- package/examples/gallery/bar-chart-horizontal/horizontal-stacked.json +239 -247
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +136 -136
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +79 -79
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +80 -80
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +67 -67
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +110 -110
- package/examples/gallery/lollipop/lollipop-style-horizontal.json +215 -219
- package/examples/gallery/paired-bar/paired-bar-chart.json +195 -195
- package/examples/horizontal-chart.json +35 -35
- package/examples/horizontal-stacked-bar-chart.json +34 -34
- package/examples/line-chart.json +75 -75
- package/examples/paired-bar-data.json +16 -14
- package/examples/paired-bar-example.json +48 -48
- package/examples/paired-bar-formatted.json +36 -36
- package/examples/planet-chart-horizontal-example-config.json +33 -33
- package/examples/planet-combo-example-config.json +34 -31
- package/examples/planet-example-config.json +35 -33
- package/examples/planet-example-data.json +56 -56
- package/examples/planet-pie-example-config.json +28 -28
- package/examples/private/filters.json +170 -0
- package/examples/private/line-test-data.json +21 -21
- package/examples/private/line-test-two.json +209 -215
- package/examples/private/line-test.json +101 -101
- package/examples/private/new.json +48800 -0
- package/examples/private/shawn.json +1105 -1295
- package/examples/private/test.json +10123 -10123
- package/examples/private/yaxis-test.json +4 -3
- package/examples/private/yaxis.json +26 -26
- package/examples/stacked-vertical-bar-example.json +1 -1
- package/examples/temp-example-config.json +61 -54
- package/examples/temp-example-data.json +1 -1
- package/package.json +2 -2
- package/src/CdcChart.tsx +339 -380
- package/src/components/BarChart.tsx +425 -469
- package/src/components/DataTable.tsx +164 -195
- package/src/components/EditorPanel.js +1009 -710
- package/src/components/Legend.js +279 -329
- package/src/components/LineChart.tsx +90 -79
- package/src/components/LinearChart.tsx +376 -434
- package/src/components/PairedBarChart.tsx +197 -213
- package/src/components/PieChart.tsx +95 -151
- package/src/components/SparkLine.js +179 -201
- package/src/components/useIntersectionObserver.tsx +17 -20
- package/src/context.tsx +3 -3
- package/src/data/initial-state.js +37 -16
- package/src/hooks/useActiveElement.js +13 -13
- package/src/hooks/useChartClasses.js +34 -28
- package/src/hooks/useColorPalette.ts +56 -63
- package/src/hooks/useLegendClasses.js +18 -10
- package/src/hooks/useReduceData.ts +62 -78
- package/src/hooks/useRightAxis.js +25 -0
- package/src/hooks/useTopAxis.js +6 -0
- package/src/index.html +45 -45
- package/src/index.tsx +13 -16
- package/src/scss/DataTable.scss +5 -4
- package/src/scss/editor-panel.scss +71 -69
- package/src/scss/main.scss +157 -114
- package/src/scss/variables.scss +1 -1
package/src/CdcChart.tsx
CHANGED
|
@@ -1,156 +1,140 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from 'react'
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react'
|
|
2
2
|
|
|
3
3
|
// IE11
|
|
4
|
-
import 'core-js/stable'
|
|
5
|
-
import ResizeObserver from 'resize-observer-polyfill'
|
|
6
|
-
import 'whatwg-fetch'
|
|
4
|
+
import 'core-js/stable'
|
|
5
|
+
import ResizeObserver from 'resize-observer-polyfill'
|
|
6
|
+
import 'whatwg-fetch'
|
|
7
7
|
|
|
8
8
|
// External Libraries
|
|
9
|
-
import { scaleOrdinal } from '@visx/scale'
|
|
10
|
-
import ParentSize from '@visx/responsive/lib/components/ParentSize'
|
|
11
|
-
import { timeParse, timeFormat } from 'd3-time-format'
|
|
12
|
-
import Papa from 'papaparse'
|
|
13
|
-
import parse from 'html-react-parser'
|
|
14
|
-
|
|
9
|
+
import { scaleOrdinal } from '@visx/scale'
|
|
10
|
+
import ParentSize from '@visx/responsive/lib/components/ParentSize'
|
|
11
|
+
import { timeParse, timeFormat } from 'd3-time-format'
|
|
12
|
+
import Papa from 'papaparse'
|
|
13
|
+
import parse from 'html-react-parser'
|
|
15
14
|
|
|
16
15
|
// Primary Components
|
|
17
|
-
import Context from './context'
|
|
18
|
-
import PieChart from './components/PieChart'
|
|
19
|
-
import LinearChart from './components/LinearChart'
|
|
16
|
+
import Context from './context'
|
|
17
|
+
import PieChart from './components/PieChart'
|
|
18
|
+
import LinearChart from './components/LinearChart'
|
|
20
19
|
|
|
21
|
-
import {colorPalettesChart as colorPalettes} from '../../core/data/colorPalettes'
|
|
20
|
+
import { colorPalettesChart as colorPalettes } from '../../core/data/colorPalettes'
|
|
22
21
|
|
|
23
|
-
import { publish, subscribe, unsubscribe } from '@cdc/core/helpers/events'
|
|
22
|
+
import { publish, subscribe, unsubscribe } from '@cdc/core/helpers/events'
|
|
24
23
|
|
|
25
|
-
import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
|
|
24
|
+
import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
|
|
26
25
|
|
|
27
|
-
import SparkLine from './components/SparkLine'
|
|
28
|
-
import Legend from './components/Legend'
|
|
29
|
-
import DataTable from './components/DataTable'
|
|
30
|
-
import defaults from './data/initial-state'
|
|
31
|
-
import EditorPanel from './components/EditorPanel'
|
|
32
|
-
import Loading from '@cdc/core/components/Loading'
|
|
26
|
+
import SparkLine from './components/SparkLine'
|
|
27
|
+
import Legend from './components/Legend'
|
|
28
|
+
import DataTable from './components/DataTable'
|
|
29
|
+
import defaults from './data/initial-state'
|
|
30
|
+
import EditorPanel from './components/EditorPanel'
|
|
31
|
+
import Loading from '@cdc/core/components/Loading'
|
|
33
32
|
|
|
34
33
|
// helpers
|
|
35
34
|
import numberFromString from '@cdc/core/helpers/numberFromString'
|
|
36
|
-
import getViewport from '@cdc/core/helpers/getViewport'
|
|
37
|
-
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
38
|
-
import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
|
|
39
|
-
|
|
40
|
-
import './scss/main.scss'
|
|
41
|
-
|
|
42
|
-
export default function CdcChart(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const [
|
|
52
|
-
const [
|
|
53
|
-
const [
|
|
54
|
-
const [
|
|
55
|
-
const [
|
|
56
|
-
const [
|
|
57
|
-
const [
|
|
58
|
-
const [
|
|
59
|
-
const [dimensions, setDimensions] = useState<Array<Number>>([]);
|
|
60
|
-
const [externalFilters, setExternalFilters] = useState(null);
|
|
35
|
+
import getViewport from '@cdc/core/helpers/getViewport'
|
|
36
|
+
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
37
|
+
import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
|
|
38
|
+
|
|
39
|
+
import './scss/main.scss'
|
|
40
|
+
|
|
41
|
+
export default function CdcChart({ configUrl, config: configObj, isEditor = false, isDashboard = false, setConfig: setParentConfig, setEditing, hostname, link }: { configUrl?: string; config?: any; isEditor?: boolean; isDashboard?: boolean; setConfig?; setEditing?; hostname?; link?: any }) {
|
|
42
|
+
const transform = new DataTransform()
|
|
43
|
+
|
|
44
|
+
interface keyable {
|
|
45
|
+
[key: string]: any
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const [loading, setLoading] = useState<Boolean>(true)
|
|
49
|
+
const [colorScale, setColorScale] = useState<any>(null)
|
|
50
|
+
const [config, setConfig] = useState<keyable>({})
|
|
51
|
+
const [stateData, setStateData] = useState<Array<Object>>(config.data || [])
|
|
52
|
+
const [excludedData, setExcludedData] = useState<Array<Object>>()
|
|
53
|
+
const [filteredData, setFilteredData] = useState<Array<Object>>()
|
|
54
|
+
const [seriesHighlight, setSeriesHighlight] = useState<Array<String>>([])
|
|
55
|
+
const [currentViewport, setCurrentViewport] = useState<String>('lg')
|
|
56
|
+
const [dimensions, setDimensions] = useState<Array<Number>>([])
|
|
57
|
+
const [externalFilters, setExternalFilters] = useState(null)
|
|
61
58
|
const [container, setContainer] = useState()
|
|
62
59
|
const [coveLoadedEventRan, setCoveLoadedEventRan] = useState(false)
|
|
63
60
|
const [dynamicLegendItems, setDynamicLegendItems] = useState([])
|
|
64
61
|
|
|
65
|
-
const legendGlyphSize = 15
|
|
66
|
-
const legendGlyphSizeHalf = legendGlyphSize / 2
|
|
62
|
+
const legendGlyphSize = 15
|
|
63
|
+
const legendGlyphSizeHalf = legendGlyphSize / 2
|
|
67
64
|
|
|
68
|
-
const {
|
|
69
|
-
barBorderClass,
|
|
70
|
-
lineDatapointClass,
|
|
71
|
-
contentClasses,
|
|
72
|
-
innerContainerClasses,
|
|
73
|
-
sparkLineStyles
|
|
74
|
-
} = useDataVizClasses(config)
|
|
65
|
+
const { barBorderClass, lineDatapointClass, contentClasses, innerContainerClasses, sparkLineStyles } = useDataVizClasses(config)
|
|
75
66
|
|
|
76
67
|
const handleChartTabbing = config.showSidebar ? `#legend` : config?.title ? `#dataTableSection__${config.title.replace(/\s/g, '')}` : `#dataTableSection`
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
69
|
const handleChartAriaLabels = (state, testing = false) => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
70
|
+
if (testing) console.log(`handleChartAriaLabels Testing On:`, state)
|
|
71
|
+
try {
|
|
72
|
+
if (!state.visualizationType) throw Error('handleChartAriaLabels: no visualization type found in state')
|
|
73
|
+
let ariaLabel = ''
|
|
86
74
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if(state.title && state.visualizationType) {
|
|
92
|
-
ariaLabel += ` with the title: ${state.title}`
|
|
93
|
-
}
|
|
75
|
+
if (state.visualizationType) {
|
|
76
|
+
ariaLabel += `${state.visualizationType} chart`
|
|
77
|
+
}
|
|
94
78
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
console.error(e.message)
|
|
79
|
+
if (state.title && state.visualizationType) {
|
|
80
|
+
ariaLabel += ` with the title: ${state.title}`
|
|
98
81
|
}
|
|
82
|
+
|
|
83
|
+
return ariaLabel
|
|
84
|
+
} catch (e) {
|
|
85
|
+
console.error(e.message)
|
|
86
|
+
}
|
|
99
87
|
}
|
|
100
88
|
|
|
101
89
|
const loadConfig = async () => {
|
|
102
|
-
let response = configObj || await (await fetch(configUrl)).json()
|
|
90
|
+
let response = configObj || (await (await fetch(configUrl)).json())
|
|
103
91
|
|
|
104
92
|
// If data is included through a URL, fetch that and store
|
|
105
|
-
let data = response.formattedData || response.data || {}
|
|
93
|
+
let data = response.formattedData || response.data || {}
|
|
106
94
|
|
|
107
95
|
if (response.dataUrl) {
|
|
108
|
-
|
|
109
96
|
try {
|
|
110
97
|
const regex = /(?:\.([^.]+))?$/
|
|
111
98
|
|
|
112
|
-
const ext =
|
|
99
|
+
const ext = regex.exec(response.dataUrl)[1]
|
|
113
100
|
if ('csv' === ext) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
101
|
+
data = await fetch(response.dataUrl + `?v=${cacheBustingString()}`)
|
|
102
|
+
.then(response => response.text())
|
|
103
|
+
.then(responseText => {
|
|
104
|
+
const parsedCsv = Papa.parse(responseText, {
|
|
105
|
+
header: true,
|
|
106
|
+
dynamicTyping: true,
|
|
107
|
+
skipEmptyLines: true
|
|
108
|
+
})
|
|
109
|
+
return parsedCsv.data
|
|
110
|
+
})
|
|
124
111
|
}
|
|
125
112
|
|
|
126
113
|
if ('json' === ext) {
|
|
127
|
-
|
|
128
|
-
.then(response => response.json())
|
|
114
|
+
data = await fetch(response.dataUrl + `?v=${cacheBustingString()}`).then(response => response.json())
|
|
129
115
|
}
|
|
130
116
|
} catch {
|
|
131
|
-
console.error(`Cannot parse URL: ${response.dataUrl}`)
|
|
132
|
-
data = []
|
|
117
|
+
console.error(`Cannot parse URL: ${response.dataUrl}`)
|
|
118
|
+
data = []
|
|
133
119
|
}
|
|
134
120
|
|
|
135
|
-
if(response.dataDescription) {
|
|
136
|
-
data = transform.autoStandardize(data)
|
|
137
|
-
data = transform.developerStandardize(data, response.dataDescription)
|
|
121
|
+
if (response.dataDescription) {
|
|
122
|
+
data = transform.autoStandardize(data)
|
|
123
|
+
data = transform.developerStandardize(data, response.dataDescription)
|
|
138
124
|
}
|
|
139
125
|
}
|
|
140
126
|
|
|
141
|
-
if(data) {
|
|
127
|
+
if (data) {
|
|
142
128
|
setStateData(data)
|
|
143
129
|
setExcludedData(data)
|
|
144
130
|
}
|
|
145
131
|
|
|
146
|
-
let newConfig = {...defaults, ...response}
|
|
147
|
-
if(undefined === newConfig.table.show) newConfig.table.show = !isDashboard
|
|
148
|
-
updateConfig(newConfig, data)
|
|
132
|
+
let newConfig = { ...defaults, ...response }
|
|
133
|
+
if (undefined === newConfig.table.show) newConfig.table.show = !isDashboard
|
|
134
|
+
updateConfig(newConfig, data)
|
|
149
135
|
}
|
|
150
136
|
|
|
151
|
-
|
|
152
137
|
const updateConfig = (newConfig, dataOverride = undefined) => {
|
|
153
|
-
|
|
154
138
|
let data = dataOverride || stateData
|
|
155
139
|
|
|
156
140
|
// Deeper copy
|
|
@@ -158,23 +142,17 @@ export default function CdcChart(
|
|
|
158
142
|
if (newConfig[key] && 'object' === typeof newConfig[key] && !Array.isArray(newConfig[key])) {
|
|
159
143
|
newConfig[key] = { ...defaults[key], ...newConfig[key] }
|
|
160
144
|
}
|
|
161
|
-
})
|
|
145
|
+
})
|
|
162
146
|
|
|
163
147
|
// Loop through and set initial data with exclusions - this should persist through any following data transformations (ie. filters)
|
|
164
148
|
let newExcludedData
|
|
165
149
|
|
|
166
150
|
if (newConfig.exclusions && newConfig.exclusions.active) {
|
|
167
|
-
|
|
168
151
|
if (newConfig.xAxis.type === 'categorical' && newConfig.exclusions.keys?.length > 0) {
|
|
169
152
|
newExcludedData = data.filter(e => !newConfig.exclusions.keys.includes(e[newConfig.xAxis.dataKey]))
|
|
170
|
-
} else if (
|
|
171
|
-
newConfig.xAxis.type === 'date' &&
|
|
172
|
-
(newConfig.exclusions.dateStart || newConfig.exclusions.dateEnd) &&
|
|
173
|
-
newConfig.xAxis.dateParseFormat
|
|
174
|
-
) {
|
|
175
|
-
|
|
153
|
+
} else if (newConfig.xAxis.type === 'date' && (newConfig.exclusions.dateStart || newConfig.exclusions.dateEnd) && newConfig.xAxis.dateParseFormat) {
|
|
176
154
|
// Filter dates
|
|
177
|
-
const timestamp =
|
|
155
|
+
const timestamp = e => new Date(e).getTime()
|
|
178
156
|
|
|
179
157
|
let startDate = timestamp(newConfig.exclusions.dateStart)
|
|
180
158
|
let endDate = timestamp(newConfig.exclusions.dateEnd) + 86399999 //Increase by 24h in ms (86400000ms - 1ms) to include selected end date for .getTime() comparative
|
|
@@ -183,16 +161,12 @@ export default function CdcChart(
|
|
|
183
161
|
let endDateValid = undefined !== typeof endDate && false === isNaN(endDate)
|
|
184
162
|
|
|
185
163
|
if (startDateValid && endDateValid) {
|
|
186
|
-
newExcludedData = data.filter(e =>
|
|
187
|
-
(timestamp(e[newConfig.xAxis.dataKey]) >= startDate) &&
|
|
188
|
-
(timestamp(e[newConfig.xAxis.dataKey]) <= endDate)
|
|
189
|
-
)
|
|
164
|
+
newExcludedData = data.filter(e => timestamp(e[newConfig.xAxis.dataKey]) >= startDate && timestamp(e[newConfig.xAxis.dataKey]) <= endDate)
|
|
190
165
|
} else if (startDateValid) {
|
|
191
166
|
newExcludedData = data.filter(e => timestamp(e[newConfig.xAxis.dataKey]) >= startDate)
|
|
192
167
|
} else if (endDateValid) {
|
|
193
168
|
newExcludedData = data.filter(e => timestamp(e[newConfig.xAxis.dataKey]) <= endDate)
|
|
194
169
|
}
|
|
195
|
-
|
|
196
170
|
} else {
|
|
197
171
|
newExcludedData = dataOverride || stateData
|
|
198
172
|
}
|
|
@@ -203,134 +177,132 @@ export default function CdcChart(
|
|
|
203
177
|
setExcludedData(newExcludedData)
|
|
204
178
|
|
|
205
179
|
// After data is grabbed, loop through and generate filter column values if there are any
|
|
206
|
-
let currentData
|
|
180
|
+
let currentData
|
|
207
181
|
if (newConfig.filters) {
|
|
208
|
-
|
|
209
182
|
newConfig.filters.forEach((filter, index) => {
|
|
183
|
+
let filterValues = []
|
|
210
184
|
|
|
211
|
-
|
|
185
|
+
filterValues = filter.orderedValues || generateValuesForFilter(filter.columnName, newExcludedData)
|
|
212
186
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
});
|
|
220
|
-
currentData = filterData(newConfig.filters, newExcludedData);
|
|
221
|
-
setFilteredData(currentData);
|
|
187
|
+
newConfig.filters[index].values = filterValues
|
|
188
|
+
// Initial filter should be active
|
|
189
|
+
newConfig.filters[index].active = filterValues[0]
|
|
190
|
+
})
|
|
191
|
+
currentData = filterData(newConfig.filters, newExcludedData)
|
|
192
|
+
setFilteredData(currentData)
|
|
222
193
|
}
|
|
223
194
|
|
|
224
195
|
//Enforce default values that need to be calculated at runtime
|
|
225
|
-
newConfig.runtime = {}
|
|
226
|
-
newConfig.runtime.seriesLabels = {}
|
|
227
|
-
newConfig.runtime.seriesLabelsAll = []
|
|
228
|
-
newConfig.runtime.originalXAxis = newConfig.xAxis
|
|
229
|
-
|
|
196
|
+
newConfig.runtime = {}
|
|
197
|
+
newConfig.runtime.seriesLabels = {}
|
|
198
|
+
newConfig.runtime.seriesLabelsAll = []
|
|
199
|
+
newConfig.runtime.originalXAxis = newConfig.xAxis
|
|
230
200
|
|
|
231
201
|
if (newConfig.visualizationType === 'Pie') {
|
|
232
|
-
newConfig.runtime.seriesKeys = (dataOverride || data).map(d => d[newConfig.xAxis.dataKey])
|
|
233
|
-
newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
|
|
202
|
+
newConfig.runtime.seriesKeys = (dataOverride || data).map(d => d[newConfig.xAxis.dataKey])
|
|
203
|
+
newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
|
|
234
204
|
} else {
|
|
235
|
-
newConfig.runtime.seriesKeys = newConfig.series
|
|
236
|
-
newConfig.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
205
|
+
newConfig.runtime.seriesKeys = newConfig.series
|
|
206
|
+
? newConfig.series.map(series => {
|
|
207
|
+
newConfig.runtime.seriesLabels[series.dataKey] = series.label || series.dataKey
|
|
208
|
+
newConfig.runtime.seriesLabelsAll.push(series.label || series.dataKey)
|
|
209
|
+
return series.dataKey
|
|
210
|
+
})
|
|
211
|
+
: []
|
|
240
212
|
}
|
|
241
213
|
|
|
242
214
|
if (newConfig.visualizationType === 'Combo' && newConfig.series) {
|
|
243
|
-
newConfig.runtime.barSeriesKeys = []
|
|
244
|
-
newConfig.runtime.lineSeriesKeys = []
|
|
245
|
-
newConfig.series.forEach(
|
|
246
|
-
if(series.type === 'Bar'){
|
|
247
|
-
newConfig.runtime.barSeriesKeys.push(series.dataKey)
|
|
215
|
+
newConfig.runtime.barSeriesKeys = []
|
|
216
|
+
newConfig.runtime.lineSeriesKeys = []
|
|
217
|
+
newConfig.series.forEach(series => {
|
|
218
|
+
if (series.type === 'Bar') {
|
|
219
|
+
newConfig.runtime.barSeriesKeys.push(series.dataKey)
|
|
248
220
|
}
|
|
249
|
-
if(series.type === 'Line' || series.type === 'dashed-sm' || series.type === 'dashed-md' || series.type === 'dashed-lg'){
|
|
250
|
-
newConfig.runtime.lineSeriesKeys.push(series.dataKey)
|
|
221
|
+
if (series.type === 'Line' || series.type === 'dashed-sm' || series.type === 'dashed-md' || series.type === 'dashed-lg') {
|
|
222
|
+
newConfig.runtime.lineSeriesKeys.push(series.dataKey)
|
|
251
223
|
}
|
|
252
|
-
})
|
|
224
|
+
})
|
|
253
225
|
}
|
|
254
226
|
|
|
255
|
-
if (
|
|
256
|
-
newConfig.runtime.xAxis = newConfig.yAxis
|
|
257
|
-
newConfig.runtime.yAxis = newConfig.xAxis
|
|
258
|
-
newConfig.runtime.horizontal = true
|
|
227
|
+
if ((newConfig.visualizationType === 'Bar' && newConfig.orientation === 'horizontal') || newConfig.visualizationType === 'Paired Bar') {
|
|
228
|
+
newConfig.runtime.xAxis = newConfig.yAxis
|
|
229
|
+
newConfig.runtime.yAxis = newConfig.xAxis
|
|
230
|
+
newConfig.runtime.horizontal = true
|
|
259
231
|
} else {
|
|
260
|
-
newConfig.runtime.xAxis = newConfig.xAxis
|
|
261
|
-
newConfig.runtime.yAxis = newConfig.yAxis
|
|
262
|
-
newConfig.runtime.horizontal = false
|
|
232
|
+
newConfig.runtime.xAxis = newConfig.xAxis
|
|
233
|
+
newConfig.runtime.yAxis = newConfig.yAxis
|
|
234
|
+
newConfig.runtime.horizontal = false
|
|
263
235
|
}
|
|
264
|
-
newConfig.runtime.uniqueId = Date.now()
|
|
265
|
-
newConfig.runtime.editorErrorMessage = newConfig.visualizationType === 'Pie' && !newConfig.yAxis.dataKey ? 'Data Key property in Y Axis section must be set for pie charts.' : ''
|
|
236
|
+
newConfig.runtime.uniqueId = Date.now()
|
|
237
|
+
newConfig.runtime.editorErrorMessage = newConfig.visualizationType === 'Pie' && !newConfig.yAxis.dataKey ? 'Data Key property in Y Axis section must be set for pie charts.' : ''
|
|
266
238
|
|
|
267
|
-
setConfig(newConfig)
|
|
268
|
-
}
|
|
239
|
+
setConfig(newConfig)
|
|
240
|
+
}
|
|
269
241
|
|
|
270
242
|
const filterData = (filters, data) => {
|
|
271
|
-
let filteredData = []
|
|
243
|
+
let filteredData = []
|
|
272
244
|
|
|
273
|
-
data.forEach(
|
|
274
|
-
let add = true
|
|
275
|
-
filters.forEach(
|
|
245
|
+
data.forEach(row => {
|
|
246
|
+
let add = true
|
|
247
|
+
filters.forEach(filter => {
|
|
276
248
|
if (row[filter.columnName] !== filter.active) {
|
|
277
|
-
add = false
|
|
249
|
+
add = false
|
|
278
250
|
}
|
|
279
|
-
})
|
|
251
|
+
})
|
|
280
252
|
|
|
281
|
-
if(add) filteredData.push(row)
|
|
282
|
-
})
|
|
283
|
-
return filteredData
|
|
253
|
+
if (add) filteredData.push(row)
|
|
254
|
+
})
|
|
255
|
+
return filteredData
|
|
284
256
|
}
|
|
285
257
|
|
|
286
258
|
// Gets filer values from dataset
|
|
287
259
|
const generateValuesForFilter = (columnName, data = this.state.data) => {
|
|
288
|
-
const values = []
|
|
260
|
+
const values = []
|
|
289
261
|
|
|
290
|
-
data.forEach(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
})
|
|
262
|
+
data.forEach(row => {
|
|
263
|
+
const value = row[columnName]
|
|
264
|
+
if (value && false === values.includes(value)) {
|
|
265
|
+
values.push(value)
|
|
266
|
+
}
|
|
267
|
+
})
|
|
296
268
|
|
|
297
|
-
return values
|
|
269
|
+
return values
|
|
298
270
|
}
|
|
299
271
|
|
|
300
272
|
// Sorts data series for horizontal bar charts
|
|
301
273
|
const sortData = (a, b) => {
|
|
302
|
-
let sortKey = config.visualizationType === 'Bar' && config.visualizationSubType === 'horizontal' ? config.xAxis.dataKey : config.yAxis.sortKey
|
|
303
|
-
let aData = parseFloat(a[sortKey])
|
|
304
|
-
let bData = parseFloat(b[sortKey])
|
|
305
|
-
|
|
306
|
-
if(aData < bData){
|
|
307
|
-
return config.sortData === 'ascending' ? 1 : -1
|
|
308
|
-
} else if (aData > bData){
|
|
309
|
-
return config.sortData === 'ascending' ? -1 : 1
|
|
274
|
+
let sortKey = config.visualizationType === 'Bar' && config.visualizationSubType === 'horizontal' ? config.xAxis.dataKey : config.yAxis.sortKey
|
|
275
|
+
let aData = parseFloat(a[sortKey])
|
|
276
|
+
let bData = parseFloat(b[sortKey])
|
|
277
|
+
|
|
278
|
+
if (aData < bData) {
|
|
279
|
+
return config.sortData === 'ascending' ? 1 : -1
|
|
280
|
+
} else if (aData > bData) {
|
|
281
|
+
return config.sortData === 'ascending' ? -1 : 1
|
|
310
282
|
} else {
|
|
311
|
-
return 0
|
|
283
|
+
return 0
|
|
312
284
|
}
|
|
313
285
|
}
|
|
314
286
|
|
|
315
287
|
// Observes changes to outermost container and changes viewport size in state
|
|
316
|
-
const resizeObserver:ResizeObserver = new ResizeObserver(entries => {
|
|
288
|
+
const resizeObserver: ResizeObserver = new ResizeObserver(entries => {
|
|
317
289
|
for (let entry of entries) {
|
|
318
290
|
let { width, height } = entry.contentRect
|
|
319
291
|
let newViewport = getViewport(width)
|
|
320
|
-
let svgMarginWidth = 32
|
|
321
|
-
let editorWidth = 350
|
|
292
|
+
let svgMarginWidth = 32
|
|
293
|
+
let editorWidth = 350
|
|
322
294
|
|
|
323
295
|
setCurrentViewport(newViewport)
|
|
324
296
|
|
|
325
|
-
if(isEditor) {
|
|
326
|
-
width = width - editorWidth
|
|
297
|
+
if (isEditor) {
|
|
298
|
+
width = width - editorWidth
|
|
327
299
|
}
|
|
328
300
|
|
|
329
|
-
if(entry.target.dataset.lollipop === 'true') {
|
|
330
|
-
width = width - 2.5
|
|
301
|
+
if (entry.target.dataset.lollipop === 'true') {
|
|
302
|
+
width = width - 2.5
|
|
331
303
|
}
|
|
332
304
|
|
|
333
|
-
width = width - svgMarginWidth
|
|
305
|
+
width = width - svgMarginWidth
|
|
334
306
|
|
|
335
307
|
setDimensions([width, height])
|
|
336
308
|
}
|
|
@@ -338,268 +310,277 @@ export default function CdcChart(
|
|
|
338
310
|
|
|
339
311
|
const outerContainerRef = useCallback(node => {
|
|
340
312
|
if (node !== null) {
|
|
341
|
-
|
|
313
|
+
resizeObserver.observe(node)
|
|
342
314
|
}
|
|
343
315
|
|
|
344
316
|
setContainer(node)
|
|
345
|
-
},[])
|
|
317
|
+
}, [])
|
|
346
318
|
|
|
347
319
|
function isEmpty(obj) {
|
|
348
|
-
return Object.keys(obj).length === 0
|
|
320
|
+
return Object.keys(obj).length === 0
|
|
349
321
|
}
|
|
350
322
|
|
|
351
323
|
// Load data when component first mounts
|
|
352
324
|
useEffect(() => {
|
|
353
|
-
loadConfig()
|
|
354
|
-
}, [])
|
|
325
|
+
loadConfig()
|
|
326
|
+
}, [])
|
|
355
327
|
|
|
356
328
|
/**
|
|
357
329
|
* When cove has a config and container ref publish the cove_loaded event.
|
|
358
330
|
*/
|
|
359
331
|
useEffect(() => {
|
|
360
|
-
if(container && !isEmpty(config) && !coveLoadedEventRan) {
|
|
332
|
+
if (container && !isEmpty(config) && !coveLoadedEventRan) {
|
|
361
333
|
publish('cove_loaded', { config: config })
|
|
362
334
|
setCoveLoadedEventRan(true)
|
|
363
335
|
}
|
|
336
|
+
}, [container, config])
|
|
364
337
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
* Updates externalFilters state
|
|
371
|
-
* Another useEffect listens to externalFilterChanges and updates the config.
|
|
372
|
-
*/
|
|
338
|
+
/**
|
|
339
|
+
* Handles filter change events outside of COVE
|
|
340
|
+
* Updates externalFilters state
|
|
341
|
+
* Another useEffect listens to externalFilterChanges and updates the config.
|
|
342
|
+
*/
|
|
373
343
|
useEffect(() => {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
setExternalFilters(tmp)
|
|
344
|
+
const handleFilterData = (e: CustomEvent) => {
|
|
345
|
+
let tmp = []
|
|
346
|
+
tmp.push(e.detail)
|
|
347
|
+
setExternalFilters(tmp)
|
|
379
348
|
}
|
|
380
|
-
|
|
381
|
-
subscribe('cove_filterData', (e:CustomEvent) => handleFilterData(e))
|
|
349
|
+
|
|
350
|
+
subscribe('cove_filterData', (e: CustomEvent) => handleFilterData(e))
|
|
382
351
|
|
|
383
352
|
return () => {
|
|
384
|
-
unsubscribe('cove_filterData', handleFilterData)
|
|
353
|
+
unsubscribe('cove_filterData', handleFilterData)
|
|
385
354
|
}
|
|
355
|
+
}, [config])
|
|
386
356
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
* For some reason e.detail is returning [order: "asc"] even though
|
|
393
|
-
* we're not passing that in. The code here checks for an active prop instead of an empty array.
|
|
394
|
-
*/
|
|
357
|
+
/**
|
|
358
|
+
* Handles changes to externalFilters
|
|
359
|
+
* For some reason e.detail is returning [order: "asc"] even though
|
|
360
|
+
* we're not passing that in. The code here checks for an active prop instead of an empty array.
|
|
361
|
+
*/
|
|
395
362
|
useEffect(() => {
|
|
396
|
-
|
|
397
|
-
if(externalFilters && externalFilters[0]) {
|
|
363
|
+
if (externalFilters && externalFilters[0]) {
|
|
398
364
|
const hasActiveProperty = externalFilters[0].hasOwnProperty('active')
|
|
399
365
|
|
|
400
|
-
if(!hasActiveProperty) {
|
|
401
|
-
let configCopy = {...config }
|
|
366
|
+
if (!hasActiveProperty) {
|
|
367
|
+
let configCopy = { ...config }
|
|
402
368
|
delete configCopy['filters']
|
|
403
369
|
setConfig(configCopy)
|
|
404
|
-
setFilteredData(filterData(externalFilters, excludedData))
|
|
370
|
+
setFilteredData(filterData(externalFilters, excludedData))
|
|
405
371
|
}
|
|
406
372
|
}
|
|
407
373
|
|
|
408
|
-
if(externalFilters && externalFilters.length > 0 && externalFilters.length > 0 && externalFilters[0].hasOwnProperty('active')) {
|
|
409
|
-
let newConfigHere = {...config, filters: externalFilters }
|
|
374
|
+
if (externalFilters && externalFilters.length > 0 && externalFilters.length > 0 && externalFilters[0].hasOwnProperty('active')) {
|
|
375
|
+
let newConfigHere = { ...config, filters: externalFilters }
|
|
410
376
|
setConfig(newConfigHere)
|
|
411
|
-
setFilteredData(filterData(externalFilters, excludedData))
|
|
377
|
+
setFilteredData(filterData(externalFilters, excludedData))
|
|
412
378
|
}
|
|
413
|
-
|
|
414
|
-
}, [externalFilters]);
|
|
379
|
+
}, [externalFilters])
|
|
415
380
|
|
|
416
|
-
|
|
417
381
|
// Load data when configObj data changes
|
|
418
|
-
if(configObj){
|
|
382
|
+
if (configObj) {
|
|
419
383
|
useEffect(() => {
|
|
420
|
-
loadConfig()
|
|
421
|
-
}, [configObj.data])
|
|
384
|
+
loadConfig()
|
|
385
|
+
}, [configObj.data])
|
|
422
386
|
}
|
|
423
387
|
|
|
424
388
|
// Generates color palette to pass to child chart component
|
|
425
389
|
useEffect(() => {
|
|
426
|
-
if(stateData && config.xAxis && config.runtime.seriesKeys) {
|
|
390
|
+
if (stateData && config.xAxis && config.runtime.seriesKeys) {
|
|
427
391
|
let palette = config.customColors || colorPalettes[config.palette]
|
|
428
392
|
let numberOfKeys = config.runtime.seriesKeys.length
|
|
429
|
-
let newColorScale
|
|
393
|
+
let newColorScale
|
|
430
394
|
|
|
431
|
-
while(numberOfKeys > palette.length) {
|
|
432
|
-
palette = palette.concat(palette)
|
|
395
|
+
while (numberOfKeys > palette.length) {
|
|
396
|
+
palette = palette.concat(palette)
|
|
433
397
|
}
|
|
434
398
|
|
|
435
|
-
palette = palette.slice(0, numberOfKeys)
|
|
399
|
+
palette = palette.slice(0, numberOfKeys)
|
|
436
400
|
|
|
437
|
-
newColorScale = () =>
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
401
|
+
newColorScale = () =>
|
|
402
|
+
scaleOrdinal({
|
|
403
|
+
domain: config.runtime.seriesLabelsAll,
|
|
404
|
+
range: palette
|
|
405
|
+
})
|
|
441
406
|
|
|
442
|
-
setColorScale(newColorScale)
|
|
443
|
-
setLoading(false)
|
|
407
|
+
setColorScale(newColorScale)
|
|
408
|
+
setLoading(false)
|
|
444
409
|
}
|
|
445
410
|
|
|
446
|
-
if(config && stateData && config.sortData){
|
|
447
|
-
stateData.sort(sortData)
|
|
411
|
+
if (config && stateData && config.sortData) {
|
|
412
|
+
stateData.sort(sortData)
|
|
448
413
|
}
|
|
449
414
|
}, [config, stateData])
|
|
450
415
|
|
|
451
416
|
// Called on legend click, highlights/unhighlights the data series with the given label
|
|
452
|
-
const highlight =
|
|
453
|
-
const newSeriesHighlight = []
|
|
417
|
+
const highlight = label => {
|
|
418
|
+
const newSeriesHighlight = []
|
|
454
419
|
|
|
455
420
|
// If we're highlighting all the series, reset them
|
|
456
|
-
if
|
|
421
|
+
if (seriesHighlight.length + 1 === config.runtime.seriesKeys.length && !config.legend.dynamicLegend) {
|
|
457
422
|
highlightReset()
|
|
458
423
|
return
|
|
459
424
|
}
|
|
460
425
|
|
|
461
|
-
seriesHighlight.forEach(
|
|
462
|
-
newSeriesHighlight.push(value)
|
|
463
|
-
})
|
|
426
|
+
seriesHighlight.forEach(value => {
|
|
427
|
+
newSeriesHighlight.push(value)
|
|
428
|
+
})
|
|
464
429
|
|
|
465
|
-
let newHighlight = label.datum
|
|
466
|
-
if(config.runtime.seriesLabels){
|
|
467
|
-
for(let i = 0; i < config.runtime.seriesKeys.length; i++) {
|
|
468
|
-
if(config.runtime.seriesLabels[config.runtime.seriesKeys[i]] === label.datum){
|
|
469
|
-
newHighlight = config.runtime.seriesKeys[i]
|
|
470
|
-
break
|
|
430
|
+
let newHighlight = label.datum
|
|
431
|
+
if (config.runtime.seriesLabels) {
|
|
432
|
+
for (let i = 0; i < config.runtime.seriesKeys.length; i++) {
|
|
433
|
+
if (config.runtime.seriesLabels[config.runtime.seriesKeys[i]] === label.datum) {
|
|
434
|
+
newHighlight = config.runtime.seriesKeys[i]
|
|
435
|
+
break
|
|
471
436
|
}
|
|
472
437
|
}
|
|
473
438
|
}
|
|
474
439
|
|
|
475
440
|
if (newSeriesHighlight.indexOf(newHighlight) !== -1) {
|
|
476
|
-
newSeriesHighlight.splice(newSeriesHighlight.indexOf(newHighlight), 1)
|
|
441
|
+
newSeriesHighlight.splice(newSeriesHighlight.indexOf(newHighlight), 1)
|
|
477
442
|
} else {
|
|
478
|
-
newSeriesHighlight.push(newHighlight)
|
|
443
|
+
newSeriesHighlight.push(newHighlight)
|
|
479
444
|
}
|
|
480
|
-
setSeriesHighlight(newSeriesHighlight)
|
|
481
|
-
}
|
|
445
|
+
setSeriesHighlight(newSeriesHighlight)
|
|
446
|
+
}
|
|
482
447
|
|
|
483
448
|
// Called on reset button click, unhighlights all data series
|
|
484
449
|
const highlightReset = () => {
|
|
485
|
-
if(config.legend.dynamicLegend && dynamicLegendItems) {
|
|
486
|
-
setSeriesHighlight(dynamicLegendItems.map(
|
|
450
|
+
if (config.legend.dynamicLegend && dynamicLegendItems) {
|
|
451
|
+
setSeriesHighlight(dynamicLegendItems.map(item => item.text))
|
|
487
452
|
} else {
|
|
488
|
-
setSeriesHighlight([])
|
|
453
|
+
setSeriesHighlight([])
|
|
489
454
|
}
|
|
490
455
|
}
|
|
491
456
|
|
|
492
|
-
const section = config.orientation ==='horizontal' ? 'yAxis' :'xAxis'
|
|
457
|
+
const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
|
|
493
458
|
|
|
494
459
|
const parseDate = (dateString: string) => {
|
|
495
|
-
let date = timeParse(config.runtime[section].dateParseFormat)(dateString)
|
|
496
|
-
if(!date) {
|
|
497
|
-
config.runtime.editorErrorMessage = `Error parsing date "${dateString}". Try reviewing your data and date parse settings in the X Axis section
|
|
498
|
-
return new Date()
|
|
460
|
+
let date = timeParse(config.runtime[section].dateParseFormat)(dateString)
|
|
461
|
+
if (!date) {
|
|
462
|
+
config.runtime.editorErrorMessage = `Error parsing date "${dateString}". Try reviewing your data and date parse settings in the X Axis section.`
|
|
463
|
+
return new Date()
|
|
499
464
|
} else {
|
|
500
|
-
return date
|
|
465
|
+
return date
|
|
501
466
|
}
|
|
502
|
-
}
|
|
503
|
-
|
|
467
|
+
}
|
|
504
468
|
|
|
505
469
|
const formatDate = (date: Date) => {
|
|
506
|
-
return timeFormat(config.runtime[section].dateDisplayFormat)(date)
|
|
507
|
-
}
|
|
470
|
+
return timeFormat(config.runtime[section].dateDisplayFormat)(date)
|
|
471
|
+
}
|
|
508
472
|
|
|
509
473
|
// Format numeric data based on settings in config
|
|
510
|
-
const formatNumber = (num) => {
|
|
474
|
+
const formatNumber = (num, axis) => {
|
|
511
475
|
// check if value contains comma and remove it. later will add comma below.
|
|
512
|
-
if(String(num).indexOf(',') !== -1)
|
|
476
|
+
if (String(num).indexOf(',') !== -1) num = num.replaceAll(',', '')
|
|
513
477
|
// if num is NaN return num
|
|
514
|
-
if(isNaN(num)|| !num) return num
|
|
478
|
+
if (isNaN(num) || !num) return num
|
|
515
479
|
|
|
516
|
-
let original = num
|
|
517
|
-
let prefix = config.dataFormat.prefix
|
|
480
|
+
let original = num
|
|
481
|
+
let prefix = config.dataFormat.prefix
|
|
482
|
+
let stringFormattingOptions
|
|
518
483
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
484
|
+
if (axis !== 'right') {
|
|
485
|
+
stringFormattingOptions = {
|
|
486
|
+
useGrouping: config.dataFormat.commas ? true : false,
|
|
487
|
+
minimumFractionDigits: config.dataFormat.roundTo ? Number(config.dataFormat.roundTo) : 0,
|
|
488
|
+
maximumFractionDigits: config.dataFormat.roundTo ? Number(config.dataFormat.roundTo) : 0
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
491
|
+
stringFormattingOptions = {
|
|
492
|
+
useGrouping: config.dataFormat.rightCommas ? true : false,
|
|
493
|
+
minimumFractionDigits: config.dataFormat.rightRoundTo ? Number(config.dataFormat.rightRoundTo) : 0,
|
|
494
|
+
maximumFractionDigits: config.dataFormat.rightRoundTo ? Number(config.dataFormat.rightRoundTo) : 0
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
num = numberFromString(num)
|
|
524
499
|
|
|
525
|
-
num = numberFromString(num);
|
|
526
|
-
|
|
527
500
|
if (isNaN(num)) {
|
|
528
|
-
config.runtime.editorErrorMessage = `Unable to parse number from data ${original}. Try reviewing your data and selections in the Data Series section
|
|
501
|
+
config.runtime.editorErrorMessage = `Unable to parse number from data ${original}. Try reviewing your data and selections in the Data Series section.`
|
|
529
502
|
return original
|
|
530
503
|
}
|
|
531
504
|
|
|
532
|
-
if (!config.dataFormat) return num
|
|
533
|
-
if (config.dataCutoff){
|
|
505
|
+
if (!config.dataFormat) return num
|
|
506
|
+
if (config.dataCutoff) {
|
|
534
507
|
let cutoff = numberFromString(config.dataCutoff)
|
|
535
508
|
|
|
536
|
-
if(num < cutoff) {
|
|
537
|
-
num = cutoff
|
|
509
|
+
if (num < cutoff) {
|
|
510
|
+
num = cutoff
|
|
538
511
|
}
|
|
539
512
|
}
|
|
540
513
|
num = num.toLocaleString('en-US', stringFormattingOptions)
|
|
541
514
|
|
|
542
|
-
let result =
|
|
515
|
+
let result = ''
|
|
543
516
|
|
|
544
|
-
if(prefix) {
|
|
517
|
+
if (prefix && axis !== 'right') {
|
|
545
518
|
result += prefix
|
|
546
519
|
}
|
|
547
520
|
|
|
521
|
+
if (config.dataFormat.rightPrefix && axis === 'right') {
|
|
522
|
+
result += config.dataFormat.rightPrefix
|
|
523
|
+
}
|
|
524
|
+
|
|
548
525
|
result += num
|
|
549
526
|
|
|
550
|
-
if(config.dataFormat.suffix) {
|
|
527
|
+
if (config.dataFormat.suffix && axis !== 'right') {
|
|
551
528
|
result += config.dataFormat.suffix
|
|
552
529
|
}
|
|
530
|
+
|
|
531
|
+
if (config.dataFormat.rightSuffix && axis === 'right') {
|
|
532
|
+
result += config.dataFormat.rightSuffix
|
|
533
|
+
}
|
|
534
|
+
|
|
553
535
|
return String(result)
|
|
554
|
-
}
|
|
536
|
+
}
|
|
555
537
|
|
|
556
538
|
// Destructure items from config for more readable JSX
|
|
557
|
-
const { legend, title, description, visualizationType } = config
|
|
539
|
+
const { legend, title, description, visualizationType } = config
|
|
558
540
|
|
|
559
541
|
// Select appropriate chart type
|
|
560
542
|
const chartComponents = {
|
|
561
|
-
'Paired Bar'
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
543
|
+
'Paired Bar': <LinearChart />,
|
|
544
|
+
Bar: <LinearChart />,
|
|
545
|
+
Line: <LinearChart />,
|
|
546
|
+
Combo: <LinearChart />,
|
|
547
|
+
Pie: <PieChart />
|
|
566
548
|
}
|
|
567
549
|
|
|
568
550
|
const Filters = () => {
|
|
569
551
|
const changeFilterActive = (index, value) => {
|
|
570
|
-
let newFilters = config.filters
|
|
552
|
+
let newFilters = config.filters
|
|
571
553
|
|
|
572
|
-
newFilters[index].active = value
|
|
554
|
+
newFilters[index].active = value
|
|
573
555
|
|
|
574
|
-
setConfig({...config, filters: newFilters})
|
|
556
|
+
setConfig({ ...config, filters: newFilters })
|
|
575
557
|
|
|
576
|
-
setFilteredData(filterData(newFilters, excludedData))
|
|
577
|
-
}
|
|
558
|
+
setFilteredData(filterData(newFilters, excludedData))
|
|
559
|
+
}
|
|
578
560
|
|
|
579
|
-
const announceChange =
|
|
561
|
+
const announceChange = text => {}
|
|
580
562
|
|
|
581
|
-
let filterList = ''
|
|
563
|
+
let filterList = ''
|
|
582
564
|
if (config.filters) {
|
|
583
|
-
|
|
584
565
|
filterList = config.filters.map((singleFilter, index) => {
|
|
585
|
-
const values = []
|
|
566
|
+
const values = []
|
|
586
567
|
const sortAsc = (a, b) => {
|
|
587
568
|
return a.toString().localeCompare(b.toString(), 'en', { numeric: true })
|
|
588
|
-
}
|
|
569
|
+
}
|
|
589
570
|
|
|
590
571
|
const sortDesc = (a, b) => {
|
|
591
572
|
return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
|
|
592
|
-
}
|
|
573
|
+
}
|
|
593
574
|
|
|
594
|
-
if(!singleFilter.order || singleFilter.order === ''
|
|
575
|
+
if (!singleFilter.order || singleFilter.order === '') {
|
|
595
576
|
singleFilter.order = 'asc'
|
|
596
577
|
}
|
|
597
578
|
|
|
598
|
-
if(singleFilter.order === 'desc') {
|
|
579
|
+
if (singleFilter.order === 'desc') {
|
|
599
580
|
singleFilter.values = singleFilter.values.sort(sortDesc)
|
|
600
581
|
}
|
|
601
582
|
|
|
602
|
-
if(singleFilter.order === 'asc') {
|
|
583
|
+
if (singleFilter.order === 'asc') {
|
|
603
584
|
singleFilter.values = singleFilter.values.sort(sortAsc)
|
|
604
585
|
}
|
|
605
586
|
|
|
@@ -608,81 +589,68 @@ export default function CdcChart(
|
|
|
608
589
|
<option key={index} value={filterOption}>
|
|
609
590
|
{filterOption}
|
|
610
591
|
</option>
|
|
611
|
-
)
|
|
612
|
-
})
|
|
592
|
+
)
|
|
593
|
+
})
|
|
613
594
|
|
|
614
595
|
return (
|
|
615
|
-
<div className=
|
|
596
|
+
<div className='single-filter' key={index}>
|
|
616
597
|
<label htmlFor={`filter-${index}`}>{singleFilter.label}</label>
|
|
617
598
|
<select
|
|
618
599
|
id={`filter-${index}`}
|
|
619
|
-
className=
|
|
620
|
-
data-index=
|
|
600
|
+
className='filter-select'
|
|
601
|
+
data-index='0'
|
|
621
602
|
value={singleFilter.active}
|
|
622
|
-
onChange={
|
|
623
|
-
changeFilterActive(index, val.target.value)
|
|
624
|
-
announceChange(`Filter ${singleFilter.label} value has been changed to ${val.target.value}, please reference the data table to see updated values.`)
|
|
603
|
+
onChange={val => {
|
|
604
|
+
changeFilterActive(index, val.target.value)
|
|
605
|
+
announceChange(`Filter ${singleFilter.label} value has been changed to ${val.target.value}, please reference the data table to see updated values.`)
|
|
625
606
|
}}
|
|
626
607
|
>
|
|
627
608
|
{values}
|
|
628
609
|
</select>
|
|
629
610
|
</div>
|
|
630
|
-
)
|
|
631
|
-
})
|
|
611
|
+
)
|
|
612
|
+
})
|
|
632
613
|
}
|
|
633
614
|
|
|
634
|
-
return
|
|
615
|
+
return <section className='filters-section'>{filterList}</section>
|
|
635
616
|
}
|
|
636
617
|
|
|
637
618
|
const missingRequiredSections = () => {
|
|
638
619
|
if (config.visualizationType === 'Pie') {
|
|
639
620
|
if (undefined === config?.yAxis.dataKey) {
|
|
640
|
-
return true
|
|
621
|
+
return true
|
|
641
622
|
}
|
|
642
623
|
} else {
|
|
643
624
|
if (undefined === config?.series || false === config?.series.length > 0) {
|
|
644
|
-
return true
|
|
625
|
+
return true
|
|
645
626
|
}
|
|
646
627
|
}
|
|
647
628
|
|
|
648
629
|
if (!config.xAxis.dataKey) {
|
|
649
|
-
return true
|
|
630
|
+
return true
|
|
650
631
|
}
|
|
651
632
|
|
|
652
|
-
return false
|
|
653
|
-
}
|
|
633
|
+
return false
|
|
634
|
+
}
|
|
654
635
|
|
|
655
636
|
// Prevent render if loading
|
|
656
|
-
let body =
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
if(!loading) {
|
|
660
|
-
|
|
637
|
+
let body = <Loading />
|
|
661
638
|
|
|
639
|
+
if (!loading) {
|
|
662
640
|
body = (
|
|
663
641
|
<>
|
|
664
642
|
{isEditor && <EditorPanel />}
|
|
665
643
|
{!missingRequiredSections() && !config.newViz && (
|
|
666
|
-
<div className=
|
|
644
|
+
<div className='cdc-chart-inner-container'>
|
|
667
645
|
{/* Title */}
|
|
668
|
-
|
|
646
|
+
|
|
669
647
|
{title && (
|
|
670
|
-
<div
|
|
671
|
-
|
|
672
|
-
className={`chart-title ${config.theme} cove-component__header`}
|
|
673
|
-
aria-level={2}
|
|
674
|
-
>
|
|
675
|
-
{config && (
|
|
676
|
-
<sup className="superTitle">{parse(config.superTitle || '')}</sup>
|
|
677
|
-
)}
|
|
648
|
+
<div role='heading' className={`chart-title ${config.theme} cove-component__header`} aria-level={2}>
|
|
649
|
+
{config && <sup className='superTitle'>{parse(config.superTitle || '')}</sup>}
|
|
678
650
|
<div>{parse(title)}</div>
|
|
679
651
|
</div>
|
|
680
652
|
)}
|
|
681
|
-
<a
|
|
682
|
-
id="skip-chart-container"
|
|
683
|
-
className="cdcdataviz-sr-only-focusable"
|
|
684
|
-
href={handleChartTabbing}
|
|
685
|
-
>
|
|
653
|
+
<a id='skip-chart-container' className='cdcdataviz-sr-only-focusable' href={handleChartTabbing}>
|
|
686
654
|
Skip Over Chart Container
|
|
687
655
|
</a>
|
|
688
656
|
{/* Filters */}
|
|
@@ -690,22 +658,20 @@ export default function CdcChart(
|
|
|
690
658
|
{/* Visualization */}
|
|
691
659
|
{config?.introText && <section className="introText">{parse(config.introText)}</section>}
|
|
692
660
|
<div
|
|
693
|
-
className={`chart-container${
|
|
694
|
-
|
|
661
|
+
className={`chart-container ${config.legend.position==='bottom'? "bottom":""
|
|
662
|
+
}${config.legend.hide ? " legend-hidden" : ""
|
|
695
663
|
}${lineDatapointClass}${barBorderClass} ${contentClasses.join(' ')}`}
|
|
696
664
|
>
|
|
697
665
|
{/* All charts except sparkline */}
|
|
698
|
-
{config.visualizationType !==
|
|
699
|
-
chartComponents[visualizationType]
|
|
700
|
-
}
|
|
666
|
+
{config.visualizationType !== 'Spark Line' && chartComponents[visualizationType]}
|
|
701
667
|
|
|
702
668
|
{/* Sparkline */}
|
|
703
|
-
{config.visualizationType ===
|
|
669
|
+
{config.visualizationType === 'Spark Line' && (
|
|
704
670
|
<>
|
|
705
|
-
{
|
|
671
|
+
{description && <div className='subtext'>{parse(description)}</div>}
|
|
706
672
|
<div style={sparkLineStyles}>
|
|
707
673
|
<ParentSize>
|
|
708
|
-
{
|
|
674
|
+
{parent => (
|
|
709
675
|
<>
|
|
710
676
|
<SparkLine width={parent.width} height={parent.height} />
|
|
711
677
|
</>
|
|
@@ -713,26 +679,25 @@ export default function CdcChart(
|
|
|
713
679
|
</ParentSize>
|
|
714
680
|
</div>
|
|
715
681
|
</>
|
|
716
|
-
)
|
|
717
|
-
}
|
|
718
|
-
{!config.legend.hide && config.visualizationType !== "Spark Line" && <Legend />}
|
|
682
|
+
)}
|
|
683
|
+
{!config.legend.hide && config.visualizationType !== 'Spark Line' && <Legend />}
|
|
719
684
|
</div>
|
|
720
685
|
{/* Link */}
|
|
721
686
|
{link && link}
|
|
722
687
|
{/* Description */}
|
|
723
|
-
{description && config.visualizationType !==
|
|
688
|
+
{description && config.visualizationType !== 'Spark Line' && <div className='subtext'>{parse(description)}</div>}
|
|
724
689
|
{/* Data Table */}
|
|
725
690
|
|
|
726
|
-
{config.xAxis.dataKey && config.table.show && config.visualizationType !==
|
|
727
|
-
{config?.footnotes && <section className=
|
|
691
|
+
{config.xAxis.dataKey && config.table.show && config.visualizationType !== 'Spark Line' && <DataTable />}
|
|
692
|
+
{config?.footnotes && <section className='footnotes'>{parse(config.footnotes)}</section>}
|
|
728
693
|
</div>
|
|
729
694
|
)}
|
|
730
695
|
</>
|
|
731
|
-
)
|
|
696
|
+
)
|
|
732
697
|
}
|
|
733
698
|
|
|
734
|
-
const getXAxisData = (d: any) => config.runtime.xAxis.type === 'date' ?
|
|
735
|
-
const getYAxisData = (d: any, seriesKey: string) => d[seriesKey]
|
|
699
|
+
const getXAxisData = (d: any) => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
|
|
700
|
+
const getYAxisData = (d: any, seriesKey: string) => d[seriesKey]
|
|
736
701
|
|
|
737
702
|
const contextValues = {
|
|
738
703
|
getXAxisData,
|
|
@@ -767,15 +732,9 @@ export default function CdcChart(
|
|
|
767
732
|
setDynamicLegendItems
|
|
768
733
|
}
|
|
769
734
|
|
|
770
|
-
const classes = [
|
|
771
|
-
'cdc-open-viz-module',
|
|
772
|
-
'type-chart',
|
|
773
|
-
`${currentViewport}`,
|
|
774
|
-
`font-${config.fontSize}`,
|
|
775
|
-
`${config.theme}`
|
|
776
|
-
]
|
|
735
|
+
const classes = ['cdc-open-viz-module', 'type-chart', `${currentViewport}`, `font-${config.fontSize}`, `${config.theme}`]
|
|
777
736
|
|
|
778
|
-
config.visualizationType ===
|
|
737
|
+
config.visualizationType === 'Spark Line' && classes.push(`type-sparkline`)
|
|
779
738
|
isEditor && classes.push('spacing-wrapper')
|
|
780
739
|
isEditor && classes.push('isEditor')
|
|
781
740
|
|
|
@@ -785,5 +744,5 @@ export default function CdcChart(
|
|
|
785
744
|
{body}
|
|
786
745
|
</div>
|
|
787
746
|
</Context.Provider>
|
|
788
|
-
)
|
|
747
|
+
)
|
|
789
748
|
}
|