@cdc/chart 4.23.10-alpha → 4.23.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +30598 -28669
- package/examples/feature/bar/example-bar-chart.json +1 -46
- package/examples/feature/dev-4261.json +399 -0
- package/examples/feature/forest-plot/{broken.json → linear.json} +55 -50
- package/examples/{private/forest-plot.json → feature/forest-plot/logarithmic.json} +305 -355
- package/examples/feature/line/line-points.json +340 -0
- package/examples/feature/regions/index.json +462 -0
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +181 -48
- package/examples/sparkline-multilple.json +846 -0
- package/index.html +11 -8
- package/package.json +3 -3
- package/src/CdcChart.tsx +71 -60
- package/src/_stories/Chart.tooltip.stories.tsx +305 -0
- package/src/_stories/ChartBrush.stories.tsx +19 -0
- package/src/_stories/ChartSuppress.stories.tsx +19 -0
- package/{examples/private/missing-color.json → src/_stories/_mock/brush_mock.json} +392 -332
- package/src/_stories/_mock/suppress_mock.json +911 -0
- package/src/components/AreaChart.Stacked.jsx +4 -5
- package/src/components/AreaChart.jsx +6 -35
- package/src/components/{BarChart.Horizontal.jsx → BarChart.Horizontal.tsx} +106 -29
- package/src/components/{BarChart.StackedHorizontal.jsx → BarChart.StackedHorizontal.tsx} +22 -17
- package/src/components/{BarChart.StackedVertical.jsx → BarChart.StackedVertical.tsx} +22 -26
- package/src/components/{BarChart.Vertical.jsx → BarChart.Vertical.tsx} +175 -31
- package/src/components/BarChart.jsx +1 -1
- package/src/components/DeviationBar.jsx +4 -3
- package/src/components/EditorPanel.jsx +176 -50
- package/src/components/ForestPlot/ForestPlotProps.ts +11 -0
- package/src/components/ForestPlot/index.scss +1 -0
- package/src/components/{ForestPlot.jsx → ForestPlot/index.tsx} +51 -31
- package/src/components/ForestPlotSettings.jsx +162 -112
- package/src/components/Legend.jsx +35 -2
- package/src/components/{LineChart.Circle.tsx → LineChart/LineChart.Circle.tsx} +26 -23
- package/src/components/LineChart/LineChartProps.ts +17 -0
- package/src/components/LineChart/index.scss +1 -0
- package/src/components/{LineChart.tsx → LineChart/index.tsx} +60 -24
- package/src/components/LinearChart.jsx +120 -60
- package/src/components/PairedBarChart.jsx +2 -2
- package/src/components/Series.jsx +22 -3
- package/src/components/ZoomBrush.tsx +168 -0
- package/src/data/initial-state.js +27 -12
- package/src/hooks/useBarChart.js +70 -6
- package/src/hooks/useColorScale.ts +50 -0
- package/src/hooks/useEditorPermissions.js +22 -4
- package/src/hooks/{useMinMax.js → useMinMax.ts} +75 -23
- package/src/hooks/{useRightAxis.js → useRightAxis.ts} +10 -2
- package/src/hooks/{useScales.js → useScales.ts} +64 -17
- package/src/hooks/useTooltip.jsx +68 -41
- package/src/scss/main.scss +3 -35
- package/src/types/ChartConfig.ts +43 -0
- package/src/types/ChartContext.ts +38 -0
- package/src/types/ChartProps.ts +7 -0
- package/src/types/ForestPlot.ts +60 -0
- package/examples/private/full.json +0 -45324
- /package/{examples/private/TESTING.json → src/components/ForestPlot/Readme.md} +0 -0
|
@@ -7,11 +7,11 @@ export default {
|
|
|
7
7
|
title: '',
|
|
8
8
|
showTitle: true,
|
|
9
9
|
showDownloadMediaButton: false,
|
|
10
|
-
showChartBrush: false,
|
|
11
10
|
theme: 'theme-blue',
|
|
12
11
|
animate: false,
|
|
13
12
|
fontSize: 'medium',
|
|
14
13
|
lineDatapointStyle: 'hover',
|
|
14
|
+
lineDatapointColor: 'Same as Line',
|
|
15
15
|
barHasBorder: 'false',
|
|
16
16
|
isLollipopChart: false,
|
|
17
17
|
lollipopShape: 'circle',
|
|
@@ -28,6 +28,8 @@ export default {
|
|
|
28
28
|
left: 5,
|
|
29
29
|
right: 5
|
|
30
30
|
},
|
|
31
|
+
suppressedData: [],
|
|
32
|
+
|
|
31
33
|
yAxis: {
|
|
32
34
|
hideAxis: false,
|
|
33
35
|
displayNumbersOnBar: false,
|
|
@@ -134,6 +136,7 @@ export default {
|
|
|
134
136
|
// start with a blank list
|
|
135
137
|
},
|
|
136
138
|
legend: {
|
|
139
|
+
hide: false,
|
|
137
140
|
behavior: 'isolate',
|
|
138
141
|
singleRow: false,
|
|
139
142
|
colorCode: '',
|
|
@@ -145,7 +148,13 @@ export default {
|
|
|
145
148
|
dynamicLegendItemLimitMessage: 'Dynamic Legend Item Limit Hit.',
|
|
146
149
|
dynamicLegendChartMessage: 'Select Options from the Legend',
|
|
147
150
|
lineMode: false,
|
|
148
|
-
verticalSorted: false
|
|
151
|
+
verticalSorted: false,
|
|
152
|
+
highlightOnHover: false
|
|
153
|
+
},
|
|
154
|
+
brush: {
|
|
155
|
+
height: 25,
|
|
156
|
+
data: [],
|
|
157
|
+
active: false
|
|
149
158
|
},
|
|
150
159
|
exclusions: {
|
|
151
160
|
active: false,
|
|
@@ -180,22 +189,27 @@ export default {
|
|
|
180
189
|
highlightedBarValues: [],
|
|
181
190
|
series: [],
|
|
182
191
|
tooltips: {
|
|
183
|
-
opacity: 90
|
|
192
|
+
opacity: 90,
|
|
193
|
+
singleSeries: false
|
|
184
194
|
},
|
|
185
195
|
forestPlot: {
|
|
186
196
|
startAt: 0,
|
|
187
|
-
width: 'auto',
|
|
188
197
|
colors: {
|
|
189
198
|
line: '',
|
|
190
199
|
shape: ''
|
|
191
200
|
},
|
|
201
|
+
lineOfNoEffect: {
|
|
202
|
+
show: true
|
|
203
|
+
},
|
|
204
|
+
type: '',
|
|
205
|
+
pooledResult: {
|
|
206
|
+
diamondHeight: 5,
|
|
207
|
+
column: ''
|
|
208
|
+
},
|
|
192
209
|
estimateField: '',
|
|
193
210
|
estimateRadius: '',
|
|
194
|
-
lowerCiField: '',
|
|
195
|
-
upperCiField: '',
|
|
196
211
|
shape: '',
|
|
197
212
|
rowHeight: 20,
|
|
198
|
-
showZeroLine: false,
|
|
199
213
|
description: {
|
|
200
214
|
show: true,
|
|
201
215
|
text: 'description',
|
|
@@ -217,12 +231,13 @@ export default {
|
|
|
217
231
|
estimateField: 0
|
|
218
232
|
},
|
|
219
233
|
leftWidthOffset: 0,
|
|
220
|
-
rightWidthOffset: 0
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
234
|
+
rightWidthOffset: 0,
|
|
235
|
+
showZeroLine: false,
|
|
236
|
+
hideDateCategoryCol: false,
|
|
237
|
+
leftLabel: '',
|
|
238
|
+
rightLabel: ''
|
|
225
239
|
},
|
|
240
|
+
|
|
226
241
|
area: {
|
|
227
242
|
isStacked: false
|
|
228
243
|
}
|
package/src/hooks/useBarChart.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import React, { useContext, useEffect } from 'react'
|
|
1
|
+
import React, { useContext, useEffect, useState } from 'react'
|
|
2
2
|
import ConfigContext from '../ConfigContext'
|
|
3
|
-
|
|
3
|
+
import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
|
|
4
4
|
export const useBarChart = () => {
|
|
5
|
-
const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate } = useContext(ConfigContext)
|
|
5
|
+
const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate, setSeriesHighlight } = useContext(ConfigContext)
|
|
6
6
|
const { orientation } = config
|
|
7
|
+
const [hoveredBar, setHoveredBar] = useState(null)
|
|
7
8
|
|
|
8
9
|
const isHorizontal = orientation === 'horizontal'
|
|
9
10
|
const barBorderWidth = 1
|
|
@@ -144,10 +145,10 @@ export const useBarChart = () => {
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
const getHighlightedBarColorByValue = value => {
|
|
147
|
-
const match = config?.highlightedBarValues.
|
|
148
|
+
const match = config?.highlightedBarValues.find(item => {
|
|
148
149
|
if (!item.value) return
|
|
149
150
|
return config.xAxis.type === 'date' ? formatDate(parseDate(item.value)) === value : item.value === value
|
|
150
|
-
})
|
|
151
|
+
})
|
|
151
152
|
|
|
152
153
|
if (!match?.color) return `rgba(255, 102, 1)`
|
|
153
154
|
return match.color
|
|
@@ -161,8 +162,66 @@ export const useBarChart = () => {
|
|
|
161
162
|
if (!match?.color) return false
|
|
162
163
|
return match
|
|
163
164
|
}
|
|
165
|
+
const generateIconSize = barWidth => {
|
|
166
|
+
if (barWidth < 4) {
|
|
167
|
+
return 1
|
|
168
|
+
}
|
|
169
|
+
if (barWidth < 5) {
|
|
170
|
+
return 4
|
|
171
|
+
}
|
|
172
|
+
if (barWidth < 10) {
|
|
173
|
+
return 6
|
|
174
|
+
}
|
|
175
|
+
if (barWidth < 15) {
|
|
176
|
+
return 7
|
|
177
|
+
}
|
|
178
|
+
if (barWidth < 20) {
|
|
179
|
+
return 8
|
|
180
|
+
}
|
|
181
|
+
if (barWidth < 90) {
|
|
182
|
+
return 8
|
|
183
|
+
}
|
|
184
|
+
return 0
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const getAdditionalColumn = xAxisDataValue => {
|
|
188
|
+
if (!xAxisDataValue) return ''
|
|
189
|
+
const columns = config.columns
|
|
190
|
+
const columnsWithTooltips = []
|
|
191
|
+
let additionalTooltipItems = ''
|
|
192
|
+
const closestVal =
|
|
193
|
+
tableData.find(d => {
|
|
194
|
+
return d[config.xAxis.dataKey] === xAxisDataValue
|
|
195
|
+
}) || {}
|
|
196
|
+
for (const [colKeys, colVals] of Object.entries(columns)) {
|
|
197
|
+
const formattingParams = {
|
|
198
|
+
addColPrefix: config.columns[colKeys].prefix,
|
|
199
|
+
addColSuffix: config.columns[colKeys].suffix,
|
|
200
|
+
addColRoundTo: config.columns[colKeys].roundToPlace ? config.columns[colKeys].roundToPlace : '',
|
|
201
|
+
addColCommas: config.columns[colKeys].commas
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const formattedValue = formatColNumber(closestVal[colVals?.name], 'left', true, config, formattingParams)
|
|
205
|
+
if (colVals.tooltips) {
|
|
206
|
+
columnsWithTooltips.push([colVals.label, formattedValue])
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
columnsWithTooltips.forEach(columnData => {
|
|
210
|
+
additionalTooltipItems += `${columnData[0]} : ${columnData[1]} <br/>`
|
|
211
|
+
})
|
|
212
|
+
return additionalTooltipItems
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const onMouseOverBar = (categoryValue, barKey) => {
|
|
216
|
+
if (config.legend.highlightOnHover && config.legend.behavior === 'highlight' && barKey) setSeriesHighlight([barKey])
|
|
217
|
+
setHoveredBar(categoryValue)
|
|
218
|
+
}
|
|
219
|
+
const onMouseLeaveBar = () => {
|
|
220
|
+
if (config.legend.highlightOnHover && config.legend.behavior === 'highlight') setSeriesHighlight([])
|
|
221
|
+
}
|
|
164
222
|
|
|
165
223
|
return {
|
|
224
|
+
generateIconSize,
|
|
166
225
|
isHorizontal,
|
|
167
226
|
barBorderWidth,
|
|
168
227
|
lollipopBarWidth,
|
|
@@ -181,6 +240,11 @@ export const useBarChart = () => {
|
|
|
181
240
|
updateBars,
|
|
182
241
|
assignColorsToValues,
|
|
183
242
|
getHighlightedBarColorByValue,
|
|
184
|
-
getHighlightedBarByValue
|
|
243
|
+
getHighlightedBarByValue,
|
|
244
|
+
getAdditionalColumn,
|
|
245
|
+
hoveredBar,
|
|
246
|
+
setHoveredBar,
|
|
247
|
+
onMouseOverBar,
|
|
248
|
+
onMouseLeaveBar
|
|
185
249
|
}
|
|
186
250
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { colorPalettesChart as colorPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
|
|
2
|
+
import { scaleOrdinal } from '@visx/scale'
|
|
3
|
+
import { useContext } from 'react'
|
|
4
|
+
import ConfigContext from '../ConfigContext'
|
|
5
|
+
|
|
6
|
+
const useColorScale = () => {
|
|
7
|
+
const { config, data } = useContext(ConfigContext)
|
|
8
|
+
const { visualizationSubType, visualizationType, series, legend } = config
|
|
9
|
+
|
|
10
|
+
const generatePalette = colorsCount => {
|
|
11
|
+
if (!series?.length) return []
|
|
12
|
+
const isSpecialType = ['Paired Bar', 'Deviation Bar'].includes(visualizationType)
|
|
13
|
+
const chosenPalette = isSpecialType ? config.twoColor.palette : config.palette
|
|
14
|
+
const allPalettes = { ...colorPalettes, ...twoColorPalette }
|
|
15
|
+
let palette = config.customColors || allPalettes[chosenPalette]
|
|
16
|
+
while (colorsCount > palette.length) palette = palette.concat(palette)
|
|
17
|
+
return palette.slice(0, colorsCount)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let colorScale = scaleOrdinal({
|
|
21
|
+
domain: config?.runtime?.seriesLabelsAll,
|
|
22
|
+
range: generatePalette(series.length)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
if (visualizationType === 'Deviation Bar') {
|
|
26
|
+
const { targetLabel } = config.xAxis
|
|
27
|
+
colorScale = scaleOrdinal({
|
|
28
|
+
domain: [`Below ${targetLabel}`, `Above ${targetLabel}`],
|
|
29
|
+
range: generatePalette(2)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
if (visualizationType === 'Bar' && visualizationSubType === 'regular' && series?.length === 1 && legend?.colorCode) {
|
|
33
|
+
const set = new Set(data.map(d => d[legend.colorCode]))
|
|
34
|
+
colorScale = scaleOrdinal({
|
|
35
|
+
domain: [...set],
|
|
36
|
+
range: generatePalette([...set].length)
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
if (config.series.some(s => s.name)) {
|
|
40
|
+
const set = new Set(series.map(d => d.name || d.dataKey))
|
|
41
|
+
colorScale = colorScale = scaleOrdinal({
|
|
42
|
+
domain: [...set],
|
|
43
|
+
range: generatePalette(series.length)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { colorScale }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default useColorScale
|
|
@@ -3,7 +3,7 @@ import ConfigContext from '../ConfigContext'
|
|
|
3
3
|
|
|
4
4
|
export const useEditorPermissions = () => {
|
|
5
5
|
const { config } = useContext(ConfigContext)
|
|
6
|
-
const { visualizationType, series, orientation } = config
|
|
6
|
+
const { visualizationType, series, orientation, visualizationSubType } = config
|
|
7
7
|
|
|
8
8
|
// Overall support for the chart types
|
|
9
9
|
// prettier-ignore
|
|
@@ -84,6 +84,15 @@ export const useEditorPermissions = () => {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
const visHasDataSuppression = () => {
|
|
88
|
+
if ((visualizationType === 'Bar' || 'Combo') && visualizationSubType === 'regular') {
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const visHasBrushChart = () => {
|
|
93
|
+
return ['Line', 'Bar', 'Area Chart', 'Combo'].includes(visualizationType) && orientation === 'vertical'
|
|
94
|
+
}
|
|
95
|
+
|
|
87
96
|
const visHasBarBorders = () => {
|
|
88
97
|
const disabledCharts = ['Box Plot', 'Scatter Plot', 'Pie']
|
|
89
98
|
if (disabledCharts.includes(visualizationType)) return false
|
|
@@ -154,13 +163,13 @@ export const useEditorPermissions = () => {
|
|
|
154
163
|
}
|
|
155
164
|
|
|
156
165
|
const visSupportsDateCategoryTickRotation = () => {
|
|
157
|
-
const disabledCharts = ['
|
|
166
|
+
const disabledCharts = ['Spark Line']
|
|
158
167
|
if (disabledCharts.includes(visualizationType)) return false
|
|
159
168
|
return true
|
|
160
169
|
}
|
|
161
170
|
|
|
162
171
|
const visSupportsDateCategoryNumTicks = () => {
|
|
163
|
-
const disabledCharts = ['
|
|
172
|
+
const disabledCharts = ['Spark Line']
|
|
164
173
|
if (disabledCharts.includes(visualizationType)) return false
|
|
165
174
|
return true
|
|
166
175
|
}
|
|
@@ -242,6 +251,12 @@ export const useEditorPermissions = () => {
|
|
|
242
251
|
return true
|
|
243
252
|
}
|
|
244
253
|
|
|
254
|
+
const visSupportsReactTooltip = () => {
|
|
255
|
+
if (['Deviation Bar', 'Box Plot', 'Scatter Plot', 'Paired Bar'].includes(visualizationType) || (visualizationType === 'Bar' && config.tooltips.singleSeries)) {
|
|
256
|
+
return true
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
245
260
|
return {
|
|
246
261
|
enabledChartTypes,
|
|
247
262
|
headerColors,
|
|
@@ -250,7 +265,9 @@ export const useEditorPermissions = () => {
|
|
|
250
265
|
visHasBarBorders,
|
|
251
266
|
visHasDataCutoff,
|
|
252
267
|
visHasLabelOnData,
|
|
268
|
+
visHasDataSuppression,
|
|
253
269
|
visHasLegend,
|
|
270
|
+
visHasBrushChart,
|
|
254
271
|
visHasNumbersOnBars,
|
|
255
272
|
visSupportsBarSpace,
|
|
256
273
|
visSupportsBarThickness,
|
|
@@ -276,6 +293,7 @@ export const useEditorPermissions = () => {
|
|
|
276
293
|
visSupportsValueAxisGridLines,
|
|
277
294
|
visSupportsValueAxisLabels,
|
|
278
295
|
visSupportsValueAxisLine,
|
|
279
|
-
visSupportsValueAxisTicks
|
|
296
|
+
visSupportsValueAxisTicks,
|
|
297
|
+
visSupportsReactTooltip
|
|
280
298
|
}
|
|
281
299
|
}
|
|
@@ -1,14 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
type UseMinMaxProps = {
|
|
4
|
+
/** config - standard chart config */
|
|
5
|
+
config: ChartConfig
|
|
6
|
+
/** minValue - starting minimum value */
|
|
7
|
+
minValue: number
|
|
8
|
+
/** maxValue - starting maximum value before transformations */
|
|
9
|
+
maxValue: number
|
|
10
|
+
/** existsPositiveValue - determines if axis should show values above/below 0 */
|
|
11
|
+
existPositiveValue: boolean
|
|
12
|
+
/** data - standard data array */
|
|
13
|
+
data: Object[]
|
|
14
|
+
/** isAllLine: if all series are line type including dashed lines */
|
|
15
|
+
isAllLine: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAllLine }: UseMinMaxProps) => {
|
|
2
19
|
let min = 0
|
|
3
20
|
let max = 0
|
|
4
21
|
|
|
22
|
+
// Implementation for left and right axis
|
|
23
|
+
let leftMax = 0
|
|
24
|
+
let rightMax = 0
|
|
25
|
+
|
|
5
26
|
if (!data) {
|
|
6
27
|
return { min, max }
|
|
7
28
|
}
|
|
8
29
|
|
|
30
|
+
const { visualizationType, series } = config
|
|
9
31
|
const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
|
|
10
32
|
const minRequiredCIPadding = 1.15 // regardless of Editor if CI data, there must be 10% padding added
|
|
11
|
-
|
|
33
|
+
|
|
12
34
|
// do validation bafore applying t0 charts
|
|
13
35
|
const isMaxValid = existPositiveValue ? enteredMaxValue >= maxValue : enteredMaxValue >= 0
|
|
14
36
|
const isMinValid = config.useLogScale ? enteredMinValue >= 0 : (enteredMinValue <= 0 && minValue >= 0) || (enteredMinValue <= minValue && minValue < 0)
|
|
@@ -16,31 +38,19 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
16
38
|
min = enteredMinValue && isMinValid ? enteredMinValue : minValue
|
|
17
39
|
max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
|
|
18
40
|
|
|
19
|
-
|
|
20
|
-
if (config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || config.visualizationType === 'Deviation Bar') {
|
|
21
|
-
let ciYMax = 0
|
|
22
|
-
if (config.hasOwnProperty('confidenceKeys')) {
|
|
23
|
-
let upperCIValues = data.map(function (d) {
|
|
24
|
-
return d[config.confidenceKeys.upper]
|
|
25
|
-
})
|
|
26
|
-
ciYMax = Math.max.apply(Math, upperCIValues)
|
|
27
|
-
if (ciYMax > max) max = ciYMax * minRequiredCIPadding // bump up the max plus some padding always
|
|
41
|
+
const { lower, upper } = config?.confidenceKeys || {}
|
|
28
42
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
36
|
-
}
|
|
43
|
+
if (lower && upper && config.visualizationType === 'Bar') {
|
|
44
|
+
const buffer = min < 0 ? 1.1 : 0
|
|
45
|
+
max = Math.max(maxValue, Math.max(...data.flatMap(d => [d[upper], d[lower]])) * 1.15)
|
|
46
|
+
min = Math.min(minValue, Math.min(...data.flatMap(d => [d[upper], d[lower]])) * 1.15) * buffer
|
|
37
47
|
}
|
|
38
48
|
|
|
39
|
-
if (config.
|
|
49
|
+
if (config.series.filter(s => s?.type === 'Forecasting')) {
|
|
40
50
|
const {
|
|
41
51
|
runtime: { forecastingSeriesKeys }
|
|
42
52
|
} = config
|
|
43
|
-
if (forecastingSeriesKeys
|
|
53
|
+
if (forecastingSeriesKeys?.length > 0) {
|
|
44
54
|
// push all keys into an array
|
|
45
55
|
let columnNames = []
|
|
46
56
|
|
|
@@ -73,10 +83,52 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
73
83
|
}
|
|
74
84
|
}
|
|
75
85
|
|
|
86
|
+
if (visualizationType === 'Combo') {
|
|
87
|
+
try {
|
|
88
|
+
if (!data) throw new Error('COVE: missing data while getting min/max for combo chart.')
|
|
89
|
+
// seperate the left and right axis items & get each sides series keys
|
|
90
|
+
let leftAxisSeriesItems = series.filter(s => s.axis === 'Left')
|
|
91
|
+
let rightAxisSeriesItems = series.filter(s => s.axis === 'Right')
|
|
92
|
+
|
|
93
|
+
const findMaxFromSeriesKeys = (data, seriesData, max, axis = 'left') => {
|
|
94
|
+
let stackedBarMax = 0
|
|
95
|
+
let axisSeriesKeys = seriesData.map(i => i.dataKey) || []
|
|
96
|
+
|
|
97
|
+
axisSeriesKeys.forEach(key => {
|
|
98
|
+
let _seriesData = seriesData.find(s => s.dataKey === key)
|
|
99
|
+
let _data = data.map(d => d[key])
|
|
100
|
+
let seriesMax = Math.max.apply(null, _data)
|
|
101
|
+
if (config.visualizationSubType === 'stacked' && axis === 'left' && _seriesData.type === 'Bar') {
|
|
102
|
+
stackedBarMax += seriesMax
|
|
103
|
+
}
|
|
104
|
+
if (seriesMax > max) {
|
|
105
|
+
max = seriesMax
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (max < stackedBarMax) {
|
|
109
|
+
max = stackedBarMax
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
return max
|
|
113
|
+
}
|
|
114
|
+
leftMax = findMaxFromSeriesKeys(data, leftAxisSeriesItems, leftMax, 'left')
|
|
115
|
+
rightMax = findMaxFromSeriesKeys(data, rightAxisSeriesItems, rightMax, 'right')
|
|
116
|
+
|
|
117
|
+
if (leftMax < enteredMaxValue) {
|
|
118
|
+
leftMax = enteredMaxValue
|
|
119
|
+
}
|
|
120
|
+
} catch (e) {
|
|
121
|
+
console.error(e.message)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
76
125
|
// this should not apply to bar charts if there is negative CI data
|
|
77
|
-
if ((
|
|
126
|
+
if ((visualizationType === 'Bar' || (visualizationType === 'Combo' && !isAllLine)) && min > 0) {
|
|
78
127
|
min = 0
|
|
79
128
|
}
|
|
129
|
+
if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min < 0) {
|
|
130
|
+
min = min * 1.1
|
|
131
|
+
}
|
|
80
132
|
|
|
81
133
|
if (config.visualizationType === 'Combo' && isAllLine) {
|
|
82
134
|
if ((enteredMinValue === undefined || enteredMinValue === null || enteredMinValue === '') && min > 0) {
|
|
@@ -136,6 +188,6 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
136
188
|
}
|
|
137
189
|
}
|
|
138
190
|
|
|
139
|
-
return { min, max }
|
|
191
|
+
return { min, max, leftMax, rightMax }
|
|
140
192
|
}
|
|
141
193
|
export default useMinMax
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { scaleLinear } from '@visx/scale'
|
|
2
|
-
import useReduceData from '
|
|
2
|
+
import useReduceData from './useReduceData'
|
|
3
3
|
|
|
4
4
|
export default function useRightAxis({ config, yMax = 0, data = [], updateConfig }) {
|
|
5
5
|
const hasRightAxis = config.visualizationType === 'Combo' && config.orientation === 'vertical'
|
|
@@ -15,7 +15,15 @@ export default function useRightAxis({ config, yMax = 0, data = [], updateConfig
|
|
|
15
15
|
return rightAxisData
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
let max = Math.max.apply(null, allRightAxisData(rightSeriesKeys))
|
|
19
|
+
|
|
20
|
+
if (config.yAxis.rightMax > max) {
|
|
21
|
+
max = config.yAxis.rightMax
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (config.yAxis.rightMin < minValue) {
|
|
25
|
+
minValue = config.yAxis.rightMin
|
|
26
|
+
}
|
|
19
27
|
|
|
20
28
|
// if there is a bar series & the right axis doesn't include a negative number, default to zero
|
|
21
29
|
const hasBarSeries = config.runtime?.barSeriesKeys?.length > 0
|
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
import { scaleBand, scaleLinear, scaleLog, scalePoint, scaleTime } from '@visx/scale'
|
|
1
|
+
import { LogScaleConfig, scaleBand, scaleLinear, scaleLog, scalePoint, scaleTime } from '@visx/scale'
|
|
2
2
|
import { useContext } from 'react'
|
|
3
3
|
import ConfigContext from '../ConfigContext'
|
|
4
|
-
|
|
4
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
5
|
+
import { ChartContext } from '../types/ChartContext'
|
|
6
|
+
|
|
7
|
+
type useScaleProps = {
|
|
8
|
+
config: ChartConfig // standard chart config
|
|
9
|
+
data: Object[] // standard data array
|
|
10
|
+
max: number // maximum value from useMinMax hook
|
|
11
|
+
min: number // minimum value from useMinMax hook
|
|
12
|
+
xAxisDataMapped: Object[] // array of x axis date/category items
|
|
13
|
+
xMax: number // chart svg width
|
|
14
|
+
yMax: number // chart svg height
|
|
15
|
+
}
|
|
5
16
|
|
|
6
|
-
const useScales = properties => {
|
|
17
|
+
const useScales = (properties: useScaleProps) => {
|
|
7
18
|
let { xAxisDataMapped, xMax, yMax, min, max, config, data } = properties
|
|
8
|
-
|
|
19
|
+
|
|
20
|
+
const { rawData, dimensions } = useContext<ChartContext>(ConfigContext)
|
|
9
21
|
|
|
10
22
|
const [screenWidth, screenHeight] = dimensions
|
|
11
23
|
const seriesDomain = config.runtime.barSeriesKeys || config.runtime.seriesKeys
|
|
12
24
|
const xAxisType = config.runtime.xAxis.type
|
|
13
25
|
const isHorizontal = config.orientation === 'horizontal'
|
|
26
|
+
const getXAxisDataKeys = d => d[config.runtime.originalXAxis.dataKey]
|
|
27
|
+
const xAxisDataKeysMapped = data.map(d => getXAxisDataKeys(d))
|
|
14
28
|
|
|
15
29
|
const { visualizationType } = config
|
|
16
30
|
|
|
@@ -21,6 +35,7 @@ const useScales = properties => {
|
|
|
21
35
|
let g1xScale = null
|
|
22
36
|
let seriesScale = null
|
|
23
37
|
let xScaleNoPadding = null
|
|
38
|
+
let xScaleBrush = null
|
|
24
39
|
|
|
25
40
|
const scaleTypes = {
|
|
26
41
|
TIME: 'time',
|
|
@@ -41,6 +56,7 @@ const useScales = properties => {
|
|
|
41
56
|
|
|
42
57
|
// handle Vertical bars
|
|
43
58
|
if (!isHorizontal) {
|
|
59
|
+
xScaleBrush = composeScalePoint(xAxisDataKeysMapped, [0, xMax], 0.5)
|
|
44
60
|
xScale = composeScalePoint(xAxisDataMapped, [0, xMax], 0.5)
|
|
45
61
|
xScale.type = scaleTypes.POINT
|
|
46
62
|
yScale = composeYScale(properties)
|
|
@@ -53,6 +69,7 @@ const useScales = properties => {
|
|
|
53
69
|
domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)],
|
|
54
70
|
range: [0, xMax]
|
|
55
71
|
})
|
|
72
|
+
xScaleBrush = xScale
|
|
56
73
|
xScale.type = scaleTypes.LINEAR
|
|
57
74
|
}
|
|
58
75
|
|
|
@@ -170,21 +187,49 @@ const useScales = properties => {
|
|
|
170
187
|
const leftWidthOffsetMobile = (Number(config.forestPlot.leftWidthOffsetMobile) / 100) * xMax
|
|
171
188
|
|
|
172
189
|
if (screenWidth > 480) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
190
|
+
if (config.forestPlot.type === 'Linear') {
|
|
191
|
+
xScale = scaleLinear({
|
|
192
|
+
domain: [Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower]))) - xAxisPadding, Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper]))) + xAxisPadding],
|
|
193
|
+
range: [leftWidthOffset, xMax - rightWidthOffset]
|
|
194
|
+
})
|
|
195
|
+
xScale.type = scaleTypes.LINEAR
|
|
196
|
+
}
|
|
197
|
+
if (config.forestPlot.type === 'Logarithmic') {
|
|
198
|
+
let max = Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper])))
|
|
199
|
+
let fp_min = Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower])))
|
|
200
|
+
|
|
201
|
+
xScale = scaleLog<LogScaleConfig>({
|
|
202
|
+
domain: [fp_min, max],
|
|
203
|
+
range: [leftWidthOffset, xMax - rightWidthOffset],
|
|
204
|
+
nice: true
|
|
205
|
+
})
|
|
206
|
+
xScale.type = scaleTypes.LOG
|
|
207
|
+
}
|
|
178
208
|
} else {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
209
|
+
if (config.forestPlot.type === 'Linear') {
|
|
210
|
+
xScale = scaleLinear({
|
|
211
|
+
domain: [Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower]))) - xAxisPadding, Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper]))) + xAxisPadding],
|
|
212
|
+
range: [leftWidthOffsetMobile, xMax - rightWidthOffsetMobile],
|
|
213
|
+
type: scaleTypes.LINEAR
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (config.forestPlot.type === 'Logarithmic') {
|
|
218
|
+
let max = Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper])))
|
|
219
|
+
let fp_min = Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower])))
|
|
220
|
+
|
|
221
|
+
xScale = scaleLog<LogScaleConfig>({
|
|
222
|
+
domain: [fp_min, max],
|
|
223
|
+
range: [leftWidthOffset, xMax - rightWidthOffset],
|
|
224
|
+
nice: true,
|
|
225
|
+
base: max > 1 ? 10 : 2,
|
|
226
|
+
round: false,
|
|
227
|
+
type: scaleTypes.LOG
|
|
228
|
+
})
|
|
229
|
+
}
|
|
184
230
|
}
|
|
185
231
|
}
|
|
186
|
-
|
|
187
|
-
return { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding }
|
|
232
|
+
return { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleBrush }
|
|
188
233
|
}
|
|
189
234
|
|
|
190
235
|
export default useScales
|
|
@@ -205,11 +250,13 @@ const composeXScale = ({ min, max, xMax, config }) => {
|
|
|
205
250
|
})
|
|
206
251
|
}
|
|
207
252
|
|
|
208
|
-
const composeYScale = ({ min, max, yMax, config }) => {
|
|
253
|
+
const composeYScale = ({ min, max, yMax, config, leftMax }) => {
|
|
209
254
|
// Adjust min value if using logarithmic scale
|
|
210
255
|
min = config.useLogScale && min >= 0 && min < 1 ? min + 0.1 : min
|
|
211
256
|
// Select the appropriate scale function
|
|
212
257
|
const scaleFunc = config.useLogScale ? scaleLog : scaleLinear
|
|
258
|
+
|
|
259
|
+
if (config.visualizationType === 'Combo') max = leftMax
|
|
213
260
|
// Return the configured scale function
|
|
214
261
|
return scaleFunc({
|
|
215
262
|
domain: [min, max],
|