@cdc/chart 4.23.4 → 4.23.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +54845 -51755
- package/examples/feature/__data__/planet-example-data.json +14 -32
- package/examples/feature/__data__/planet-logaritmic-data.json +56 -0
- package/examples/feature/area/area-chart-category.json +240 -0
- package/examples/feature/bar/example-bar-chart.json +544 -22
- package/examples/feature/bar/new.json +561 -0
- package/examples/feature/bar/planet-chart-logaritmic-config.json +170 -0
- package/examples/feature/boxplot/valid-boxplot.csv +17 -0
- package/examples/feature/combo/right-issues.json +190 -0
- package/examples/feature/filters/filter-testing.json +37 -3
- package/examples/feature/forecasting/combo-forecasting.json +245 -0
- package/examples/feature/forecasting/forecasting.json +5325 -0
- package/examples/feature/forecasting/index.json +203 -0
- package/examples/feature/forecasting/random_data.csv +366 -0
- package/examples/feature/line/line-chart.json +3 -3
- package/examples/feature/test-highlight/test-highlight-2.json +789 -0
- package/examples/feature/test-highlight/test-highlight-vertical.json +561 -0
- package/examples/feature/test-highlight/test-highlight.json +100 -0
- package/examples/feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json +1 -2
- package/examples/gallery/bar-chart-horizontal/horizontal-highlight.json +345 -0
- package/examples/gallery/line/line.json +173 -1
- package/index.html +14 -8
- package/package.json +2 -2
- package/src/CdcChart.jsx +342 -25
- package/src/components/AreaChart.jsx +32 -40
- package/src/components/BarChart.jsx +147 -25
- package/src/components/DataTable.jsx +30 -12
- package/src/components/DeviationBar.jsx +32 -32
- package/src/components/EditorPanel.jsx +1902 -1126
- package/src/components/Forecasting.jsx +147 -0
- package/src/components/Legend.jsx +193 -243
- package/src/components/LineChart.jsx +4 -9
- package/src/components/LinearChart.jsx +263 -285
- package/src/components/Series.jsx +518 -0
- package/src/components/SparkLine.jsx +3 -3
- package/src/data/initial-state.js +24 -5
- package/src/hooks/useHighlightedBars.js +154 -0
- package/src/hooks/useMinMax.js +128 -0
- package/src/hooks/useReduceData.js +31 -57
- package/src/hooks/useRightAxis.js +8 -2
- package/src/hooks/useScales.js +196 -0
- /package/examples/feature/area/{area-chart.json → area-chart-date.json} +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import ConfigContext from '../ConfigContext'
|
|
3
|
+
|
|
4
|
+
export const useHighlightedBars = (config, updateConfig) => {
|
|
5
|
+
const { formatDate, parseDate } = useContext(ConfigContext)
|
|
6
|
+
|
|
7
|
+
let highlightedSeries = [] // only allow single series for highlights
|
|
8
|
+
let highlightedSeriesKey = ''
|
|
9
|
+
let highlightedBarValues = []
|
|
10
|
+
let highlightedSeriesValues = []
|
|
11
|
+
|
|
12
|
+
if (config.series?.length > 0 && config.data) {
|
|
13
|
+
highlightedSeries = config.series[0] // only allow single series for highlights
|
|
14
|
+
highlightedSeriesKey = config.series[0].dataKey
|
|
15
|
+
highlightedBarValues = config.highlightedBarValues
|
|
16
|
+
highlightedSeriesValues = config.data.map(item => item[config.xAxis.dataKey])
|
|
17
|
+
} else {
|
|
18
|
+
highlightedSeries = [] // only allow single series for highlights
|
|
19
|
+
highlightedSeriesKey = ''
|
|
20
|
+
highlightedBarValues = []
|
|
21
|
+
highlightedSeriesValues = []
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const handleUpdateHighlightedBorderWidth = (e, index) => {
|
|
25
|
+
const copyOfHighlightedBarValues = [...config.highlightedBarValues]
|
|
26
|
+
copyOfHighlightedBarValues[index].borderWidth = e.target.value
|
|
27
|
+
|
|
28
|
+
updateConfig({
|
|
29
|
+
...config,
|
|
30
|
+
highlightedBarValues: copyOfHighlightedBarValues
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const handleUpdateHighlightedBar = (e, index) => {
|
|
35
|
+
e.preventDefault()
|
|
36
|
+
|
|
37
|
+
const copyOfHighlightedBarValues = [...config.highlightedBarValues]
|
|
38
|
+
|
|
39
|
+
copyOfHighlightedBarValues[index].value = e.target.value
|
|
40
|
+
copyOfHighlightedBarValues[index].dataKey = highlightedSeriesKey
|
|
41
|
+
|
|
42
|
+
updateConfig({
|
|
43
|
+
...config,
|
|
44
|
+
highlightedBarValues: copyOfHighlightedBarValues
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const handleAddNewHighlightedBar = (e, index) => {
|
|
49
|
+
e.preventDefault()
|
|
50
|
+
const copyOfHighlightedBarValues = [...config.highlightedBarValues]
|
|
51
|
+
copyOfHighlightedBarValues.push({ dataKey: highlightedSeriesKey })
|
|
52
|
+
|
|
53
|
+
updateConfig({
|
|
54
|
+
...config,
|
|
55
|
+
highlightedBarValues: copyOfHighlightedBarValues
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const handleRemoveHighlightedBar = (e, index) => {
|
|
60
|
+
e.preventDefault()
|
|
61
|
+
const copyOfHighlightedBarValues = [...config.highlightedBarValues]
|
|
62
|
+
copyOfHighlightedBarValues.splice(index, 1)
|
|
63
|
+
updateConfig({
|
|
64
|
+
...config,
|
|
65
|
+
highlightedBarValues: copyOfHighlightedBarValues
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const handleUpdateHighlightedBarColor = (e, index) => {
|
|
70
|
+
const copyOfHighlightedBarValues = [...config.highlightedBarValues]
|
|
71
|
+
copyOfHighlightedBarValues[index].color = e.target.value
|
|
72
|
+
|
|
73
|
+
updateConfig({
|
|
74
|
+
...config
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const handleHighlightedBarLegendLabel = (e, index) => {
|
|
79
|
+
const copyOfHighlightedBarValues = [...config.highlightedBarValues]
|
|
80
|
+
copyOfHighlightedBarValues[index].legendLabel = e.target.value
|
|
81
|
+
|
|
82
|
+
updateConfig({
|
|
83
|
+
...config,
|
|
84
|
+
copyOfHighlightedBarValues
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const HighLightedBarUtils = () => {}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} formattedValue - The value to check for.
|
|
92
|
+
* @param {Array<string>} highlightedBarValuesIn - An array of highlighted bar values.
|
|
93
|
+
* @param {string} labelColor - The default label color.
|
|
94
|
+
* @returns {string} - Returns the font color for the given value.
|
|
95
|
+
*/
|
|
96
|
+
HighLightedBarUtils.checkFontColor = (formattedValue, highlightedBarValuesIn, labelColor) => {
|
|
97
|
+
if (config.xAxis.type === 'date') {
|
|
98
|
+
if (HighLightedBarUtils.formatDates(highlightedBarValuesIn).includes(formattedValue)) {
|
|
99
|
+
return '#000'
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
if (highlightedBarValuesIn.includes(formattedValue)) {
|
|
103
|
+
return '#000'
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return labelColor
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Formats an array of date values using the formatDate and parseDate functions.
|
|
111
|
+
*
|
|
112
|
+
* @param {string[]} highlightedBarValues - An array of date values to format.
|
|
113
|
+
* @returns {?Date[]} - An array of formatted date objects, or null if invalid input is provided.
|
|
114
|
+
*/
|
|
115
|
+
HighLightedBarUtils.formatDates = highlightedBarValues => {
|
|
116
|
+
return highlightedBarValues.map(dateItem => {
|
|
117
|
+
if (!dateItem) return false
|
|
118
|
+
return formatDate(parseDate(dateItem))
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Finds duplicate objects in an array based on the legendLabel property.
|
|
124
|
+
*
|
|
125
|
+
* @param {Array} arr - The array of objects to be checked.
|
|
126
|
+
* @return {Array} - An array of arrays, each containing objects that have the same legendLabel property.
|
|
127
|
+
*/
|
|
128
|
+
HighLightedBarUtils.findDuplicates = arr => {
|
|
129
|
+
const duplicates = {}
|
|
130
|
+
const result = arr.filter(obj => {
|
|
131
|
+
const { legendLabel } = obj
|
|
132
|
+
if (!duplicates[legendLabel]) {
|
|
133
|
+
duplicates[legendLabel] = true
|
|
134
|
+
return true
|
|
135
|
+
}
|
|
136
|
+
return false
|
|
137
|
+
})
|
|
138
|
+
return result
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
HighLightedBarUtils,
|
|
143
|
+
highlightedSeries,
|
|
144
|
+
highlightedSeriesKey,
|
|
145
|
+
highlightedBarValues,
|
|
146
|
+
highlightedSeriesValues,
|
|
147
|
+
handleUpdateHighlightedBar,
|
|
148
|
+
handleAddNewHighlightedBar,
|
|
149
|
+
handleRemoveHighlightedBar,
|
|
150
|
+
handleUpdateHighlightedBarColor,
|
|
151
|
+
handleHighlightedBarLegendLabel,
|
|
152
|
+
handleUpdateHighlightedBorderWidth
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAllLine }) => {
|
|
2
|
+
let min = 0
|
|
3
|
+
let max = 0
|
|
4
|
+
|
|
5
|
+
if (!data) {
|
|
6
|
+
return { min, max }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
|
|
10
|
+
|
|
11
|
+
// do validation bafore applying t0 charts
|
|
12
|
+
const isMaxValid = existPositiveValue ? enteredMaxValue >= maxValue : enteredMaxValue >= 0
|
|
13
|
+
const isMinValid = config.useLogScale ? enteredMinValue >= 0 : (enteredMinValue <= 0 && minValue >= 0) || (enteredMinValue <= minValue && minValue < 0)
|
|
14
|
+
|
|
15
|
+
min = enteredMinValue && isMinValid ? enteredMinValue : minValue
|
|
16
|
+
max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
|
|
17
|
+
|
|
18
|
+
if (config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || config.visualizationType === 'Deviation Bar') {
|
|
19
|
+
let ciYMax = 0
|
|
20
|
+
if (config.hasOwnProperty('confidenceKeys')) {
|
|
21
|
+
let upperCIValues = data.map(function (d) {
|
|
22
|
+
return d[config.confidenceKeys.upper]
|
|
23
|
+
})
|
|
24
|
+
ciYMax = Math.max.apply(Math, upperCIValues)
|
|
25
|
+
if (ciYMax > max) max = ciYMax // bump up the max
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (config.visualizationType === 'Forecasting') {
|
|
30
|
+
const {
|
|
31
|
+
runtime: { forecastingSeriesKeys }
|
|
32
|
+
} = config
|
|
33
|
+
if (forecastingSeriesKeys.length > 0) {
|
|
34
|
+
// push all keys into an array
|
|
35
|
+
let columnNames = []
|
|
36
|
+
|
|
37
|
+
forecastingSeriesKeys.forEach(f => {
|
|
38
|
+
f.confidenceIntervals?.map(ciGroup => {
|
|
39
|
+
columnNames.push(ciGroup.high)
|
|
40
|
+
columnNames.push(ciGroup.low)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Using the columnNames or "keys" get the returned result
|
|
45
|
+
const result = data.map(obj => columnNames.map(key => obj[key]))
|
|
46
|
+
|
|
47
|
+
const highCIGroup = Math.max.apply(
|
|
48
|
+
null,
|
|
49
|
+
result.map(item => item[0])
|
|
50
|
+
) // Extract ages from result
|
|
51
|
+
const lowCIGroup = Math.min.apply(
|
|
52
|
+
null,
|
|
53
|
+
result.map(item => item[1])
|
|
54
|
+
) // Extract ages from result
|
|
55
|
+
|
|
56
|
+
if (highCIGroup > max) {
|
|
57
|
+
max = highCIGroup
|
|
58
|
+
}
|
|
59
|
+
if (lowCIGroup < min) {
|
|
60
|
+
min = lowCIGroup
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min > 0) {
|
|
66
|
+
min = 0
|
|
67
|
+
}
|
|
68
|
+
if (config.visualizationType === 'Combo' && isAllLine) {
|
|
69
|
+
if ((enteredMinValue === undefined || enteredMinValue === null || enteredMinValue === '') && min > 0) {
|
|
70
|
+
min = 0
|
|
71
|
+
}
|
|
72
|
+
if (enteredMinValue) {
|
|
73
|
+
const isMinValid = config.useLogScale ? enteredMinValue >= 0 && enteredMinValue < minValue : enteredMinValue < minValue
|
|
74
|
+
min = enteredMinValue && isMinValid ? enteredMinValue : minValue
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (config.visualizationType === 'Deviation Bar' && min > 0) {
|
|
79
|
+
const isMinValid = Number(enteredMinValue) < Math.min(minValue, Number(config.xAxis.target))
|
|
80
|
+
min = enteredMinValue && isMinValid ? enteredMinValue : 0
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (config.visualizationType === 'Line') {
|
|
84
|
+
const isMinValid = config.useLogScale ? enteredMinValue >= 0 && enteredMinValue < minValue : enteredMinValue < minValue
|
|
85
|
+
min = enteredMinValue && isMinValid ? enteredMinValue : minValue
|
|
86
|
+
}
|
|
87
|
+
//If data value max wasn't provided, calculate it
|
|
88
|
+
if (max === Number.MIN_VALUE) {
|
|
89
|
+
// if all values in data are negative set max = 0
|
|
90
|
+
max = existPositiveValue ? maxValue : 0
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//Adds Y Axis data padding if applicable
|
|
94
|
+
if (config.runtime.yAxis.paddingPercent) {
|
|
95
|
+
let paddingValue = (max - min) * config.runtime.yAxis.paddingPercent
|
|
96
|
+
min -= paddingValue
|
|
97
|
+
max += paddingValue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (config.isLollipopChart && config.yAxis.displayNumbersOnBar) {
|
|
101
|
+
const dataKey = data.map(item => item[config.series[0].dataKey])
|
|
102
|
+
const maxDataVal = Math.max(...dataKey).toString().length
|
|
103
|
+
|
|
104
|
+
switch (true) {
|
|
105
|
+
case maxDataVal > 8 && maxDataVal <= 12:
|
|
106
|
+
max = max * 1.3
|
|
107
|
+
break
|
|
108
|
+
case maxDataVal > 4 && maxDataVal <= 7:
|
|
109
|
+
max = max * 1.1
|
|
110
|
+
break
|
|
111
|
+
default:
|
|
112
|
+
break
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (config.yAxis.enablePadding) {
|
|
117
|
+
if (min < 0) {
|
|
118
|
+
// sets with negative data need more padding on the max
|
|
119
|
+
max *= 1.2
|
|
120
|
+
min *= 1.2
|
|
121
|
+
} else {
|
|
122
|
+
max *= 1.1
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { min, max }
|
|
127
|
+
}
|
|
128
|
+
export default useMinMax
|
|
@@ -1,81 +1,55 @@
|
|
|
1
1
|
import isNumber from '@cdc/core/helpers/isNumber'
|
|
2
2
|
|
|
3
3
|
function useReduceData(config, data) {
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const cleanChars = value => {
|
|
9
|
-
// remove comma and $ signs
|
|
10
|
-
let tmp
|
|
11
|
-
if (typeof value === 'string') {
|
|
12
|
-
tmp = value !== null && value !== '' ? value.replace(/[,$]/g, '') : ''
|
|
13
|
-
} else {
|
|
14
|
-
tmp = value !== null && value !== '' ? value : ''
|
|
15
|
-
}
|
|
16
|
-
return tmp
|
|
17
|
-
}
|
|
4
|
+
const isBar = config.series.every(({ type }) => type === 'Bar')
|
|
5
|
+
const isAllLine = config.series.every(({ type }) => ['Line', 'dashed-sm', 'dashed-md', 'dashed-lg'].includes(type))
|
|
6
|
+
const sumYValues = seriesKeys => xValue => seriesKeys.reduce((yTotal, k) => yTotal + Number(xValue[k]), 0)
|
|
7
|
+
|
|
18
8
|
const getMaxValueFromData = () => {
|
|
19
|
-
let max
|
|
20
|
-
if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && isBar)) && config.visualizationSubType === 'stacked') {
|
|
21
|
-
const yTotals = data.reduce((allTotals, xValue) => {
|
|
22
|
-
const totalYValues = config.runtime.seriesKeys.reduce((yTotal, k) => {
|
|
23
|
-
yTotal += Number(xValue[k])
|
|
24
|
-
return yTotal
|
|
25
|
-
}, 0)
|
|
26
|
-
|
|
27
|
-
allTotals.push(totalYValues)
|
|
28
|
-
if (totalYValues > max) {
|
|
29
|
-
max = totalYValues
|
|
30
|
-
}
|
|
31
|
-
return allTotals
|
|
32
|
-
}, [])
|
|
9
|
+
let max = Math.max(...data.map(d => Math.max(...config.runtime.seriesKeys.map(key => (isNumber(d[key]) ? Number(cleanChars(d[key])) : 0)))))
|
|
33
10
|
|
|
11
|
+
if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && isBar)) && config.visualizationSubType === 'stacked') {
|
|
12
|
+
const yTotals = data.map(sumYValues(config.runtime.seriesKeys))
|
|
34
13
|
max = Math.max(...yTotals)
|
|
35
|
-
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if ((config.visualizationType === 'Bar' || config.visualizationType === 'Deviation Bar') && config.series && config.series.dataKey) {
|
|
36
17
|
max = Math.max(...data.map(d => (isNumber(d[config.series.dataKey]) ? Number(cleanChars(d[config.series.dataKey])) : 0)))
|
|
37
|
-
|
|
38
|
-
} else if (config.visualizationType === 'Combo' && config.visualizationSubType === 'stacked' && !isBar) {
|
|
39
|
-
let total = []
|
|
18
|
+
}
|
|
40
19
|
|
|
20
|
+
if (config.visualizationType === 'Combo' && config.visualizationSubType === 'stacked' && !isBar) {
|
|
41
21
|
if (config.runtime.barSeriesKeys && config.runtime.lineSeriesKeys) {
|
|
42
|
-
|
|
43
|
-
data.map(function (d, index) {
|
|
44
|
-
const totalYValues = config.runtime.barSeriesKeys.reduce((yTotal, k) => {
|
|
45
|
-
yTotal += Number(d[k])
|
|
46
|
-
return yTotal
|
|
47
|
-
}, 0)
|
|
48
|
-
return total.push(totalYValues)
|
|
49
|
-
})
|
|
50
|
-
// get lineSeries largest values
|
|
51
|
-
const lineMax = Math.max(...data.map(d => Math.max(...config.runtime.lineSeriesKeys.map(key => Number(cleanChars(d[key]))))))
|
|
22
|
+
const yTotals = data.map(sumYValues(config.runtime.barSeriesKeys))
|
|
52
23
|
|
|
53
|
-
const
|
|
24
|
+
const lineMax = Math.max(...data.map(d => Math.max(...config.runtime.lineSeriesKeys.map(key => Number(cleanChars(d[key]))))))
|
|
25
|
+
const barMax = Math.max(...yTotals)
|
|
54
26
|
|
|
55
|
-
max =
|
|
27
|
+
max = Math.max(barMax, lineMax)
|
|
56
28
|
}
|
|
57
|
-
} else {
|
|
58
|
-
max = Math.max(...data.map(d => Math.max(...config.runtime.seriesKeys.map(key => (isNumber(d[key]) ? Number(cleanChars(d[key])) : 0)))))
|
|
59
29
|
}
|
|
30
|
+
|
|
60
31
|
return max
|
|
61
32
|
}
|
|
62
33
|
|
|
63
34
|
const getMinValueFromData = () => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return min
|
|
35
|
+
const minNumberFromData = Math.min(...data.map(d => Math.min(...config.runtime.seriesKeys.map(key => (isNumber(d[key]) ? Number(cleanChars(d[key])) : Infinity)))))
|
|
36
|
+
|
|
37
|
+
return String(minNumberFromData)
|
|
68
38
|
}
|
|
69
39
|
|
|
70
40
|
const findPositiveNum = () => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (config.runtime.seriesKeys) {
|
|
74
|
-
for (let i = 0; i < config.runtime.seriesKeys.length; i++) {
|
|
75
|
-
existPositiveValue = data.some(d => d[config.runtime.seriesKeys[i]] >= 0)
|
|
76
|
-
}
|
|
41
|
+
if (!config.runtime.seriesKeys) {
|
|
42
|
+
return false
|
|
77
43
|
}
|
|
78
|
-
return
|
|
44
|
+
return config.runtime.seriesKeys.some(key => data.some(d => d[key] >= 0))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const cleanChars = value => {
|
|
48
|
+
if (value === null || value === '') {
|
|
49
|
+
return ''
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return typeof value === 'string' ? value.replace(/[,$]/g, '') : value
|
|
79
53
|
}
|
|
80
54
|
|
|
81
55
|
const maxValue = Number(getMaxValueFromData())
|
|
@@ -4,7 +4,7 @@ import useReduceData from '../hooks/useReduceData'
|
|
|
4
4
|
export default function useRightAxis({ config, yMax = 0, data = [], updateConfig }) {
|
|
5
5
|
const hasRightAxis = config.visualizationType === 'Combo' && config.orientation === 'vertical'
|
|
6
6
|
const rightSeriesKeys = config.series && config.series.filter(series => series.axis === 'Right').map(key => key.dataKey)
|
|
7
|
-
|
|
7
|
+
let { minValue } = useReduceData(config, data)
|
|
8
8
|
|
|
9
9
|
const allRightAxisData = rightSeriesKeys => {
|
|
10
10
|
if (!rightSeriesKeys) return [0]
|
|
@@ -15,9 +15,15 @@ export default function useRightAxis({ config, yMax = 0, data = [], updateConfig
|
|
|
15
15
|
return rightAxisData
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const min = Math.min.apply(null, allRightAxisData(rightSeriesKeys))
|
|
19
18
|
const max = Math.max.apply(null, allRightAxisData(rightSeriesKeys))
|
|
20
19
|
|
|
20
|
+
// if there is a bar series & the right axis doesn't include a negative number, default to zero
|
|
21
|
+
const hasBarSeries = config.runtime?.barSeriesKeys?.length > 0
|
|
22
|
+
|
|
23
|
+
if (hasBarSeries && minValue > 0) {
|
|
24
|
+
minValue = 0
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
const yScaleRight = scaleLinear({
|
|
22
28
|
domain: [minValue, max],
|
|
23
29
|
range: [yMax, 0]
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { scaleBand, scaleLinear, scaleLog, scalePoint, scaleTime } from '@visx/scale'
|
|
2
|
+
|
|
3
|
+
const useScales = properties => {
|
|
4
|
+
let { xAxisDataMapped, xMax, yMax, min, max, config, data } = properties
|
|
5
|
+
const seriesDomain = config.runtime.barSeriesKeys || config.runtime.seriesKeys
|
|
6
|
+
const xAxisType = config.runtime.xAxis.type
|
|
7
|
+
const isHorizontal = config.orientation === 'horizontal'
|
|
8
|
+
|
|
9
|
+
const { visualizationType } = config
|
|
10
|
+
|
|
11
|
+
// define scales
|
|
12
|
+
let xScale = null
|
|
13
|
+
let yScale = null
|
|
14
|
+
let g2xScale = null
|
|
15
|
+
let g1xScale = null
|
|
16
|
+
let seriesScale = null
|
|
17
|
+
let xScaleNoPadding = null
|
|
18
|
+
|
|
19
|
+
const scaleTypes = {
|
|
20
|
+
TIME: 'time',
|
|
21
|
+
LOG: 'log',
|
|
22
|
+
POINT: 'point',
|
|
23
|
+
LINEAR: 'linear',
|
|
24
|
+
BAND: 'band'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// handle Horizontal bars
|
|
28
|
+
if (isHorizontal) {
|
|
29
|
+
xScale = composeXScale({ min: min * 1.03, ...properties })
|
|
30
|
+
xScale.type = config.useLogScale ? scaleTypes.LOG : scaleTypes.LINEAR
|
|
31
|
+
yScale = getYScaleFunction(xAxisType, xAxisDataMapped)
|
|
32
|
+
yScale.rangeRound([0, yMax])
|
|
33
|
+
seriesScale = composeScalePoint(seriesDomain, [0, yMax])
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// handle Vertical bars
|
|
37
|
+
if (!isHorizontal) {
|
|
38
|
+
xScale = composeScalePoint(xAxisDataMapped, [0, xMax], 0.5)
|
|
39
|
+
xScale.type = scaleTypes.POINT
|
|
40
|
+
yScale = composeYScale(properties)
|
|
41
|
+
seriesScale = composeScalePoint(seriesDomain, [0, xMax])
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// handle Area chart
|
|
45
|
+
if (config.visualizationType === 'Area Chart' && config.xAxis.type === 'date') {
|
|
46
|
+
xScale = scaleTime({
|
|
47
|
+
domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)],
|
|
48
|
+
range: [0, xMax]
|
|
49
|
+
})
|
|
50
|
+
xScale.type = scaleTypes.TIME
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// handle Deviation bar
|
|
54
|
+
if (config.visualizationType === 'Deviation Bar') {
|
|
55
|
+
const leftOffset = config.isLollipopChart ? 1.05 : 1.03
|
|
56
|
+
yScale = scaleBand({
|
|
57
|
+
domain: xAxisDataMapped,
|
|
58
|
+
range: [0, yMax]
|
|
59
|
+
})
|
|
60
|
+
xScale = scaleLinear({
|
|
61
|
+
domain: [min * leftOffset, Math.max(Number(config.xAxis.target), max)],
|
|
62
|
+
range: [0, xMax],
|
|
63
|
+
round: true,
|
|
64
|
+
nice: true
|
|
65
|
+
})
|
|
66
|
+
xScale.type = scaleTypes.LINEAR
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// handle Scatter plot
|
|
70
|
+
if (config.visualizationType === 'Scatter Plot') {
|
|
71
|
+
if (config.xAxis.type === 'continuous') {
|
|
72
|
+
xScale = scaleLinear({
|
|
73
|
+
domain: [0, Math.max.apply(null, xScale.domain())],
|
|
74
|
+
range: [0, xMax]
|
|
75
|
+
})
|
|
76
|
+
xScale.type = scaleTypes.LINEAR
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// handle Box plot
|
|
81
|
+
if (visualizationType === 'Box Plot') {
|
|
82
|
+
const allOutliers = []
|
|
83
|
+
const hasOutliers = config.boxplot.plots.map(b => b.columnOutliers.map(outlier => allOutliers.push(outlier))) && !config.boxplot.hideOutliers
|
|
84
|
+
|
|
85
|
+
// check if outliers are lower
|
|
86
|
+
if (hasOutliers) {
|
|
87
|
+
let outlierMin = Math.min(...allOutliers)
|
|
88
|
+
let outlierMax = Math.max(...allOutliers)
|
|
89
|
+
|
|
90
|
+
// check if outliers exceed standard bounds
|
|
91
|
+
if (outlierMin < min) min = outlierMin
|
|
92
|
+
if (outlierMax > max) max = outlierMax
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// check fences for max/min
|
|
96
|
+
let lowestFence = Math.min(...config.boxplot.plots.map(item => item.columnLowerBounds))
|
|
97
|
+
let highestFence = Math.max(...config.boxplot.plots.map(item => item.columnUpperBounds))
|
|
98
|
+
|
|
99
|
+
if (lowestFence < min) min = lowestFence
|
|
100
|
+
if (highestFence > max) max = highestFence
|
|
101
|
+
|
|
102
|
+
// Set Scales
|
|
103
|
+
yScale = scaleLinear({
|
|
104
|
+
range: [yMax, 0],
|
|
105
|
+
round: true,
|
|
106
|
+
domain: [min, max]
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
xScale = scaleBand({
|
|
110
|
+
range: [0, xMax],
|
|
111
|
+
round: true,
|
|
112
|
+
domain: config.boxplot.categories,
|
|
113
|
+
padding: 0.4
|
|
114
|
+
})
|
|
115
|
+
xScale.type = scaleTypes.BAND
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// handle Paired bar
|
|
119
|
+
if (visualizationType === 'Paired Bar') {
|
|
120
|
+
const offset = 1.02 // Offset of the ticks/values from the Axis
|
|
121
|
+
let groupOneMax = Math.max.apply(
|
|
122
|
+
Math,
|
|
123
|
+
data.map(d => d[config.series[0].dataKey])
|
|
124
|
+
)
|
|
125
|
+
let groupTwoMax = Math.max.apply(
|
|
126
|
+
Math,
|
|
127
|
+
data.map(d => d[config.series[1].dataKey])
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// group one
|
|
131
|
+
g1xScale = scaleLinear({
|
|
132
|
+
domain: [0, Math.max(groupOneMax, groupTwoMax) * offset],
|
|
133
|
+
range: [xMax / 2, 0]
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// group 2
|
|
137
|
+
g2xScale = scaleLinear({
|
|
138
|
+
domain: g1xScale.domain(),
|
|
139
|
+
range: [xMax / 2, xMax],
|
|
140
|
+
nice: true
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export default useScales
|
|
148
|
+
|
|
149
|
+
/// helper functions
|
|
150
|
+
const composeXScale = ({ min, max, xMax, config }) => {
|
|
151
|
+
// Adjust min value if using logarithmic scale
|
|
152
|
+
min = config.useLogScale && min >= 0 && min < 1 ? min + 0.1 : min
|
|
153
|
+
// Select the appropriate scale function
|
|
154
|
+
const scaleFunc = config.useLogScale ? scaleLog : scaleLinear
|
|
155
|
+
// Return the configured scale function
|
|
156
|
+
return scaleFunc({
|
|
157
|
+
domain: [min, max],
|
|
158
|
+
range: [0, xMax],
|
|
159
|
+
nice: config.useLogScale,
|
|
160
|
+
zero: config.useLogScale,
|
|
161
|
+
type: config.useLogScale ? 'log' : 'linear'
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const composeYScale = ({ min, max, yMax, config }) => {
|
|
166
|
+
// Adjust min value if using logarithmic scale
|
|
167
|
+
min = config.useLogScale && min >= 0 && min < 1 ? min + 0.1 : min
|
|
168
|
+
// Select the appropriate scale function
|
|
169
|
+
const scaleFunc = config.useLogScale ? scaleLog : scaleLinear
|
|
170
|
+
// Return the configured scale function
|
|
171
|
+
return scaleFunc({
|
|
172
|
+
domain: [min, max],
|
|
173
|
+
range: [yMax, 0],
|
|
174
|
+
nice: config.useLogScale,
|
|
175
|
+
zero: config.useLogScale
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const getYScaleFunction = (xAxisType, xAxisDataMapped) => {
|
|
180
|
+
if (xAxisType === 'date') {
|
|
181
|
+
return scaleLinear({
|
|
182
|
+
domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)]
|
|
183
|
+
})
|
|
184
|
+
} else {
|
|
185
|
+
return scalePoint({ domain: xAxisDataMapped, padding: 0.5 })
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const composeScalePoint = (domain, range, padding = 0) => {
|
|
190
|
+
return scalePoint({
|
|
191
|
+
domain: domain,
|
|
192
|
+
range: range,
|
|
193
|
+
padding: padding,
|
|
194
|
+
type: 'point'
|
|
195
|
+
})
|
|
196
|
+
}
|
|
File without changes
|