import { AxisBottom, AxisLeft } from "@visx/axis"; import { bottomTickLabelProps } from "@visx/axis/lib/axis/AxisBottom"; import { leftTickLabelProps } from "@visx/axis/lib/axis/AxisLeft"; import { localPoint } from "@visx/event"; import { GridColumns, GridRows } from "@visx/grid"; import { Group } from "@visx/group"; import { scaleBand, scaleLinear } from "@visx/scale"; import { LinePath } from "@visx/shape"; import { useTooltip, useTooltipInPortal } from "@visx/tooltip"; import React from "react"; import { Color } from "utils/Color"; import styles from "./LineGraph.module.css"; interface LineData { label: string; yValues: number[]; } interface PointData { x: string; y: number; } interface LineGraphData { xValues: string[]; lines: LineData[]; } interface LineGraphProps { data: LineGraphData; /** 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 tick labels, in pixels. Default is 16px. */ xTickLabelSize?: number; /** Font size of the value tick labels, in pixels. Default is 16px. */ yTickLabelSize?: number; /** Font size of the value that appears when hovering over a bar, in pixels. */ hoverLabelSize?: number; /** Label text for the category axis. */ xAxisLabel?: string; /** Font size of the label for the cateogry axis, in pixels. */ xAxisLabelSize?: number; /** Controls the distance between the category axis label and the category axis. */ xAxisLabelOffset?: number; /** Label text for the value axis. */ yAxisLabel?: string; /** Font size of the label for the value axis, in pixels. */ yAxisLabelSize?: number; /** Controls the distance between the value axis label and the value axis. */ yAxisLabelOffset?: number; } const DEFAULT_LABEL_SIZE = 16; export function LineGraph(props: LineGraphProps) { const { width, height, margin, data, className, xTickLabelSize = DEFAULT_LABEL_SIZE, yTickLabelSize = DEFAULT_LABEL_SIZE, hoverLabelSize, xAxisLabel, xAxisLabelSize = DEFAULT_LABEL_SIZE, xAxisLabelOffset = 0, yAxisLabel, yAxisLabelSize = DEFAULT_LABEL_SIZE, yAxisLabelOffset = 0, } = props; const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, showTooltip, hideTooltip, } = useTooltip(); // If you don't want to use a Portal, simply replace `TooltipInPortal` below with // `Tooltip` or `TooltipWithBounds` and remove `containerRef` const { containerRef, TooltipInPortal } = useTooltipInPortal({ // use TooltipWithBounds detectBounds: true, // when tooltip containers are scrolled, this will correctly update the Tooltip position scroll: true, }); const yMax = height - margin.top - margin.bottom; const xMax = width - margin.left - margin.right; const actualData = data.lines.map((line) => { return line.yValues.map((val, idx) => { return { x: data.xValues[idx], y: val }; }); }); // data accessors const getX = (d: PointData) => d.x; const getY = (d: PointData) => d.y; // scales const xScale = scaleBand({ range: [0, xMax], domain: data.xValues, }); const yScale = scaleLinear({ range: [0, yMax], nice: true, domain: [100, 0], }); return ( <> { return { ...bottomTickLabelProps(), className: styles.tickLabel, dy: "-0.25rem", fontSize: `${xTickLabelSize / 16}rem`, width: xScale.bandwidth(), }; }} /> { return { ...leftTickLabelProps(), className: styles.tickLabel, dx: "0.75rem", dy: "0.25rem", fontSize: `${yTickLabelSize / 16}rem`, }; }} /> {actualData.map((lineData, i) => { const even = i % 2 === 0; return ( { const eventSvgCoords = localPoint( // ownerSVGElement is given by visx docs but not recognized by typescript // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore e.target.ownerSVGElement as Element, e ); showTooltip({ tooltipData: data.lines[i].label, tooltipTop: eventSvgCoords.y, tooltipLeft: eventSvgCoords.x, }); }} onMouseOut={hideTooltip} data={lineData} className={styles.line} x={(d) => xScale(getX(d)) ?? 0} y={(d) => yScale(getY(d)) ?? 0} stroke={even ? Color.primaryAccent : Color.secondaryAccent} strokeWidth={3} strokeOpacity={2} /> ); })} {tooltipOpen && ( {tooltipData} )} ); }