import { TokenIcon } from '@apps/base/components/core' import { CountUp } from '@apps/dumb-components' import { createMemo } from 'react-use' import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from 'recharts' import styled from 'styled-components' import { ActiveDial } from './ActiveDial' import { DIALS_METADATA } from './constants' import { useEpochData } from './context/EpochContext' import { useHoveredDialId, useSelectedDialId } from './context/ViewOptionsContext' import type { FC } from 'react' import type { EpochDialVotes } from './types' const Header = styled.div` display: flex; flex: 1; flex-direction: row; justify-content: space-between; > div { display: flex; align-items: center; gap: 0.5rem; min-height: 1.5rem; } > span { ${({ theme }) => theme.mixins.numeric}; font-weight: 400; } .vote-share { color: ${({ theme }) => theme.color.bodyTransparent}; font-size: 0.8rem; } ` const StyledTokenIcon = styled(TokenIcon)` img { height: 1.3rem; } ` const Container = styled.div` display: flex; flex-direction: column; border: 1px solid ${({ theme }) => theme.color.defaultBorder}; background: ${({ theme }) => theme.color.background[0]}; padding: 1rem; border-radius: 0.75rem; gap: 1rem; ` const Inner = styled.div` display: flex; flex-direction: column; gap: 1rem; h4 { color: ${({ theme }) => theme.color.body}; font-weight: 500; } .recharts-tooltip-wrapper { .recharts-tooltip-label { display: none; } } .recharts-bar { cursor: pointer; &:hover { * { opacity: 0.9; } } } ` const renderRadius = (idx: number, count: number) => { if (idx === 0) return [10, 0, 0, 10] if (idx === count - 1) return [0, 10, 10, 0] return [0, 0, 0, 0] } const useScaledDialVotes = createMemo((dialVotes?: EpochDialVotes): [{ [dialId: number]: number }] => { if (!dialVotes) return [{}] const totalVotes = Object.values(dialVotes).reduce((prev, dialVote) => prev + dialVote.votes, 0) // TODO improve; >100 total breaks the chart const weightMultiplier = 99.9 / totalVotes const entries = Object.entries(dialVotes).map(([dialId, dialVote]) => [dialId, dialVote.votes * weightMultiplier]) return [Object.fromEntries(entries)] }) export const DistributionBar: FC = () => { const [epochData] = useEpochData() const [, setSelectedDialId] = useSelectedDialId() const [, setHoveredDialId] = useHoveredDialId() const scaledDialVotes = useScaledDialVotes(epochData?.dialVotes) return ( <Container> <Inner> <Header> <div> <h4> <span>Distribution</span> </h4> </div> <div> <CountUp end={epochData?.emission} decimals={0} duration={0.3} /> <StyledTokenIcon symbol="MTA" /> </div> </Header> <ResponsiveContainer height={24} width="100%"> <BarChart layout="vertical" stackOffset="none" data={scaledDialVotes} margin={{ top: 0, bottom: 0, left: 0, right: 0 }}> <XAxis hide type="number" /> <YAxis hide type="category" /> {Object.values(epochData?.dialVotes ?? {}) .filter(dialVote => dialVote.votes > 0) .map(({ dialId }, idx, arr) => ( <Bar key={dialId} dataKey={dialId} fill={DIALS_METADATA[dialId].color} stackId="bar" radius={renderRadius(idx, arr.length)} onClick={() => { setSelectedDialId(dialId) }} onMouseEnter={() => { setHoveredDialId(dialId) }} onMouseLeave={() => { setHoveredDialId(undefined) }} /> ))} </BarChart> </ResponsiveContainer> <ActiveDial /> </Inner> </Container> ) }