@cdc/chart 4.23.1 → 4.23.3
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 +56289 -702
- package/examples/Barchart_with_negative.json +34 -0
- package/examples/area-chart.json +187 -0
- package/examples/big-small-test-bar.json +328 -0
- package/examples/big-small-test-line.json +328 -0
- package/examples/big-small-test-negative.json +328 -0
- package/examples/box-plot.json +1 -2
- package/examples/dynamic-legends.json +1 -1
- package/examples/example-bar-chart-nonnumeric.json +36 -0
- package/examples/example-bar-chart.json +36 -0
- package/examples/example-combo-bar-nonnumeric.json +105 -0
- package/examples/example-sparkline.json +76 -0
- package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +1 -1
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json +1 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +96 -14
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +2 -2
- package/examples/gallery/line/line.json +1 -0
- package/examples/gallery/paired-bar/paired-bar-chart.json +65 -13
- package/examples/horizontal-chart-max-increase.json +38 -0
- package/examples/line-chart-max-increase.json +32 -0
- package/examples/line-chart-nonnumeric.json +32 -0
- package/examples/line-chart.json +21 -63
- package/examples/newdata.json +1 -1
- package/examples/planet-combo-example-config.json +143 -20
- package/examples/planet-deviation-config.json +168 -0
- package/examples/planet-deviation-data.json +38 -0
- package/examples/planet-example-config.json +139 -20
- package/examples/planet-example-data-max-increase.json +56 -0
- package/examples/planet-example-data-nonnumeric.json +56 -0
- package/examples/planet-example-data.json +9 -9
- package/examples/planet-pie-example-config-nonnumeric.json +30 -0
- package/examples/scatterplot-continuous.csv +17 -0
- package/examples/scatterplot.json +136 -0
- package/examples/sparkline-chart-nonnumeric.json +76 -0
- package/examples/stacked-vertical-bar-example-negative.json +154 -0
- package/examples/stacked-vertical-bar-example-nonnumerics.json +154 -0
- package/index.html +91 -0
- package/package.json +33 -24
- package/src/{CdcChart.tsx → CdcChart.jsx} +196 -124
- package/src/components/AreaChart.jsx +198 -0
- package/src/components/{BarChart.tsx → BarChart.jsx} +154 -122
- package/src/components/BoxPlot.jsx +101 -0
- package/src/components/{DataTable.tsx → DataTable.jsx} +109 -28
- package/src/components/DeviationBar.jsx +191 -0
- package/src/components/{EditorPanel.js → EditorPanel.jsx} +676 -157
- package/src/components/{Filters.js → Filters.jsx} +6 -11
- package/src/components/Legend.jsx +316 -0
- package/src/components/{LineChart.tsx → LineChart.jsx} +22 -26
- package/src/components/{LinearChart.tsx → LinearChart.jsx} +214 -91
- package/src/components/{PairedBarChart.tsx → PairedBarChart.jsx} +44 -78
- package/src/components/{PieChart.tsx → PieChart.jsx} +26 -44
- package/src/components/ScatterPlot.jsx +51 -0
- package/src/components/SparkLine.jsx +218 -0
- package/src/components/{useIntersectionObserver.tsx → useIntersectionObserver.jsx} +2 -2
- package/src/data/initial-state.js +51 -5
- package/src/hooks/useColorPalette.js +68 -0
- package/src/hooks/{useReduceData.ts → useReduceData.js} +26 -16
- package/src/hooks/useRightAxis.js +3 -1
- package/src/index.jsx +16 -0
- package/src/scss/DataTable.scss +22 -0
- package/src/scss/editor-panel.scss +5 -0
- package/src/scss/main.scss +30 -10
- package/src/test/CdcChart.test.jsx +6 -0
- package/vite.config.js +4 -0
- package/dist/495.js +0 -3
- package/dist/703.js +0 -1
- package/src/components/BoxPlot.js +0 -92
- package/src/components/Legend.js +0 -291
- package/src/components/SparkLine.js +0 -185
- package/src/hooks/useColorPalette.ts +0 -76
- package/src/index.html +0 -67
- package/src/index.tsx +0 -18
- /package/src/{context.tsx → ConfigContext.jsx} +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback } from 'react'
|
|
2
2
|
|
|
3
3
|
// IE11
|
|
4
|
-
import 'core-js/stable'
|
|
5
4
|
import ResizeObserver from 'resize-observer-polyfill'
|
|
6
5
|
import 'whatwg-fetch'
|
|
7
6
|
import * as d3 from 'd3-array'
|
|
@@ -13,14 +12,14 @@ import { timeParse, timeFormat } from 'd3-time-format'
|
|
|
13
12
|
import { format } from 'd3-format'
|
|
14
13
|
import Papa from 'papaparse'
|
|
15
14
|
import parse from 'html-react-parser'
|
|
16
|
-
import
|
|
15
|
+
import 'react-tooltip/dist/react-tooltip.css'
|
|
17
16
|
|
|
18
17
|
// Primary Components
|
|
19
|
-
import
|
|
18
|
+
import ConfigContext from './ConfigContext'
|
|
20
19
|
import PieChart from './components/PieChart'
|
|
21
20
|
import LinearChart from './components/LinearChart'
|
|
22
21
|
|
|
23
|
-
import { colorPalettesChart as colorPalettes } from '
|
|
22
|
+
import { colorPalettesChart as colorPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
|
|
24
23
|
|
|
25
24
|
import { publish, subscribe, unsubscribe } from '@cdc/core/helpers/events'
|
|
26
25
|
|
|
@@ -33,46 +32,57 @@ import defaults from './data/initial-state'
|
|
|
33
32
|
import EditorPanel from './components/EditorPanel'
|
|
34
33
|
import Loading from '@cdc/core/components/Loading'
|
|
35
34
|
import Filters from './components/Filters'
|
|
36
|
-
import CoveMediaControls from '@cdc/core/
|
|
35
|
+
import CoveMediaControls from '@cdc/core/components/CoveMediaControls'
|
|
37
36
|
|
|
38
|
-
//
|
|
37
|
+
// Helpers
|
|
39
38
|
import numberFromString from '@cdc/core/helpers/numberFromString'
|
|
40
39
|
import getViewport from '@cdc/core/helpers/getViewport'
|
|
41
40
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
42
41
|
import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
|
|
42
|
+
import isNumber from '@cdc/core/helpers/isNumber'
|
|
43
|
+
import cleanData from '@cdc/core/helpers/cleanData'
|
|
43
44
|
|
|
44
45
|
import './scss/main.scss'
|
|
45
46
|
|
|
46
|
-
export default function CdcChart({ configUrl, config: configObj, isEditor = false, isDashboard = false, setConfig: setParentConfig, setEditing, hostname, link }
|
|
47
|
+
export default function CdcChart({ configUrl, config: configObj, isEditor = false, isDashboard = false, setConfig: setParentConfig, setEditing, hostname, link }) {
|
|
47
48
|
const transform = new DataTransform()
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const [
|
|
53
|
-
const [
|
|
54
|
-
const [
|
|
55
|
-
const [
|
|
56
|
-
const [
|
|
57
|
-
const [filteredData, setFilteredData] = useState<Array<Object>>()
|
|
58
|
-
const [seriesHighlight, setSeriesHighlight] = useState<Array<String>>([])
|
|
59
|
-
const [currentViewport, setCurrentViewport] = useState<String>('lg')
|
|
60
|
-
const [dimensions, setDimensions] = useState<Array<Number>>([])
|
|
49
|
+
const [loading, setLoading] = useState(true)
|
|
50
|
+
const [colorScale, setColorScale] = useState(null)
|
|
51
|
+
const [config, setConfig] = useState({})
|
|
52
|
+
const [stateData, setStateData] = useState(config.data || [])
|
|
53
|
+
const [excludedData, setExcludedData] = useState()
|
|
54
|
+
const [filteredData, setFilteredData] = useState()
|
|
55
|
+
const [seriesHighlight, setSeriesHighlight] = useState([])
|
|
56
|
+
const [currentViewport, setCurrentViewport] = useState('lg')
|
|
57
|
+
const [dimensions, setDimensions] = useState([])
|
|
61
58
|
const [externalFilters, setExternalFilters] = useState(null)
|
|
62
59
|
const [container, setContainer] = useState()
|
|
63
60
|
const [coveLoadedEventRan, setCoveLoadedEventRan] = useState(false)
|
|
64
61
|
const [dynamicLegendItems, setDynamicLegendItems] = useState([])
|
|
65
|
-
const [imageId
|
|
66
|
-
|
|
67
|
-
const legendGlyphSize = 15
|
|
68
|
-
const legendGlyphSizeHalf = legendGlyphSize / 2
|
|
62
|
+
const [imageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`)
|
|
69
63
|
|
|
70
64
|
// Destructure items from config for more readable JSX
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
let { legend, title, description, visualizationType } = config
|
|
66
|
+
|
|
67
|
+
// set defaults on titles if blank AND only in editor
|
|
68
|
+
if (isEditor) {
|
|
69
|
+
if (!title || title === '') title = 'Chart Title'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (config.table && (!config.table?.label || config.table?.label === '')) config.table.label = 'Data Table'
|
|
73
|
+
|
|
74
|
+
const { barBorderClass, lineDatapointClass, contentClasses, sparkLineStyles } = useDataVizClasses(config)
|
|
73
75
|
|
|
74
76
|
const handleChartTabbing = config.showSidebar ? `#legend` : config?.title ? `#dataTableSection__${config.title.replace(/\s/g, '')}` : `#dataTableSection`
|
|
75
77
|
|
|
78
|
+
const sortAsc = (a, b) => {
|
|
79
|
+
return a.toString().localeCompare(b.toString(), 'en', { numeric: true })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const sortDesc = (a, b) => {
|
|
83
|
+
return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
|
|
84
|
+
}
|
|
85
|
+
|
|
76
86
|
const handleChartAriaLabels = (state, testing = false) => {
|
|
77
87
|
if (testing) console.log(`handleChartAriaLabels Testing On:`, state)
|
|
78
88
|
try {
|
|
@@ -89,7 +99,20 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
89
99
|
|
|
90
100
|
return ariaLabel
|
|
91
101
|
} catch (e) {
|
|
92
|
-
console.error(e.message)
|
|
102
|
+
console.error('COVE: ', e.message) // eslint-disable-line
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const handleLineType = lineType => {
|
|
107
|
+
switch (lineType) {
|
|
108
|
+
case 'dashed-sm':
|
|
109
|
+
return '5 5'
|
|
110
|
+
case 'dashed-md':
|
|
111
|
+
return '10 5'
|
|
112
|
+
case 'dashed-lg':
|
|
113
|
+
return '15 5'
|
|
114
|
+
default:
|
|
115
|
+
return 0
|
|
93
116
|
}
|
|
94
117
|
}
|
|
95
118
|
|
|
@@ -121,7 +144,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
121
144
|
data = await fetch(response.dataUrl + `?v=${cacheBustingString()}`).then(response => response.json())
|
|
122
145
|
}
|
|
123
146
|
} catch {
|
|
124
|
-
console.error(`Cannot parse URL: ${response.dataUrl}`)
|
|
147
|
+
console.error(`COVE: Cannot parse URL: ${response.dataUrl}`)
|
|
125
148
|
data = []
|
|
126
149
|
}
|
|
127
150
|
|
|
@@ -137,6 +160,9 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
137
160
|
}
|
|
138
161
|
|
|
139
162
|
let newConfig = { ...defaults, ...response }
|
|
163
|
+
if (newConfig.visualizationType === 'Box Plot') {
|
|
164
|
+
newConfig.legend.hide = true
|
|
165
|
+
}
|
|
140
166
|
if (undefined === newConfig.table.show) newConfig.table.show = !isDashboard
|
|
141
167
|
updateConfig(newConfig, data)
|
|
142
168
|
}
|
|
@@ -189,7 +215,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
189
215
|
newConfig.filters.forEach((filter, index) => {
|
|
190
216
|
let filterValues = []
|
|
191
217
|
|
|
192
|
-
filterValues = filter.orderedValues || generateValuesForFilter(filter.columnName, newExcludedData)
|
|
218
|
+
filterValues = filter.orderedValues || generateValuesForFilter(filter.columnName, newExcludedData).sort(filter.order === 'desc' ? sortDesc : sortAsc)
|
|
193
219
|
|
|
194
220
|
newConfig.filters[index].values = filterValues
|
|
195
221
|
// Initial filter should be active
|
|
@@ -219,11 +245,8 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
219
245
|
}
|
|
220
246
|
|
|
221
247
|
if (newConfig.visualizationType === 'Box Plot' && newConfig.series) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// stats
|
|
225
|
-
let allKeys = data.map(d => d[newConfig.xAxis.dataKey])
|
|
226
|
-
let allValues = data.map(d => Number(d[newConfig?.series[0]?.dataKey]))
|
|
248
|
+
let allKeys = newExcludedData ? newExcludedData.map(d => d[newConfig.xAxis.dataKey]) : data.map(d => d[newConfig.xAxis.dataKey])
|
|
249
|
+
let allValues = newExcludedData ? newExcludedData.map(d => Number(d[newConfig?.series[0]?.dataKey])) : data.map(d => Number(d[newConfig?.series[0]?.dataKey]))
|
|
227
250
|
|
|
228
251
|
const uniqueArray = function (arrArg) {
|
|
229
252
|
return arrArg.filter(function (elem, pos, arr) {
|
|
@@ -232,50 +255,75 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
232
255
|
}
|
|
233
256
|
|
|
234
257
|
const groups = uniqueArray(allKeys)
|
|
258
|
+
let tableData = []
|
|
235
259
|
const plots = []
|
|
236
260
|
|
|
237
|
-
console.log('d', data)
|
|
238
|
-
console.log('newConfig', newConfig)
|
|
239
|
-
console.log('groups', groups)
|
|
240
|
-
console.log('allKeys', allKeys)
|
|
241
|
-
console.log('allValues', allValues)
|
|
242
|
-
|
|
243
261
|
// group specific statistics
|
|
244
262
|
// prevent re-renders
|
|
245
|
-
groups
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
263
|
+
if (!groups) return
|
|
264
|
+
groups.forEach((g, index) => {
|
|
265
|
+
try {
|
|
266
|
+
if (!g) throw new Error('No groups resolved in box plots')
|
|
267
|
+
|
|
268
|
+
// filter data by group
|
|
269
|
+
let filteredData = newExcludedData ? newExcludedData.filter(item => item[newConfig.xAxis.dataKey] === g) : data.filter(item => item[newConfig.xAxis.dataKey] === g)
|
|
270
|
+
let filteredDataValues = filteredData.map(item => Number(item[newConfig?.series[0]?.dataKey]))
|
|
271
|
+
// let filteredDataValues = filteredData.map(item => Number(item[newConfig.yAxis.dataKey]))
|
|
272
|
+
|
|
273
|
+
if (!filteredData) throw new Error('boxplots dont have data yet')
|
|
274
|
+
if (!plots) throw new Error('boxplots dont have plots yet')
|
|
275
|
+
if (newConfig.boxplot.firstQuartilePercentage === '') {
|
|
276
|
+
newConfig.boxplot.firstQuartilePercentage = 0
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (newConfig.boxplot.thirdQuartilePercentage === '') {
|
|
280
|
+
newConfig.boxplot.thirdQuartilePercentage = 0
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const q1 = d3.quantile(filteredDataValues, parseFloat(newConfig.boxplot.firstQuartilePercentage) / 100)
|
|
284
|
+
const q3 = d3.quantile(filteredDataValues, parseFloat(newConfig.boxplot.thirdQuartilePercentage) / 100)
|
|
285
|
+
const iqr = q3 - q1
|
|
286
|
+
const lowerBounds = q1 - (q3 - q1) * 1.5
|
|
287
|
+
const upperBounds = q3 + (q3 - q1) * 1.5
|
|
288
|
+
const outliers = filteredDataValues.filter(v => v < lowerBounds || v > upperBounds)
|
|
289
|
+
let nonOutliers = filteredDataValues
|
|
290
|
+
|
|
291
|
+
nonOutliers = nonOutliers.filter(item => !outliers.includes(item))
|
|
292
|
+
|
|
293
|
+
plots.push({
|
|
294
|
+
columnCategory: g,
|
|
295
|
+
columnMax: Number(q3 + 1.5 * iqr).toFixed(newConfig.dataFormat.roundTo),
|
|
296
|
+
columnThirdQuartile: Number(q3).toFixed(newConfig.dataFormat.roundTo),
|
|
297
|
+
columnMedian: Number(d3.median(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
|
|
298
|
+
columnFirstQuartile: q1.toFixed(newConfig.dataFormat.roundTo),
|
|
299
|
+
columnMin: Number(q1 - 1.5 * iqr).toFixed(newConfig.dataFormat.roundTo),
|
|
300
|
+
columnTotal: filteredDataValues.reduce((partialSum, a) => partialSum + a, 0),
|
|
301
|
+
columnSd: Number(d3.deviation(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
|
|
302
|
+
columnMean: Number(d3.mean(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
|
|
303
|
+
columnIqr: Number(iqr).toFixed(newConfig.dataFormat.roundTo),
|
|
304
|
+
columnOutliers: outliers,
|
|
305
|
+
values: filteredDataValues,
|
|
306
|
+
nonOutlierValues: nonOutliers
|
|
307
|
+
})
|
|
308
|
+
} catch (e) {
|
|
309
|
+
console.error('COVE: ', e.message) // eslint-disable-line
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
// make deep copy so we can remove some fields for data
|
|
314
|
+
// this appears to be the easiest option instead of running logic against the datatable cell...
|
|
315
|
+
tableData = JSON.parse(JSON.stringify(plots))
|
|
316
|
+
tableData.map(table => {
|
|
317
|
+
delete table.columnIqr
|
|
318
|
+
delete table.nonOutlierValues
|
|
319
|
+
return null // resolve eslint
|
|
273
320
|
})
|
|
274
321
|
|
|
275
322
|
// any other data we can add to boxplots
|
|
276
323
|
newConfig.boxplot['allValues'] = allValues
|
|
277
324
|
newConfig.boxplot['categories'] = groups
|
|
278
|
-
newConfig.boxplot.
|
|
325
|
+
newConfig.boxplot.plots = plots
|
|
326
|
+
newConfig.boxplot.tableData = tableData
|
|
279
327
|
}
|
|
280
328
|
|
|
281
329
|
if (newConfig.visualizationType === 'Combo' && newConfig.series) {
|
|
@@ -291,7 +339,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
291
339
|
})
|
|
292
340
|
}
|
|
293
341
|
|
|
294
|
-
if ((newConfig.visualizationType === 'Bar' && newConfig.orientation === 'horizontal') || newConfig.visualizationType === 'Paired Bar') {
|
|
342
|
+
if (((newConfig.visualizationType === 'Bar' || newConfig.visualizationType === 'Deviation Bar') && newConfig.orientation === 'horizontal') || newConfig.visualizationType === 'Paired Bar') {
|
|
295
343
|
newConfig.runtime.xAxis = newConfig.yAxis
|
|
296
344
|
newConfig.runtime.yAxis = newConfig.xAxis
|
|
297
345
|
newConfig.runtime.horizontal = true
|
|
@@ -328,6 +376,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
328
376
|
|
|
329
377
|
data.forEach(row => {
|
|
330
378
|
const value = row[columnName]
|
|
379
|
+
//@ts-ignore
|
|
331
380
|
if (value && false === values.includes(value)) {
|
|
332
381
|
values.push(value)
|
|
333
382
|
}
|
|
@@ -352,7 +401,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
352
401
|
}
|
|
353
402
|
|
|
354
403
|
// Observes changes to outermost container and changes viewport size in state
|
|
355
|
-
const resizeObserver
|
|
404
|
+
const resizeObserver = new ResizeObserver(entries => {
|
|
356
405
|
for (let entry of entries) {
|
|
357
406
|
let { width, height } = entry.contentRect
|
|
358
407
|
let newViewport = getViewport(width)
|
|
@@ -381,7 +430,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
381
430
|
}
|
|
382
431
|
|
|
383
432
|
setContainer(node)
|
|
384
|
-
}, [])
|
|
433
|
+
}, []) // eslint-disable-line
|
|
385
434
|
|
|
386
435
|
function isEmpty(obj) {
|
|
387
436
|
return Object.keys(obj).length === 0
|
|
@@ -390,7 +439,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
390
439
|
// Load data when component first mounts
|
|
391
440
|
useEffect(() => {
|
|
392
441
|
loadConfig()
|
|
393
|
-
}, [])
|
|
442
|
+
}, []) // eslint-disable-line
|
|
394
443
|
|
|
395
444
|
/**
|
|
396
445
|
* When cove has a config and container ref publish the cove_loaded event.
|
|
@@ -400,7 +449,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
400
449
|
publish('cove_loaded', { config: config })
|
|
401
450
|
setCoveLoadedEventRan(true)
|
|
402
451
|
}
|
|
403
|
-
}, [container, config])
|
|
452
|
+
}, [container, config]) // eslint-disable-line
|
|
404
453
|
|
|
405
454
|
/**
|
|
406
455
|
* Handles filter change events outside of COVE
|
|
@@ -408,13 +457,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
408
457
|
* Another useEffect listens to externalFilterChanges and updates the config.
|
|
409
458
|
*/
|
|
410
459
|
useEffect(() => {
|
|
411
|
-
const handleFilterData =
|
|
460
|
+
const handleFilterData = e => {
|
|
412
461
|
let tmp = []
|
|
413
462
|
tmp.push(e.detail)
|
|
414
463
|
setExternalFilters(tmp)
|
|
415
464
|
}
|
|
416
465
|
|
|
417
|
-
subscribe('cove_filterData',
|
|
466
|
+
subscribe('cove_filterData', e => handleFilterData(e))
|
|
418
467
|
|
|
419
468
|
return () => {
|
|
420
469
|
unsubscribe('cove_filterData', handleFilterData)
|
|
@@ -443,19 +492,22 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
443
492
|
setConfig(newConfigHere)
|
|
444
493
|
setFilteredData(filterData(externalFilters, excludedData))
|
|
445
494
|
}
|
|
446
|
-
}, [externalFilters])
|
|
495
|
+
}, [externalFilters]) // eslint-disable-line
|
|
447
496
|
|
|
448
497
|
// Load data when configObj data changes
|
|
449
498
|
if (configObj) {
|
|
499
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
450
500
|
useEffect(() => {
|
|
451
501
|
loadConfig()
|
|
452
|
-
}, [configObj.data])
|
|
502
|
+
}, [configObj.data]) // eslint-disable-line
|
|
453
503
|
}
|
|
454
504
|
|
|
455
505
|
// Generates color palette to pass to child chart component
|
|
456
506
|
useEffect(() => {
|
|
457
507
|
if (stateData && config.xAxis && config.runtime.seriesKeys) {
|
|
458
|
-
|
|
508
|
+
const configPalette = config.visualizationType === 'Paired Bar' || config.visualizationType === 'Deviation Bar' ? config.twoColor.palette : config.palette
|
|
509
|
+
const allPalettes = { ...colorPalettes, ...twoColorPalette }
|
|
510
|
+
let palette = config.customColors || allPalettes[configPalette]
|
|
459
511
|
let numberOfKeys = config.runtime.seriesKeys.length
|
|
460
512
|
let newColorScale
|
|
461
513
|
|
|
@@ -478,7 +530,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
478
530
|
if (config && stateData && config.sortData) {
|
|
479
531
|
stateData.sort(sortData)
|
|
480
532
|
}
|
|
481
|
-
}, [config, stateData])
|
|
533
|
+
}, [config, stateData]) // eslint-disable-line
|
|
482
534
|
|
|
483
535
|
// Called on legend click, highlights/unhighlights the data series with the given label
|
|
484
536
|
const highlight = label => {
|
|
@@ -523,7 +575,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
523
575
|
|
|
524
576
|
const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
|
|
525
577
|
|
|
526
|
-
const parseDate =
|
|
578
|
+
const parseDate = dateString => {
|
|
527
579
|
let date = timeParse(config.runtime[section].dateParseFormat)(dateString)
|
|
528
580
|
if (!date) {
|
|
529
581
|
config.runtime.editorErrorMessage = `Error parsing date "${dateString}". Try reviewing your data and date parse settings in the X Axis section.`
|
|
@@ -533,48 +585,37 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
533
585
|
}
|
|
534
586
|
}
|
|
535
587
|
|
|
536
|
-
const formatDate =
|
|
588
|
+
const formatDate = date => {
|
|
537
589
|
return timeFormat(config.runtime[section].dateDisplayFormat)(date)
|
|
538
590
|
}
|
|
539
591
|
|
|
540
|
-
|
|
541
|
-
|
|
592
|
+
// function calculates the width of given text and its font-size
|
|
593
|
+
function getTextWidth(text, font) {
|
|
594
|
+
const canvas = document.createElement('canvas')
|
|
595
|
+
const context = canvas.getContext('2d')
|
|
542
596
|
|
|
543
|
-
|
|
597
|
+
context.font = font || getComputedStyle(document.body).font
|
|
544
598
|
|
|
545
|
-
|
|
546
|
-
//@ts-ignore
|
|
547
|
-
if (typeof window.navigator.msSaveBlob === 'function') {
|
|
548
|
-
const dataBlob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' })
|
|
549
|
-
//@ts-ignore
|
|
550
|
-
window.navigator.msSaveBlob(dataBlob, fileName)
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
if (type === 'download') {
|
|
555
|
-
return (
|
|
556
|
-
<a download={fileName} onClick={saveBlob} href={`data:text/csv;base64,${Base64.encode(csvData)}`} aria-label='Download this data in a CSV file format.' className={`btn btn-download no-border`}>
|
|
557
|
-
Download Data (CSV)
|
|
558
|
-
</a>
|
|
559
|
-
)
|
|
560
|
-
} else {
|
|
561
|
-
return (
|
|
562
|
-
<a download={fileName} onClick={saveBlob} href={`data:text/csv;base64,${Base64.encode(csvData)}`} aria-label='Download this data in a CSV file format.' className={`btn no-border`}>
|
|
563
|
-
Download Data (CSV)
|
|
564
|
-
</a>
|
|
565
|
-
)
|
|
566
|
-
}
|
|
599
|
+
return Math.ceil(context.measureText(text).width)
|
|
567
600
|
}
|
|
568
601
|
|
|
569
602
|
// Format numeric data based on settings in config
|
|
570
603
|
const formatNumber = (num, axis) => {
|
|
571
604
|
// if num is NaN return num
|
|
572
605
|
if (isNaN(num) || !num) return num
|
|
606
|
+
// Check if the input number is negative
|
|
607
|
+
const isNegative = num < 0
|
|
608
|
+
|
|
609
|
+
// If the input number is negative, take the absolute value
|
|
610
|
+
if (isNegative) {
|
|
611
|
+
num = Math.abs(num)
|
|
612
|
+
}
|
|
573
613
|
|
|
574
614
|
// destructure dataFormat values
|
|
575
615
|
let {
|
|
576
|
-
dataFormat: { commas, abbreviated, roundTo, prefix, suffix, rightRoundTo, rightPrefix, rightSuffix }
|
|
616
|
+
dataFormat: { commas, abbreviated, roundTo, prefix, suffix, rightRoundTo, bottomRoundTo, rightPrefix, rightSuffix, bottomPrefix, bottomSuffix, bottomAbbreviated }
|
|
577
617
|
} = config
|
|
618
|
+
|
|
578
619
|
let formatSuffix = format('.2s')
|
|
579
620
|
|
|
580
621
|
// check if value contains comma and remove it. later will add comma below.
|
|
@@ -582,14 +623,15 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
582
623
|
|
|
583
624
|
let original = num
|
|
584
625
|
let stringFormattingOptions
|
|
585
|
-
|
|
586
|
-
if (axis !== 'right') {
|
|
626
|
+
if (axis === 'left') {
|
|
587
627
|
stringFormattingOptions = {
|
|
588
628
|
useGrouping: config.dataFormat.commas ? true : false,
|
|
589
629
|
minimumFractionDigits: roundTo ? Number(roundTo) : 0,
|
|
590
630
|
maximumFractionDigits: roundTo ? Number(roundTo) : 0
|
|
591
631
|
}
|
|
592
|
-
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (axis === 'right') {
|
|
593
635
|
stringFormattingOptions = {
|
|
594
636
|
useGrouping: config.dataFormat.rightCommas ? true : false,
|
|
595
637
|
minimumFractionDigits: rightRoundTo ? Number(rightRoundTo) : 0,
|
|
@@ -597,6 +639,14 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
597
639
|
}
|
|
598
640
|
}
|
|
599
641
|
|
|
642
|
+
if (axis === 'bottom') {
|
|
643
|
+
stringFormattingOptions = {
|
|
644
|
+
useGrouping: config.dataFormat.bottomCommas ? true : false,
|
|
645
|
+
minimumFractionDigits: bottomRoundTo ? Number(bottomRoundTo) : 0,
|
|
646
|
+
maximumFractionDigits: bottomRoundTo ? Number(bottomRoundTo) : 0
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
600
650
|
num = numberFromString(num)
|
|
601
651
|
|
|
602
652
|
if (isNaN(num)) {
|
|
@@ -617,8 +667,11 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
617
667
|
// Use commas also updates bars and the data table
|
|
618
668
|
// We can't use commas when we're formatting the dataFormatted number
|
|
619
669
|
// Example: commas -> 12,000; abbreviated -> 12k (correct); abbreviated & commas -> 12 (incorrect)
|
|
620
|
-
|
|
621
|
-
|
|
670
|
+
//
|
|
671
|
+
// Edge case for small numbers with decimals
|
|
672
|
+
// - if roundTo undefined which means it is blank, then do not round
|
|
673
|
+
if ((axis === 'left' && commas && abbreviated) || (axis === 'bottom' && commas && abbreviated)) {
|
|
674
|
+
num = num // eslint-disable-line
|
|
622
675
|
} else {
|
|
623
676
|
num = num.toLocaleString('en-US', stringFormattingOptions)
|
|
624
677
|
}
|
|
@@ -628,11 +681,11 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
628
681
|
num = formatSuffix(parseFloat(num)).replace('G', 'B')
|
|
629
682
|
}
|
|
630
683
|
|
|
631
|
-
if (
|
|
684
|
+
if (bottomAbbreviated && axis === 'bottom') {
|
|
632
685
|
num = formatSuffix(parseFloat(num)).replace('G', 'B')
|
|
633
686
|
}
|
|
634
687
|
|
|
635
|
-
if (prefix && axis
|
|
688
|
+
if (prefix && axis === 'left') {
|
|
636
689
|
result += prefix
|
|
637
690
|
}
|
|
638
691
|
|
|
@@ -640,9 +693,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
640
693
|
result += rightPrefix
|
|
641
694
|
}
|
|
642
695
|
|
|
696
|
+
if (bottomPrefix && axis === 'bottom') {
|
|
697
|
+
result += bottomPrefix
|
|
698
|
+
}
|
|
699
|
+
|
|
643
700
|
result += num
|
|
644
701
|
|
|
645
|
-
if (suffix && axis
|
|
702
|
+
if (suffix && axis === 'left') {
|
|
646
703
|
result += suffix
|
|
647
704
|
}
|
|
648
705
|
|
|
@@ -650,6 +707,13 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
650
707
|
result += rightSuffix
|
|
651
708
|
}
|
|
652
709
|
|
|
710
|
+
if (bottomSuffix && axis === 'bottom') {
|
|
711
|
+
result += bottomSuffix
|
|
712
|
+
}
|
|
713
|
+
if (isNegative) {
|
|
714
|
+
result = '-' + result
|
|
715
|
+
}
|
|
716
|
+
|
|
653
717
|
return String(result)
|
|
654
718
|
}
|
|
655
719
|
|
|
@@ -660,7 +724,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
660
724
|
Line: <LinearChart />,
|
|
661
725
|
Combo: <LinearChart />,
|
|
662
726
|
Pie: <PieChart />,
|
|
663
|
-
'Box Plot': <LinearChart
|
|
727
|
+
'Box Plot': <LinearChart />,
|
|
728
|
+
'Area Chart': <LinearChart />,
|
|
729
|
+
'Scatter Plot': <LinearChart />,
|
|
730
|
+
'Deviation Bar': <LinearChart />
|
|
664
731
|
}
|
|
665
732
|
|
|
666
733
|
const missingRequiredSections = () => {
|
|
@@ -692,7 +759,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
692
759
|
<div className='cdc-chart-inner-container'>
|
|
693
760
|
{/* Title */}
|
|
694
761
|
|
|
695
|
-
{title && (
|
|
762
|
+
{title && config.showTitle && (
|
|
696
763
|
<div role='heading' className={`chart-title ${config.theme} cove-component__header`} aria-level={2}>
|
|
697
764
|
{config && <sup className='superTitle'>{parse(config.superTitle || '')}</sup>}
|
|
698
765
|
<div>{parse(title)}</div>
|
|
@@ -706,7 +773,7 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
706
773
|
{/* Visualization */}
|
|
707
774
|
{config?.introText && <section className='introText'>{parse(config.introText)}</section>}
|
|
708
775
|
<div
|
|
709
|
-
style={{ marginBottom: config.legend.position !== 'bottom' && config.orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` : '0px' }}
|
|
776
|
+
style={{ marginBottom: config.legend.position !== 'bottom' && currentViewport !== 'sm' && currentViewport !== 'xs' && config.orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` : '0px' }}
|
|
710
777
|
className={`chart-container ${config.legend.position === 'bottom' ? 'bottom' : ''}${config.legend.hide ? ' legend-hidden' : ''}${lineDatapointClass}${barBorderClass} ${contentClasses.join(' ')}`}
|
|
711
778
|
>
|
|
712
779
|
{/* All charts except sparkline */}
|
|
@@ -750,8 +817,8 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
750
817
|
)
|
|
751
818
|
}
|
|
752
819
|
|
|
753
|
-
const getXAxisData =
|
|
754
|
-
const getYAxisData = (d
|
|
820
|
+
const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
|
|
821
|
+
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
755
822
|
|
|
756
823
|
const contextValues = {
|
|
757
824
|
getXAxisData,
|
|
@@ -785,7 +852,12 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
785
852
|
dynamicLegendItems,
|
|
786
853
|
setDynamicLegendItems,
|
|
787
854
|
filterData,
|
|
788
|
-
imageId
|
|
855
|
+
imageId,
|
|
856
|
+
handleLineType,
|
|
857
|
+
isNumber,
|
|
858
|
+
cleanData,
|
|
859
|
+
getTextWidth,
|
|
860
|
+
twoColorPalette
|
|
789
861
|
}
|
|
790
862
|
|
|
791
863
|
const classes = ['cdc-open-viz-module', 'type-chart', `${currentViewport}`, `font-${config.fontSize}`, `${config.theme}`]
|
|
@@ -795,10 +867,10 @@ export default function CdcChart({ configUrl, config: configObj, isEditor = fals
|
|
|
795
867
|
isEditor && classes.push('isEditor')
|
|
796
868
|
|
|
797
869
|
return (
|
|
798
|
-
<
|
|
870
|
+
<ConfigContext.Provider value={contextValues}>
|
|
799
871
|
<div className={`${classes.join(' ')}`} ref={outerContainerRef} data-lollipop={config.isLollipopChart} data-download-id={imageId}>
|
|
800
872
|
{body}
|
|
801
873
|
</div>
|
|
802
|
-
</
|
|
874
|
+
</ConfigContext.Provider>
|
|
803
875
|
)
|
|
804
876
|
}
|