forked from plouc/nivo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtotals.ts
142 lines (119 loc) · 5.25 KB
/
totals.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { AnyScale, ScaleBand } from '@nivo/scales'
import { defaultProps } from '../props'
import { BarCommonProps, BarDatum, ComputedBarDatum } from '../types'
interface BarTotalsData {
key: string
x: number
y: number
value: number
}
export const computeBarTotals = <RawDatum extends BarDatum>(
enableTotals: boolean,
bars: ComputedBarDatum<RawDatum>[],
xScale: ScaleBand<string> | AnyScale,
yScale: ScaleBand<string> | AnyScale,
layout: BarCommonProps<RawDatum>['layout'] = defaultProps.layout,
groupMode: BarCommonProps<RawDatum>['groupMode'] = defaultProps.groupMode,
totalsOffset: number
) => {
const totals = [] as BarTotalsData[]
if (!enableTotals || bars.length === 0) return totals
const totalsByIndex = new Map<string | number, number>()
const barWidth = bars[0].width
const barHeight = bars[0].height
if (groupMode === 'stacked') {
const totalsPositivesByIndex = new Map<string | number, number>()
bars.forEach(bar => {
const { indexValue, value } = bar.data
updateTotalsByIndex(totalsByIndex, indexValue, Number(value))
updateTotalsPositivesByIndex(totalsPositivesByIndex, indexValue, Number(value))
})
totalsPositivesByIndex.forEach((totalsPositive, indexValue) => {
const indexTotal = totalsByIndex.get(indexValue) || 0
let xPosition: number
let yPosition: number
if (layout === 'vertical') {
xPosition = xScale(indexValue)
yPosition = yScale(totalsPositive)
} else {
xPosition = xScale(totalsPositive)
yPosition = yScale(indexValue)
}
xPosition += layout === 'vertical' ? barWidth / 2 : totalsOffset
yPosition += layout === 'vertical' ? -totalsOffset : barHeight / 2
totals.push({
key: 'total_' + indexValue,
x: xPosition,
y: yPosition,
value: indexTotal,
})
})
} else if (groupMode === 'grouped') {
const greatestValueByIndex = new Map<string | number, number>()
const numberOfBarsByIndex = new Map()
bars.forEach(bar => {
const { indexValue, value } = bar.data
updateTotalsByIndex(totalsByIndex, indexValue, Number(value))
updateGreatestValueByIndex(greatestValueByIndex, indexValue, Number(value))
updateNumberOfBarsByIndex(numberOfBarsByIndex, indexValue)
})
greatestValueByIndex.forEach((greatestValue, indexValue) => {
const indexTotal = totalsByIndex.get(indexValue) || 0
let xPosition: number
let yPosition: number
if (layout === 'vertical') {
xPosition = xScale(indexValue)
yPosition = yScale(greatestValue)
} else {
xPosition = xScale(greatestValue)
yPosition = yScale(indexValue)
}
const indexBarsWidth = numberOfBarsByIndex.get(indexValue) * barWidth
const indexBarsHeight = numberOfBarsByIndex.get(indexValue) * barHeight
xPosition += layout === 'vertical' ? indexBarsWidth / 2 : totalsOffset
yPosition += layout === 'vertical' ? -totalsOffset : indexBarsHeight / 2
totals.push({
key: 'total_' + indexValue,
x: xPosition,
y: yPosition,
value: indexTotal,
})
})
}
return totals
}
// this function is used to compute the total value for the indexes. The total value is later rendered on the chart
export const updateTotalsByIndex = (
totalsByIndex: Map<string | number, number>,
indexValue: string | number,
value: number
) => {
const currentIndexValue = totalsByIndex.get(indexValue) || 0
totalsByIndex.set(indexValue, currentIndexValue + value)
}
// this function is used to compute only the positive values of the indexes. Useful to position the text right above the last stacked bar. It prevents overlapping in case of negative values
export const updateTotalsPositivesByIndex = (
totalsPositivesByIndex: Map<string | number, number>,
indexValue: string | number,
value: number
) => {
const currentIndexValue = totalsPositivesByIndex.get(indexValue) || 0
totalsPositivesByIndex.set(indexValue, currentIndexValue + (value > 0 ? value : 0))
}
// this function is used to keep track of the highest value for the indexes. Useful to position the text above the longest grouped bar
export const updateGreatestValueByIndex = (
greatestValueByIndex: Map<string | number, number>,
indexValue: string | number,
value: number
) => {
const currentGreatestValue = greatestValueByIndex.get(indexValue) || 0
greatestValueByIndex.set(indexValue, Math.max(currentGreatestValue, Number(value)))
}
// this function is used to save the number of bars for the indexes. Useful to position the text in the middle of the grouped bars
export const updateNumberOfBarsByIndex = (
numberOfBarsByIndex: Map<string | number, number>,
indexValue: string | number
) => {
const currentNumberOfBars = numberOfBarsByIndex.get(indexValue) || 0
numberOfBarsByIndex.set(indexValue, currentNumberOfBars + 1)
}