mon-entreprise/source/components/StackedBarChart.js

118 lines
2.9 KiB
JavaScript

import React from 'react'
import styled from 'styled-components'
import RuleLink from 'Components/RuleLink'
import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting'
import { animated, useSpring } from 'react-spring'
import { capitalise0 } from '../utils'
const BarStack = styled.div`
display: flex;
border-radius: 0.4em;
overflow: hidden;
`
const BarItem = styled.div`
height: 26px;
border-right: 2px solid white;
transition: width 0.3s ease-out;
&:last-child {
border-right: none;
}
`
const BarStackLegend = styled.div`
display: flex;
margin-top: 10px;
flex-direction: column;
@media (min-width: 800px) {
flex-direction: row;
text-align: center;
}
`
const BarStackLegendItem = styled.div`
flex: 1 1 0px;
color: #555;
strong {
display: inline-block;
color: #111;
margin-left: 8px;
}
`
const SmallCircle = styled.span`
display: inline-block;
height: 11px;
width: 11px;
margin-right: 10px;
border-radius: 100%;
`
function integerAndDecimalParts(value) {
const integer = Math.floor(value)
const decimal = value - integer
return { integer, decimal }
}
// This function calculates rounded percentages so that the sum of all
// returned values is always 100. For instance: [60, 30, 10].
export function roundedPercentages(values) {
const sum = (a = 0, b) => a + b
const total = values.reduce(sum)
const percentages = values.map(value =>
integerAndDecimalParts((value / total) * 100)
)
const totalRoundedPercentage = percentages.map(v => v.integer).reduce(sum)
const indexesToIncrement = percentages
.map((percentage, index) => ({ ...percentage, index }))
.sort((a, b) => b.decimal - a.decimal)
.map(({ index }) => index)
.splice(0, 100 - totalRoundedPercentage)
return percentages.map(
({ integer }, index) =>
integer + (indexesToIncrement.includes(index) ? 1 : 0)
)
}
export default function StackedBarChart({ data }) {
const [intersectionRef, displayChart] = useDisplayOnIntersecting({
threshold: 0.5
})
const percentages = roundedPercentages(data.map(d => d.nodeValue))
const dataWithPercentage = data.map((data, index) => ({
...data,
percentage: percentages[index]
}))
const styles = useSpring({ opacity: displayChart ? 1 : 0 })
return (
<animated.div ref={intersectionRef} style={styles}>
<BarStack>
{dataWithPercentage.map(({ dottedName, color, percentage }) => (
<BarItem
style={{
width: `${percentage}%`,
backgroundColor: color || 'green'
}}
key={dottedName}
/>
))}
</BarStack>
<BarStackLegend>
{dataWithPercentage.map(({ percentage, color, ...rule }) => (
<BarStackLegendItem key={rule.dottedName}>
<SmallCircle style={{ backgroundColor: color }} />
<RuleLink {...rule}>{capitalise0(rule.name)}</RuleLink>
<strong>{percentage} %</strong>
</BarStackLegendItem>
))}
</BarStackLegend>
</animated.div>
)
}