/* eslint-disable @typescript-eslint/no-explicit-any */ import { FunctionComponent, useMemo, useState } from 'react' import dayjs from 'dayjs' import relativeTime from 'dayjs/plugin/relativeTime' import { AreaChart, Area, XAxis, YAxis, Tooltip as RechartsTooltip, ResponsiveContainer, ReferenceLine, BarChart, Bar, Cell, Label, } from 'recharts' import FlipNumbers from 'react-flip-numbers' import ContentBox from './ContentBox' import SheenLoader from './SheenLoader' import { COLORS } from '../../styles/colors' import { ArrowsRightLeftIcon, NoSymbolIcon } from '@heroicons/react/20/solid' import { FadeInFadeOut } from './Transitions' import ChartRangeButtons from './ChartRangeButtons' import Change from './Change' import useLocalStorageState from 'hooks/useLocalStorageState' import { ANIMATION_SETTINGS_KEY, DAILY_MILLISECONDS, PRIVACY_MODE, PRIVATE_MODE_STRING, } from 'utils/constants' import { formatNumericValue } from 'utils/numbers' import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings' import { AxisDomain } from 'recharts/types/util/types' import { useTranslation } from 'next-i18next' import FormatNumericValue from './FormatNumericValue' import { ContentType } from 'recharts/types/component/Tooltip' import Tooltip from './Tooltip' import useThemeWrapper from 'hooks/useThemeWrapper' dayjs.extend(relativeTime) const titleClasses = 'mb-0.5 text-base' interface DetailedAreaOrBarChartProps { changeAsPercent?: boolean chartType?: 'area' | 'bar' customTooltip?: ContentType data: any[] | undefined daysToShow?: string domain?: AxisDomain heightClass?: string hideChange?: boolean hideAxis?: boolean isPrivate?: boolean loaderHeightClass?: string loading?: boolean prefix?: string setDaysToShow?: (x: string) => void small?: boolean suffix?: string tickFormat?: (x: number) => string title?: string tooltipContent?: string xKey: string formatXKeyHeading?: (k: string | number) => string xAxisLabel?: string xAxisType?: 'number' | 'category' | undefined yDecimals?: number yKey: string showZeroLine?: boolean tooltipDateFormat?: string } export const formatDateAxis = (date: string, days: number) => { if (days === 1) { return dayjs(date).format('h:mma') } else { return dayjs(date).format('D MMM') } } const DetailedAreaOrBarChart: FunctionComponent< DetailedAreaOrBarChartProps > = ({ changeAsPercent, chartType = 'area', customTooltip, data, daysToShow = '1', domain, heightClass, hideChange, hideAxis, isPrivate, loaderHeightClass, loading, prefix = '', setDaysToShow, showZeroLine, small, suffix = '', tickFormat, title, tooltipContent, tooltipDateFormat, xKey, formatXKeyHeading, xAxisLabel, xAxisType, yDecimals, yKey, }) => { const { t } = useTranslation('common') const [mouseData, setMouseData] = useState(null) const [showChangePercentage, setShowChangePercentage] = useState( changeAsPercent || false, ) const { theme } = useThemeWrapper() const [animationSettings] = useLocalStorageState( ANIMATION_SETTINGS_KEY, INITIAL_ANIMATION_SETTINGS, ) const [privacyMode] = useLocalStorageState(PRIVACY_MODE) const handleMouseMove = (coords: any) => { if (coords.activePayload) { setMouseData(coords.activePayload[0].payload) } } const handleMouseLeave = () => { setMouseData(null) } const flipGradientCoords = useMemo(() => { if (!data || !data.length) return return data[0][yKey] <= 0 && data[data.length - 1][yKey] < 0 }, [data]) const filteredData = useMemo(() => { if (!data || !data.length) return [] if (xAxisType === 'number') return data const start = Number(daysToShow) * DAILY_MILLISECONDS const filtered = data.filter((d: any) => { const dataTime = new Date(d[xKey]).getTime() const now = new Date().getTime() const limit = now - start return dataTime >= limit }) return filtered }, [data, daysToShow, xAxisType]) const calculateChartChange = () => { if (filteredData.length) { let firstValue = filteredData[0][yKey] if (xAxisType === 'number') { const minValue = filteredData.reduce( (min, current) => (current[xKey] < min[xKey] ? current : min), filteredData[0], ) firstValue = minValue[yKey] } if (mouseData) { const index = filteredData.findIndex( (d: any) => d[xKey] === mouseData[xKey], ) const currentValue = filteredData[index]?.[yKey] const change = index >= 0 ? showChangePercentage ? ((currentValue - firstValue) / Math.abs(firstValue)) * 100 : currentValue - firstValue : 0 return isNaN(change) ? 0 : change } else { const currentValue = filteredData[filteredData.length - 1][yKey] return showChangePercentage ? ((currentValue - firstValue) / Math.abs(firstValue)) * 100 : currentValue - firstValue } } return 0 } return ( {loading ? (
) : (
{setDaysToShow ? (
setDaysToShow(v)} />
) : null} {title ? ( tooltipContent ? (

{title}

) : (

{title}

) ) : null} {filteredData.length ? ( <>
{mouseData ? (
{animationSettings['number-scroll'] ? ( isPrivate && privacyMode ? ( {PRIVATE_MODE_STRING} ) : ( ) ) : ( {mouseData[yKey] < 0 ? '-' : ''} {prefix} {suffix} )} {!hideChange ? (
{changeAsPercent ? ( ) : null}
) : null}

{formatXKeyHeading ? formatXKeyHeading(mouseData[xKey]) : dayjs(mouseData[xKey]).format( tooltipDateFormat ? tooltipDateFormat : 'DD MMM YY, h:mma', )}

) : (
{animationSettings['number-scroll'] ? ( isPrivate && privacyMode ? ( {PRIVATE_MODE_STRING} ) : ( ) ) : ( {filteredData[filteredData.length - 1][yKey] < 0 ? '-' : ''} {prefix} {suffix} )} {!hideChange ? (
{changeAsPercent ? ( ) : null}
) : null}

{formatXKeyHeading ? formatXKeyHeading( filteredData[filteredData.length - 1][xKey], ) : dayjs( filteredData[filteredData.length - 1][xKey], ).format( tooltipDateFormat ? tooltipDateFormat : 'DD MMM YY, h:mma', )}

)}
{chartType === 'area' ? ( } /> = 0 ? COLORS.UP[theme] : COLORS.DOWN[theme] } stopOpacity={0.15} /> = 0 ? COLORS.UP[theme] : COLORS.DOWN[theme] } stopOpacity={0} /> = 0 ? COLORS.UP[theme] : COLORS.DOWN[theme] } strokeWidth={1.5} fill={`url(#gradientArea-${title?.replace( /[^a-zA-Z]/g, '', )})`} /> formatDateAxis(d, parseInt(daysToShow)) : undefined } type={xAxisType} > {xAxisLabel ? ( { const difference = dataMax - dataMin if (difference < 0.01) { return [dataMin - 0.001, dataMax + 0.001] } else if (difference < 0.1) { return [dataMin - 0.01, dataMax + 0.01] } else if (difference < 1) { return [dataMin - 0.1, dataMax + 0.11] } else if (difference < 10) { return [dataMin - 1, dataMax + 1] } else { return [dataMin, dataMax] } } } padding={{ top: 20, bottom: 20 }} tick={{ fill: 'var(--fgd-4)', fontSize: 10, }} tickFormatter={ tickFormat ? (v) => tickFormat(v) : undefined } tickLine={false} /> {showZeroLine ? ( ) : null} ) : ( } /> {filteredData.map((entry, index) => { return ( 0 ? 'url(#greenGradientBar)' : 'url(#redGradientBar)' } /> ) })} formatDateAxis(v, parseInt(daysToShow)) } /> tickFormat(v) : undefined } type="number" /> )}
) : (

{t('no-data')}

)}
)} ) } export default DetailedAreaOrBarChart const ToggleChangeTypeButton = ({ changeType, setChangeType, }: { changeType: boolean setChangeType: (isPercent: boolean) => void }) => { return ( ) }