ICTU/quality-time

View on GitHub
components/frontend/src/dashboard/StatusBarChart.js

Summary

Maintainability
A
1 hr
Test Coverage
import { number, object } from "prop-types"
import { VictoryBar, VictoryStack } from "victory"

import { STATUS_COLORS, STATUS_NAME, STATUSES } from "../metric/status"
import { labelPropType, stringsPropType } from "../sharedPropTypes"
import { pluralize, sum } from "../utils"

function nrMetricsLabel(nrMetrics) {
    return nrMetrics === 0 ? "No metrics" : nrMetrics + pluralize(" metric", nrMetrics)
}

export function StatusBarChart({ animate, colors, label, tooltip, summary, maxY, style, width, height }) {
    const nrMetrics = sum(summary[Object.keys(summary)[0]])
    const nrDates = Object.keys(summary).length
    // Calculate how many metrics this chart displays compared to the chart with the most metrics.
    // The ratio is used below to set the bar width so that bars in charts that represent fewer
    // metrics are smaller than bars in charts that represent more metrics. The calculatation is not
    // simply `nrMetrics / maxY` because that makes the bars in charts that represent just a few metrics
    // too small to see. By adding maxY to both the numerator and the denominator the smallest bars are
    // at least half as wide as the maximum width.
    const barRatio = maxY > 0 ? (nrMetrics + maxY) / (2 * maxY) : 1
    // Create a VictoryBar for each status
    const bars = STATUSES.map((status) => {
        const data = []
        Object.entries(summary).forEach(([date, count]) => {
            const dateString = new Date(date).toLocaleDateString()
            const y = count[STATUS_COLORS[status]]
            data.push({
                x: date,
                y: y,
                label: `${dateString}\n${STATUS_NAME[status]}: ${nrMetricsLabel(y)}`,
            })
        })
        return (
            <VictoryBar
                barRatio={barRatio}
                key={status}
                style={style}
                labels={() => null}
                labelComponent={tooltip}
                data={data}
                animate={animate}
            />
        )
    })
    // Reverse the order of the bars and the colors because apparently VictoryStack reverses the order (again)
    bars.reverse()
    colors.reverse()
    // Because the bars are wider if the chart is wider, horizontal padding needs to be relative to chart width
    const horizontalPadding = width / 8
    const verticalPadding = 10
    return nrMetrics === 0 ? (
        label
    ) : (
        <VictoryStack
            colorScale={colors}
            key={nrDates} // Make sure the stack is redrawn when the users changes the number of dates to display
            padding={{
                left: horizontalPadding,
                right: horizontalPadding,
                top: verticalPadding,
                bottom: verticalPadding,
            }}
            width={width}
            height={height}
            standalone={false}
        >
            {bars}
        </VictoryStack>
    )
}
StatusBarChart.propTypes = {
    animate: object,
    colors: stringsPropType,
    label: labelPropType,
    tooltip: object,
    summary: object,
    maxY: number,
    style: object,
    width: number,
    height: number,
}