manifold/web/components/charts/stats.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

82 lines
2.7 KiB
TypeScript

import { useMemo } from 'react'
import { scaleTime, scaleLinear } from 'd3-scale'
import { min, max } from 'lodash'
import dayjs from 'dayjs'
import { formatPercent } from 'common/util/format'
import { Row } from '../layout/row'
import { HistoryPoint, SingleValueHistoryChart } from './generic-charts'
import { TooltipProps } from './helpers'
import { SizedContainer } from 'web/components/sized-container'
const MARGIN = { top: 20, right: 10, bottom: 20, left: 40 }
const MARGIN_X = MARGIN.left + MARGIN.right
const MARGIN_Y = MARGIN.top + MARGIN.bottom
const getPoints = (startDate: number, dailyValues: number[]) => {
const startDateDayJs = dayjs(startDate)
return dailyValues.map((y, i) => ({
x: startDateDayJs.add(i, 'day').toDate(),
y: y,
}))
}
const DailyCountTooltip = (props: TooltipProps<Date, HistoryPoint>) => {
const { data, mouseX, xScale } = props
const d = xScale.invert(mouseX)
return (
<Row className="items-center gap-2">
<span className="font-semibold">{dayjs(d).format('MMM DD')}</span>
<span className="text-greyscale-6">{data.y}</span>
</Row>
)
}
const DailyPercentTooltip = (props: TooltipProps<Date, HistoryPoint>) => {
const { data, mouseX, xScale } = props
const d = xScale.invert(mouseX)
return (
<Row className="items-center gap-2">
<span className="font-semibold">{dayjs(d).format('MMM DD')}</span>
<span className="text-greyscale-6">{formatPercent(data.y)}</span>
</Row>
)
}
export function DailyChart(props: {
startDate: number
dailyValues: number[]
excludeFirstDays?: number
pct?: boolean
}) {
const { dailyValues, startDate, excludeFirstDays, pct } = props
const data = useMemo(
() => getPoints(startDate, dailyValues).slice(excludeFirstDays ?? 0),
[startDate, dailyValues, excludeFirstDays]
)
// 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 maxValue = max(data.map((d) => d.y))!
return (
<SizedContainer fullHeight={250} mobileHeight={250}>
{(width, height) => (
<SingleValueHistoryChart
w={width}
h={height}
margin={MARGIN}
xScale={scaleTime([minDate, maxDate], [0, width - MARGIN_X])}
yScale={scaleLinear([0, maxValue], [height - MARGIN_Y, 0])}
yKind={pct ? 'percent' : 'amount'}
data={data}
Tooltip={pct ? DailyPercentTooltip : DailyCountTooltip}
color="#11b981"
/>
)}
</SizedContainer>
)
}