import React, { useMemo } from 'react'; import styled from 'styled-components'; import { AxisScaleOutput } from '@visx/axis'; import { Axis, LineSeries, XYChart, Tooltip, GlyphSeries } from '@visx/xychart'; import { curveMonotoneX } from '@visx/curve'; import { ScaleConfig, scaleOrdinal } from '@visx/scale'; import { TimeSeriesChart as TimeSeriesChartType, NumericDataPoint, NamedLine } from '../../../types.generated'; import { lineColors } from './lineColors'; import Legend from './Legend'; import { addInterval } from '../../shared/time/timeUtils'; import { formatNumber } from '../../shared/formatNumber'; type AxisConfig = { formatter: (tick: number) => string; }; type Props = { chartData: TimeSeriesChartType; width: number; height: number; hideLegend?: boolean; style?: { axisColor?: string; axisWidth?: number; lineColor?: string; lineWidth?: string; crossHairLineColor?: string; }; insertBlankPoints?: boolean; yScale?: ScaleConfig; yAxis?: AxisConfig; }; const StyledTooltip = styled(Tooltip)` font-family: inherit !important; font-weight: 400 !important; `; const MARGIN = { TOP: 40, RIGHT: 45, BOTTOM: 40, LEFT: 40, }; const accessors = { xAccessor: (d) => d.x, yAccessor: (d) => d.y, }; function insertBlankAt(ts: number, newLine: Array) { const dateString = new Date(ts).toISOString(); for (let i = 0; i < newLine.length; i++) { if (new Date(newLine[i].x).getTime() > ts) { newLine.splice(i, 0, { x: dateString, y: 0 }); return; } } newLine.push({ x: dateString, y: 0 }); } export function computeLines(chartData: TimeSeriesChartType, insertBlankPoints: boolean) { if (!insertBlankPoints) { return chartData.lines; } const startDate = new Date(Number(chartData.dateRange.start)); const endDate = new Date(Number(chartData.dateRange.end)); const returnLines: NamedLine[] = []; chartData.lines.forEach((line) => { const newLine = [...line.data]; for (let i = startDate; i <= endDate; i = addInterval(1, i, chartData.interval)) { const pointOverlap = line.data.filter((point) => { return Math.abs(new Date(point.x).getTime() - i.getTime()) === 0; }); if (pointOverlap.length === 0) { insertBlankAt(i.getTime(), newLine); } } returnLines.push({ name: line.name, data: newLine }); }); return returnLines; } export const TimeSeriesChart = ({ chartData, width, height, hideLegend, style, insertBlankPoints, yScale, yAxis, }: Props) => { const ordinalColorScale = scaleOrdinal({ domain: chartData.lines.map((data) => data.name), range: lineColors.slice(0, chartData.lines.length), }); const lines = useMemo(() => computeLines(chartData, insertBlankPoints || false), [chartData, insertBlankPoints]); return ( <> (yAxis?.formatter ? yAxis.formatter(tick) : formatNumber(tick))} tickLabelProps={{ fill: 'black', fontFamily: 'inherit', fontSize: 10 }} numTicks={3} /> {lines.map((line, i) => ( <> ({ x: new Date(point.x), y: point.y }))} stroke={(style && style.lineColor) || lineColors[i]} curve={curveMonotoneX} {...accessors} /> ({ x: new Date(point.x), y: point.y }))} {...accessors} /> ))} tooltipData?.nearestDatum && (
{new Date( Number(accessors.xAccessor(tooltipData.nearestDatum.datum)), ).toDateString()}
{accessors.yAccessor(tooltipData.nearestDatum.datum)}
) } />
{!hideLegend && } ); };