manifold/web/components/portfolio/portfolio-value-graph.tsx
Marshall Polaris 31c6cb7739
Rewrite portfolio graphs with new machinery (#986)
* Fix chart `onMouseOver` propagation

* Make generic charts support money on y-axis

* Fix somewhat ridiculous `formatMoney` to work with negative amounts

* Make margins on charts configurable

* Implement color as function of point on SingleValueHistoryChart

* Rewrite portfolio history graphs with new graph machinery

* Toast Nivo
2022-10-04 01:18:22 -07:00

85 lines
2.7 KiB
TypeScript

import { useMemo } from 'react'
import { scaleTime, scaleLinear } from 'd3-scale'
import { curveStepAfter } from 'd3-shape'
import { min, max } from 'lodash'
import dayjs from 'dayjs'
import { PortfolioMetrics } from 'common/user'
import { Col } from '../layout/col'
import { TooltipProps } from 'web/components/charts/helpers'
import {
HistoryPoint,
SingleValueHistoryChart,
} from 'web/components/charts/generic-charts'
const MARGIN = { top: 20, right: 10, bottom: 20, left: 70 }
const MARGIN_X = MARGIN.left + MARGIN.right
const MARGIN_Y = MARGIN.top + MARGIN.bottom
export type GraphMode = 'profit' | 'value'
export const PortfolioTooltip = (props: TooltipProps<Date, HistoryPoint>) => {
const { mouseX, xScale } = props
const d = dayjs(xScale.invert(mouseX))
return (
<Col className="text-xs font-semibold sm:text-sm">
<div>{d.format('MMM/D/YY')}</div>
<div className="text-greyscale-6 text-2xs font-normal sm:text-xs">
{d.format('h:mm A')}
</div>
</Col>
)
}
const getY = (mode: GraphMode, p: PortfolioMetrics) =>
p.balance + p.investmentValue - (mode === 'profit' ? p.totalDeposits : 0)
export function getPoints(mode: GraphMode, history: PortfolioMetrics[]) {
return history.map((p) => ({
x: new Date(p.timestamp),
y: getY(mode, p),
obj: p,
}))
}
export const PortfolioGraph = (props: {
mode: 'profit' | 'value'
history: PortfolioMetrics[]
width: number
height: number
onMouseOver?: (p: HistoryPoint<PortfolioMetrics> | undefined) => void
}) => {
const { mode, history, onMouseOver, width, height } = props
const { data, minDate, maxDate, minValue, maxValue } = useMemo(() => {
const data = getPoints(mode, history)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const minDate = min(data.map((d) => d.x))!
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const maxDate = max(data.map((d) => d.x))!
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const minValue = min(data.map((d) => d.y))!
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const maxValue = max(data.map((d) => d.y))!
return { data, minDate, maxDate, minValue, maxValue }
}, [mode, history])
return (
<SingleValueHistoryChart
w={width}
h={height}
margin={MARGIN}
xScale={scaleTime([minDate, maxDate], [0, width - MARGIN_X])}
yScale={scaleLinear([minValue, maxValue], [height - MARGIN_Y, 0])}
yKind="m$"
data={data}
curve={curveStepAfter}
Tooltip={PortfolioTooltip}
onMouseOver={onMouseOver}
color={
mode === 'value'
? '#4f46e5'
: (p: HistoryPoint) => (p.y >= 0 ? '#14b8a6' : '#f00')
}
/>
)
}