@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.
Files changed (42) hide show
  1. package/dist/cdcchart.js +54845 -51755
  2. package/examples/feature/__data__/planet-example-data.json +14 -32
  3. package/examples/feature/__data__/planet-logaritmic-data.json +56 -0
  4. package/examples/feature/area/area-chart-category.json +240 -0
  5. package/examples/feature/bar/example-bar-chart.json +544 -22
  6. package/examples/feature/bar/new.json +561 -0
  7. package/examples/feature/bar/planet-chart-logaritmic-config.json +170 -0
  8. package/examples/feature/boxplot/valid-boxplot.csv +17 -0
  9. package/examples/feature/combo/right-issues.json +190 -0
  10. package/examples/feature/filters/filter-testing.json +37 -3
  11. package/examples/feature/forecasting/combo-forecasting.json +245 -0
  12. package/examples/feature/forecasting/forecasting.json +5325 -0
  13. package/examples/feature/forecasting/index.json +203 -0
  14. package/examples/feature/forecasting/random_data.csv +366 -0
  15. package/examples/feature/line/line-chart.json +3 -3
  16. package/examples/feature/test-highlight/test-highlight-2.json +789 -0
  17. package/examples/feature/test-highlight/test-highlight-vertical.json +561 -0
  18. package/examples/feature/test-highlight/test-highlight.json +100 -0
  19. package/examples/feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json +1 -2
  20. package/examples/gallery/bar-chart-horizontal/horizontal-highlight.json +345 -0
  21. package/examples/gallery/line/line.json +173 -1
  22. package/index.html +14 -8
  23. package/package.json +2 -2
  24. package/src/CdcChart.jsx +342 -25
  25. package/src/components/AreaChart.jsx +32 -40
  26. package/src/components/BarChart.jsx +147 -25
  27. package/src/components/DataTable.jsx +30 -12
  28. package/src/components/DeviationBar.jsx +32 -32
  29. package/src/components/EditorPanel.jsx +1902 -1126
  30. package/src/components/Forecasting.jsx +147 -0
  31. package/src/components/Legend.jsx +193 -243
  32. package/src/components/LineChart.jsx +4 -9
  33. package/src/components/LinearChart.jsx +263 -285
  34. package/src/components/Series.jsx +518 -0
  35. package/src/components/SparkLine.jsx +3 -3
  36. package/src/data/initial-state.js +24 -5
  37. package/src/hooks/useHighlightedBars.js +154 -0
  38. package/src/hooks/useMinMax.js +128 -0
  39. package/src/hooks/useReduceData.js +31 -57
  40. package/src/hooks/useRightAxis.js +8 -2
  41. package/src/hooks/useScales.js +196 -0
  42. /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
- // for combo charts check if all Data Series selected to Bar;
5
- const isBar = config?.series?.every(element => element?.type === 'Bar')
6
- // for combo charts check if all Data series selected Line or dashed-md/sm/lg.
7
- const isAllLine = config?.series?.every(el => el.type === 'Line' || el.type === 'dashed-sm' || el.type === 'dashed-md' || el.type === 'dashed-lg')
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 // will hold max number from data.
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
- } else if ((config.visualizationType === 'Bar' || config.visualizationType === 'Deviation Bar') && config.series && config.series.dataKey) {
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
- //max = Math.max(...data.map(d => Number(d[config.series.dataKey])))
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
- // get barSeries max Values added to each other
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 barMax = Math.max(...total)
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 = Number(barMax) > Number(lineMax) ? barMax : lineMax
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
- let min
65
- const minNumberFromData = Math.min(...data.map(d => Math.min(...config.runtime.seriesKeys.map(key => (isNumber(d[key]) ? Number(cleanChars(d[key])) : 1000000000)))))
66
- min = String(minNumberFromData)
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
- // loop throught provided data to find positve number in arr based on series keys.
72
- let existPositiveValue = false
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 existPositiveValue
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
- const { minValue } = useReduceData(config, data)
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
+ }