import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { AreaClosed, Line, Bar } from '@visx/shape'; import { LinearGradient } from '@visx/gradient'; import { max, bisector, extent } from 'd3-array'; import { scaleLinear } from '@visx/scale'; import { useTooltip, Tooltip } from '@visx/tooltip'; import PropTypes from 'prop-types'; import { clsm } from '../../../../../../utils'; import { getDate, getDataValue, getXScale, getYScale } from '../utils'; import { useStreams } from '../../../../../../contexts/Streams'; import { useSynchronizedCharts } from '../../../../../../contexts/SynchronizedCharts'; import usePrevious from '../../../../../../hooks/usePrevious'; const bisectDate = bisector(getDate).left; const Chart = ({ eventMarkers, formatter, height, initialData, width, zoomBounds }) => { const { activeStreamSession, hasActiveStreamChanged } = useStreams(); const { isLive } = activeStreamSession || {}; const hasLiveIndicator = isLive && zoomBounds[1] === initialData.length - 1; const { hideTooltip, showTooltip, tooltipData, tooltipLeft = 0, tooltipOpen, tooltipTop = 0 } = useTooltip(); const { handleSynchronizedTooltips, hideSynchronizedTooltips, onPointerDown, onPointerMove, onPointerUp, originX, showSynchronizedTooltips, xValue: x, zoomAreaDx } = useSynchronizedCharts(); const tooltipRef = useRef(); const [hasTooltipRendered, setHasTooltipRendered] = useState(false); const [isTooltipReady, setIsTooltipReady] = useState(false); const [transformedData, setTransformedData] = useState(initialData); const prevZoomBounds = usePrevious( // Reset the zoom bounds when we change the active stream hasActiveStreamChanged ? undefined : zoomBounds ); const xScale = useMemo( () => getXScale(width, transformedData), [width, transformedData] ); const yScale = useMemo( () => getYScale(height, max(transformedData, getDataValue)), [transformedData, height] ); const lastPoint = transformedData[transformedData.length - 1] || 0; const lastPointCoords = { x: xScale(getDate(lastPoint)) - 6, y: yScale(getDataValue(lastPoint)) - 5 }; const draggableChartRef = useRef(); // Update the transformed data when the zoom bounds have been updated useEffect(() => { const [lowerBound, upperBound] = zoomBounds; const [prevLowerBound, prevUpperBound] = prevZoomBounds || []; if (prevLowerBound !== lowerBound || prevUpperBound !== upperBound) { setTransformedData(initialData.slice(lowerBound, upperBound + 1)); } }, [initialData, prevZoomBounds, zoomBounds]); /** * Tooltip logic START */ const clearTooltip = useCallback(() => { setIsTooltipReady(false); setHasTooltipRendered(false); hideTooltip(); }, [hideTooltip]); // This is triggered when the value of x changes, on hover useEffect(() => { if (typeof x !== 'number') { clearTooltip(); return; } const dateToPixels = scaleLinear({ range: [0, width], domain: extent(transformedData, getDate), nice: false }); const x0 = xScale.invert(x); const index = bisectDate(transformedData, x0, 1); const d0 = transformedData[index - 1]; const d1 = transformedData[index]; if (!d0 || !d1) { clearTooltip(); return; } const { timestamp: t0, value: v0 } = d0; const { timestamp: t1, value: v1 } = d1; const t0px = dateToPixels(t0); const t1px = dateToPixels(t1); const v0px = yScale(v0); const v1px = yScale(v1); const slope = (v1px - v0px) / (t1px - t0px); const yInt = v0px - slope * t0px; const y = slope * x + yInt; let d = d0; if (d1 && getDate(d1)) { d = x0.valueOf() - getDate(d0).valueOf() > getDate(d1).valueOf() - x0.valueOf() ? d1 : d0; } showTooltip({ tooltipData: formatter({ timestamp: x0, value: d.value }), tooltipLeft: x, tooltipTop: y }); }, [ clearTooltip, formatter, showTooltip, transformedData, width, x, xScale, yScale ]); // When the tooltip should be open, render it but keep it hidden. // We first need to get it's width and height to position it correctly. useEffect(() => { setHasTooltipRendered(tooltipOpen); }, [tooltipOpen]); // Once the tooltip has been rendered and positioned, it is ready to become visible useEffect(() => { setIsTooltipReady(hasTooltipRendered); }, [hasTooltipRendered]); /** * Tooltip logic END */ useEffect(() => { const augmentedOnPointerUp = (_event) => onPointerUp(_event, draggableChartRef); document.addEventListener('pointerup', augmentedOnPointerUp); return () => document.removeEventListener('pointerup', augmentedOnPointerUp); }, [onPointerUp]); if (width < 10 || !initialData?.length) return null; return (
{tooltipData && getDate(tooltipData)}