-
Matiss Janis Aboltins authoredMatiss Janis Aboltins authored
BarGraph.tsx 7.03 KiB
// @ts-strict-ignore
import React from 'react';
import { css } from 'glamor';
import {
BarChart,
Bar,
CartesianGrid,
Cell,
ReferenceLine,
XAxis,
YAxis,
Tooltip,
LabelList,
ResponsiveContainer,
} from 'recharts';
import { usePrivacyMode } from 'loot-core/src/client/privacy';
import {
amountToCurrency,
amountToCurrencyNoDecimal,
} from 'loot-core/src/shared/util';
import { theme } from '../../../style';
import { type CSSProperties } from '../../../style';
import { AlignedText } from '../../common/AlignedText';
import { PrivacyFilter } from '../../PrivacyFilter';
import { Container } from '../Container';
import { type DataEntity } from '../entities';
import { getCustomTick } from '../getCustomTick';
import { numberFormatterTooltip } from '../numberFormatter';
import { adjustTextSize } from './adjustTextSize';
import { renderCustomLabel } from './renderCustomLabel';
type PayloadChild = {
props: {
name: string;
fill: string;
};
};
type PayloadItem = {
payload: {
name: string;
totalAssets: number | string;
totalDebts: number | string;
totalTotals: number | string;
networth: number | string;
totalChange: number | string;
children: [PayloadChild];
};
};
type CustomTooltipProps = {
active?: boolean;
payload?: PayloadItem[];
balanceTypeOp?: string;
yAxis?: string;
};
const CustomTooltip = ({
active,
payload,
balanceTypeOp,
yAxis,
}: CustomTooltipProps) => {
if (active && payload && payload.length) {
return (
<div
className={`${css({
zIndex: 1000,
pointerEvents: 'none',
borderRadius: 2,
boxShadow: '0 1px 6px rgba(0, 0, 0, .20)',
backgroundColor: theme.menuBackground,
color: theme.menuItemText,
padding: 10,
})}`}
>
<div>
<div style={{ marginBottom: 10 }}>
<strong>{payload[0].payload[yAxis]}</strong>
</div>
<div style={{ lineHeight: 1.5 }}>
<PrivacyFilter>
{['totalAssets', 'totalTotals'].includes(balanceTypeOp) && (
<AlignedText
left="Assets:"
right={amountToCurrency(payload[0].payload.totalAssets)}
/>
)}
{['totalDebts', 'totalTotals'].includes(balanceTypeOp) && (
<AlignedText
left="Debt:"
right={amountToCurrency(payload[0].payload.totalDebts)}
/>
)}
{['totalTotals'].includes(balanceTypeOp) && (
<AlignedText
left="Net:"
right={
<strong>
{amountToCurrency(payload[0].payload.totalTotals)}
</strong>
}
/>
)}
</PrivacyFilter>
</div>
</div>
</div>
);
}
};
const customLabel = props => {
const calcX = props.x + props.width / 2;
const calcY = props.y - (props.value > 0 ? 15 : -15);
const textAnchor = 'middle';
const display =
props.value !== 0 && `${amountToCurrencyNoDecimal(props.value)}`;
const textSize = adjustTextSize(props.width, 'variable', props.value);
return renderCustomLabel(calcX, calcY, textAnchor, display, textSize);
};
type BarGraphProps = {
style?: CSSProperties;
data: DataEntity;
groupBy: string;
balanceTypeOp: string;
compact?: boolean;
viewLabels: boolean;
};
export function BarGraph({
style,
data,
groupBy,
balanceTypeOp,
compact,
viewLabels,
}: BarGraphProps) {
const privacyMode = usePrivacyMode();
const yAxis = ['Month', 'Year'].includes(groupBy) ? 'date' : 'name';
const splitData = ['Month', 'Year'].includes(groupBy) ? 'monthData' : 'data';
const labelsMargin = viewLabels ? 30 : 0;
const getVal = obj => {
if (balanceTypeOp === 'totalDebts') {
return -1 * obj.totalDebts;
} else {
return obj.totalAssets;
}
};
const longestLabelLength = data[splitData]
.map(c => c[yAxis])
.reduce((acc, cur) => (cur.length > acc ? cur.length : acc), 0);
return (
<Container
style={{
...style,
...(compact && { height: 'auto' }),
}}
>
{(width, height, portalHost) =>
data[splitData] && (
<ResponsiveContainer>
<div>
{!compact && <div style={{ marginTop: '15px' }} />}
<BarChart
width={width}
height={height}
stackOffset="sign"
data={data[splitData]}
margin={{ top: labelsMargin, right: 0, left: 0, bottom: 0 }}
>
<Tooltip
cursor={{ fill: 'transparent' }}
content={
<CustomTooltip
balanceTypeOp={balanceTypeOp}
yAxis={yAxis}
/>
}
formatter={numberFormatterTooltip}
isAnimationActive={false}
/>
{!compact && <CartesianGrid strokeDasharray="3 3" />}
{!compact && (
<XAxis
dataKey={yAxis}
angle={-35}
textAnchor="end"
height={Math.sqrt(longestLabelLength) * 25}
tick={{ fill: theme.pageText }}
tickLine={{ stroke: theme.pageText }}
/>
)}
{!compact && (
<YAxis
tickFormatter={value => getCustomTick(value, privacyMode)}
tick={{ fill: theme.pageText }}
tickLine={{ stroke: theme.pageText }}
/>
)}
{!compact && (
<ReferenceLine y={0} stroke={theme.pageTextLight} />
)}
<Bar dataKey={val => getVal(val)} stackId="a">
{viewLabels && (
<LabelList
dataKey={val => getVal(val)}
content={customLabel}
/>
)}
{data.legend.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={entry.color}
name={entry.name}
/>
))}
</Bar>
{yAxis === 'date' && balanceTypeOp === 'totalTotals' && (
<Bar dataKey="totalDebts" stackId="a">
{viewLabels && (
<LabelList dataKey="totalDebts" content={customLabel} />
)}
{data[splitData].map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={theme.reportsRed}
name={entry.name}
/>
))}
</Bar>
)}
</BarChart>
</div>
</ResponsiveContainer>
)
}
</Container>
);
}