/* Copyright (C) 2017 Cloudbase Solutions SRL This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import * as React from 'react' import { observer } from 'mobx-react' import styled from 'styled-components' import StyleProps from '../../../styleUtils/StyleProps' import BarChartNiceScale from './BarChartNiceScale' const Wrapper = styled.div` position: relative; width: 100%; ` const YAxis = styled.div` height: calc(100% - 24px); position: absolute; bottom: 24px; left: 16px; ` const YTick = styled.div` position: absolute; top: ${props => 100 - props.bottom}%; font-size: 9px; font-weight: ${StyleProps.fontWeights.medium}; width: 24px; overflow: hidden; text-overflow: ellipsis; text-align: right; ` const GridLines = styled.div` width: calc(100% - 64px); height: calc(100% - 24px); position: absolute; bottom: 19px; left: 48px; ` const GridLine = styled.div` position: absolute; bottom: ${props => props.bottom}%; height: 1px; width: 100%; background: white; ` const Bars = styled.div` position: absolute; display: flex; height: calc(100% - 6px); width: calc(100% - 64px); justify-content: space-around; left: 48px; bottom: 2px; ` const Bar = styled.div` display: flex; flex-direction: column; align-items: center; ` const StackedBars = styled.div` display: flex; flex-direction: column; height: 100%; justify-content: flex-end; ` const StackedBar = styled.div` width: 16px; height: ${props => props.height}%; background: ${props => props.background}; &:first-child { border-top-left-radius: 3px; border-top-right-radius: 3px; } ` const BarLabel = styled.div` font-size: 9px; font-weight: ${StyleProps.fontWeights.medium}; margin-top: 8px; ` type DataItem = { label: string, values: number[], data?: any, } type Props = { style?: any, // eslint-disable-next-line react/no-unused-prop-types data: DataItem[], // eslint-disable-next-line react/no-unused-prop-types yNumTicks: number, colors?: string[], onBarMouseEnter?: (position: { x: number, y: number }, item: DataItem) => void, onBarMouseLeave?: () => void, } @observer class DashboardBarChart extends React.Component { barsRef: HTMLElement | null | undefined ticks: { value: number }[] = [] range: number = 1 UNSAFE_componentWillMount() { this.calculateYTicks(this.props) } UNSAFE_componentWillReceiveProps(props: Props) { this.calculateYTicks(props) } calculateYTicks(props: Props) { this.range = props.data.reduce((max, item) => Math.max( max, item.values.reduce((sum, value) => sum + value, 0), ), 1) const niceScale = new BarChartNiceScale(0, this.range, props.yNumTicks) this.ticks = [] const numTicks = Math.floor(this.range / niceScale.tickSpacing) + 1 for (let i = 0; i < numTicks; i += 1) { this.ticks.push({ value: i * niceScale.tickSpacing }) } } calculatePosition(evt: MouseEvent): { x: number, y: number } { const targetMouse: any = evt.currentTarget const target: HTMLElement = targetMouse.parentElement let height = 0 target.childNodes.forEach(node => { const element: any = node height += element.offsetHeight }) return { x: target.offsetLeft + 48, y: height + 65, } } renderYAxis() { return ( {this.ticks.map(tick => ( {tick.value} ))} ) } renderGridLines() { const gridLines: { value: number }[] = [] this.ticks.forEach((tick, i) => { gridLines.push({ value: tick.value }) if (i === this.ticks.length - 1) { return } gridLines.push({ value: (this.ticks[i + 1].value + tick.value) / 2 }) }) return ( {gridLines.map(gridline => ( ))} ) } renderBars() { let availableWidth = window.innerWidth if (this.barsRef) { availableWidth = this.barsRef.offsetWidth } let items = this.props.data if ((30 * items.length) > availableWidth) { items = items.filter((_, i) => i % 2) } return ( { this.barsRef = ref }}> {items.map(item => ( {[...item.values].reverse().map((value, i) => { const height = (value / this.range) * 100 return height > 0 ? ( { const onMouseEnter = this.props.onBarMouseEnter if (!onMouseEnter) { return } onMouseEnter(this.calculatePosition(evt), item) }} onMouseLeave={() => { if (this.props.onBarMouseLeave) this.props.onBarMouseLeave() }} /> ) : null })} {item.label} ))} ) } render() { return ( {this.renderYAxis()} {this.renderGridLines()} {this.renderBars()} ) } } export default DashboardBarChart