import React, {
MouseEventHandler,
useCallback,
useEffect,
useMemo,
useState,
} from 'react'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import {
AreaChart,
Area,
XAxis,
YAxis,
Tooltip as RechartsTooltip,
ResponsiveContainer,
Text,
ReferenceDot,
ReferenceDotProps,
} from 'recharts'
import FlipNumbers from 'react-flip-numbers'
import ContentBox from '../shared/ContentBox'
import { formatCurrencyValue, formatNumericValue } from '../../utils/numbers'
import SheenLoader from '../shared/SheenLoader'
import { COLORS } from '../../styles/colors'
import Change from '../shared/Change'
import ChartRangeButtons from '../shared/ChartRangeButtons'
import { useViewport } from 'hooks/useViewport'
import { formatTokenSymbol } from 'utils/tokens'
import { useQuery } from '@tanstack/react-query'
import { ChartDataItem, fetchChartData } from 'apis/coingecko'
import mangoStore from '@store/mangoStore'
import useJupiterSwapData from './useJupiterSwapData'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { ANIMATION_SETTINGS_KEY } from 'utils/constants'
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
import { useTranslation } from 'next-i18next'
import {
ArrowsRightLeftIcon,
EyeIcon,
EyeSlashIcon,
NoSymbolIcon,
} from '@heroicons/react/20/solid'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import { CategoricalChartFunc } from 'recharts/types/chart/generateCategoricalChart'
import { interpolateNumber } from 'd3-interpolate'
import { IconButton } from '@components/shared/Button'
import Tooltip from '@components/shared/Tooltip'
import { SwapHistoryItem } from 'types'
import useThemeWrapper from 'hooks/useThemeWrapper'
import FavoriteSwapButton from './FavoriteSwapButton'
dayjs.extend(relativeTime)
const set = mangoStore.getState().set
const CustomizedLabel = ({
chartData,
x,
y,
value,
index,
}: {
chartData: ChartDataItem[]
x?: number
y?: string | number
value?: number
index?: number
}) => {
const { width } = useViewport()
const [min, max] = useMemo(() => {
if (chartData.length) {
const prices = chartData.map((d) => d.price)
return [Math.min(...prices), Math.max(...prices)]
}
return ['', '']
}, [chartData])
const [minIndex, maxIndex] = useMemo(() => {
const minIndex = chartData.findIndex((d) => d.price === min)
const maxIndex = chartData.findIndex((d) => d.price === max)
return [minIndex, maxIndex]
}, [min, max, chartData])
if (value && (minIndex === index || maxIndex === index)) {
return (
{`${t( side, )} ${amount} ${symbol} at ${formatNumericValue( price, )} ${priceSymbol} for ${formatCurrencyValue(value)}`}
{coingeckoPercentageDifference ? ({coingeckoPercentageDifference.toFixed(2)}% {' '} {betterThanCoingecko ? 'better than' : 'worse than'} Coingecko
) : null} > ) }, [flipPrices, swapMarketName, swapTooltipCoingeckoPrice], ) const { data: coingeckoData, isLoading, isFetching, } = useQuery( ['swap-chart-data', baseTokenId, quoteTokenId, daysToShow, flipPrices], () => fetchChartData( baseTokenId, inputBank, quoteTokenId, outputBank, daysToShow, flipPrices, ), { cacheTime: 1000 * 60 * 15, staleTime: 1000 * 60 * 1, enabled: !!(baseTokenId && quoteTokenId), refetchOnWindowFocus: false, }, ) const chartSwapTimes = useMemo(() => { if ( loadSwapHistory || !swapHistory || !swapHistory.length || !inputBankName || !outputBankName ) return [] const chartSymbols = [ inputBankName === 'ETH (Portal)' ? 'ETH' : inputBankName, outputBankName === 'ETH (Portal)' ? 'ETH' : outputBankName, ] return swapHistory .filter( (swap) => chartSymbols.includes(swap.swap_in_symbol) && chartSymbols.includes(swap.swap_out_symbol), ) .map((val) => dayjs(val.block_datetime).unix() * 1000) }, [swapHistory, loadSwapHistory, inputBankName, outputBankName]) const swapHistoryPoints = useMemo(() => { if (!coingeckoData || !coingeckoData.length || !chartSwapTimes.length) return [] return chartSwapTimes.map((x) => { const makeChartDataItem = { inputTokenPrice: 1, outputTokenPrice: 1 } const index = coingeckoData.findIndex((d) => d.time > x) // find index of data point with x value greater than highlight x if (index === 0) { return { time: x, price: coingeckoData[0].price, ...makeChartDataItem } // return first data point y value if highlight x is less than first data point x } else if (index === -1) { return { time: x, price: coingeckoData[coingeckoData.length - 1].price, ...makeChartDataItem, } // return last data point y value if highlight x is greater than last data point x } else { const x0 = coingeckoData[index - 1].time const x1 = coingeckoData[index].time const y0 = coingeckoData[index - 1].price const y1 = coingeckoData[index].price const interpolateY = interpolateNumber(y0, y1) // create interpolate function for y values const y = interpolateY((x - x0) / (x1 - x0)) // estimate y value at highlight x using interpolate function return { time: x, price: y, ...makeChartDataItem } } }) }, [coingeckoData, chartSwapTimes]) const chartData = useMemo(() => { if (!coingeckoData || !coingeckoData.length || coingeckoData.length < 2) return [] const minTime = coingeckoData[0].time const maxTime = coingeckoData[coingeckoData.length - 1].time if (swapHistoryPoints.length && showSwaps) { const swapPoints = swapHistoryPoints.filter( (point) => point.time >= minTime && point.time <= maxTime, ) return coingeckoData.concat(swapPoints).sort((a, b) => a.time - b.time) } else return coingeckoData }, [coingeckoData, swapHistoryPoints, showSwaps]) const handleMouseMove: CategoricalChartFunc = useCallback( (coords) => { if (coords.activePayload) { setMouseData(coords.activePayload[0].payload) } }, [setMouseData], ) const handleMouseLeave = useCallback(() => { setMouseData(undefined) }, [setMouseData]) useEffect(() => { if (!inputCoingeckoId || !outputCoingeckoId) return setBaseTokenId(inputCoingeckoId) setQuoteTokenId(outputCoingeckoId) }, [inputCoingeckoId, outputCoingeckoId]) const calculateChartChange = useCallback(() => { if (!chartData?.length) return 0 if (mouseData) { const index = chartData.findIndex((d) => d.time === mouseData.time) if (index === -1) return 0 return ( ((chartData[index]['price'] - chartData[0]['price']) / chartData[0]['price']) * 100 ) } else { return ( ((chartData[chartData.length - 1]['price'] - chartData[0]['price']) / chartData[0]['price']) * 100 ) } }, [chartData, mouseData]) return ({swapMarketName}
{dayjs(mouseData.time).format('DD MMM YY, h:mma')}
> ) : ( <>{dayjs(chartData[chartData.length - 1].time).format( 'DD MMM YY, h:mma', )}
> )}{t('chart-unavailable')}