@cdc/chart 4.24.10 → 4.24.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 +34618 -33995
- 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 +0 -1
- package/examples/private/test.json +20092 -0
- package/index.html +3 -3
- package/package.json +2 -2
- package/src/CdcChart.tsx +86 -86
- package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +27 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
- package/src/_stories/Chart.stories.tsx +7 -8
- 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/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/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
- package/src/_stories/_mock/suppression_mock.json +1549 -0
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
- package/src/components/BarChart/components/BarChart.Vertical.tsx +60 -42
- package/src/components/BarChart/helpers/index.ts +1 -2
- package/src/components/BoxPlot/BoxPlot.tsx +189 -0
- package/src/components/EditorPanel/EditorPanel.tsx +64 -62
- 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 +121 -56
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
- package/src/components/EditorPanel/useEditorPermissions.ts +15 -1
- package/src/components/Legend/Legend.Component.tsx +9 -10
- package/src/components/Legend/Legend.tsx +16 -16
- package/src/components/LineChart/helpers.ts +48 -43
- package/src/components/LineChart/index.tsx +88 -82
- package/src/components/LinearChart.tsx +17 -10
- package/src/components/Sankey/index.tsx +50 -32
- package/src/components/Sankey/sankey.scss +6 -5
- package/src/components/Sankey/useSankeyAlert.tsx +60 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
- package/src/data/initial-state.js +3 -9
- package/src/hooks/useLegendClasses.ts +10 -23
- package/src/hooks/useMinMax.ts +27 -13
- package/src/hooks/useReduceData.ts +43 -10
- package/src/hooks/useScales.ts +56 -35
- package/src/hooks/useTooltip.tsx +54 -49
- package/src/scss/main.scss +0 -18
- 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
|
@@ -4,7 +4,14 @@ import { Group } from '@visx/group'
|
|
|
4
4
|
import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
|
|
5
5
|
|
|
6
6
|
const ScatterPlot = ({ xScale, yScale }) => {
|
|
7
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
transformedData: data,
|
|
9
|
+
config,
|
|
10
|
+
tableData,
|
|
11
|
+
formatNumber,
|
|
12
|
+
seriesHighlight,
|
|
13
|
+
colorPalettes
|
|
14
|
+
} = useContext(ConfigContext)
|
|
8
15
|
|
|
9
16
|
// TODO: copied from line chart should probably be a constant somewhere.
|
|
10
17
|
const circleRadii = 4.5
|
|
@@ -23,10 +30,19 @@ const ScatterPlot = ({ xScale, yScale }) => {
|
|
|
23
30
|
}
|
|
24
31
|
])
|
|
25
32
|
const handleTooltip = (item, s, dataIndex) => `<div>
|
|
26
|
-
${
|
|
33
|
+
${
|
|
34
|
+
config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries
|
|
35
|
+
? `${config.runtime.seriesLabels[s] || ''}<br/>`
|
|
36
|
+
: ''
|
|
37
|
+
}
|
|
27
38
|
${config.xAxis.label}: ${formatNumber(item[config.xAxis.dataKey], 'bottom')} <br/>
|
|
28
39
|
${config.yAxis.label}: ${formatNumber(item[s], 'left')}<br/>
|
|
29
|
-
${additionalColumns
|
|
40
|
+
${additionalColumns
|
|
41
|
+
.map(
|
|
42
|
+
([label, name, options]) =>
|
|
43
|
+
`${label} : ${formatColNumber(tableData[dataIndex][name], 'left', false, config, options)}<br/>`
|
|
44
|
+
)
|
|
45
|
+
.join('')}
|
|
30
46
|
</div>`
|
|
31
47
|
|
|
32
48
|
return (
|
|
@@ -36,7 +52,7 @@ const ScatterPlot = ({ xScale, yScale }) => {
|
|
|
36
52
|
return config.runtime.seriesKeys.map((s, index) => {
|
|
37
53
|
const transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(s) === -1
|
|
38
54
|
const displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(s) !== -1
|
|
39
|
-
const seriesColor = config.palette ? colorPalettes[config.palette][index] : '#000'
|
|
55
|
+
const seriesColor = config?.customColors ? config.customColors[index] : config.palette ? colorPalettes[config.palette][index] : '#000'
|
|
40
56
|
|
|
41
57
|
let pointStyles = {
|
|
42
58
|
filter: 'unset',
|
|
@@ -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',
|
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
legend: {
|
|
3
|
-
position: 'left' | 'bottom' | 'top' | 'right'
|
|
4
|
-
singleRow?: boolean
|
|
5
|
-
reverseLabelOrder?: boolean
|
|
6
|
-
verticalSorted?: boolean
|
|
7
|
-
hideBorder: {
|
|
8
|
-
side?: boolean
|
|
9
|
-
topBottom?: boolean
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
1
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
13
2
|
|
|
14
|
-
const useLegendClasses = (config:
|
|
3
|
+
const useLegendClasses = (config: ChartConfig) => {
|
|
4
|
+
const { position, singleRow, reverseLabelOrder, verticalSorted, hideBorder } = config.legend
|
|
15
5
|
const containerClasses = ['legend-container']
|
|
16
6
|
const innerClasses = ['legend-container__inner']
|
|
17
7
|
|
|
18
8
|
// Handle legend positioning
|
|
19
|
-
switch (
|
|
9
|
+
switch (position) {
|
|
20
10
|
case 'left':
|
|
21
11
|
containerClasses.push('left')
|
|
22
12
|
break
|
|
@@ -34,33 +24,30 @@ const useLegendClasses = (config: ConfigType) => {
|
|
|
34
24
|
}
|
|
35
25
|
|
|
36
26
|
// Handle single row configuration for 'bottom' and 'top' positions
|
|
37
|
-
if (['bottom', 'top'].includes(
|
|
27
|
+
if (['bottom', 'top'].includes(position) && singleRow) {
|
|
38
28
|
innerClasses.push('single-row')
|
|
39
29
|
}
|
|
40
30
|
|
|
41
31
|
// Reverse label order
|
|
42
|
-
if (
|
|
32
|
+
if (reverseLabelOrder) {
|
|
43
33
|
innerClasses.push('d-flex', 'flex-column-reverse')
|
|
44
34
|
}
|
|
45
35
|
|
|
46
36
|
// Vertical sorting for 'bottom' and 'top' positions
|
|
47
|
-
if (['bottom', 'top'].includes(
|
|
37
|
+
if (['bottom', 'top'].includes(position) && verticalSorted) {
|
|
48
38
|
innerClasses.push('vertical-sorted')
|
|
49
39
|
}
|
|
50
40
|
|
|
51
41
|
// Configure border classes
|
|
52
|
-
if (
|
|
53
|
-
config.legend.hideBorder.side &&
|
|
54
|
-
(['right', 'left'].includes(config.legend.position) || !config.legend.position)
|
|
55
|
-
) {
|
|
42
|
+
if (hideBorder.side && (['right', 'left'].includes(position) || !position)) {
|
|
56
43
|
containerClasses.push('no-border')
|
|
57
44
|
}
|
|
58
45
|
|
|
59
|
-
if (
|
|
46
|
+
if (hideBorder.topBottom && ['top', 'bottom'].includes(position)) {
|
|
60
47
|
containerClasses.push('no-border')
|
|
61
48
|
}
|
|
62
49
|
|
|
63
|
-
if (
|
|
50
|
+
if (hideBorder.topBottom && ['top'].includes(position)) {
|
|
64
51
|
containerClasses.push('p-0')
|
|
65
52
|
}
|
|
66
53
|
|
package/src/hooks/useMinMax.ts
CHANGED
|
@@ -40,11 +40,13 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
40
40
|
const minRequiredCIPadding = 1.15 // regardless of Editor if CI data, there must be 10% padding added
|
|
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
|
|
|
@@ -122,8 +124,8 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
122
124
|
leftMax = findMaxFromSeriesKeys(data, leftAxisSeriesItems, leftMax, 'left')
|
|
123
125
|
rightMax = findMaxFromSeriesKeys(data, rightAxisSeriesItems, rightMax, 'right')
|
|
124
126
|
|
|
125
|
-
if (leftMax < enteredMaxValue) {
|
|
126
|
-
leftMax = enteredMaxValue
|
|
127
|
+
if (leftMax < Number(enteredMaxValue)) {
|
|
128
|
+
leftMax = Number(enteredMaxValue)
|
|
127
129
|
}
|
|
128
130
|
} catch (e) {
|
|
129
131
|
console.error(e.message)
|
|
@@ -131,10 +133,18 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
// this should not apply to bar charts if there is negative CI data
|
|
134
|
-
if (
|
|
136
|
+
if (
|
|
137
|
+
(visualizationType === 'Bar' || checkLineToBarGraph() || (visualizationType === 'Combo' && !isAllLine)) &&
|
|
138
|
+
min > 0
|
|
139
|
+
) {
|
|
135
140
|
min = 0
|
|
136
141
|
}
|
|
137
|
-
if (
|
|
142
|
+
if (
|
|
143
|
+
(config.visualizationType === 'Bar' ||
|
|
144
|
+
checkLineToBarGraph() ||
|
|
145
|
+
(config.visualizationType === 'Combo' && !isAllLine)) &&
|
|
146
|
+
min < 0
|
|
147
|
+
) {
|
|
138
148
|
min = min * 1.1
|
|
139
149
|
}
|
|
140
150
|
|
|
@@ -143,18 +153,22 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
143
153
|
min = 0
|
|
144
154
|
}
|
|
145
155
|
if (enteredMinValue) {
|
|
146
|
-
const isMinValid = isLogarithmicAxis
|
|
147
|
-
|
|
156
|
+
const isMinValid = isLogarithmicAxis
|
|
157
|
+
? Number(enteredMinValue) >= 0 && Number(enteredMinValue) < minValue
|
|
158
|
+
: Number(enteredMinValue) < minValue
|
|
159
|
+
min = Number(enteredMinValue) && isMinValid ? Number(enteredMinValue) : minValue
|
|
148
160
|
}
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
if (config.visualizationType === 'Deviation Bar' && min > 0) {
|
|
152
164
|
const isMinValid = Number(enteredMinValue) < Math.min(minValue, Number(config.xAxis.target))
|
|
153
|
-
min = enteredMinValue && isMinValid ? enteredMinValue : 0
|
|
165
|
+
min = Number(enteredMinValue) && isMinValid ? Number(enteredMinValue) : 0
|
|
154
166
|
}
|
|
155
167
|
|
|
156
168
|
if (config.visualizationType === 'Line' && !checkLineToBarGraph()) {
|
|
157
|
-
const isMinValid = isLogarithmicAxis
|
|
169
|
+
const isMinValid = isLogarithmicAxis
|
|
170
|
+
? Number(enteredMinValue) >= 0 && Number(enteredMinValue) < minValue
|
|
171
|
+
: Number(enteredMinValue) < minValue
|
|
158
172
|
// update minValue for (0) Suppression points
|
|
159
173
|
const suppressedMinValue = tableData?.some((dataItem, index) => {
|
|
160
174
|
return config.preliminaryData?.some(pd => {
|
|
@@ -171,7 +185,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
171
185
|
return valueMatch && (index === 0 || index === tableData.length - 1)
|
|
172
186
|
})
|
|
173
187
|
})
|
|
174
|
-
min = enteredMinValue && isMinValid ? enteredMinValue : suppressedMinValue ? 0 : minValue
|
|
188
|
+
min = Number(enteredMinValue) && isMinValid ? Number(enteredMinValue) : suppressedMinValue ? 0 : minValue
|
|
175
189
|
}
|
|
176
190
|
//If data value max wasn't provided, calculate it
|
|
177
191
|
if (max === Number.MIN_VALUE) {
|
|
@@ -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,9 @@
|
|
|
1
|
-
import { LogScaleConfig, scaleBand, scaleLinear, scaleLog, scalePoint, scaleTime } from '@visx/scale'
|
|
1
|
+
import { LinearScaleConfig, 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
5
|
import { ChartContext } from '../types/ChartContext'
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
const scaleTypes = {
|
|
8
8
|
TIME: 'time',
|
|
9
9
|
LOG: 'log',
|
|
@@ -31,8 +31,7 @@ const useScales = (properties: useScaleProps) => {
|
|
|
31
31
|
const seriesDomain = config.runtime.barSeriesKeys || config.runtime.seriesKeys
|
|
32
32
|
const xAxisType = config.runtime.xAxis.type
|
|
33
33
|
const isHorizontal = config.orientation === 'horizontal'
|
|
34
|
-
|
|
35
|
-
const { visualizationType } = config
|
|
34
|
+
const { visualizationType, xAxis, forestPlot } = config
|
|
36
35
|
|
|
37
36
|
// define scales
|
|
38
37
|
let xScale = null
|
|
@@ -64,11 +63,11 @@ const useScales = (properties: useScaleProps) => {
|
|
|
64
63
|
|
|
65
64
|
// handle Linear scaled viz
|
|
66
65
|
if (config.xAxis.type === 'date' && !isHorizontal) {
|
|
67
|
-
const xAxisDataMappedSorted = xAxisDataMapped
|
|
66
|
+
const xAxisDataMappedSorted = sortXAxisData(xAxisDataMapped, config.xAxis.sortByRecentDate)
|
|
68
67
|
xScale = composeScaleBand(xAxisDataMappedSorted, [0, xMax], 1 - config.barThickness)
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
if (
|
|
70
|
+
if (xAxis.type === 'date-time' || xAxis.type === 'continuous') {
|
|
72
71
|
let xAxisMin = Math.min(...xAxisDataMapped.map(Number))
|
|
73
72
|
let xAxisMax = Math.max(...xAxisDataMapped.map(Number))
|
|
74
73
|
xAxisMin -= (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
|
|
@@ -76,15 +75,17 @@ const useScales = (properties: useScaleProps) => {
|
|
|
76
75
|
visualizationType === 'Line'
|
|
77
76
|
? 0
|
|
78
77
|
: (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
|
|
78
|
+
const range = config.xAxis.sortByRecentDate ? [xMax, 0] : [0, xMax]
|
|
79
79
|
xScale = scaleTime({
|
|
80
80
|
domain: [xAxisMin, xAxisMax],
|
|
81
|
-
range:
|
|
81
|
+
range: range
|
|
82
82
|
})
|
|
83
83
|
|
|
84
84
|
xScale.type = scaleTypes.TIME
|
|
85
85
|
|
|
86
86
|
let minDistance = Number.MAX_VALUE
|
|
87
|
-
let xAxisDataMappedSorted = xAxisDataMapped
|
|
87
|
+
let xAxisDataMappedSorted = sortXAxisData(xAxisDataMapped, config.xAxis.sortByRecentDate)
|
|
88
|
+
|
|
88
89
|
for (let i = 0; i < xAxisDataMappedSorted.length - 1; i++) {
|
|
89
90
|
let distance = xScale(xAxisDataMappedSorted[i + 1]) - xScale(xAxisDataMappedSorted[i])
|
|
90
91
|
|
|
@@ -106,7 +107,7 @@ const useScales = (properties: useScaleProps) => {
|
|
|
106
107
|
range: [0, yMax]
|
|
107
108
|
})
|
|
108
109
|
xScale = scaleLinear({
|
|
109
|
-
domain: [min * leftOffset, Math.max(Number(
|
|
110
|
+
domain: [min * leftOffset, Math.max(Number(xAxis.target), max)],
|
|
110
111
|
range: [0, xMax],
|
|
111
112
|
round: true,
|
|
112
113
|
nice: true
|
|
@@ -116,9 +117,11 @@ const useScales = (properties: useScaleProps) => {
|
|
|
116
117
|
|
|
117
118
|
// handle Scatter plot
|
|
118
119
|
if (config.visualizationType === 'Scatter Plot') {
|
|
119
|
-
if (
|
|
120
|
+
if (xAxis.type === 'continuous') {
|
|
121
|
+
let min = xAxis.min ? xAxis.min : Math.min.apply(null, xScale.domain())
|
|
122
|
+
let max = xAxis.max ? xAxis.max : Math.max.apply(null, xScale.domain())
|
|
120
123
|
xScale = scaleLinear({
|
|
121
|
-
domain: [
|
|
124
|
+
domain: [min, max],
|
|
122
125
|
range: [0, xMax]
|
|
123
126
|
})
|
|
124
127
|
xScale.type = scaleTypes.LINEAR
|
|
@@ -150,19 +153,19 @@ const useScales = (properties: useScaleProps) => {
|
|
|
150
153
|
if (highestFence > max) max = highestFence
|
|
151
154
|
|
|
152
155
|
// Set Scales
|
|
156
|
+
|
|
153
157
|
yScale = scaleLinear({
|
|
154
158
|
range: [yMax, 0],
|
|
155
159
|
round: true,
|
|
156
160
|
domain: [min, max]
|
|
157
161
|
})
|
|
158
|
-
|
|
159
162
|
xScale = scaleBand({
|
|
160
163
|
range: [0, xMax],
|
|
161
|
-
|
|
162
|
-
domain: config.boxplot.categories,
|
|
163
|
-
padding: 0.4
|
|
164
|
+
domain: config.boxplot.categories
|
|
164
165
|
})
|
|
165
166
|
xScale.type = scaleTypes.BAND
|
|
167
|
+
|
|
168
|
+
seriesScale = composeScalePoint(seriesDomain, [0, yMax])
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
// handle Paired bar
|
|
@@ -193,10 +196,10 @@ const useScales = (properties: useScaleProps) => {
|
|
|
193
196
|
|
|
194
197
|
if (visualizationType === 'Forest Plot') {
|
|
195
198
|
const resolvedYRange = () => {
|
|
196
|
-
if (
|
|
197
|
-
return [0 +
|
|
199
|
+
if (forestPlot.regression.showDiamond || forestPlot.regression.description) {
|
|
200
|
+
return [0 + forestPlot.rowHeight * 2, yMax - forestPlot.rowHeight]
|
|
198
201
|
} else {
|
|
199
|
-
return [0 +
|
|
202
|
+
return [0 + forestPlot.rowHeight * 2, yMax]
|
|
200
203
|
}
|
|
201
204
|
}
|
|
202
205
|
|
|
@@ -207,26 +210,26 @@ const useScales = (properties: useScaleProps) => {
|
|
|
207
210
|
|
|
208
211
|
const xAxisPadding = 5
|
|
209
212
|
|
|
210
|
-
const leftWidthOffset = (Number(
|
|
211
|
-
const rightWidthOffset = (Number(
|
|
213
|
+
const leftWidthOffset = (Number(forestPlot.leftWidthOffset) / 100) * xMax
|
|
214
|
+
const rightWidthOffset = (Number(forestPlot.rightWidthOffset) / 100) * xMax
|
|
212
215
|
|
|
213
|
-
const rightWidthOffsetMobile = (Number(
|
|
214
|
-
const leftWidthOffsetMobile = (Number(
|
|
216
|
+
const rightWidthOffsetMobile = (Number(forestPlot.rightWidthOffsetMobile) / 100) * xMax
|
|
217
|
+
const leftWidthOffsetMobile = (Number(forestPlot.leftWidthOffsetMobile) / 100) * xMax
|
|
215
218
|
|
|
216
219
|
if (screenWidth > 480) {
|
|
217
|
-
if (
|
|
220
|
+
if (forestPlot.type === 'Linear') {
|
|
218
221
|
xScale = scaleLinear({
|
|
219
222
|
domain: [
|
|
220
|
-
Math.min(...data.map(d => parseFloat(d[
|
|
221
|
-
Math.max(...data.map(d => parseFloat(d[
|
|
223
|
+
Math.min(...data.map(d => parseFloat(d[forestPlot.lower]))) - xAxisPadding,
|
|
224
|
+
Math.max(...data.map(d => parseFloat(d[forestPlot.upper]))) + xAxisPadding
|
|
222
225
|
],
|
|
223
226
|
range: [leftWidthOffset, Number(screenWidth) - rightWidthOffset]
|
|
224
227
|
})
|
|
225
228
|
xScale.type = scaleTypes.LINEAR
|
|
226
229
|
}
|
|
227
|
-
if (
|
|
228
|
-
let max = Math.max(...data.map(d => parseFloat(d[
|
|
229
|
-
let fp_min = Math.min(...data.map(d => parseFloat(d[
|
|
230
|
+
if (forestPlot.type === 'Logarithmic') {
|
|
231
|
+
let max = Math.max(...data.map(d => parseFloat(d[forestPlot.upper])))
|
|
232
|
+
let fp_min = Math.min(...data.map(d => parseFloat(d[forestPlot.lower])))
|
|
230
233
|
|
|
231
234
|
xScale = scaleLog<LogScaleConfig>({
|
|
232
235
|
domain: [fp_min, max],
|
|
@@ -236,20 +239,20 @@ const useScales = (properties: useScaleProps) => {
|
|
|
236
239
|
xScale.type = scaleTypes.LOG
|
|
237
240
|
}
|
|
238
241
|
} else {
|
|
239
|
-
if (
|
|
240
|
-
xScale = scaleLinear({
|
|
242
|
+
if (forestPlot.type === 'Linear') {
|
|
243
|
+
xScale = scaleLinear<LinearScaleConfig>({
|
|
241
244
|
domain: [
|
|
242
|
-
Math.min(...data.map(d => parseFloat(d[
|
|
243
|
-
Math.max(...data.map(d => parseFloat(d[
|
|
245
|
+
Math.min(...data.map(d => parseFloat(d[forestPlot.lower]))) - xAxisPadding,
|
|
246
|
+
Math.max(...data.map(d => parseFloat(d[forestPlot.upper]))) + xAxisPadding
|
|
244
247
|
],
|
|
245
248
|
range: [leftWidthOffsetMobile, xMax - rightWidthOffsetMobile],
|
|
246
249
|
type: scaleTypes.LINEAR
|
|
247
250
|
})
|
|
248
251
|
}
|
|
249
252
|
|
|
250
|
-
if (
|
|
251
|
-
let max = Math.max(...data.map(d => parseFloat(d[
|
|
252
|
-
let fp_min = Math.min(...data.map(d => parseFloat(d[
|
|
253
|
+
if (forestPlot.type === 'Logarithmic') {
|
|
254
|
+
let max = Math.max(...data.map(d => parseFloat(d[forestPlot.upper])))
|
|
255
|
+
let fp_min = Math.min(...data.map(d => parseFloat(d[forestPlot.lower])))
|
|
253
256
|
|
|
254
257
|
xScale = scaleLog<LogScaleConfig>({
|
|
255
258
|
domain: [fp_min, max],
|
|
@@ -386,3 +389,21 @@ const composeScaleBand = (domain, range, padding = 0) => {
|
|
|
386
389
|
padding: padding
|
|
387
390
|
})
|
|
388
391
|
}
|
|
392
|
+
|
|
393
|
+
const sortXAxisData = (xAxisData, sortByRecentDate) => {
|
|
394
|
+
if (!xAxisData || xAxisData.length === 0) {
|
|
395
|
+
return []
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Check if the array has only one item
|
|
399
|
+
if (xAxisData.length === 1) {
|
|
400
|
+
return xAxisData
|
|
401
|
+
}
|
|
402
|
+
if (sortByRecentDate) {
|
|
403
|
+
// Sort from newest to oldes (recent dates first)
|
|
404
|
+
return xAxisData.sort((a, b) => Number(b) - Number(a))
|
|
405
|
+
} else {
|
|
406
|
+
// Sort from oldest to newest
|
|
407
|
+
return xAxisData.sort((a, b) => Number(a) - Number(b))
|
|
408
|
+
}
|
|
409
|
+
}
|