import { AxisBottom, AxisLeft } from "@visx/axis"; import { bottomTickLabelProps } from "@visx/axis/lib/axis/AxisBottom"; import { leftTickLabelProps } from "@visx/axis/lib/axis/AxisLeft"; import { GridColumns, GridRows } from "@visx/grid"; import { Group } from "@visx/group"; import { scaleBand, scaleLinear } from "@visx/scale"; import { Bar } from "@visx/shape"; import { Text } from "@visx/text"; import React from "react"; import { Color } from "utils/Color"; import styles from "./BarGraph.module.css"; interface BarGraphProps { data: BarGraphData[]; /** Width of the entire graph, in pixels. */ width: number; /** Height of the entire graph, in pixels. */ height: number; /** Distance between the edge of the graph and the area where the bars are drawn, in pixels. */ margin: { top: number; bottom: number; left: number; right: number; }; className?: string; /** Font size of the category axis tick labels, in pixels. Default is 16px. */ categoryTickLabelSize?: number; /** Font size of the value axis tick labels, in pixels. Default is 16px. */ valueTickLabelSize?: number; /** Font size of the value that appears when hovering over a bar, in pixels. */ hoverLabelSize?: number; /** Label text for the category axis. */ categoryAxisLabel?: string; /** Font size of the label for the cateogry axis, in pixels. */ categoryAxisLabelSize?: number; /** Controls the distance between the category axis label and the category axis. */ categoryAxisLabelOffset?: number; /** Label text for the value axis. */ valueAxisLabel?: string; /** Font size of the label for the value axis, in pixels. */ valueAxisLabelSize?: number; /** Controls the distance between the value axis label and the value axis. */ valueAxisLabelOffset?: number; /** Minimum width of the graph. */ minWidth?: number; /** Breakpoint width of graph where alernating labels are displayed. Only for Vertical graphs */ widthAlternatingLabel?: number; /** Space added to the bottom of the graph to show overflowing labels. Only for Vertical graphs */ alternatingLabelSpace?: number; /** Default position of labels in x-axis, in px. */ defaultLabelDy?: string; /** Position of lower labels in x-axis, in px. Only for Vertical graphs */ lowerLabelDy?: string; } interface BarGraphData { category: string; value: number; } const DEFAULT_LABEL_SIZE = 16; export function BarGraphHorizontal(props: BarGraphProps) { const { height, margin, data, className, minWidth = 500, categoryTickLabelSize = DEFAULT_LABEL_SIZE, valueTickLabelSize = DEFAULT_LABEL_SIZE, hoverLabelSize, categoryAxisLabel, categoryAxisLabelSize = DEFAULT_LABEL_SIZE, categoryAxisLabelOffset = 0, valueAxisLabel, valueAxisLabelSize = DEFAULT_LABEL_SIZE, valueAxisLabelOffset = 0, defaultLabelDy = "0", } = props; const width = props.width < minWidth ? minWidth : props.width; // Ensuring graph's width >= minWidth const barPadding = 0.4; const categoryMax = height - margin.top - margin.bottom; const valueMax = width - margin.left - margin.right; const getCategory = (d: BarGraphData) => d.category; const getValue = (d: BarGraphData) => d.value; const categoryScale = scaleBand({ range: [0, categoryMax], domain: data.map(getCategory), padding: barPadding, }); const valueScale = scaleLinear({ range: [0, valueMax], nice: true, domain: [0, Math.max(...data.map(getValue))], }); const categoryPoint = (d: BarGraphData) => categoryScale(getCategory(d)); const valuePoint = (d: BarGraphData) => valueScale(getValue(d)); return ( {data.map((d, idx) => { const barName = `${getCategory(d)}-${idx}`; const barWidth = categoryScale.bandwidth(); const backgroundBarWidth = barWidth / (1 - barPadding); return idx % 2 === 0 ? ( ) : null; })} {data.map((d, idx) => { const barName = `${getCategory(d)}-${idx}`; const barLength = valuePoint(d); const barWidth = categoryScale.bandwidth(); return ( {getValue(d)} ); })} { return { ...leftTickLabelProps(), className: styles.tickLabel, fontSize: `${categoryTickLabelSize / 16}rem`, }; }} label={categoryAxisLabel} labelClassName={styles.axisLabel} labelOffset={categoryAxisLabelOffset} labelProps={{ fontSize: `${categoryAxisLabelSize / 16}rem`, }} /> { return { ...bottomTickLabelProps(), className: styles.tickLabel, dy: defaultLabelDy, fontSize: `${valueTickLabelSize / 16}rem`, }; }} label={valueAxisLabel} labelClassName={styles.axisLabel} labelOffset={valueAxisLabelOffset} labelProps={{ fontSize: `${valueAxisLabelSize / 16}rem`, }} /> ); } export function BarGraphVertical(props: BarGraphProps) { const { height, margin, data, className, minWidth = 500, categoryTickLabelSize = DEFAULT_LABEL_SIZE, valueTickLabelSize = DEFAULT_LABEL_SIZE, hoverLabelSize, categoryAxisLabel, categoryAxisLabelSize = DEFAULT_LABEL_SIZE, categoryAxisLabelOffset = 0, valueAxisLabel, valueAxisLabelSize = DEFAULT_LABEL_SIZE, valueAxisLabelOffset = 0, widthAlternatingLabel = 600, alternatingLabelSpace = 80, defaultLabelDy = `0px`, lowerLabelDy = `30px`, } = props; const width = props.width < minWidth ? minWidth : props.width; // Ensuring graph's width >= minWidth const barPadding = 0.4; const alternatingLabel = width <= widthAlternatingLabel; const final_margin_bottom = alternatingLabel ? margin.bottom + alternatingLabelSpace : margin.bottom; const categoryMax = width - margin.left - margin.right; const valueMax = height - margin.top - final_margin_bottom; const getCategory = (d: BarGraphData) => d.category; const getValue = (d: BarGraphData) => d.value; const categoryScale = scaleBand({ range: [0, categoryMax], domain: data.map(getCategory), padding: barPadding, }); const valueScale = scaleLinear({ range: [valueMax, 0], nice: true, domain: [0, Math.max(...data.map(getValue))], }); const categoryPoint = (d: BarGraphData) => categoryScale(getCategory(d)); const valuePoint = (d: BarGraphData) => valueScale(getValue(d)); return ( {data.map((d, idx) => { const barName = `${getCategory(d)}-${idx}`; const barWidth = categoryScale.bandwidth(); const backgroundBarWidth = barWidth / (1 - barPadding); return idx % 2 === 0 ? ( ) : null; })} {data.map((d, idx) => { const barName = `${getCategory(d)}-${idx}`; const barHeight = valueMax - valuePoint(d); const barWidth = categoryScale.bandwidth(); return ( {getValue(d)} ); })} { const alternatingDy = index % 2 == 0 ? defaultLabelDy : lowerLabelDy; return { ...bottomTickLabelProps(), className: styles.tickLabel, dy: alternatingLabel ? alternatingDy : defaultLabelDy, fontSize: `${categoryTickLabelSize / 16}rem`, width: categoryScale.bandwidth(), verticalAnchor: "start", }; }} label={categoryAxisLabel} labelClassName={styles.axisLabel} labelOffset={categoryAxisLabelOffset} labelProps={{ fontSize: `${categoryAxisLabelSize / 16}rem`, }} /> { return { ...leftTickLabelProps(), className: styles.tickLabel, dx: "-0.5rem", dy: "0.25rem", fontSize: `${valueTickLabelSize / 16}rem`, }; }} label={valueAxisLabel} labelClassName={styles.axisLabel} labelOffset={valueAxisLabelOffset} labelProps={{ fontSize: `${valueAxisLabelSize / 16}rem`, }} /> ); }