Commit 60593df0 authored by cartcrom's avatar cartcrom Committed by GitHub

refactor: price chart organization (#7304)

* refactor: implement chart model type

* refactor: move PriceChart component into charts folder

* refactor: relocate pricechart test file

* lint

* fix: pr comments

* fix: use formatter hook in price chart file
parent aeef2c23
import { ScaleLinear, scaleLinear } from 'd3'
import { PricePoint } from 'graphql/data/util'
import { cleanPricePoints, getPriceBounds } from './utils'
export enum ChartErrorType {
NO_DATA_AVAILABLE,
NO_RECENT_VOLUME,
INVALID_CHART,
}
type ChartDimensions = {
width: number
height: number
marginTop: number
marginBottom: number
}
export type ErroredChartModel = { error: ChartErrorType; dimensions: ChartDimensions }
export type ChartModel = {
prices: PricePoint[]
startingPrice: PricePoint
endingPrice: PricePoint
lastValidPrice: PricePoint
blanks: PricePoint[][]
timeScale: ScaleLinear<number, number>
priceScale: ScaleLinear<number, number>
dimensions: ChartDimensions
error: undefined
}
type ChartModelArgs = { prices?: PricePoint[]; dimensions: ChartDimensions }
export function buildChartModel({ dimensions, prices }: ChartModelArgs): ChartModel | ErroredChartModel {
if (!prices) {
return { error: ChartErrorType.NO_DATA_AVAILABLE, dimensions }
}
const innerHeight = dimensions.height - dimensions.marginTop - dimensions.marginBottom
if (innerHeight < 0) {
return { error: ChartErrorType.INVALID_CHART, dimensions }
}
const { prices: fixedPrices, blanks, lastValidPrice } = cleanPricePoints(prices)
if (fixedPrices.length < 2 || !lastValidPrice) {
return { error: ChartErrorType.NO_RECENT_VOLUME, dimensions }
}
const startingPrice = prices[0]
const endingPrice = prices[prices.length - 1]
const { min, max } = getPriceBounds(prices)
// x-axis scale
const timeScale = scaleLinear().domain([startingPrice.timestamp, endingPrice.timestamp]).range([0, dimensions.width])
// y-axis scale
const priceScale = scaleLinear().domain([min, max]).range([innerHeight, 0])
return {
prices: fixedPrices,
startingPrice,
endingPrice,
lastValidPrice,
blanks,
timeScale,
priceScale,
dimensions,
error: undefined,
}
}
import { TimePeriod } from 'graphql/data/util' import { TimePeriod } from 'graphql/data/util'
import { render } from 'test-utils/render' import { render, screen } from 'test-utils/render'
import { PriceChart } from './PriceChart' import { PriceChart } from '.'
jest.mock('components/Charts/AnimatedInLineChart', () => ({ jest.mock('components/Charts/AnimatedInLineChart', () => ({
__esModule: true, __esModule: true,
...@@ -20,7 +20,7 @@ describe('PriceChart', () => { ...@@ -20,7 +20,7 @@ describe('PriceChart', () => {
})) }))
const { asFragment } = render( const { asFragment } = render(
<PriceChart prices={mockPrices} width={780} height={436} timePeriod={TimePeriod.HOUR} /> <PriceChart prices={mockPrices} width={780} height={392} timePeriod={TimePeriod.HOUR} />
) )
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
expect(asFragment().textContent).toContain('$1.00') expect(asFragment().textContent).toContain('$1.00')
...@@ -33,15 +33,42 @@ describe('PriceChart', () => { ...@@ -33,15 +33,42 @@ describe('PriceChart', () => {
})) }))
const { asFragment } = render( const { asFragment } = render(
<PriceChart prices={mockPrices} width={780} height={436} timePeriod={TimePeriod.HOUR} /> <PriceChart prices={mockPrices} width={780} height={392} timePeriod={TimePeriod.HOUR} />
) )
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
expect(asFragment().textContent).toContain('$1.00') expect(asFragment().textContent).toContain('$1.00')
expect(asFragment().textContent).toContain('0.00%') expect(asFragment().textContent).toContain('0.00%')
}) })
it('renders correctly with no prices filled', () => { it('renders correctly with empty price array', () => {
const { asFragment } = render(<PriceChart prices={[]} width={780} height={436} timePeriod={TimePeriod.HOUR} />) const { asFragment } = render(<PriceChart prices={[]} width={780} height={392} timePeriod={TimePeriod.HOUR} />)
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
expect(asFragment().textContent).toContain('Price Unavailable') expect(asFragment().textContent).toContain('Price Unavailable')
expect(asFragment().textContent).toContain('Missing price data due to recently low trading volume on Uniswap v3')
})
it('renders correctly with undefined prices', () => {
const { asFragment } = render(
<PriceChart prices={undefined} width={780} height={392} timePeriod={TimePeriod.HOUR} />
)
expect(asFragment()).toMatchSnapshot()
expect(asFragment().textContent).toContain('Price Unavailable')
expect(asFragment().textContent).toContain('Missing chart data')
})
it('renders stale UI', () => {
const { asFragment } = render(
<PriceChart
prices={[
{ value: 1, timestamp: 1694538836 },
{ value: 1, timestamp: 1694538840 },
{ value: 1, timestamp: 1694538844 },
{ value: 0, timestamp: 1694538900 },
]}
width={780}
height={392}
timePeriod={TimePeriod.HOUR}
/>
)
expect(asFragment()).toMatchSnapshot()
expect(asFragment().textContent).toContain('$1.00')
expect(screen.getByTestId('chart-stale-icon')).toBeInTheDocument()
}) })
}) })
This diff is collapsed.
...@@ -7,7 +7,7 @@ import { useAtomValue } from 'jotai/utils' ...@@ -7,7 +7,7 @@ import { useAtomValue } from 'jotai/utils'
import { pageTimePeriodAtom } from 'pages/TokenDetails' import { pageTimePeriodAtom } from 'pages/TokenDetails'
import { startTransition, Suspense, useMemo } from 'react' import { startTransition, Suspense, useMemo } from 'react'
import { PriceChart } from './PriceChart' import { PriceChart } from '../../Charts/PriceChart'
import TimePeriodSelector from './TimeSelector' import TimePeriodSelector from './TimeSelector'
function usePriceHistory(tokenPriceData: TokenPriceQuery): PricePoint[] | undefined { function usePriceHistory(tokenPriceData: TokenPriceQuery): PricePoint[] | undefined {
...@@ -60,7 +60,7 @@ function Chart({ ...@@ -60,7 +60,7 @@ function Chart({
return ( return (
<ChartContainer data-testid="chart-container"> <ChartContainer data-testid="chart-container">
<ParentSize> <ParentSize>
{({ width }) => <PriceChart prices={prices ?? null} width={width} height={436} timePeriod={timePeriod} />} {({ width }) => <PriceChart prices={prices} width={width} height={392} timePeriod={timePeriod} />}
</ParentSize> </ParentSize>
<TimePeriodSelector <TimePeriodSelector
currentTimePeriod={timePeriod} currentTimePeriod={timePeriod}
......
This diff is collapsed.
...@@ -2,12 +2,12 @@ import { SwapSkeleton } from 'components/swap/SwapSkeleton' ...@@ -2,12 +2,12 @@ import { SwapSkeleton } from 'components/swap/SwapSkeleton'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import styled, { useTheme } from 'styled-components' import styled, { useTheme } from 'styled-components'
import { ThemedText } from 'theme'
import { textFadeIn } from 'theme/styles' import { textFadeIn } from 'theme/styles'
import { LoadingBubble } from '../loading' import { LoadingBubble } from '../loading'
import { AboutContainer, AboutHeader } from './About' import { AboutContainer, AboutHeader } from './About'
import { BreadcrumbNavLink } from './BreadcrumbNavLink' import { BreadcrumbNavLink } from './BreadcrumbNavLink'
import { TokenPrice } from './PriceChart'
import { StatPair, StatsWrapper, StatWrapper } from './StatsSection' import { StatPair, StatsWrapper, StatWrapper } from './StatsSection'
const SWAP_COMPONENT_WIDTH = 360 const SWAP_COMPONENT_WIDTH = 360
...@@ -168,9 +168,9 @@ function Wave() { ...@@ -168,9 +168,9 @@ function Wave() {
export function LoadingChart() { export function LoadingChart() {
return ( return (
<ChartContainer> <ChartContainer>
<TokenPrice> <ThemedText.HeadlineLarge>
<PriceBubble /> <PriceBubble />
</TokenPrice> </ThemedText.HeadlineLarge>
<Space heightSize={6} /> <Space heightSize={6} />
<LoadingChartContainer> <LoadingChartContainer>
<div> <div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment