@cdc/chart 4.23.7 → 4.23.8

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 (38) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcchart.js +27964 -26942
  3. package/examples/feature/__data__/area-chart-date-apple.json +5122 -0
  4. package/examples/feature/__data__/city-temperature.json +2198 -0
  5. package/examples/feature/area/area-chart-category.json +45 -45
  6. package/examples/feature/area/area-chart-date-apple.json +10376 -0
  7. package/examples/feature/area/area-chart-date-city-temperature.json +4528 -0
  8. package/examples/feature/area/area-chart-date.json +111 -3
  9. package/examples/feature/forest-plot/broken.json +700 -0
  10. package/examples/feature/forest-plot/data.csv +24 -0
  11. package/examples/feature/forest-plot/forest-plot.json +717 -0
  12. package/examples/feature/pie/planet-pie-example-config.json +1 -1
  13. package/examples/private/confidence_interval_test.json +248 -0
  14. package/examples/private/tooltip-issue.json +45275 -0
  15. package/index.html +13 -11
  16. package/package.json +4 -3
  17. package/src/CdcChart.jsx +24 -14
  18. package/src/components/AreaChart.jsx +84 -59
  19. package/src/components/BarChart.Horizontal.jsx +251 -0
  20. package/src/components/BarChart.StackedHorizontal.jsx +118 -0
  21. package/src/components/BarChart.StackedVertical.jsx +93 -0
  22. package/src/components/BarChart.Vertical.jsx +204 -0
  23. package/src/components/BarChart.jsx +14 -674
  24. package/src/components/BarChartType.jsx +15 -0
  25. package/src/components/BrushHandle.jsx +17 -0
  26. package/src/components/DataTable.jsx +63 -21
  27. package/src/components/EditorPanel.jsx +351 -303
  28. package/src/components/ForestPlot.jsx +191 -0
  29. package/src/components/ForestPlotSettings.jsx +508 -0
  30. package/src/components/LineChart.jsx +2 -2
  31. package/src/components/LinearChart.jsx +115 -310
  32. package/src/data/initial-state.js +43 -0
  33. package/src/hooks/useBarChart.js +186 -0
  34. package/src/hooks/useEditorPermissions.js +218 -0
  35. package/src/hooks/useMinMax.js +15 -3
  36. package/src/hooks/useScales.js +45 -2
  37. package/src/hooks/useTooltip.jsx +407 -0
  38. package/src/scss/main.scss +7 -0
@@ -1,8 +1,10 @@
1
1
  export default {
2
2
  type: 'chart',
3
+ debugSvg: false,
3
4
  title: '',
4
5
  showTitle: true,
5
6
  showDownloadMediaButton: false,
7
+ showChartBrush: false,
6
8
  theme: 'theme-blue',
7
9
  animate: false,
8
10
  fontSize: 'medium',
@@ -173,5 +175,46 @@ export default {
173
175
  series: [],
174
176
  tooltips: {
175
177
  opacity: 90
178
+ },
179
+ forestPlot: {
180
+ startAt: 0,
181
+ width: 'auto',
182
+ colors: {
183
+ line: '',
184
+ shape: ''
185
+ },
186
+ estimateField: '',
187
+ estimateRadius: '',
188
+ lowerCiField: '',
189
+ upperCiField: '',
190
+ shape: '',
191
+ rowHeight: 20,
192
+ showZeroLine: false,
193
+ description: {
194
+ show: true,
195
+ text: 'description',
196
+ location: 0
197
+ },
198
+ result: {
199
+ show: true,
200
+ text: 'result',
201
+ location: 100
202
+ },
203
+ radius: {
204
+ min: 1,
205
+ max: 8,
206
+ scalingColumn: ''
207
+ },
208
+ regression: {
209
+ lower: 0,
210
+ upper: 0,
211
+ estimateField: 0
212
+ },
213
+ leftWidthOffset: 0,
214
+ rightWidthOffset: 0
215
+ },
216
+ brush: {
217
+ pattern_id: 'brush_pattern',
218
+ accent_color: '#ddd'
176
219
  }
177
220
  }
@@ -0,0 +1,186 @@
1
+ import React, { useContext, useEffect } from 'react'
2
+ import ConfigContext from '../ConfigContext'
3
+
4
+ export const useBarChart = () => {
5
+ const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate } = useContext(ConfigContext)
6
+ const { orientation } = config
7
+
8
+ const isHorizontal = orientation === 'horizontal'
9
+ const barBorderWidth = 1
10
+ const lollipopBarWidth = config.lollipopSize === 'large' ? 7 : config.lollipopSize === 'medium' ? 6 : 5
11
+ const lollipopShapeSize = config.lollipopSize === 'large' ? 14 : config.lollipopSize === 'medium' ? 12 : 10
12
+ const isLabelBelowBar = config.yAxis.labelPlacement === 'Below Bar'
13
+ const displayNumbersOnBar = config.yAxis.displayNumbersOnBar
14
+ const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
15
+
16
+ const isRounded = config.barStyle === 'rounded'
17
+ const isStacked = config.visualizationSubType === 'stacked'
18
+ const tipRounding = config.tipRounding
19
+ const radius = config.roundingStyle === 'standard' ? '8px' : config.roundingStyle === 'shallow' ? '5px' : config.roundingStyle === 'finger' ? '15px' : '0px'
20
+ const stackCount = config.runtime.seriesKeys.length
21
+ const fontSize = { small: 16, medium: 18, large: 20 }
22
+ const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
23
+
24
+ useEffect(() => {
25
+ if (orientation === 'horizontal' && !config.yAxis.labelPlacement) {
26
+ updateConfig({
27
+ ...config,
28
+ yAxis: {
29
+ ...config,
30
+ labelPlacement: 'Below Bar'
31
+ }
32
+ })
33
+ }
34
+ }, [config, updateConfig]) // eslint-disable-line
35
+
36
+ useEffect(() => {
37
+ if (config.isLollipopChart === false && config.barHeight < 25) {
38
+ updateConfig({ ...config, barHeight: 25 })
39
+ }
40
+ }, [config.isLollipopChart]) // eslint-disable-line
41
+
42
+ useEffect(() => {
43
+ if (config.visualizationSubType === 'horizontal') {
44
+ updateConfig({
45
+ ...config,
46
+ orientation: 'horizontal'
47
+ })
48
+ }
49
+ }, []) // eslint-disable-line
50
+
51
+ useEffect(() => {
52
+ if (config.barStyle === 'lollipop' && !config.isLollipopChart) {
53
+ updateConfig({ ...config, isLollipopChart: true })
54
+ }
55
+ if (isRounded || config.barStyle === 'flat') {
56
+ updateConfig({ ...config, isLollipopChart: false })
57
+ }
58
+ }, [config.barStyle]) // eslint-disable-line
59
+
60
+ const applyRadius = index => {
61
+ if (index === undefined || index === null || !isRounded) return {}
62
+ let style = {}
63
+
64
+ if ((isStacked && index + 1 === stackCount) || !isStacked) {
65
+ style = isHorizontal ? { borderRadius: `0 ${radius} ${radius} 0` } : { borderRadius: `${radius} ${radius} 0 0` }
66
+ }
67
+ if (!isStacked && index === -1) {
68
+ style = isHorizontal ? { borderRadius: `${radius} 0 0 ${radius} ` } : { borderRadius: ` 0 0 ${radius} ${radius}` }
69
+ }
70
+ if (tipRounding === 'full' && isStacked && index === 0 && stackCount > 1) {
71
+ style = isHorizontal ? { borderRadius: `${radius} 0 0 ${radius}` } : { borderRadius: `0 0 ${radius} ${radius}` }
72
+ }
73
+ if (tipRounding === 'full' && ((isStacked && index === 0 && stackCount === 1) || !isStacked)) {
74
+ style = { borderRadius: radius }
75
+ }
76
+ return style
77
+ }
78
+
79
+ const assignColorsToValues = (barsCount, barIndex, currentBarColor) => {
80
+ if (!config.legend.colorCode && config.series.length > 1) {
81
+ return currentBarColor
82
+ }
83
+ const palettesArr = colorPalettes[config.palette]
84
+ const values = tableData.map(d => {
85
+ return d[config.legend.colorCode]
86
+ })
87
+ // Map to hold unique values and their colors
88
+ let colorMap = new Map()
89
+ // Resultant array to hold colors to the values
90
+ let palette = []
91
+
92
+ for (let i = 0; i < values.length; i++) {
93
+ // If value not in map, add it and assign a color
94
+ if (!colorMap.has(values[i])) {
95
+ colorMap.set(values[i], palettesArr[colorMap.size % palettesArr.length])
96
+ }
97
+ // push the color to the result array
98
+ palette.push(colorMap.get(values[i]))
99
+ }
100
+
101
+ // loop throghy existing colors and extend if needed
102
+ while (palette.length < barsCount) {
103
+ palette = palette.concat(palette)
104
+ }
105
+ const barColor = palette[barIndex]
106
+ return barColor
107
+ }
108
+ const updateBars = defaultBars => {
109
+ // function updates stacked && regular && lollipop horizontal bars
110
+ if (config.visualizationType !== 'Bar' && !isHorizontal) return defaultBars
111
+
112
+ const barsArr = [...defaultBars]
113
+ let barHeight
114
+
115
+ const heights = {
116
+ stacked: config.barHeight,
117
+ lollipop: lollipopBarWidth
118
+ }
119
+
120
+ if (!isStacked) {
121
+ barHeight = heights[config.isLollipopChart ? 'lollipop' : 'stacked'] * stackCount
122
+ } else {
123
+ barHeight = heights.stacked
124
+ }
125
+
126
+ const labelHeight = isLabelBelowBar ? fontSize[config.fontSize] * 1.2 : 0
127
+ let barSpace = Number(config.barSpace)
128
+
129
+ // calculate height of container based height, space and fontSize of labels
130
+ let totalHeight = barsArr.length * (barHeight + labelHeight + barSpace)
131
+
132
+ if (isHorizontal) {
133
+ config.heights.horizontal = totalHeight
134
+ }
135
+
136
+ // return new updated bars/groupes
137
+ return barsArr.map((bar, i) => {
138
+ // set bars Y dynamically to handle space between bars
139
+ let y = 0
140
+ bar.index !== 0 && (y = (barHeight + barSpace + labelHeight) * i)
141
+
142
+ return { ...bar, y: y, height: barHeight }
143
+ })
144
+ }
145
+
146
+ const getHighlightedBarColorByValue = value => {
147
+ const match = config?.highlightedBarValues.filter(item => {
148
+ if (!item.value) return
149
+ return config.xAxis.type === 'date' ? formatDate(parseDate(item.value)) === value : item.value === value
150
+ })[0]
151
+
152
+ if (!match?.color) return `rgba(255, 102, 1)`
153
+ return match.color
154
+ }
155
+ const getHighlightedBarByValue = value => {
156
+ const match = config?.highlightedBarValues.filter(item => {
157
+ if (!item.value) return
158
+ return config.xAxis.type === 'date' ? formatDate(parseDate(item.value)) === value : item.value === value
159
+ })[0]
160
+
161
+ if (!match?.color) return false
162
+ return match
163
+ }
164
+
165
+ return {
166
+ isHorizontal,
167
+ barBorderWidth,
168
+ lollipopBarWidth,
169
+ lollipopShapeSize,
170
+ isLabelBelowBar,
171
+ displayNumbersOnBar,
172
+ section,
173
+ isRounded,
174
+ isStacked,
175
+ tipRounding,
176
+ radius,
177
+ stackCount,
178
+ fontSize,
179
+ hasMultipleSeries,
180
+ applyRadius,
181
+ updateBars,
182
+ assignColorsToValues,
183
+ getHighlightedBarColorByValue,
184
+ getHighlightedBarByValue
185
+ }
186
+ }
@@ -0,0 +1,218 @@
1
+ import React, { useContext } from 'react'
2
+ import ConfigContext from '../ConfigContext'
3
+
4
+ export const useEditorPermissions = () => {
5
+ const { config } = useContext(ConfigContext)
6
+ const { visualizationType, series, orientation } = config
7
+
8
+ // Overall support for the chart types
9
+ // prettier-ignore
10
+ const enabledChartTypes = [
11
+ 'Area Chart',
12
+ 'Bar',
13
+ 'Box Plot',
14
+ 'Combo',
15
+ 'Deviation Bar',
16
+ 'Forecasting',
17
+ // 'Forest Plot',
18
+ 'Line',
19
+ 'Paired Bar',
20
+ 'Pie',
21
+ 'Scatter Plot',
22
+ 'Spark Line'
23
+ ]
24
+
25
+ const visHasLabelOnData = () => {
26
+ const disabledCharts = ['Area Chart', 'Box Plot', 'Pie', 'Scatter Plot', 'Forest Plot']
27
+ if (disabledCharts.includes(visualizationType)) return false
28
+ return true
29
+ }
30
+
31
+ const visCanAnimate = () => {
32
+ const disabledCharts = ['Area Chart', 'Scatter Plot', 'Box Plot', 'Forest Plot']
33
+ if (disabledCharts.includes(visualizationType)) return false
34
+ return true
35
+ }
36
+
37
+ const visHasLegend = () => {
38
+ switch (visualizationType) {
39
+ case 'Box Plot':
40
+ return false
41
+ case 'Forest Plot':
42
+ return false
43
+ default:
44
+ return true
45
+ }
46
+ }
47
+
48
+ const visHasNumbersOnBars = () => {
49
+ if (visualizationType === 'Forest Plot') return false
50
+ if (config.orientation === 'horizontal' && (config.yAxis.labelPlacement === 'Below Bar' || config.yAxis.labelPlacement === 'On Date/Category Axis' || config.visualizationType === 'Paired Bar' || config.visualizationType === 'Deviation Bar')) return true
51
+ return false
52
+ }
53
+
54
+ const visHasAnchors = () => {
55
+ switch (visualizationType) {
56
+ case 'Area Chart':
57
+ return true
58
+ case 'Combo':
59
+ return true
60
+ case 'Line':
61
+ return true
62
+ case 'Bar':
63
+ return true
64
+ case 'Scatter Plot':
65
+ return true
66
+ default:
67
+ return false
68
+ }
69
+ }
70
+
71
+ const visHasBarBorders = () => {
72
+ const disabledCharts = ['Box Plot', 'Scatter Plot', 'Pie']
73
+ if (disabledCharts.includes(visualizationType)) return false
74
+ return series?.some(series => series.type === 'Bar' || series.type === 'Paired Bar' || series.type === 'Deviation Bar')
75
+ }
76
+
77
+ const visHasDataCutoff = () => {
78
+ switch (visualizationType) {
79
+ case 'Forest Plot':
80
+ return false
81
+ case 'Box Plot':
82
+ return false
83
+ case 'Pie':
84
+ return false
85
+ default:
86
+ return true
87
+ }
88
+ }
89
+
90
+ const visSupportsTooltipLines = () => {
91
+ const enabledCharts = ['Combo', 'Forecasting', 'Area Chart', 'Line', 'Bar']
92
+ if (enabledCharts.includes(visualizationType)) return true
93
+ return false
94
+ }
95
+
96
+ const visSupportsSequentialPallete = () => {
97
+ const disabledCharts = ['Paired Bar', 'Deviation Bar', 'Forest Plot']
98
+ if (disabledCharts.includes(visualizationType)) return false
99
+ return true
100
+ }
101
+
102
+ const visSupportsNonSequentialPallete = () => {
103
+ const disabledCharts = ['Paired Bar', 'Deviation Bar', 'Forest Plot']
104
+ if (disabledCharts.includes(visualizationType)) return false
105
+ return true
106
+ }
107
+
108
+ const visSupportsReverseColorPalette = () => {
109
+ const disabledCharts = ['Forest Plot', 'Paired Bar', 'Deviation Bar']
110
+ if (disabledCharts.includes(visualizationType)) return false
111
+ return true
112
+ }
113
+
114
+ const visSupportsDateCategoryAxisLabel = () => {
115
+ const disabledCharts = ['Forest Plot']
116
+ if (disabledCharts.includes(visualizationType)) return false
117
+ return true
118
+ }
119
+
120
+ const visSupportsDateCategoryAxisLine = () => {
121
+ const disabledCharts = ['Forest Plot']
122
+ if (disabledCharts.includes(visualizationType)) return false
123
+ return true
124
+ }
125
+
126
+ const visSupportsDateCategoryAxisTicks = () => {
127
+ const disabledCharts = ['Forest Plot']
128
+ if (disabledCharts.includes(visualizationType)) return false
129
+ return true
130
+ }
131
+
132
+ const visSupportsDateCategoryTickRotation = () => {
133
+ const disabledCharts = ['Forest Plot']
134
+ if (disabledCharts.includes(visualizationType)) return false
135
+ return true
136
+ }
137
+
138
+ const visSupportsDateCategoryNumTicks = () => {
139
+ const disabledCharts = ['Forest Plot']
140
+ if (disabledCharts.includes(visualizationType)) return false
141
+ return true
142
+ }
143
+
144
+ const visSupportsRegions = () => {
145
+ const disabledCharts = ['Forest Plot', 'Pie', 'Paired Bar']
146
+ if (disabledCharts.includes(visualizationType)) return false
147
+ return true
148
+ }
149
+
150
+ const visSupportsFilters = () => {
151
+ const disabledCharts = ['Forest Plot']
152
+ if (disabledCharts.includes(visualizationType)) return false
153
+ return true
154
+ }
155
+
156
+ const visSupportsValueAxisGridLines = () => {
157
+ const disabledCharts = ['Forest Plot']
158
+ if (orientation === 'horizontal') return false
159
+ if (disabledCharts.includes(visualizationType)) return false
160
+ return true
161
+ }
162
+
163
+ // implement later
164
+ const visSupportsValueAxisTicks = () => {
165
+ return true
166
+ }
167
+
168
+ // implement later
169
+ const visSupportsValueAxisLine = () => {
170
+ return true
171
+ }
172
+
173
+ // implement later
174
+ const visSupportsValueAxisLabels = () => {
175
+ return true
176
+ }
177
+
178
+ const visSupportsBarSpace = () => {
179
+ const disabledCharts = ['Forest Plot']
180
+ if (disabledCharts.includes(visualizationType)) return false
181
+ if (orientation === 'horizontal' || visualizationType === 'Paired Bar') return true
182
+ return false
183
+ }
184
+
185
+ const visSupportsBarThickness = () => {
186
+ const disabledCharts = ['Forest Plot']
187
+ if (disabledCharts.includes(visualizationType)) return false
188
+ return true
189
+ }
190
+
191
+ return {
192
+ enabledChartTypes,
193
+ visHasLabelOnData,
194
+ visHasNumbersOnBars,
195
+ visHasAnchors,
196
+ visHasBarBorders,
197
+ visHasDataCutoff,
198
+ visCanAnimate,
199
+ visHasLegend,
200
+ visSupportsTooltipLines,
201
+ visSupportsNonSequentialPallete,
202
+ visSupportsSequentialPallete,
203
+ visSupportsReverseColorPalette,
204
+ visSupportsDateCategoryAxisLabel,
205
+ visSupportsDateCategoryAxisLine,
206
+ visSupportsDateCategoryAxisTicks,
207
+ visSupportsDateCategoryTickRotation,
208
+ visSupportsDateCategoryNumTicks,
209
+ visSupportsRegions,
210
+ visSupportsFilters,
211
+ visSupportsValueAxisGridLines,
212
+ visSupportsValueAxisTicks,
213
+ visSupportsValueAxisLine,
214
+ visSupportsValueAxisLabels,
215
+ visSupportsBarSpace,
216
+ visSupportsBarThickness
217
+ }
218
+ }
@@ -7,7 +7,8 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
7
7
  }
8
8
 
9
9
  const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
10
-
10
+ const minRequiredCIPadding = 1.15 // regardless of Editor if CI data, there must be 10% padding added
11
+
11
12
  // do validation bafore applying t0 charts
12
13
  const isMaxValid = existPositiveValue ? enteredMaxValue >= maxValue : enteredMaxValue >= 0
13
14
  const isMinValid = config.useLogScale ? enteredMinValue >= 0 : (enteredMinValue <= 0 && minValue >= 0) || (enteredMinValue <= minValue && minValue < 0)
@@ -15,6 +16,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
15
16
  min = enteredMinValue && isMinValid ? enteredMinValue : minValue
16
17
  max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
17
18
 
19
+ let ciYMin = 0
18
20
  if (config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || config.visualizationType === 'Deviation Bar') {
19
21
  let ciYMax = 0
20
22
  if (config.hasOwnProperty('confidenceKeys')) {
@@ -22,7 +24,15 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
22
24
  return d[config.confidenceKeys.upper]
23
25
  })
24
26
  ciYMax = Math.max.apply(Math, upperCIValues)
25
- if (ciYMax > max) max = ciYMax // bump up the max
27
+ if (ciYMax > max) max = ciYMax * minRequiredCIPadding // bump up the max plus some padding always
28
+
29
+ // check the min if lower confidence
30
+ let lowerCIValues = data.map(function (d) {
31
+ // if no lower CI then we need lowerCIValues to have nothing in it
32
+ return d[config.confidenceKeys.lower] !== undefined ? d[config.confidenceKeys.lower] : ''
33
+ })
34
+ ciYMin = Math.min.apply(Math, lowerCIValues)
35
+ if (ciYMin < min) min = ciYMin * minRequiredCIPadding // adjust the min + 10% padding for negative numbers to separate from axis
26
36
  }
27
37
  }
28
38
 
@@ -63,9 +73,11 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
63
73
  }
64
74
  }
65
75
 
66
- if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min > 0) {
76
+ // this should not apply to bar charts if there is negative CI data
77
+ if (((config.visualizationType === 'Bar' && ciYMin >= 0) || (config.visualizationType === 'Combo' && !isAllLine)) && min > 0) {
67
78
  min = 0
68
79
  }
80
+
69
81
  if (config.visualizationType === 'Combo' && isAllLine) {
70
82
  if ((enteredMinValue === undefined || enteredMinValue === null || enteredMinValue === '') && min > 0) {
71
83
  min = 0
@@ -1,7 +1,13 @@
1
1
  import { scaleBand, scaleLinear, scaleLog, scalePoint, scaleTime } from '@visx/scale'
2
+ import { useContext } from 'react'
3
+ import ConfigContext from '../ConfigContext'
4
+ // TODO move props in
2
5
 
3
6
  const useScales = properties => {
4
7
  let { xAxisDataMapped, xMax, yMax, min, max, config, data } = properties
8
+ const { rawData, dimensions } = useContext(ConfigContext)
9
+
10
+ const [screenWidth, screenHeight] = dimensions
5
11
  const seriesDomain = config.runtime.barSeriesKeys || config.runtime.seriesKeys
6
12
  const xAxisType = config.runtime.xAxis.type
7
13
  const isHorizontal = config.orientation === 'horizontal'
@@ -120,11 +126,11 @@ const useScales = properties => {
120
126
  const offset = 1.02 // Offset of the ticks/values from the Axis
121
127
  let groupOneMax = Math.max.apply(
122
128
  Math,
123
- data.map(d => d[config.series[0].dataKey])
129
+ data.map(d => d[config.series[0]?.dataKey])
124
130
  )
125
131
  let groupTwoMax = Math.max.apply(
126
132
  Math,
127
- data.map(d => d[config.series[1].dataKey])
133
+ data.map(d => d[config.series[1]?.dataKey])
128
134
  )
129
135
 
130
136
  // group one
@@ -141,6 +147,43 @@ const useScales = properties => {
141
147
  })
142
148
  }
143
149
 
150
+ if (visualizationType === 'Forest Plot') {
151
+ const resolvedYRange = () => {
152
+ if (config.forestPlot.regression.showDiamond || config.forestPlot.regression.description) {
153
+ return [0 + config.forestPlot.rowHeight * 2, yMax - config.forestPlot.rowHeight]
154
+ } else {
155
+ return [0 + config.forestPlot.rowHeight * 2, yMax]
156
+ }
157
+ }
158
+
159
+ yScale = scaleLinear({
160
+ domain: [0, rawData.length],
161
+ range: resolvedYRange()
162
+ })
163
+
164
+ const xAxisPadding = 5
165
+
166
+ const leftWidthOffset = (Number(config.forestPlot.leftWidthOffset) / 100) * xMax
167
+ const rightWidthOffset = (Number(config.forestPlot.rightWidthOffset) / 100) * xMax
168
+
169
+ const rightWidthOffsetMobile = (Number(config.forestPlot.rightWidthOffsetMobile) / 100) * xMax
170
+ const leftWidthOffsetMobile = (Number(config.forestPlot.leftWidthOffsetMobile) / 100) * xMax
171
+
172
+ if (screenWidth > 480) {
173
+ xScale = scaleLinear({
174
+ domain: [Math.min(...data.map(d => parseFloat(d.Lower))) - xAxisPadding, Math.max(...data.map(d => parseFloat(d.Upper))) + xAxisPadding],
175
+ range: [leftWidthOffset, xMax - rightWidthOffset],
176
+ type: 'linear'
177
+ })
178
+ } else {
179
+ xScale = scaleLinear({
180
+ domain: [Math.min(...data.map(d => parseFloat(d.Lower))) - xAxisPadding, Math.max(...data.map(d => parseFloat(d.Upper))) + xAxisPadding],
181
+ range: [leftWidthOffsetMobile, xMax - rightWidthOffsetMobile],
182
+ type: 'linear'
183
+ })
184
+ }
185
+ }
186
+
144
187
  return { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding }
145
188
  }
146
189