@cdc/chart 4.24.10 → 4.24.12
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 +34651 -33978
- package/examples/feature/boxplot/boxplot-data.json +88 -22
- package/examples/feature/boxplot/boxplot.json +540 -16
- package/examples/feature/boxplot/testing.csv +7 -7
- package/examples/feature/sankey/sankey-example-data.json +126 -14
- package/examples/feature/tests-date-exclusions/date-exclusions-config.json +372 -12
- package/examples/private/DEV-8850-2.json +493 -0
- package/examples/private/DEV-9822.json +574 -0
- package/examples/private/DEV-9840.json +553 -0
- package/examples/private/DEV-9850-3.json +461 -0
- package/examples/private/chart.json +1084 -0
- package/examples/private/ci_formatted.json +202 -0
- package/examples/private/ci_issue.json +3016 -0
- package/examples/private/completed.json +634 -0
- package/examples/private/dem-data-long.csv +20 -0
- package/examples/private/dem-data-long.json +36 -0
- package/examples/private/demographic_data.csv +157 -0
- package/examples/private/demographic_data.json +2654 -0
- package/examples/private/demographic_dynamic.json +443 -0
- package/examples/private/demographic_standard.json +560 -0
- package/examples/private/test.json +493 -0
- package/index.html +10 -7
- package/package.json +2 -2
- package/src/CdcChart.tsx +132 -152
- package/src/_stories/Chart.Anchors.stories.tsx +31 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +34 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
- package/src/_stories/Chart.stories.tsx +37 -6
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
- package/src/_stories/ChartEditor.stories.tsx +27 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
- package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
- package/src/_stories/_mock/boxplot_multiseries.json +647 -0
- package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
- package/src/_stories/_mock/dynamic_series_config.json +979 -0
- package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
- package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
- package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
- package/src/_stories/_mock/short_dates.json +288 -0
- package/src/_stories/_mock/suppression_mock.json +1549 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
- package/src/components/Axis/Categorical.Axis.tsx +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
- package/src/components/BarChart/components/BarChart.Vertical.tsx +53 -47
- package/src/components/BarChart/helpers/getBarData.ts +28 -0
- package/src/components/BarChart/helpers/index.ts +1 -2
- package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
- package/src/components/BoxPlot/BoxPlot.tsx +131 -0
- package/src/components/BoxPlot/helpers/index.ts +54 -0
- package/src/components/BrushChart.tsx +23 -26
- package/src/components/EditorPanel/EditorPanel.tsx +117 -139
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +122 -56
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
- package/src/components/EditorPanel/useEditorPermissions.ts +20 -2
- package/src/components/Legend/Legend.Component.tsx +11 -12
- package/src/components/Legend/Legend.tsx +16 -16
- package/src/components/Legend/helpers/getLegendClasses.ts +59 -0
- package/src/components/Legend/helpers/index.ts +2 -1
- package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
- package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
- package/src/components/LineChart/helpers.ts +49 -43
- package/src/components/LineChart/index.tsx +135 -83
- package/src/components/LinearChart.tsx +187 -181
- package/src/components/PieChart/PieChart.tsx +7 -1
- package/src/components/Sankey/components/ColumnList.tsx +19 -0
- package/src/components/Sankey/components/Sankey.tsx +479 -0
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
- package/src/components/Sankey/index.tsx +1 -492
- package/src/components/Sankey/sankey.scss +22 -21
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/Sankey/useSankeyAlert.tsx +60 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
- package/src/data/initial-state.js +7 -12
- package/src/helpers/countNumOfTicks.ts +57 -0
- package/src/helpers/getQuartiles.ts +15 -18
- package/src/hooks/useMinMax.ts +44 -16
- package/src/hooks/useReduceData.ts +43 -10
- package/src/hooks/useScales.ts +90 -35
- package/src/hooks/useTooltip.tsx +59 -50
- package/src/scss/DataTable.scss +5 -0
- package/src/scss/main.scss +6 -20
- package/src/types/ChartConfig.ts +6 -19
- package/src/types/ChartContext.ts +4 -1
- package/src/types/ForestPlot.ts +8 -0
- package/src/components/BoxPlot/BoxPlot.jsx +0 -111
- package/src/hooks/useLegendClasses.ts +0 -72
|
@@ -49,7 +49,7 @@ export default {
|
|
|
49
49
|
labelColor: '#333',
|
|
50
50
|
tickLabelColor: '#333',
|
|
51
51
|
tickColor: '#333',
|
|
52
|
-
rightHideAxis:
|
|
52
|
+
rightHideAxis: false,
|
|
53
53
|
rightAxisSize: 0,
|
|
54
54
|
rightLabel: '',
|
|
55
55
|
rightLabelOffsetSize: 0,
|
|
@@ -68,15 +68,8 @@ export default {
|
|
|
68
68
|
boxplot: {
|
|
69
69
|
plots: [],
|
|
70
70
|
borders: 'true',
|
|
71
|
-
firstQuartilePercentage: 25,
|
|
72
|
-
thirdQuartilePercentage: 75,
|
|
73
|
-
boxWidthPercentage: 40,
|
|
74
71
|
plotOutlierValues: false,
|
|
75
72
|
plotNonOutlierValues: true,
|
|
76
|
-
legend: {
|
|
77
|
-
showHowToReadText: false,
|
|
78
|
-
howToReadText: ''
|
|
79
|
-
},
|
|
80
73
|
labels: {
|
|
81
74
|
q1: 'Lower Quartile',
|
|
82
75
|
q2: 'q2',
|
|
@@ -88,7 +81,7 @@ export default {
|
|
|
88
81
|
median: 'Median',
|
|
89
82
|
sd: 'Standard Deviation',
|
|
90
83
|
iqr: 'Interquartile Range',
|
|
91
|
-
|
|
84
|
+
count: 'Count',
|
|
92
85
|
outliers: 'Outliers',
|
|
93
86
|
values: 'Values',
|
|
94
87
|
lowerBounds: 'Lower Bounds',
|
|
@@ -128,7 +121,8 @@ export default {
|
|
|
128
121
|
target: 0,
|
|
129
122
|
maxTickRotation: 0,
|
|
130
123
|
padding: 5,
|
|
131
|
-
showYearsOnce: false
|
|
124
|
+
showYearsOnce: false,
|
|
125
|
+
sortByRecentDate: false
|
|
132
126
|
},
|
|
133
127
|
table: {
|
|
134
128
|
label: 'Data Table',
|
|
@@ -177,10 +171,11 @@ export default {
|
|
|
177
171
|
hideBorder: {
|
|
178
172
|
side: false,
|
|
179
173
|
topBottom: true
|
|
180
|
-
}
|
|
174
|
+
},
|
|
175
|
+
position: 'right'
|
|
181
176
|
},
|
|
182
177
|
brush: {
|
|
183
|
-
height:
|
|
178
|
+
height: 45,
|
|
184
179
|
active: false
|
|
185
180
|
},
|
|
186
181
|
exclusions: {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const countNumOfTicks = ({ axis, max, runtime, currentViewport, isHorizontal, data, config, min }) => {
|
|
2
|
+
let { numTicks } = runtime[axis]
|
|
3
|
+
if (runtime[axis].viewportNumTicks && runtime[axis].viewportNumTicks[currentViewport]) {
|
|
4
|
+
numTicks = runtime[axis].viewportNumTicks[currentViewport]
|
|
5
|
+
}
|
|
6
|
+
let tickCount = undefined
|
|
7
|
+
|
|
8
|
+
if (axis === 'yAxis') {
|
|
9
|
+
tickCount =
|
|
10
|
+
isHorizontal && !numTicks
|
|
11
|
+
? data.length
|
|
12
|
+
: isHorizontal && numTicks
|
|
13
|
+
? numTicks
|
|
14
|
+
: !isHorizontal && !numTicks
|
|
15
|
+
? undefined
|
|
16
|
+
: !isHorizontal && numTicks && numTicks
|
|
17
|
+
// to fix edge case of small numbers with decimals
|
|
18
|
+
if (tickCount === undefined && !config.dataFormat.roundTo) {
|
|
19
|
+
// then it is set to Auto
|
|
20
|
+
if (Number(max) <= 3) {
|
|
21
|
+
tickCount = 2
|
|
22
|
+
} else {
|
|
23
|
+
tickCount = 4 // same default as standalone components
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (Number(tickCount) > Number(max) && !isHorizontal) {
|
|
27
|
+
// cap it and round it so its an integer
|
|
28
|
+
tickCount = Number(min) < 0 ? Math.round(max) * 2 : Math.round(max)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (axis === 'xAxis') {
|
|
33
|
+
tickCount =
|
|
34
|
+
isHorizontal && !numTicks
|
|
35
|
+
? undefined
|
|
36
|
+
: isHorizontal && numTicks
|
|
37
|
+
? numTicks
|
|
38
|
+
: !isHorizontal && !numTicks
|
|
39
|
+
? undefined
|
|
40
|
+
: !isHorizontal && numTicks && numTicks
|
|
41
|
+
if (isHorizontal && tickCount === undefined && !config.dataFormat.roundTo) {
|
|
42
|
+
// then it is set to Auto
|
|
43
|
+
// - check for small numbers situation
|
|
44
|
+
if (max <= 3) {
|
|
45
|
+
tickCount = 2
|
|
46
|
+
} else {
|
|
47
|
+
tickCount = 4 // same default as standalone components
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (config.visualizationType === 'Forest Plot') {
|
|
52
|
+
tickCount = config.yAxis.numTicks !== '' ? config.yAxis.numTicks : 4
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return tickCount
|
|
57
|
+
}
|
|
@@ -4,27 +4,24 @@
|
|
|
4
4
|
* @param {Array} arr - The array of integers or decimals.
|
|
5
5
|
* @returns {Object} An object containing the q1 and q3 values.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
arr.sort((a, b) => a - b)
|
|
7
|
+
import _ from 'lodash'
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
const
|
|
9
|
+
export const getQuartiles = (values: number[]): { q1: number; q3: number } => {
|
|
10
|
+
const sortedData: number[] = _.sortBy(values)
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const quantile = (sortedData: number[], q: number): number => {
|
|
13
|
+
const position: number = (sortedData.length - 1) * q
|
|
14
|
+
const base: number = Math.floor(position)
|
|
15
|
+
const rest: number = position - base
|
|
16
|
+
if (sortedData[base + 1] !== undefined) {
|
|
17
|
+
return sortedData[base] + rest * (sortedData[base + 1] - sortedData[base])
|
|
18
|
+
} else {
|
|
19
|
+
return sortedData[base]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
const q3Array = isEvenLength ? arr.slice(medianIndex) : arr.slice(medianIndex + 1)
|
|
23
|
+
const q1: number = quantile(sortedData, 0.25)
|
|
24
|
+
const q3: number = quantile(sortedData, 0.75)
|
|
19
25
|
|
|
20
|
-
// Calculate the median of the first subarray to get the q1 value
|
|
21
|
-
const q1Index = Math.floor(q1Array.length / 2)
|
|
22
|
-
const q1 = isEvenLength ? (q1Array[q1Index - 1] + q1Array[q1Index]) / 2 : q1Array[q1Index]
|
|
23
|
-
|
|
24
|
-
// Calculate the median of the second subarray to get the q3 value
|
|
25
|
-
const q3Index = Math.floor(q3Array.length / 2)
|
|
26
|
-
const q3 = isEvenLength ? (q3Array[q3Index - 1] + q3Array[q3Index]) / 2 : q3Array[q3Index]
|
|
27
|
-
|
|
28
|
-
// Return an object containing the q1 and q3 values
|
|
29
26
|
return { q1, q3 }
|
|
30
27
|
}
|
package/src/hooks/useMinMax.ts
CHANGED
|
@@ -37,21 +37,25 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
37
37
|
|
|
38
38
|
const { visualizationType, series } = config
|
|
39
39
|
const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
|
|
40
|
-
const
|
|
40
|
+
const paddingAddedToAxis = config.yAxis.enablePadding ? 1 + config.yAxis.scalePadding / 100 : 1
|
|
41
41
|
const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
|
|
42
42
|
// do validation bafore applying t0 charts
|
|
43
|
-
const isMaxValid = existPositiveValue ? enteredMaxValue >= maxValue : enteredMaxValue >= 0
|
|
44
|
-
const isMinValid = isLogarithmicAxis
|
|
43
|
+
const isMaxValid = existPositiveValue ? Number(enteredMaxValue) >= maxValue : Number(enteredMaxValue) >= 0
|
|
44
|
+
const isMinValid = isLogarithmicAxis
|
|
45
|
+
? Number(enteredMinValue) >= 0
|
|
46
|
+
: (Number(enteredMinValue) <= 0 && minValue >= 0) || (Number(enteredMinValue) <= minValue && minValue < 0)
|
|
45
47
|
|
|
46
|
-
min = enteredMinValue && isMinValid ? enteredMinValue : minValue
|
|
47
|
-
max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
|
|
48
|
+
min = enteredMinValue && isMinValid ? Number(enteredMinValue) : minValue
|
|
49
|
+
max = enteredMaxValue && isMaxValid ? Number(enteredMaxValue) : Number.MIN_VALUE
|
|
48
50
|
|
|
49
51
|
const { lower, upper } = config?.confidenceKeys || {}
|
|
50
52
|
|
|
51
53
|
if (lower && upper && config.visualizationType === 'Bar') {
|
|
52
54
|
const buffer = min < 0 ? 1.1 : 0
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
const maxValueWithCI = Math.max(...data.flatMap(d => [d[upper], d[lower]])) * paddingAddedToAxis
|
|
56
|
+
const minValueWithCIPlusBuffer = Math.min(...data.flatMap(d => [d[upper], d[lower]])) * paddingAddedToAxis * buffer
|
|
57
|
+
max = max > maxValueWithCI ? max : maxValueWithCI
|
|
58
|
+
min = min < minValueWithCIPlusBuffer ? min : minValueWithCIPlusBuffer
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
if (config.series.filter(s => s?.type === 'Forecasting')) {
|
|
@@ -122,8 +126,8 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
122
126
|
leftMax = findMaxFromSeriesKeys(data, leftAxisSeriesItems, leftMax, 'left')
|
|
123
127
|
rightMax = findMaxFromSeriesKeys(data, rightAxisSeriesItems, rightMax, 'right')
|
|
124
128
|
|
|
125
|
-
if (leftMax < enteredMaxValue) {
|
|
126
|
-
leftMax = enteredMaxValue
|
|
129
|
+
if (leftMax < Number(enteredMaxValue)) {
|
|
130
|
+
leftMax = Number(enteredMaxValue)
|
|
127
131
|
}
|
|
128
132
|
} catch (e) {
|
|
129
133
|
console.error(e.message)
|
|
@@ -131,10 +135,18 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
// this should not apply to bar charts if there is negative CI data
|
|
134
|
-
if (
|
|
138
|
+
if (
|
|
139
|
+
(visualizationType === 'Bar' || checkLineToBarGraph() || (visualizationType === 'Combo' && !isAllLine)) &&
|
|
140
|
+
min > 0
|
|
141
|
+
) {
|
|
135
142
|
min = 0
|
|
136
143
|
}
|
|
137
|
-
if (
|
|
144
|
+
if (
|
|
145
|
+
(config.visualizationType === 'Bar' ||
|
|
146
|
+
checkLineToBarGraph() ||
|
|
147
|
+
(config.visualizationType === 'Combo' && !isAllLine)) &&
|
|
148
|
+
min < 0
|
|
149
|
+
) {
|
|
138
150
|
min = min * 1.1
|
|
139
151
|
}
|
|
140
152
|
|
|
@@ -143,18 +155,22 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
143
155
|
min = 0
|
|
144
156
|
}
|
|
145
157
|
if (enteredMinValue) {
|
|
146
|
-
const isMinValid = isLogarithmicAxis
|
|
147
|
-
|
|
158
|
+
const isMinValid = isLogarithmicAxis
|
|
159
|
+
? Number(enteredMinValue) >= 0 && Number(enteredMinValue) < minValue
|
|
160
|
+
: Number(enteredMinValue) < minValue
|
|
161
|
+
min = Number(enteredMinValue) && isMinValid ? Number(enteredMinValue) : minValue
|
|
148
162
|
}
|
|
149
163
|
}
|
|
150
164
|
|
|
151
165
|
if (config.visualizationType === 'Deviation Bar' && min > 0) {
|
|
152
166
|
const isMinValid = Number(enteredMinValue) < Math.min(minValue, Number(config.xAxis.target))
|
|
153
|
-
min = enteredMinValue && isMinValid ? enteredMinValue : 0
|
|
167
|
+
min = Number(enteredMinValue) && isMinValid ? Number(enteredMinValue) : 0
|
|
154
168
|
}
|
|
155
169
|
|
|
156
170
|
if (config.visualizationType === 'Line' && !checkLineToBarGraph()) {
|
|
157
|
-
const isMinValid = isLogarithmicAxis
|
|
171
|
+
const isMinValid = isLogarithmicAxis
|
|
172
|
+
? Number(enteredMinValue) >= 0 && Number(enteredMinValue) < minValue
|
|
173
|
+
: Number(enteredMinValue) < minValue
|
|
158
174
|
// update minValue for (0) Suppression points
|
|
159
175
|
const suppressedMinValue = tableData?.some((dataItem, index) => {
|
|
160
176
|
return config.preliminaryData?.some(pd => {
|
|
@@ -171,7 +187,15 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
171
187
|
return valueMatch && (index === 0 || index === tableData.length - 1)
|
|
172
188
|
})
|
|
173
189
|
})
|
|
174
|
-
|
|
190
|
+
let isCategoricalAxis = config.yAxis.type === 'categorical'
|
|
191
|
+
min =
|
|
192
|
+
enteredMinValue !== '' && isMinValid
|
|
193
|
+
? Number(enteredMinValue)
|
|
194
|
+
: suppressedMinValue
|
|
195
|
+
? 0
|
|
196
|
+
: isCategoricalAxis
|
|
197
|
+
? 0
|
|
198
|
+
: minValue
|
|
175
199
|
}
|
|
176
200
|
//If data value max wasn't provided, calculate it
|
|
177
201
|
if (max === Number.MIN_VALUE) {
|
|
@@ -212,6 +236,10 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
212
236
|
}
|
|
213
237
|
}
|
|
214
238
|
|
|
239
|
+
if (config.visualizationType === 'Area Chart' && config.visualizationSubType === 'stacked') {
|
|
240
|
+
min = 0
|
|
241
|
+
}
|
|
242
|
+
|
|
215
243
|
return { min, max, leftMax, rightMax }
|
|
216
244
|
}
|
|
217
245
|
export default useMinMax
|
|
@@ -3,12 +3,28 @@ import isNumber from '@cdc/core/helpers/isNumber'
|
|
|
3
3
|
function useReduceData(config, data) {
|
|
4
4
|
const isBar = config.series.every(({ type }) => type === 'Bar')
|
|
5
5
|
const isAllLine = config.series.every(({ type }) => ['Line', 'dashed-sm', 'dashed-md', 'dashed-lg'].includes(type))
|
|
6
|
-
const sumYValues = seriesKeys => xValue =>
|
|
7
|
-
|
|
6
|
+
const sumYValues = seriesKeys => xValue =>
|
|
7
|
+
seriesKeys.reduce((yTotal, k) => (isNaN(Number(xValue[k])) ? yTotal : yTotal + Number(xValue[k])), 0)
|
|
8
|
+
const getSeriesKey = seriesKey => {
|
|
9
|
+
const series = config.runtime.series.find(item => item.dataKey === seriesKey)
|
|
10
|
+
return series?.dynamicCategory ? series.originalDataKey : seriesKey
|
|
11
|
+
}
|
|
8
12
|
const getMaxValueFromData = () => {
|
|
9
|
-
let max = Math.max(
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
let max = Math.max(
|
|
14
|
+
...data.map(d =>
|
|
15
|
+
Math.max(
|
|
16
|
+
...config.runtime.seriesKeys.map(key => {
|
|
17
|
+
const seriesKey = getSeriesKey(key)
|
|
18
|
+
return isNumber(d[seriesKey]) ? Number(cleanChars(d[seriesKey])) : 0
|
|
19
|
+
})
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if (
|
|
25
|
+
(config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && isBar)) &&
|
|
26
|
+
config.visualizationSubType === 'stacked'
|
|
27
|
+
) {
|
|
12
28
|
const yTotals = data.map(sumYValues(config.runtime.seriesKeys)).filter(num => !isNaN(num))
|
|
13
29
|
max = Math.max(...yTotals)
|
|
14
30
|
}
|
|
@@ -18,15 +34,23 @@ function useReduceData(config, data) {
|
|
|
18
34
|
max = Math.max(...yTotals)
|
|
19
35
|
}
|
|
20
36
|
|
|
21
|
-
if (
|
|
22
|
-
|
|
37
|
+
if (
|
|
38
|
+
(config.visualizationType === 'Bar' || config.visualizationType === 'Deviation Bar') &&
|
|
39
|
+
config.series &&
|
|
40
|
+
config.series.dataKey
|
|
41
|
+
) {
|
|
42
|
+
max = Math.max(
|
|
43
|
+
...data.map(d => (isNumber(d[config.series.dataKey]) ? Number(cleanChars(d[config.series.dataKey])) : 0))
|
|
44
|
+
)
|
|
23
45
|
}
|
|
24
46
|
|
|
25
47
|
if (config.visualizationType === 'Combo' && config.visualizationSubType === 'stacked' && !isBar) {
|
|
26
48
|
if (config.runtime.barSeriesKeys && config.runtime.lineSeriesKeys) {
|
|
27
49
|
const yTotals = data.map(sumYValues(config.runtime.barSeriesKeys))
|
|
28
50
|
|
|
29
|
-
const lineMax = Math.max(
|
|
51
|
+
const lineMax = Math.max(
|
|
52
|
+
...data.map(d => Math.max(...config.runtime.lineSeriesKeys.map(key => Number(cleanChars(d[key])))))
|
|
53
|
+
)
|
|
30
54
|
const barMax = Math.max(...yTotals)
|
|
31
55
|
|
|
32
56
|
max = Math.max(barMax, lineMax)
|
|
@@ -37,7 +61,16 @@ function useReduceData(config, data) {
|
|
|
37
61
|
}
|
|
38
62
|
|
|
39
63
|
const getMinValueFromData = () => {
|
|
40
|
-
const minNumberFromData = Math.min(
|
|
64
|
+
const minNumberFromData = Math.min(
|
|
65
|
+
...data.map(d =>
|
|
66
|
+
Math.min(
|
|
67
|
+
...config.runtime.seriesKeys.map(key => {
|
|
68
|
+
const seriesKey = getSeriesKey(key)
|
|
69
|
+
return isNumber(d[seriesKey]) ? Number(cleanChars(d[seriesKey])) : Infinity
|
|
70
|
+
})
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
)
|
|
41
74
|
|
|
42
75
|
return String(minNumberFromData)
|
|
43
76
|
}
|
|
@@ -46,7 +79,7 @@ function useReduceData(config, data) {
|
|
|
46
79
|
if (!config.runtime.seriesKeys) {
|
|
47
80
|
return false
|
|
48
81
|
}
|
|
49
|
-
return config.runtime.seriesKeys.some(key => data.some(d => d[key] >= 0))
|
|
82
|
+
return config.runtime.seriesKeys.some(key => data.some(d => d[getSeriesKey(key)] >= 0))
|
|
50
83
|
}
|
|
51
84
|
|
|
52
85
|
const cleanChars = value => {
|
package/src/hooks/useScales.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
LinearScaleConfig,
|
|
3
|
+
LogScaleConfig,
|
|
4
|
+
scaleBand,
|
|
5
|
+
scaleLinear,
|
|
6
|
+
scaleLog,
|
|
7
|
+
scalePoint,
|
|
8
|
+
scaleTime,
|
|
9
|
+
getTicks
|
|
10
|
+
} from '@visx/scale'
|
|
2
11
|
import { useContext } from 'react'
|
|
3
12
|
import ConfigContext from '../ConfigContext'
|
|
4
13
|
import { ChartConfig } from '../types/ChartConfig'
|
|
5
14
|
import { ChartContext } from '../types/ChartContext'
|
|
6
|
-
import
|
|
15
|
+
import _ from 'lodash'
|
|
16
|
+
|
|
7
17
|
const scaleTypes = {
|
|
8
18
|
TIME: 'time',
|
|
9
19
|
LOG: 'log',
|
|
@@ -31,8 +41,7 @@ const useScales = (properties: useScaleProps) => {
|
|
|
31
41
|
const seriesDomain = config.runtime.barSeriesKeys || config.runtime.seriesKeys
|
|
32
42
|
const xAxisType = config.runtime.xAxis.type
|
|
33
43
|
const isHorizontal = config.orientation === 'horizontal'
|
|
34
|
-
|
|
35
|
-
const { visualizationType } = config
|
|
44
|
+
const { visualizationType, xAxis, forestPlot } = config
|
|
36
45
|
|
|
37
46
|
// define scales
|
|
38
47
|
let xScale = null
|
|
@@ -64,11 +73,11 @@ const useScales = (properties: useScaleProps) => {
|
|
|
64
73
|
|
|
65
74
|
// handle Linear scaled viz
|
|
66
75
|
if (config.xAxis.type === 'date' && !isHorizontal) {
|
|
67
|
-
const xAxisDataMappedSorted = xAxisDataMapped
|
|
76
|
+
const xAxisDataMappedSorted = sortXAxisData(xAxisDataMapped, config.xAxis.sortByRecentDate)
|
|
68
77
|
xScale = composeScaleBand(xAxisDataMappedSorted, [0, xMax], 1 - config.barThickness)
|
|
69
78
|
}
|
|
70
79
|
|
|
71
|
-
if (
|
|
80
|
+
if (xAxis.type === 'date-time' || xAxis.type === 'continuous') {
|
|
72
81
|
let xAxisMin = Math.min(...xAxisDataMapped.map(Number))
|
|
73
82
|
let xAxisMax = Math.max(...xAxisDataMapped.map(Number))
|
|
74
83
|
xAxisMin -= (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
|
|
@@ -76,15 +85,17 @@ const useScales = (properties: useScaleProps) => {
|
|
|
76
85
|
visualizationType === 'Line'
|
|
77
86
|
? 0
|
|
78
87
|
: (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
|
|
88
|
+
const range = config.xAxis.sortByRecentDate ? [xMax, 0] : [0, xMax]
|
|
79
89
|
xScale = scaleTime({
|
|
80
90
|
domain: [xAxisMin, xAxisMax],
|
|
81
|
-
range:
|
|
91
|
+
range: range
|
|
82
92
|
})
|
|
83
93
|
|
|
84
94
|
xScale.type = scaleTypes.TIME
|
|
85
95
|
|
|
86
96
|
let minDistance = Number.MAX_VALUE
|
|
87
|
-
let xAxisDataMappedSorted = xAxisDataMapped
|
|
97
|
+
let xAxisDataMappedSorted = sortXAxisData(xAxisDataMapped, config.xAxis.sortByRecentDate)
|
|
98
|
+
|
|
88
99
|
for (let i = 0; i < xAxisDataMappedSorted.length - 1; i++) {
|
|
89
100
|
let distance = xScale(xAxisDataMappedSorted[i + 1]) - xScale(xAxisDataMappedSorted[i])
|
|
90
101
|
|
|
@@ -106,7 +117,7 @@ const useScales = (properties: useScaleProps) => {
|
|
|
106
117
|
range: [0, yMax]
|
|
107
118
|
})
|
|
108
119
|
xScale = scaleLinear({
|
|
109
|
-
domain: [min * leftOffset, Math.max(Number(
|
|
120
|
+
domain: [min * leftOffset, Math.max(Number(xAxis.target), max)],
|
|
110
121
|
range: [0, xMax],
|
|
111
122
|
round: true,
|
|
112
123
|
nice: true
|
|
@@ -116,9 +127,11 @@ const useScales = (properties: useScaleProps) => {
|
|
|
116
127
|
|
|
117
128
|
// handle Scatter plot
|
|
118
129
|
if (config.visualizationType === 'Scatter Plot') {
|
|
119
|
-
if (
|
|
130
|
+
if (xAxis.type === 'continuous') {
|
|
131
|
+
let min = xAxis.min ? xAxis.min : Math.min.apply(null, xScale.domain())
|
|
132
|
+
let max = xAxis.max ? xAxis.max : Math.max.apply(null, xScale.domain())
|
|
120
133
|
xScale = scaleLinear({
|
|
121
|
-
domain: [
|
|
134
|
+
domain: [min, max],
|
|
122
135
|
range: [0, xMax]
|
|
123
136
|
})
|
|
124
137
|
xScale.type = scaleTypes.LINEAR
|
|
@@ -150,19 +163,21 @@ const useScales = (properties: useScaleProps) => {
|
|
|
150
163
|
if (highestFence > max) max = highestFence
|
|
151
164
|
|
|
152
165
|
// Set Scales
|
|
166
|
+
|
|
167
|
+
const categories = _.uniq(data.map(d => d[config.xAxis.dataKey]))
|
|
168
|
+
const range = [0, config.barThickness * 100 || 1]
|
|
169
|
+
const domain = _.map(config.series, 'dataKey')
|
|
153
170
|
yScale = scaleLinear({
|
|
154
171
|
range: [yMax, 0],
|
|
155
172
|
round: true,
|
|
156
173
|
domain: [min, max]
|
|
157
174
|
})
|
|
158
|
-
|
|
159
175
|
xScale = scaleBand({
|
|
160
176
|
range: [0, xMax],
|
|
161
|
-
|
|
162
|
-
domain: config.boxplot.categories,
|
|
163
|
-
padding: 0.4
|
|
177
|
+
domain: categories
|
|
164
178
|
})
|
|
165
179
|
xScale.type = scaleTypes.BAND
|
|
180
|
+
seriesScale = composeScaleBand(domain, range)
|
|
166
181
|
}
|
|
167
182
|
|
|
168
183
|
// handle Paired bar
|
|
@@ -193,10 +208,10 @@ const useScales = (properties: useScaleProps) => {
|
|
|
193
208
|
|
|
194
209
|
if (visualizationType === 'Forest Plot') {
|
|
195
210
|
const resolvedYRange = () => {
|
|
196
|
-
if (
|
|
197
|
-
return [0 +
|
|
211
|
+
if (forestPlot.regression.showDiamond || forestPlot.regression.description) {
|
|
212
|
+
return [0 + forestPlot.rowHeight * 2, yMax - forestPlot.rowHeight]
|
|
198
213
|
} else {
|
|
199
|
-
return [0 +
|
|
214
|
+
return [0 + forestPlot.rowHeight * 2, yMax]
|
|
200
215
|
}
|
|
201
216
|
}
|
|
202
217
|
|
|
@@ -207,26 +222,26 @@ const useScales = (properties: useScaleProps) => {
|
|
|
207
222
|
|
|
208
223
|
const xAxisPadding = 5
|
|
209
224
|
|
|
210
|
-
const leftWidthOffset = (Number(
|
|
211
|
-
const rightWidthOffset = (Number(
|
|
225
|
+
const leftWidthOffset = (Number(forestPlot.leftWidthOffset) / 100) * xMax
|
|
226
|
+
const rightWidthOffset = (Number(forestPlot.rightWidthOffset) / 100) * xMax
|
|
212
227
|
|
|
213
|
-
const rightWidthOffsetMobile = (Number(
|
|
214
|
-
const leftWidthOffsetMobile = (Number(
|
|
228
|
+
const rightWidthOffsetMobile = (Number(forestPlot.rightWidthOffsetMobile) / 100) * xMax
|
|
229
|
+
const leftWidthOffsetMobile = (Number(forestPlot.leftWidthOffsetMobile) / 100) * xMax
|
|
215
230
|
|
|
216
231
|
if (screenWidth > 480) {
|
|
217
|
-
if (
|
|
232
|
+
if (forestPlot.type === 'Linear') {
|
|
218
233
|
xScale = scaleLinear({
|
|
219
234
|
domain: [
|
|
220
|
-
Math.min(...data.map(d => parseFloat(d[
|
|
221
|
-
Math.max(...data.map(d => parseFloat(d[
|
|
235
|
+
Math.min(...data.map(d => parseFloat(d[forestPlot.lower]))) - xAxisPadding,
|
|
236
|
+
Math.max(...data.map(d => parseFloat(d[forestPlot.upper]))) + xAxisPadding
|
|
222
237
|
],
|
|
223
238
|
range: [leftWidthOffset, Number(screenWidth) - rightWidthOffset]
|
|
224
239
|
})
|
|
225
240
|
xScale.type = scaleTypes.LINEAR
|
|
226
241
|
}
|
|
227
|
-
if (
|
|
228
|
-
let max = Math.max(...data.map(d => parseFloat(d[
|
|
229
|
-
let fp_min = Math.min(...data.map(d => parseFloat(d[
|
|
242
|
+
if (forestPlot.type === 'Logarithmic') {
|
|
243
|
+
let max = Math.max(...data.map(d => parseFloat(d[forestPlot.upper])))
|
|
244
|
+
let fp_min = Math.min(...data.map(d => parseFloat(d[forestPlot.lower])))
|
|
230
245
|
|
|
231
246
|
xScale = scaleLog<LogScaleConfig>({
|
|
232
247
|
domain: [fp_min, max],
|
|
@@ -236,20 +251,20 @@ const useScales = (properties: useScaleProps) => {
|
|
|
236
251
|
xScale.type = scaleTypes.LOG
|
|
237
252
|
}
|
|
238
253
|
} else {
|
|
239
|
-
if (
|
|
240
|
-
xScale = scaleLinear({
|
|
254
|
+
if (forestPlot.type === 'Linear') {
|
|
255
|
+
xScale = scaleLinear<LinearScaleConfig>({
|
|
241
256
|
domain: [
|
|
242
|
-
Math.min(...data.map(d => parseFloat(d[
|
|
243
|
-
Math.max(...data.map(d => parseFloat(d[
|
|
257
|
+
Math.min(...data.map(d => parseFloat(d[forestPlot.lower]))) - xAxisPadding,
|
|
258
|
+
Math.max(...data.map(d => parseFloat(d[forestPlot.upper]))) + xAxisPadding
|
|
244
259
|
],
|
|
245
260
|
range: [leftWidthOffsetMobile, xMax - rightWidthOffsetMobile],
|
|
246
261
|
type: scaleTypes.LINEAR
|
|
247
262
|
})
|
|
248
263
|
}
|
|
249
264
|
|
|
250
|
-
if (
|
|
251
|
-
let max = Math.max(...data.map(d => parseFloat(d[
|
|
252
|
-
let fp_min = Math.min(...data.map(d => parseFloat(d[
|
|
265
|
+
if (forestPlot.type === 'Logarithmic') {
|
|
266
|
+
let max = Math.max(...data.map(d => parseFloat(d[forestPlot.upper])))
|
|
267
|
+
let fp_min = Math.min(...data.map(d => parseFloat(d[forestPlot.lower])))
|
|
253
268
|
|
|
254
269
|
xScale = scaleLog<LogScaleConfig>({
|
|
255
270
|
domain: [fp_min, max],
|
|
@@ -324,6 +339,28 @@ export const getTickValues = (xAxisDataMapped, xScale, num, config) => {
|
|
|
324
339
|
}
|
|
325
340
|
}
|
|
326
341
|
|
|
342
|
+
// Ensure that the last tick is shown for charts with a "Date (Linear Scale)" scale
|
|
343
|
+
export const filterAndShiftLinearDateTicks = (config, axisProps, xAxisDataMapped, formatDate) => {
|
|
344
|
+
let ticks = axisProps.ticks
|
|
345
|
+
const filteredTickValues = getTicks(axisProps.scale, axisProps.numTicks)
|
|
346
|
+
if (filteredTickValues.length < xAxisDataMapped.length) {
|
|
347
|
+
let shift = 0
|
|
348
|
+
const lastIdx = xAxisDataMapped.indexOf(filteredTickValues[filteredTickValues.length - 1])
|
|
349
|
+
if (lastIdx < xAxisDataMapped.length - 1) {
|
|
350
|
+
shift = !config.xAxis.sortByRecentDate
|
|
351
|
+
? xAxisDataMapped.length - 1 - lastIdx
|
|
352
|
+
: xAxisDataMapped.indexOf(filteredTickValues[0]) * -1
|
|
353
|
+
}
|
|
354
|
+
ticks = filteredTickValues.map(value => {
|
|
355
|
+
return axisProps.ticks[axisProps.ticks.findIndex(tick => tick.value === value) + shift]
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
ticks.forEach((tick, i) => {
|
|
359
|
+
tick.formattedValue = formatDate(tick.value, i, ticks)
|
|
360
|
+
})
|
|
361
|
+
return ticks
|
|
362
|
+
}
|
|
363
|
+
|
|
327
364
|
/// helper functions
|
|
328
365
|
const composeXScale = ({ min, max, xMax, config }) => {
|
|
329
366
|
// Adjust min value if using logarithmic scale
|
|
@@ -386,3 +423,21 @@ const composeScaleBand = (domain, range, padding = 0) => {
|
|
|
386
423
|
padding: padding
|
|
387
424
|
})
|
|
388
425
|
}
|
|
426
|
+
|
|
427
|
+
const sortXAxisData = (xAxisData, sortByRecentDate) => {
|
|
428
|
+
if (!xAxisData || xAxisData.length === 0) {
|
|
429
|
+
return []
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Check if the array has only one item
|
|
433
|
+
if (xAxisData.length === 1) {
|
|
434
|
+
return xAxisData
|
|
435
|
+
}
|
|
436
|
+
if (sortByRecentDate) {
|
|
437
|
+
// Sort from newest to oldes (recent dates first)
|
|
438
|
+
return xAxisData.sort((a, b) => Number(b) - Number(a))
|
|
439
|
+
} else {
|
|
440
|
+
// Sort from oldest to newest
|
|
441
|
+
return xAxisData.sort((a, b) => Number(a) - Number(b))
|
|
442
|
+
}
|
|
443
|
+
}
|