mango-v4-ui/components/shared/DetailedAreaChart.tsx

351 lines
12 KiB
TypeScript
Raw Normal View History

2022-10-23 18:43:07 -07:00
import { FunctionComponent, useMemo, useState } from 'react'
2022-08-03 02:49:31 -07:00
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import {
AreaChart,
Area,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
} from 'recharts'
import FlipNumbers from 'react-flip-numbers'
import ContentBox from '../shared/ContentBox'
import SheenLoader from '../shared/SheenLoader'
import { COLORS } from '../../styles/colors'
import { useTheme } from 'next-themes'
import { IconButton } from './Button'
2022-12-11 15:21:40 -08:00
import { ArrowLeftIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
2022-08-03 02:49:31 -07:00
import { FadeInFadeOut } from './Transitions'
2022-09-07 23:25:32 -07:00
import ChartRangeButtons from './ChartRangeButtons'
2022-10-05 22:11:28 -07:00
import Change from './Change'
2022-11-23 18:30:20 -08:00
import useLocalStorageState from 'hooks/useLocalStorageState'
import { ANIMATION_SETTINGS_KEY } from 'utils/constants'
import { formatFixedDecimals } from 'utils/numbers'
2022-11-24 18:39:14 -08:00
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
2022-12-06 03:58:22 -08:00
import { AxisDomain } from 'recharts/types/util/types'
2022-12-11 15:21:40 -08:00
import { useTranslation } from 'next-i18next'
2022-08-03 02:49:31 -07:00
dayjs.extend(relativeTime)
interface DetailedAreaChartProps {
data: any[]
2022-12-05 19:23:22 -08:00
daysToShow?: string
2022-12-06 03:58:22 -08:00
domain?: AxisDomain
2022-12-13 17:25:30 -08:00
heightClass?: string
2022-08-13 03:44:13 -07:00
hideChange?: boolean
2022-08-03 02:49:31 -07:00
hideChart?: () => void
loading?: boolean
2022-12-06 03:58:22 -08:00
prefix?: string
2022-12-05 19:23:22 -08:00
setDaysToShow?: (x: string) => void
2022-12-06 03:58:22 -08:00
small?: boolean
suffix?: string
tickFormat?: (x: number) => string
2022-08-03 02:49:31 -07:00
title?: string
xKey: string
yKey: string
}
export const formatDateAxis = (date: string, days: number) => {
2022-10-21 05:54:18 -07:00
if (days === 1) {
return dayjs(date).format('h:mma')
} else {
return dayjs(date).format('D MMM')
}
}
2022-08-03 02:49:31 -07:00
const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
data,
2022-11-29 21:30:18 -08:00
daysToShow = '1',
2022-12-06 03:58:22 -08:00
domain,
2022-12-13 17:25:30 -08:00
heightClass,
2022-08-13 03:44:13 -07:00
hideChange,
2022-08-03 02:49:31 -07:00
hideChart,
loading,
2022-12-06 03:58:22 -08:00
prefix = '',
2022-08-10 21:09:58 -07:00
setDaysToShow,
2022-12-06 03:58:22 -08:00
small,
suffix = '',
2022-08-10 21:09:58 -07:00
tickFormat,
2022-08-03 02:49:31 -07:00
title,
xKey,
yKey,
}) => {
2022-12-11 15:21:40 -08:00
const { t } = useTranslation('common')
2022-08-03 02:49:31 -07:00
const [mouseData, setMouseData] = useState<any>(null)
const { theme } = useTheme()
2022-11-23 18:30:20 -08:00
const [animationSettings] = useLocalStorageState(
ANIMATION_SETTINGS_KEY,
INITIAL_ANIMATION_SETTINGS
)
2022-08-03 02:49:31 -07:00
const handleMouseMove = (coords: any) => {
if (coords.activePayload) {
setMouseData(coords.activePayload[0].payload)
}
}
const handleMouseLeave = () => {
setMouseData(null)
}
const calculateChartChange = () => {
if (data.length) {
if (mouseData) {
const index = data.findIndex((d: any) => d[xKey] === mouseData[xKey])
2022-12-05 19:23:22 -08:00
const change = index >= 0 ? data[index][yKey] - data[0][yKey] : 0
2022-08-10 21:09:58 -07:00
return isNaN(change) ? 0 : change
2022-11-20 03:23:23 -08:00
} else return data[data.length - 1][yKey] - data[0][yKey]
2022-08-03 02:49:31 -07:00
}
return 0
}
2022-12-11 02:08:50 -08:00
const flipGradientCoords = useMemo(() => {
if (!data.length) return
return data[0][yKey] <= 0 && data[data.length - 1][yKey] < data[0][yKey]
}, [data])
2022-08-03 02:49:31 -07:00
return (
<FadeInFadeOut show={true}>
<ContentBox hideBorder hidePadding>
{loading ? (
2022-09-08 18:00:47 -07:00
<SheenLoader className="flex flex-1">
2022-12-13 17:25:30 -08:00
<div
className={`${
heightClass ? heightClass : 'h-96'
} w-full rounded-lg bg-th-bkg-2`}
/>
2022-08-03 02:49:31 -07:00
</SheenLoader>
) : data.length ? (
<div className="relative">
<div className="flex items-start justify-between">
<div className="flex flex-col md:flex-row md:items-start md:space-x-6">
2022-12-05 19:23:22 -08:00
{hideChart ? (
<IconButton className="mb-6" onClick={hideChart}>
<ArrowLeftIcon className="h-5 w-5" />
</IconButton>
) : null}
2022-08-03 02:49:31 -07:00
<div>
2022-12-06 03:58:22 -08:00
<p
className={`${
small ? 'text-sm' : 'mb-0.5 text-base'
} text-th-fgd-3`}
>
{title}
</p>
2022-08-03 02:49:31 -07:00
{mouseData ? (
<div>
2022-12-06 03:58:22 -08:00
<div
className={`flex ${
small
? 'h-8 items-center text-2xl'
: 'mb-1 items-end text-4xl'
2022-12-13 02:49:56 -08:00
} font-display text-th-fgd-1`}
2022-12-06 03:58:22 -08:00
>
2022-11-23 18:30:20 -08:00
{animationSettings['number-scroll'] ? (
<FlipNumbers
2022-12-06 14:52:36 -08:00
height={small ? 24 : 40}
2022-12-13 15:16:50 -08:00
width={small ? 17 : 30}
2022-11-23 18:30:20 -08:00
play
2022-12-06 03:58:22 -08:00
numbers={
prefix +
2022-12-07 15:19:18 -08:00
formatFixedDecimals(mouseData[yKey] ?? 0) +
2022-12-06 03:58:22 -08:00
suffix
}
2022-11-23 18:30:20 -08:00
/>
) : (
<span>
2022-12-06 03:58:22 -08:00
{prefix +
2022-12-07 15:19:18 -08:00
formatFixedDecimals(mouseData[yKey] ?? 0) +
2022-12-06 03:58:22 -08:00
suffix}
2022-11-23 18:30:20 -08:00
</span>
)}
2022-08-13 03:44:13 -07:00
{!hideChange ? (
2022-09-08 18:00:47 -07:00
<span className="ml-3">
2022-11-20 03:23:23 -08:00
<Change
change={calculateChartChange()}
2022-12-06 03:58:22 -08:00
prefix={prefix}
suffix={suffix}
2022-11-20 03:23:23 -08:00
/>
2022-08-03 02:49:31 -07:00
</span>
2022-08-13 03:44:13 -07:00
) : null}
2022-08-03 02:49:31 -07:00
</div>
2022-12-06 03:58:22 -08:00
<p
className={`${
small ? 'text-xs' : 'text-sm'
} text-th-fgd-4`}
>
2022-08-03 02:49:31 -07:00
{dayjs(mouseData[xKey]).format('DD MMM YY, h:mma')}
</p>
</div>
) : (
<div>
2022-12-06 03:58:22 -08:00
<div
className={`flex ${
small
? 'h-8 items-center text-2xl'
: 'mb-1 items-end text-4xl'
2022-12-13 02:49:56 -08:00
} font-display text-th-fgd-1`}
2022-12-06 03:58:22 -08:00
>
2022-11-23 18:30:20 -08:00
{animationSettings['number-scroll'] ? (
<FlipNumbers
2022-12-06 14:52:36 -08:00
height={small ? 24 : 40}
2022-12-13 15:16:50 -08:00
width={small ? 17 : 30}
2022-11-23 18:30:20 -08:00
play
2022-12-06 03:58:22 -08:00
numbers={
prefix +
formatFixedDecimals(data[data.length - 1][yKey]) +
suffix
}
2022-11-23 18:30:20 -08:00
/>
) : (
<span>
2022-12-06 03:58:22 -08:00
{prefix +
formatFixedDecimals(data[data.length - 1][yKey]) +
suffix}
2022-11-23 18:30:20 -08:00
</span>
)}
2022-08-13 03:44:13 -07:00
{!hideChange ? (
2022-09-08 18:00:47 -07:00
<span className="ml-3">
2022-11-20 03:23:23 -08:00
<Change
change={calculateChartChange()}
2022-12-06 03:58:22 -08:00
prefix={prefix}
suffix={suffix}
2022-11-20 03:23:23 -08:00
/>
2022-08-03 02:49:31 -07:00
</span>
2022-08-13 03:44:13 -07:00
) : null}
2022-08-03 02:49:31 -07:00
</div>
2022-12-06 03:58:22 -08:00
<p
className={`${
small ? 'text-xs' : 'text-sm'
} text-th-fgd-4`}
>
2022-08-03 02:49:31 -07:00
{dayjs(data[data.length - 1][xKey]).format(
'DD MMM YY, h:mma'
)}
</p>
</div>
)}
</div>
</div>
</div>
2022-12-13 17:25:30 -08:00
<div
className={`-mt-1 ${heightClass ? heightClass : 'h-96'} w-auto`}
>
2022-12-05 19:23:22 -08:00
{setDaysToShow ? (
<div className="absolute -top-1 right-0 -mb-2 flex justify-end">
<ChartRangeButtons
activeValue={daysToShow}
names={['24H', '7D', '30D']}
values={['1', '7', '30']}
onChange={(v) => setDaysToShow(v)}
/>
</div>
) : null}
2022-08-03 02:49:31 -07:00
<div className="-mx-6 mt-6 h-full">
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={data}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
<Tooltip
cursor={{
strokeOpacity: 0.09,
}}
content={<></>}
/>
<defs>
<linearGradient
2022-12-13 19:02:23 -08:00
id={`gradientArea-${title?.replace(/\s/g, '')}`}
2022-08-03 02:49:31 -07:00
x1="0"
y1={flipGradientCoords ? '1' : '0'}
2022-08-03 02:49:31 -07:00
x2="0"
y2={flipGradientCoords ? '0' : '1'}
2022-08-03 02:49:31 -07:00
>
<stop
offset="0%"
stopColor={
calculateChartChange() >= 0
2022-11-30 19:32:32 -08:00
? COLORS.UP[theme]
: COLORS.DOWN[theme]
2022-08-03 02:49:31 -07:00
}
stopOpacity={0.15}
/>
<stop
offset="99%"
stopColor={
calculateChartChange() >= 0
2022-11-30 19:32:32 -08:00
? COLORS.UP[theme]
: COLORS.DOWN[theme]
2022-08-03 02:49:31 -07:00
}
stopOpacity={0}
/>
</linearGradient>
</defs>
<Area
isAnimationActive={false}
type="monotone"
dataKey={yKey}
stroke={
calculateChartChange() >= 0
2022-11-30 19:32:32 -08:00
? COLORS.UP[theme]
: COLORS.DOWN[theme]
2022-08-03 02:49:31 -07:00
}
strokeWidth={1.5}
2022-12-13 19:02:23 -08:00
fill={`url(#gradientArea-${title?.replace(/\s/g, '')})`}
2022-08-03 02:49:31 -07:00
/>
<XAxis
axisLine={false}
dataKey={xKey}
2022-12-06 03:58:22 -08:00
minTickGap={20}
2022-08-03 02:49:31 -07:00
padding={{ left: 20, right: 20 }}
tick={{
fill: 'var(--fgd-4)',
2022-08-03 02:49:31 -07:00
fontSize: 10,
}}
tickLine={false}
2022-11-29 21:30:18 -08:00
tickFormatter={(d) =>
formatDateAxis(d, parseInt(daysToShow))
}
2022-08-03 02:49:31 -07:00
/>
<YAxis
axisLine={false}
dataKey={yKey}
2022-12-06 03:58:22 -08:00
minTickGap={20}
2022-08-03 02:49:31 -07:00
type="number"
2022-12-06 03:58:22 -08:00
domain={domain ? domain : ['dataMin', 'dataMax']}
2022-08-03 02:49:31 -07:00
padding={{ top: 20, bottom: 20 }}
tick={{
fill: 'var(--fgd-4)',
2022-08-03 02:49:31 -07:00
fontSize: 10,
}}
2022-08-10 21:09:58 -07:00
tickFormatter={
tickFormat ? (v) => tickFormat(v) : undefined
}
2022-08-03 02:49:31 -07:00
tickLine={false}
/>
</AreaChart>
</ResponsiveContainer>
</div>
</div>
</div>
) : (
2022-12-13 17:25:30 -08:00
<div
className={`flex ${
heightClass ? heightClass : 'h-96'
} items-center justify-center p-4 text-th-fgd-3`}
>
2022-08-03 02:49:31 -07:00
<div className="">
2022-12-11 15:21:40 -08:00
<NoSymbolIcon className="mx-auto mb-1 h-6 w-6 text-th-fgd-4" />
<p className="text-th-fgd-4">{t('chart-unavailable')}</p>
2022-08-03 02:49:31 -07:00
</div>
</div>
)}
</ContentBox>
</FadeInFadeOut>
)
}
export default DetailedAreaChart