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
This commit is contained in:
parent
23ca3ff56a
commit
31c6cb7739
|
@ -13,7 +13,9 @@ export function formatMoney(amount: number) {
|
||||||
Math.round(amount) === 0
|
Math.round(amount) === 0
|
||||||
? 0
|
? 0
|
||||||
: // Handle 499.9999999999999 case
|
: // Handle 499.9999999999999 case
|
||||||
Math.floor(amount + 0.00000000001 * Math.sign(amount))
|
(amount > 0 ? Math.floor : Math.ceil)(
|
||||||
|
amount + 0.00000000001 * Math.sign(amount)
|
||||||
|
)
|
||||||
return ENV_CONFIG.moneyMoniker + formatter.format(newAmount).replace('$', '')
|
return ENV_CONFIG.moneyMoniker + formatter.format(newAmount).replace('$', '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,6 @@ import { BinaryContract } from 'common/contract'
|
||||||
import { DAY_MS } from 'common/util/time'
|
import { DAY_MS } from 'common/util/time'
|
||||||
import {
|
import {
|
||||||
TooltipProps,
|
TooltipProps,
|
||||||
MARGIN_X,
|
|
||||||
MARGIN_Y,
|
|
||||||
getDateRange,
|
getDateRange,
|
||||||
getRightmostVisibleDate,
|
getRightmostVisibleDate,
|
||||||
formatDateInRange,
|
formatDateInRange,
|
||||||
|
@ -20,6 +18,10 @@ import { HistoryPoint, SingleValueHistoryChart } from '../generic-charts'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { Avatar } from 'web/components/avatar'
|
import { Avatar } from 'web/components/avatar'
|
||||||
|
|
||||||
|
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 getBetPoints = (bets: Bet[]) => {
|
const getBetPoints = (bets: Bet[]) => {
|
||||||
return sortBy(bets, (b) => b.createdTime).map((b) => ({
|
return sortBy(bets, (b) => b.createdTime).map((b) => ({
|
||||||
x: new Date(b.createdTime),
|
x: new Date(b.createdTime),
|
||||||
|
@ -73,14 +75,15 @@ export const BinaryContractChart = (props: {
|
||||||
<SingleValueHistoryChart
|
<SingleValueHistoryChart
|
||||||
w={width}
|
w={width}
|
||||||
h={height}
|
h={height}
|
||||||
|
margin={MARGIN}
|
||||||
xScale={xScale}
|
xScale={xScale}
|
||||||
yScale={yScale}
|
yScale={yScale}
|
||||||
|
yKind="percent"
|
||||||
data={data}
|
data={data}
|
||||||
color="#11b981"
|
color="#11b981"
|
||||||
curve={curveStepAfter}
|
curve={curveStepAfter}
|
||||||
onMouseOver={onMouseOver}
|
onMouseOver={onMouseOver}
|
||||||
Tooltip={BinaryChartTooltip}
|
Tooltip={BinaryChartTooltip}
|
||||||
pct
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,6 @@ import { getOutcomeProbability } from 'common/calculate'
|
||||||
import { DAY_MS } from 'common/util/time'
|
import { DAY_MS } from 'common/util/time'
|
||||||
import {
|
import {
|
||||||
TooltipProps,
|
TooltipProps,
|
||||||
MARGIN_X,
|
|
||||||
MARGIN_Y,
|
|
||||||
getDateRange,
|
getDateRange,
|
||||||
getRightmostVisibleDate,
|
getRightmostVisibleDate,
|
||||||
formatPct,
|
formatPct,
|
||||||
|
@ -79,6 +77,10 @@ const CATEGORY_COLORS = [
|
||||||
'#70a560',
|
'#70a560',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
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 getTrackedAnswers = (
|
const getTrackedAnswers = (
|
||||||
contract: FreeResponseContract | MultipleChoiceContract,
|
contract: FreeResponseContract | MultipleChoiceContract,
|
||||||
topN: number
|
topN: number
|
||||||
|
@ -211,14 +213,15 @@ export const ChoiceContractChart = (props: {
|
||||||
<MultiValueHistoryChart
|
<MultiValueHistoryChart
|
||||||
w={width}
|
w={width}
|
||||||
h={height}
|
h={height}
|
||||||
|
margin={MARGIN}
|
||||||
xScale={xScale}
|
xScale={xScale}
|
||||||
yScale={yScale}
|
yScale={yScale}
|
||||||
|
yKind="percent"
|
||||||
data={data}
|
data={data}
|
||||||
colors={CATEGORY_COLORS}
|
colors={CATEGORY_COLORS}
|
||||||
curve={curveStepAfter}
|
curve={curveStepAfter}
|
||||||
onMouseOver={onMouseOver}
|
onMouseOver={onMouseOver}
|
||||||
Tooltip={ChoiceTooltip}
|
Tooltip={ChoiceTooltip}
|
||||||
pct
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,13 @@ import { formatLargeNumber } from 'common/util/format'
|
||||||
import { getDpmOutcomeProbabilities } from 'common/calculate-dpm'
|
import { getDpmOutcomeProbabilities } from 'common/calculate-dpm'
|
||||||
import { NumericContract } from 'common/contract'
|
import { NumericContract } from 'common/contract'
|
||||||
import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants'
|
import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants'
|
||||||
import { TooltipProps, MARGIN_X, MARGIN_Y, formatPct } from '../helpers'
|
import { TooltipProps, formatPct } from '../helpers'
|
||||||
import { DistributionPoint, DistributionChart } from '../generic-charts'
|
import { DistributionPoint, DistributionChart } from '../generic-charts'
|
||||||
|
|
||||||
|
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 getNumericChartData = (contract: NumericContract) => {
|
const getNumericChartData = (contract: NumericContract) => {
|
||||||
const { totalShares, bucketCount, min, max } = contract
|
const { totalShares, bucketCount, min, max } = contract
|
||||||
const step = (max - min) / bucketCount
|
const step = (max - min) / bucketCount
|
||||||
|
@ -48,6 +52,7 @@ export const NumericContractChart = (props: {
|
||||||
<DistributionChart
|
<DistributionChart
|
||||||
w={width}
|
w={width}
|
||||||
h={height}
|
h={height}
|
||||||
|
margin={MARGIN}
|
||||||
xScale={xScale}
|
xScale={xScale}
|
||||||
yScale={yScale}
|
yScale={yScale}
|
||||||
data={data}
|
data={data}
|
||||||
|
|
|
@ -11,8 +11,6 @@ import { PseudoNumericContract } from 'common/contract'
|
||||||
import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants'
|
import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants'
|
||||||
import {
|
import {
|
||||||
TooltipProps,
|
TooltipProps,
|
||||||
MARGIN_X,
|
|
||||||
MARGIN_Y,
|
|
||||||
getDateRange,
|
getDateRange,
|
||||||
getRightmostVisibleDate,
|
getRightmostVisibleDate,
|
||||||
formatDateInRange,
|
formatDateInRange,
|
||||||
|
@ -21,6 +19,10 @@ import { HistoryPoint, SingleValueHistoryChart } from '../generic-charts'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { Avatar } from 'web/components/avatar'
|
import { Avatar } from 'web/components/avatar'
|
||||||
|
|
||||||
|
const MARGIN = { top: 20, right: 10, bottom: 20, left: 40 }
|
||||||
|
const MARGIN_X = MARGIN.left + MARGIN.right
|
||||||
|
const MARGIN_Y = MARGIN.top + MARGIN.bottom
|
||||||
|
|
||||||
// mqp: note that we have an idiosyncratic version of 'log scale'
|
// mqp: note that we have an idiosyncratic version of 'log scale'
|
||||||
// contracts. the values are stored "linearly" and can include zero.
|
// contracts. the values are stored "linearly" and can include zero.
|
||||||
// as a result, we have to do some weird-looking stuff in this code
|
// as a result, we have to do some weird-looking stuff in this code
|
||||||
|
@ -95,6 +97,7 @@ export const PseudoNumericContractChart = (props: {
|
||||||
<SingleValueHistoryChart
|
<SingleValueHistoryChart
|
||||||
w={width}
|
w={width}
|
||||||
h={height}
|
h={height}
|
||||||
|
margin={MARGIN}
|
||||||
xScale={xScale}
|
xScale={xScale}
|
||||||
yScale={yScale}
|
yScale={yScale}
|
||||||
data={data}
|
data={data}
|
||||||
|
|
|
@ -14,18 +14,23 @@ import { range } from 'lodash'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ContinuousScale,
|
ContinuousScale,
|
||||||
|
Margin,
|
||||||
SVGChart,
|
SVGChart,
|
||||||
AreaPath,
|
AreaPath,
|
||||||
AreaWithTopStroke,
|
AreaWithTopStroke,
|
||||||
Point,
|
Point,
|
||||||
TooltipComponent,
|
TooltipComponent,
|
||||||
|
computeColorStops,
|
||||||
formatPct,
|
formatPct,
|
||||||
} from './helpers'
|
} from './helpers'
|
||||||
import { useEvent } from 'web/hooks/use-event'
|
import { useEvent } from 'web/hooks/use-event'
|
||||||
|
import { formatMoney } from 'common/util/format'
|
||||||
|
import { nanoid } from 'nanoid'
|
||||||
|
|
||||||
export type MultiPoint<T = unknown> = Point<Date, number[], T>
|
export type MultiPoint<T = unknown> = Point<Date, number[], T>
|
||||||
export type HistoryPoint<T = unknown> = Point<Date, number, T>
|
export type HistoryPoint<T = unknown> = Point<Date, number, T>
|
||||||
export type DistributionPoint<T = unknown> = Point<number, number, T>
|
export type DistributionPoint<T = unknown> = Point<number, number, T>
|
||||||
|
export type ValueKind = 'm$' | 'percent' | 'amount'
|
||||||
|
|
||||||
const getTickValues = (min: number, max: number, n: number) => {
|
const getTickValues = (min: number, max: number, n: number) => {
|
||||||
const step = (max - min) / (n - 1)
|
const step = (max - min) / (n - 1)
|
||||||
|
@ -50,13 +55,14 @@ export const DistributionChart = <P extends DistributionPoint>(props: {
|
||||||
w: number
|
w: number
|
||||||
h: number
|
h: number
|
||||||
color: string
|
color: string
|
||||||
|
margin: Margin
|
||||||
xScale: ScaleContinuousNumeric<number, number>
|
xScale: ScaleContinuousNumeric<number, number>
|
||||||
yScale: ScaleContinuousNumeric<number, number>
|
yScale: ScaleContinuousNumeric<number, number>
|
||||||
curve?: CurveFactory
|
curve?: CurveFactory
|
||||||
onMouseOver?: (p: P | undefined) => void
|
onMouseOver?: (p: P | undefined) => void
|
||||||
Tooltip?: TooltipComponent<number, P>
|
Tooltip?: TooltipComponent<number, P>
|
||||||
}) => {
|
}) => {
|
||||||
const { color, data, yScale, w, h, curve, Tooltip } = props
|
const { data, w, h, color, margin, yScale, curve, Tooltip } = props
|
||||||
|
|
||||||
const [viewXScale, setViewXScale] =
|
const [viewXScale, setViewXScale] =
|
||||||
useState<ScaleContinuousNumeric<number, number>>()
|
useState<ScaleContinuousNumeric<number, number>>()
|
||||||
|
@ -72,7 +78,12 @@ export const DistributionChart = <P extends DistributionPoint>(props: {
|
||||||
return { xAxis, yAxis }
|
return { xAxis, yAxis }
|
||||||
}, [w, xScale, yScale])
|
}, [w, xScale, yScale])
|
||||||
|
|
||||||
const onMouseOver = useEvent(betAtPointSelector(data, xScale))
|
const selector = betAtPointSelector(data, xScale)
|
||||||
|
const onMouseOver = useEvent((mouseX: number) => {
|
||||||
|
const p = selector(mouseX)
|
||||||
|
props.onMouseOver?.(p)
|
||||||
|
return p
|
||||||
|
})
|
||||||
|
|
||||||
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
||||||
if (ev.selection) {
|
if (ev.selection) {
|
||||||
|
@ -89,6 +100,7 @@ export const DistributionChart = <P extends DistributionPoint>(props: {
|
||||||
<SVGChart
|
<SVGChart
|
||||||
w={w}
|
w={w}
|
||||||
h={h}
|
h={h}
|
||||||
|
margin={margin}
|
||||||
xAxis={xAxis}
|
xAxis={xAxis}
|
||||||
yAxis={yAxis}
|
yAxis={yAxis}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
@ -112,14 +124,15 @@ export const MultiValueHistoryChart = <P extends MultiPoint>(props: {
|
||||||
w: number
|
w: number
|
||||||
h: number
|
h: number
|
||||||
colors: readonly string[]
|
colors: readonly string[]
|
||||||
|
margin: Margin
|
||||||
xScale: ScaleTime<number, number>
|
xScale: ScaleTime<number, number>
|
||||||
yScale: ScaleContinuousNumeric<number, number>
|
yScale: ScaleContinuousNumeric<number, number>
|
||||||
|
yKind?: ValueKind
|
||||||
curve?: CurveFactory
|
curve?: CurveFactory
|
||||||
onMouseOver?: (p: P | undefined) => void
|
onMouseOver?: (p: P | undefined) => void
|
||||||
Tooltip?: TooltipComponent<Date, P>
|
Tooltip?: TooltipComponent<Date, P>
|
||||||
pct?: boolean
|
|
||||||
}) => {
|
}) => {
|
||||||
const { colors, data, yScale, w, h, curve, Tooltip, pct } = props
|
const { data, w, h, colors, margin, yScale, yKind, curve, Tooltip } = props
|
||||||
|
|
||||||
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||||
const xScale = viewXScale ?? props.xScale
|
const xScale = viewXScale ?? props.xScale
|
||||||
|
@ -131,25 +144,36 @@ export const MultiValueHistoryChart = <P extends MultiPoint>(props: {
|
||||||
|
|
||||||
const { xAxis, yAxis } = useMemo(() => {
|
const { xAxis, yAxis } = useMemo(() => {
|
||||||
const [min, max] = yScale.domain()
|
const [min, max] = yScale.domain()
|
||||||
const pctTickValues = getTickValues(min, max, h < 200 ? 3 : 5)
|
const nTicks = h < 200 ? 3 : 5
|
||||||
|
const pctTickValues = getTickValues(min, max, nTicks)
|
||||||
const xAxis = axisBottom<Date>(xScale).ticks(w / 100)
|
const xAxis = axisBottom<Date>(xScale).ticks(w / 100)
|
||||||
const yAxis = pct
|
const yAxis =
|
||||||
? axisLeft<number>(yScale)
|
yKind === 'percent'
|
||||||
.tickValues(pctTickValues)
|
? axisLeft<number>(yScale)
|
||||||
.tickFormat((n) => formatPct(n))
|
.tickValues(pctTickValues)
|
||||||
: axisLeft<number>(yScale)
|
.tickFormat((n) => formatPct(n))
|
||||||
|
: yKind === 'm$'
|
||||||
|
? axisLeft<number>(yScale)
|
||||||
|
.ticks(nTicks)
|
||||||
|
.tickFormat((n) => formatMoney(n))
|
||||||
|
: axisLeft<number>(yScale).ticks(nTicks)
|
||||||
return { xAxis, yAxis }
|
return { xAxis, yAxis }
|
||||||
}, [w, h, pct, xScale, yScale])
|
}, [w, h, yKind, xScale, yScale])
|
||||||
|
|
||||||
const series = useMemo(() => {
|
const series = useMemo(() => {
|
||||||
const d3Stack = stack<P, number>()
|
const d3Stack = stack<P, number>()
|
||||||
.keys(range(0, Math.max(...data.map(({ y }) => y.length))))
|
.keys(range(0, Math.max(...data.map(({ y }) => y.length))))
|
||||||
.value(({ y }, o) => y[o])
|
.value(({ y }, k) => y[k])
|
||||||
.order(stackOrderReverse)
|
.order(stackOrderReverse)
|
||||||
return d3Stack(data)
|
return d3Stack(data)
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const onMouseOver = useEvent(betAtPointSelector(data, xScale))
|
const selector = betAtPointSelector(data, xScale)
|
||||||
|
const onMouseOver = useEvent((mouseX: number) => {
|
||||||
|
const p = selector(mouseX)
|
||||||
|
props.onMouseOver?.(p)
|
||||||
|
return p
|
||||||
|
})
|
||||||
|
|
||||||
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
||||||
if (ev.selection) {
|
if (ev.selection) {
|
||||||
|
@ -166,6 +190,7 @@ export const MultiValueHistoryChart = <P extends MultiPoint>(props: {
|
||||||
<SVGChart
|
<SVGChart
|
||||||
w={w}
|
w={w}
|
||||||
h={h}
|
h={h}
|
||||||
|
margin={margin}
|
||||||
xAxis={xAxis}
|
xAxis={xAxis}
|
||||||
yAxis={yAxis}
|
yAxis={yAxis}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
@ -191,15 +216,17 @@ export const SingleValueHistoryChart = <P extends HistoryPoint>(props: {
|
||||||
data: P[]
|
data: P[]
|
||||||
w: number
|
w: number
|
||||||
h: number
|
h: number
|
||||||
color: string
|
color: string | ((p: P) => string)
|
||||||
|
margin: Margin
|
||||||
xScale: ScaleTime<number, number>
|
xScale: ScaleTime<number, number>
|
||||||
yScale: ScaleContinuousNumeric<number, number>
|
yScale: ScaleContinuousNumeric<number, number>
|
||||||
|
yKind?: ValueKind
|
||||||
curve?: CurveFactory
|
curve?: CurveFactory
|
||||||
onMouseOver?: (p: P | undefined) => void
|
onMouseOver?: (p: P | undefined) => void
|
||||||
Tooltip?: TooltipComponent<Date, P>
|
Tooltip?: TooltipComponent<Date, P>
|
||||||
pct?: boolean
|
pct?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const { color, data, yScale, w, h, curve, Tooltip, pct } = props
|
const { data, w, h, color, margin, yScale, yKind, curve, Tooltip } = props
|
||||||
|
|
||||||
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||||
const xScale = viewXScale ?? props.xScale
|
const xScale = viewXScale ?? props.xScale
|
||||||
|
@ -210,17 +237,28 @@ export const SingleValueHistoryChart = <P extends HistoryPoint>(props: {
|
||||||
|
|
||||||
const { xAxis, yAxis } = useMemo(() => {
|
const { xAxis, yAxis } = useMemo(() => {
|
||||||
const [min, max] = yScale.domain()
|
const [min, max] = yScale.domain()
|
||||||
const pctTickValues = getTickValues(min, max, h < 200 ? 3 : 5)
|
const nTicks = h < 200 ? 3 : 5
|
||||||
|
const pctTickValues = getTickValues(min, max, nTicks)
|
||||||
const xAxis = axisBottom<Date>(xScale).ticks(w / 100)
|
const xAxis = axisBottom<Date>(xScale).ticks(w / 100)
|
||||||
const yAxis = pct
|
const yAxis =
|
||||||
? axisLeft<number>(yScale)
|
yKind === 'percent'
|
||||||
.tickValues(pctTickValues)
|
? axisLeft<number>(yScale)
|
||||||
.tickFormat((n) => formatPct(n))
|
.tickValues(pctTickValues)
|
||||||
: axisLeft<number>(yScale)
|
.tickFormat((n) => formatPct(n))
|
||||||
|
: yKind === 'm$'
|
||||||
|
? axisLeft<number>(yScale)
|
||||||
|
.ticks(nTicks)
|
||||||
|
.tickFormat((n) => formatMoney(n))
|
||||||
|
: axisLeft<number>(yScale).ticks(nTicks)
|
||||||
return { xAxis, yAxis }
|
return { xAxis, yAxis }
|
||||||
}, [w, h, pct, xScale, yScale])
|
}, [w, h, yKind, xScale, yScale])
|
||||||
|
|
||||||
const onMouseOver = useEvent(betAtPointSelector(data, xScale))
|
const selector = betAtPointSelector(data, xScale)
|
||||||
|
const onMouseOver = useEvent((mouseX: number) => {
|
||||||
|
const p = selector(mouseX)
|
||||||
|
props.onMouseOver?.(p)
|
||||||
|
return p
|
||||||
|
})
|
||||||
|
|
||||||
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
||||||
if (ev.selection) {
|
if (ev.selection) {
|
||||||
|
@ -233,18 +271,35 @@ export const SingleValueHistoryChart = <P extends HistoryPoint>(props: {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const gradientId = useMemo(() => nanoid(), [])
|
||||||
|
const stops = useMemo(
|
||||||
|
() =>
|
||||||
|
typeof color !== 'string' ? computeColorStops(data, color, px) : null,
|
||||||
|
[color, data, px]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SVGChart
|
<SVGChart
|
||||||
w={w}
|
w={w}
|
||||||
h={h}
|
h={h}
|
||||||
|
margin={margin}
|
||||||
xAxis={xAxis}
|
xAxis={xAxis}
|
||||||
yAxis={yAxis}
|
yAxis={yAxis}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
onMouseOver={onMouseOver}
|
onMouseOver={onMouseOver}
|
||||||
Tooltip={Tooltip}
|
Tooltip={Tooltip}
|
||||||
>
|
>
|
||||||
|
{stops && (
|
||||||
|
<defs>
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse" id={gradientId}>
|
||||||
|
{stops.map((s, i) => (
|
||||||
|
<stop key={i} offset={`${s.x / w}`} stopColor={s.color} />
|
||||||
|
))}
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
)}
|
||||||
<AreaWithTopStroke
|
<AreaWithTopStroke
|
||||||
color={color}
|
color={typeof color === 'string' ? color : `url(#${gradientId})`}
|
||||||
data={data}
|
data={data}
|
||||||
px={px}
|
px={px}
|
||||||
py0={py0}
|
py0={py0}
|
||||||
|
|
|
@ -27,11 +27,12 @@ export interface ContinuousScale<T> extends AxisScale<T> {
|
||||||
export type XScale<P> = P extends Point<infer X, infer _> ? AxisScale<X> : never
|
export type XScale<P> = P extends Point<infer X, infer _> ? AxisScale<X> : never
|
||||||
export type YScale<P> = P extends Point<infer _, infer Y> ? AxisScale<Y> : never
|
export type YScale<P> = P extends Point<infer _, infer Y> ? AxisScale<Y> : never
|
||||||
|
|
||||||
export const MARGIN = { top: 20, right: 10, bottom: 20, left: 40 }
|
export type Margin = {
|
||||||
export const MARGIN_X = MARGIN.right + MARGIN.left
|
top: number
|
||||||
export const MARGIN_Y = MARGIN.top + MARGIN.bottom
|
right: number
|
||||||
const MARGIN_STYLE = `${MARGIN.top}px ${MARGIN.right}px ${MARGIN.bottom}px ${MARGIN.left}px`
|
bottom: number
|
||||||
const MARGIN_XFORM = `translate(${MARGIN.left}, ${MARGIN.top})`
|
left: number
|
||||||
|
}
|
||||||
|
|
||||||
export const XAxis = <X,>(props: { w: number; h: number; axis: Axis<X> }) => {
|
export const XAxis = <X,>(props: { w: number; h: number; axis: Axis<X> }) => {
|
||||||
const { h, axis } = props
|
const { h, axis } = props
|
||||||
|
@ -55,8 +56,6 @@ export const YAxis = <Y,>(props: { w: number; h: number; axis: Axis<Y> }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (axisRef.current != null) {
|
if (axisRef.current != null) {
|
||||||
select(axisRef.current)
|
select(axisRef.current)
|
||||||
.transition()
|
|
||||||
.duration(250)
|
|
||||||
.call(axis)
|
.call(axis)
|
||||||
.call((g) =>
|
.call((g) =>
|
||||||
g.selectAll('.tick line').attr('x2', w).attr('stroke-opacity', 0.1)
|
g.selectAll('.tick line').attr('x2', w).attr('stroke-opacity', 0.1)
|
||||||
|
@ -100,14 +99,14 @@ const AreaPathInternal = <P,>(
|
||||||
export const AreaPath = memo(AreaPathInternal) as typeof AreaPathInternal
|
export const AreaPath = memo(AreaPathInternal) as typeof AreaPathInternal
|
||||||
|
|
||||||
export const AreaWithTopStroke = <P,>(props: {
|
export const AreaWithTopStroke = <P,>(props: {
|
||||||
color: string
|
|
||||||
data: P[]
|
data: P[]
|
||||||
|
color: string
|
||||||
px: number | ((p: P) => number)
|
px: number | ((p: P) => number)
|
||||||
py0: number | ((p: P) => number)
|
py0: number | ((p: P) => number)
|
||||||
py1: number | ((p: P) => number)
|
py1: number | ((p: P) => number)
|
||||||
curve: CurveFactory
|
curve: CurveFactory
|
||||||
}) => {
|
}) => {
|
||||||
const { color, data, px, py0, py1, curve } = props
|
const { data, color, px, py0, py1, curve } = props
|
||||||
return (
|
return (
|
||||||
<g>
|
<g>
|
||||||
<AreaPath
|
<AreaPath
|
||||||
|
@ -128,18 +127,29 @@ export const SVGChart = <X, TT>(props: {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
w: number
|
w: number
|
||||||
h: number
|
h: number
|
||||||
|
margin: Margin
|
||||||
xAxis: Axis<X>
|
xAxis: Axis<X>
|
||||||
yAxis: Axis<number>
|
yAxis: Axis<number>
|
||||||
onSelect?: (ev: D3BrushEvent<any>) => void
|
onSelect?: (ev: D3BrushEvent<any>) => void
|
||||||
onMouseOver?: (mouseX: number, mouseY: number) => TT | undefined
|
onMouseOver?: (mouseX: number, mouseY: number) => TT | undefined
|
||||||
Tooltip?: TooltipComponent<X, TT>
|
Tooltip?: TooltipComponent<X, TT>
|
||||||
}) => {
|
}) => {
|
||||||
const { children, w, h, xAxis, yAxis, onMouseOver, onSelect, Tooltip } = props
|
const {
|
||||||
|
children,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
margin,
|
||||||
|
xAxis,
|
||||||
|
yAxis,
|
||||||
|
onMouseOver,
|
||||||
|
onSelect,
|
||||||
|
Tooltip,
|
||||||
|
} = props
|
||||||
const [mouse, setMouse] = useState<{ x: number; y: number; data: TT }>()
|
const [mouse, setMouse] = useState<{ x: number; y: number; data: TT }>()
|
||||||
const tooltipMeasure = useMeasureSize()
|
const tooltipMeasure = useMeasureSize()
|
||||||
const overlayRef = useRef<SVGGElement>(null)
|
const overlayRef = useRef<SVGGElement>(null)
|
||||||
const innerW = w - MARGIN_X
|
const innerW = w - (margin.left + margin.right)
|
||||||
const innerH = h - MARGIN_Y
|
const innerH = h - (margin.top + margin.bottom)
|
||||||
const clipPathId = useMemo(() => nanoid(), [])
|
const clipPathId = useMemo(() => nanoid(), [])
|
||||||
|
|
||||||
const justSelected = useRef(false)
|
const justSelected = useRef(false)
|
||||||
|
@ -194,6 +204,7 @@ export const SVGChart = <X, TT>(props: {
|
||||||
{mouse && Tooltip && (
|
{mouse && Tooltip && (
|
||||||
<TooltipContainer
|
<TooltipContainer
|
||||||
setElem={tooltipMeasure.setElem}
|
setElem={tooltipMeasure.setElem}
|
||||||
|
margin={margin}
|
||||||
pos={getTooltipPosition(
|
pos={getTooltipPosition(
|
||||||
mouse.x,
|
mouse.x,
|
||||||
mouse.y,
|
mouse.y,
|
||||||
|
@ -215,7 +226,7 @@ export const SVGChart = <X, TT>(props: {
|
||||||
<clipPath id={clipPathId}>
|
<clipPath id={clipPathId}>
|
||||||
<rect x={0} y={0} width={innerW} height={innerH} />
|
<rect x={0} y={0} width={innerW} height={innerH} />
|
||||||
</clipPath>
|
</clipPath>
|
||||||
<g transform={MARGIN_XFORM}>
|
<g transform={`translate(${margin.left}, ${margin.top})`}>
|
||||||
<XAxis axis={xAxis} w={innerW} h={innerH} />
|
<XAxis axis={xAxis} w={innerW} h={innerH} />
|
||||||
<YAxis axis={yAxis} w={innerW} h={innerH} />
|
<YAxis axis={yAxis} w={innerW} h={innerH} />
|
||||||
<g clipPath={`url(#${clipPathId})`}>{children}</g>
|
<g clipPath={`url(#${clipPathId})`}>{children}</g>
|
||||||
|
@ -275,10 +286,11 @@ export type TooltipComponent<X, T> = React.ComponentType<TooltipProps<X, T>>
|
||||||
export const TooltipContainer = (props: {
|
export const TooltipContainer = (props: {
|
||||||
setElem: (e: HTMLElement | null) => void
|
setElem: (e: HTMLElement | null) => void
|
||||||
pos: TooltipPosition
|
pos: TooltipPosition
|
||||||
|
margin: Margin
|
||||||
className?: string
|
className?: string
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const { setElem, pos, className, children } = props
|
const { setElem, pos, margin, className, children } = props
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={setElem}
|
ref={setElem}
|
||||||
|
@ -286,13 +298,43 @@ export const TooltipContainer = (props: {
|
||||||
className,
|
className,
|
||||||
'pointer-events-none absolute z-10 whitespace-pre rounded border border-gray-200 bg-white/80 p-2 px-4 py-2 text-xs sm:text-sm'
|
'pointer-events-none absolute z-10 whitespace-pre rounded border border-gray-200 bg-white/80 p-2 px-4 py-2 text-xs sm:text-sm'
|
||||||
)}
|
)}
|
||||||
style={{ margin: MARGIN_STYLE, ...pos }}
|
style={{
|
||||||
|
margin: `${margin.top}px ${margin.right}px ${margin.bottom}px ${margin.left}px`,
|
||||||
|
...pos,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const computeColorStops = <P,>(
|
||||||
|
data: P[],
|
||||||
|
pc: (p: P) => string,
|
||||||
|
px: (p: P) => number
|
||||||
|
) => {
|
||||||
|
const segments: { x: number; color: string }[] = []
|
||||||
|
let currOffset = 0
|
||||||
|
let currColor = pc(data[0])
|
||||||
|
for (const p of data) {
|
||||||
|
const c = pc(p)
|
||||||
|
if (c !== currColor) {
|
||||||
|
segments.push({ x: currOffset, color: currColor })
|
||||||
|
currOffset = px(p)
|
||||||
|
currColor = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
segments.push({ x: currOffset, color: currColor })
|
||||||
|
|
||||||
|
const stops: { x: number; color: string }[] = []
|
||||||
|
stops.push({ x: segments[0].x, color: segments[0].color })
|
||||||
|
for (const s of segments.slice(1)) {
|
||||||
|
stops.push({ x: s.x, color: stops[stops.length - 1].color })
|
||||||
|
stops.push({ x: s.x, color: s.color })
|
||||||
|
}
|
||||||
|
return stops
|
||||||
|
}
|
||||||
|
|
||||||
export const getDateRange = (contract: Contract) => {
|
export const getDateRange = (contract: Contract) => {
|
||||||
const { createdTime, closeTime, resolutionTime } = contract
|
const { createdTime, closeTime, resolutionTime } = contract
|
||||||
const isClosed = !!closeTime && Date.now() > closeTime
|
const isClosed = !!closeTime && Date.now() > closeTime
|
||||||
|
|
|
@ -6,9 +6,13 @@ import dayjs from 'dayjs'
|
||||||
import { formatPercent } from 'common/util/format'
|
import { formatPercent } from 'common/util/format'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { HistoryPoint, SingleValueHistoryChart } from './generic-charts'
|
import { HistoryPoint, SingleValueHistoryChart } from './generic-charts'
|
||||||
import { TooltipProps, MARGIN_X, MARGIN_Y } from './helpers'
|
import { TooltipProps } from './helpers'
|
||||||
import { SizedContainer } from 'web/components/sized-container'
|
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 getPoints = (startDate: number, dailyValues: number[]) => {
|
||||||
const startDateDayJs = dayjs(startDate)
|
const startDateDayJs = dayjs(startDate)
|
||||||
return dailyValues.map((y, i) => ({
|
return dailyValues.map((y, i) => ({
|
||||||
|
@ -63,12 +67,13 @@ export function DailyChart(props: {
|
||||||
<SingleValueHistoryChart
|
<SingleValueHistoryChart
|
||||||
w={width}
|
w={width}
|
||||||
h={height}
|
h={height}
|
||||||
|
margin={MARGIN}
|
||||||
xScale={scaleTime([minDate, maxDate], [0, width - MARGIN_X])}
|
xScale={scaleTime([minDate, maxDate], [0, width - MARGIN_X])}
|
||||||
yScale={scaleLinear([0, maxValue], [height - MARGIN_Y, 0])}
|
yScale={scaleLinear([0, maxValue], [height - MARGIN_Y, 0])}
|
||||||
|
yKind={pct ? 'percent' : 'amount'}
|
||||||
data={data}
|
data={data}
|
||||||
Tooltip={pct ? DailyPercentTooltip : DailyCountTooltip}
|
Tooltip={pct ? DailyPercentTooltip : DailyCountTooltip}
|
||||||
color="#11b981"
|
color="#11b981"
|
||||||
pct={pct}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</SizedContainer>
|
</SizedContainer>
|
||||||
|
|
|
@ -1,155 +1,84 @@
|
||||||
import { ResponsiveLine } from '@nivo/line'
|
import { useMemo } from 'react'
|
||||||
import { PortfolioMetrics } from 'common/user'
|
import { scaleTime, scaleLinear } from 'd3-scale'
|
||||||
import { filterDefined } from 'common/util/array'
|
import { curveStepAfter } from 'd3-shape'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { min, max } from 'lodash'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { last } from 'lodash'
|
import { PortfolioMetrics } from 'common/user'
|
||||||
import { memo } from 'react'
|
|
||||||
import { useWindowSize } from 'web/hooks/use-window-size'
|
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
|
import { TooltipProps } from 'web/components/charts/helpers'
|
||||||
|
import {
|
||||||
|
HistoryPoint,
|
||||||
|
SingleValueHistoryChart,
|
||||||
|
} from 'web/components/charts/generic-charts'
|
||||||
|
|
||||||
export const PortfolioValueGraph = memo(function PortfolioValueGraph(props: {
|
const MARGIN = { top: 20, right: 10, bottom: 20, left: 70 }
|
||||||
portfolioHistory: PortfolioMetrics[]
|
const MARGIN_X = MARGIN.left + MARGIN.right
|
||||||
mode: 'value' | 'profit'
|
const MARGIN_Y = MARGIN.top + MARGIN.bottom
|
||||||
handleGraphDisplayChange: (arg0: string | number | null) => void
|
|
||||||
height?: number
|
|
||||||
}) {
|
|
||||||
const { portfolioHistory, height, mode, handleGraphDisplayChange } = props
|
|
||||||
const { width } = useWindowSize()
|
|
||||||
|
|
||||||
const valuePoints = getPoints('value', portfolioHistory)
|
export type GraphMode = 'profit' | 'value'
|
||||||
const posProfitPoints = getPoints('posProfit', portfolioHistory)
|
|
||||||
const negProfitPoints = getPoints('negProfit', portfolioHistory)
|
|
||||||
|
|
||||||
const valuePointsY = valuePoints.map((p) => p.y)
|
export const PortfolioTooltip = (props: TooltipProps<Date, HistoryPoint>) => {
|
||||||
const posProfitPointsY = posProfitPoints.map((p) => p.y)
|
const { mouseX, xScale } = props
|
||||||
const negProfitPointsY = negProfitPoints.map((p) => p.y)
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let data
|
const getY = (mode: GraphMode, p: PortfolioMetrics) =>
|
||||||
|
p.balance + p.investmentValue - (mode === 'profit' ? p.totalDeposits : 0)
|
||||||
|
|
||||||
if (mode === 'value') {
|
export function getPoints(mode: GraphMode, history: PortfolioMetrics[]) {
|
||||||
data = [{ id: 'value', data: valuePoints, color: '#4f46e5' }]
|
return history.map((p) => ({
|
||||||
} else {
|
x: new Date(p.timestamp),
|
||||||
data = [
|
y: getY(mode, p),
|
||||||
{
|
obj: p,
|
||||||
id: 'negProfit',
|
}))
|
||||||
data: negProfitPoints,
|
}
|
||||||
color: '#dc2626',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'posProfit',
|
|
||||||
data: posProfitPoints,
|
|
||||||
color: '#14b8a6',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
const numYTickValues = 2
|
|
||||||
const endDate = last(data[0].data)?.x
|
|
||||||
|
|
||||||
const yMin =
|
export const PortfolioGraph = (props: {
|
||||||
mode === 'value'
|
mode: 'profit' | 'value'
|
||||||
? Math.min(...filterDefined(valuePointsY))
|
history: PortfolioMetrics[]
|
||||||
: Math.min(
|
width: number
|
||||||
...filterDefined(negProfitPointsY),
|
height: number
|
||||||
...filterDefined(posProfitPointsY)
|
onMouseOver?: (p: HistoryPoint<PortfolioMetrics> | undefined) => void
|
||||||
)
|
}) => {
|
||||||
|
const { mode, history, onMouseOver, width, height } = props
|
||||||
const yMax =
|
const { data, minDate, maxDate, minValue, maxValue } = useMemo(() => {
|
||||||
mode === 'value'
|
const data = getPoints(mode, history)
|
||||||
? Math.max(...filterDefined(valuePointsY))
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
: Math.max(
|
const minDate = min(data.map((d) => d.x))!
|
||||||
...filterDefined(negProfitPointsY),
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
...filterDefined(posProfitPointsY)
|
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 (
|
return (
|
||||||
<div
|
<SingleValueHistoryChart
|
||||||
className="w-full overflow-hidden"
|
w={width}
|
||||||
style={{ height: height ?? (!width || width >= 800 ? 200 : 100) }}
|
h={height}
|
||||||
onMouseLeave={() => handleGraphDisplayChange(null)}
|
margin={MARGIN}
|
||||||
>
|
xScale={scaleTime([minDate, maxDate], [0, width - MARGIN_X])}
|
||||||
<ResponsiveLine
|
yScale={scaleLinear([minValue, maxValue], [height - MARGIN_Y, 0])}
|
||||||
margin={{ top: 10, right: 0, left: 40, bottom: 10 }}
|
yKind="m$"
|
||||||
data={data}
|
data={data}
|
||||||
xScale={{
|
curve={curveStepAfter}
|
||||||
type: 'time',
|
Tooltip={PortfolioTooltip}
|
||||||
min: valuePoints[0]?.x,
|
onMouseOver={onMouseOver}
|
||||||
max: endDate,
|
color={
|
||||||
}}
|
mode === 'value'
|
||||||
yScale={{
|
? '#4f46e5'
|
||||||
type: 'linear',
|
: (p: HistoryPoint) => (p.y >= 0 ? '#14b8a6' : '#f00')
|
||||||
stacked: false,
|
}
|
||||||
min: yMin,
|
/>
|
||||||
max: yMax,
|
|
||||||
}}
|
|
||||||
curve="stepAfter"
|
|
||||||
enablePoints={false}
|
|
||||||
colors={{ datum: 'color' }}
|
|
||||||
axisBottom={{
|
|
||||||
tickValues: 0,
|
|
||||||
}}
|
|
||||||
pointBorderColor="#fff"
|
|
||||||
pointSize={valuePoints.length > 100 ? 0 : 6}
|
|
||||||
axisLeft={{
|
|
||||||
tickValues: numYTickValues,
|
|
||||||
format: '.3s',
|
|
||||||
}}
|
|
||||||
enableGridX={false}
|
|
||||||
enableGridY={true}
|
|
||||||
gridYValues={numYTickValues}
|
|
||||||
enableSlices="x"
|
|
||||||
animate={false}
|
|
||||||
yFormat={(value) => formatMoney(+value)}
|
|
||||||
enableArea={true}
|
|
||||||
areaOpacity={0.1}
|
|
||||||
sliceTooltip={({ slice }) => {
|
|
||||||
handleGraphDisplayChange(slice.points[0].data.yFormatted)
|
|
||||||
return (
|
|
||||||
<div className="rounded border border-gray-200 bg-white px-4 py-2 opacity-80">
|
|
||||||
<div
|
|
||||||
key={slice.points[0].id}
|
|
||||||
className="text-xs font-semibold sm:text-sm"
|
|
||||||
>
|
|
||||||
<Col>
|
|
||||||
<div>
|
|
||||||
{dayjs(slice.points[0].data.xFormatted).format('MMM/D/YY')}
|
|
||||||
</div>
|
|
||||||
<div className="text-greyscale-6 text-2xs font-normal sm:text-xs">
|
|
||||||
{dayjs(slice.points[0].data.xFormatted).format('h:mm A')}
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
</div>
|
|
||||||
{/* ))} */}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
></ResponsiveLine>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
export function getPoints(
|
|
||||||
line: 'value' | 'posProfit' | 'negProfit',
|
|
||||||
portfolioHistory: PortfolioMetrics[]
|
|
||||||
) {
|
|
||||||
const points = portfolioHistory.map((p) => {
|
|
||||||
const { timestamp, balance, investmentValue, totalDeposits } = p
|
|
||||||
const value = balance + investmentValue
|
|
||||||
|
|
||||||
const profit = value - totalDeposits
|
|
||||||
let posProfit = null
|
|
||||||
let negProfit = null
|
|
||||||
if (profit < 0) {
|
|
||||||
negProfit = profit
|
|
||||||
} else {
|
|
||||||
posProfit = profit
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: new Date(timestamp),
|
|
||||||
y:
|
|
||||||
line === 'value' ? value : line === 'posProfit' ? posProfit : negProfit,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return points
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,29 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { last } from 'lodash'
|
import { last } from 'lodash'
|
||||||
import { memo, useRef, useState } from 'react'
|
import { memo, useState } from 'react'
|
||||||
import { usePortfolioHistory } from 'web/hooks/use-portfolio-history'
|
import { usePortfolioHistory } from 'web/hooks/use-portfolio-history'
|
||||||
import { Period } from 'web/lib/firebase/users'
|
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { PortfolioValueGraph } from './portfolio-value-graph'
|
import { GraphMode, PortfolioGraph } from './portfolio-value-graph'
|
||||||
|
import { SizedContainer } from 'web/components/sized-container'
|
||||||
|
|
||||||
export const PortfolioValueSection = memo(
|
export const PortfolioValueSection = memo(
|
||||||
function PortfolioValueSection(props: { userId: string }) {
|
function PortfolioValueSection(props: { userId: string }) {
|
||||||
const { userId } = props
|
const { userId } = props
|
||||||
|
|
||||||
const [portfolioPeriod, setPortfolioPeriod] = useState<Period>('weekly')
|
const portfolioHistory = usePortfolioHistory(userId, 'allTime')
|
||||||
const portfolioHistory = usePortfolioHistory(userId, portfolioPeriod)
|
const [graphMode, setGraphMode] = useState<GraphMode>('profit')
|
||||||
const [graphMode, setGraphMode] = useState<'profit' | 'value'>('profit')
|
|
||||||
const [graphDisplayNumber, setGraphDisplayNumber] = useState<
|
const [graphDisplayNumber, setGraphDisplayNumber] = useState<
|
||||||
number | string | null
|
number | string | null
|
||||||
>(null)
|
>(null)
|
||||||
const handleGraphDisplayChange = (num: string | number | null) => {
|
const handleGraphDisplayChange = (p: { y: number } | undefined) => {
|
||||||
setGraphDisplayNumber(num)
|
console.log(p)
|
||||||
|
setGraphDisplayNumber(p != null ? formatMoney(p.y) : null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember the last defined portfolio history.
|
const lastPortfolioMetrics = last(portfolioHistory)
|
||||||
const portfolioRef = useRef(portfolioHistory)
|
if (!portfolioHistory || !lastPortfolioMetrics) {
|
||||||
if (portfolioHistory) portfolioRef.current = portfolioHistory
|
|
||||||
const currPortfolioHistory = portfolioRef.current
|
|
||||||
|
|
||||||
const lastPortfolioMetrics = last(currPortfolioHistory)
|
|
||||||
if (!currPortfolioHistory || !lastPortfolioMetrics) {
|
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +41,10 @@ export const PortfolioValueSection = memo(
|
||||||
? 'cursor-pointer opacity-40 hover:opacity-80'
|
? 'cursor-pointer opacity-40 hover:opacity-80'
|
||||||
: ''
|
: ''
|
||||||
)}
|
)}
|
||||||
onClick={() => setGraphMode('profit')}
|
onClick={() => {
|
||||||
|
setGraphMode('profit')
|
||||||
|
setGraphDisplayNumber(null)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="text-greyscale-6 text-xs sm:text-sm">Profit</div>
|
<div className="text-greyscale-6 text-xs sm:text-sm">Profit</div>
|
||||||
<div
|
<div
|
||||||
|
@ -78,7 +76,10 @@ export const PortfolioValueSection = memo(
|
||||||
'cursor-pointer',
|
'cursor-pointer',
|
||||||
graphMode != 'value' ? 'opacity-40 hover:opacity-80' : ''
|
graphMode != 'value' ? 'opacity-40 hover:opacity-80' : ''
|
||||||
)}
|
)}
|
||||||
onClick={() => setGraphMode('value')}
|
onClick={() => {
|
||||||
|
setGraphMode('value')
|
||||||
|
setGraphDisplayNumber(null)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="text-greyscale-6 text-xs sm:text-sm">
|
<div className="text-greyscale-6 text-xs sm:text-sm">
|
||||||
Portfolio value
|
Portfolio value
|
||||||
|
@ -93,56 +94,18 @@ export const PortfolioValueSection = memo(
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Row>
|
</Row>
|
||||||
<PortfolioValueGraph
|
<SizedContainer fullHeight={200} mobileHeight={100}>
|
||||||
portfolioHistory={currPortfolioHistory}
|
{(width, height) => (
|
||||||
mode={graphMode}
|
<PortfolioGraph
|
||||||
handleGraphDisplayChange={handleGraphDisplayChange}
|
mode={graphMode}
|
||||||
/>
|
history={portfolioHistory}
|
||||||
<PortfolioPeriodSelection
|
width={width}
|
||||||
portfolioPeriod={portfolioPeriod}
|
height={height}
|
||||||
setPortfolioPeriod={setPortfolioPeriod}
|
onMouseOver={handleGraphDisplayChange}
|
||||||
className="border-greyscale-2 mt-2 gap-4 border-b"
|
/>
|
||||||
selectClassName="text-indigo-600 text-bold border-b border-indigo-600"
|
)}
|
||||||
/>
|
</SizedContainer>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export function PortfolioPeriodSelection(props: {
|
|
||||||
setPortfolioPeriod: (string: any) => void
|
|
||||||
portfolioPeriod: string
|
|
||||||
className?: string
|
|
||||||
selectClassName?: string
|
|
||||||
}) {
|
|
||||||
const { setPortfolioPeriod, portfolioPeriod, className, selectClassName } =
|
|
||||||
props
|
|
||||||
return (
|
|
||||||
<Row className={clsx(className, 'text-greyscale-4')}>
|
|
||||||
<button
|
|
||||||
className={clsx(portfolioPeriod === 'daily' ? selectClassName : '')}
|
|
||||||
onClick={() => setPortfolioPeriod('daily' as Period)}
|
|
||||||
>
|
|
||||||
1D
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={clsx(portfolioPeriod === 'weekly' ? selectClassName : '')}
|
|
||||||
onClick={() => setPortfolioPeriod('weekly' as Period)}
|
|
||||||
>
|
|
||||||
1W
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={clsx(portfolioPeriod === 'monthly' ? selectClassName : '')}
|
|
||||||
onClick={() => setPortfolioPeriod('monthly' as Period)}
|
|
||||||
>
|
|
||||||
1M
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={clsx(portfolioPeriod === 'allTime' ? selectClassName : '')}
|
|
||||||
onClick={() => setPortfolioPeriod('allTime' as Period)}
|
|
||||||
>
|
|
||||||
ALL
|
|
||||||
</button>
|
|
||||||
</Row>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -23,9 +23,6 @@
|
||||||
"@floating-ui/react-dom-interactions": "0.9.2",
|
"@floating-ui/react-dom-interactions": "0.9.2",
|
||||||
"@headlessui/react": "1.6.1",
|
"@headlessui/react": "1.6.1",
|
||||||
"@heroicons/react": "1.0.6",
|
"@heroicons/react": "1.0.6",
|
||||||
"@nivo/core": "0.80.0",
|
|
||||||
"@nivo/line": "0.80.0",
|
|
||||||
"@nivo/tooltip": "0.80.0",
|
|
||||||
"@react-query-firebase/firestore": "0.4.2",
|
"@react-query-firebase/firestore": "0.4.2",
|
||||||
"@tiptap/core": "2.0.0-beta.182",
|
"@tiptap/core": "2.0.0-beta.182",
|
||||||
"@tiptap/extension-character-count": "2.0.0-beta.31",
|
"@tiptap/extension-character-count": "2.0.0-beta.31",
|
||||||
|
|
239
yarn.lock
239
yarn.lock
|
@ -2553,103 +2553,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.1.tgz#27d71a95247a9eaee03d47adee7e3bd594514136"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.1.tgz#27d71a95247a9eaee03d47adee7e3bd594514136"
|
||||||
integrity sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA==
|
integrity sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA==
|
||||||
|
|
||||||
"@nivo/annotations@0.80.0":
|
|
||||||
version "0.80.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/annotations/-/annotations-0.80.0.tgz#127e4801fff7370dcfb9acfe1e335781dd65cfd5"
|
|
||||||
integrity sha512-bC9z0CLjU07LULTMWsqpjovRtHxP7n8oJjqBQBLmHOGB4IfiLbrryBfu9+aEZH3VN2jXHhdpWUz+HxeZzOzsLg==
|
|
||||||
dependencies:
|
|
||||||
"@nivo/colors" "0.80.0"
|
|
||||||
"@react-spring/web" "9.4.5"
|
|
||||||
lodash "^4.17.21"
|
|
||||||
|
|
||||||
"@nivo/axes@0.80.0":
|
|
||||||
version "0.80.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/axes/-/axes-0.80.0.tgz#22788855ddc45bb6a619dcd03d62d4bd8c0fc35f"
|
|
||||||
integrity sha512-AsUyaSHGwQVSEK8QXpsn8X+poZxvakLMYW7crKY1xTGPNw+SU4SSBohPVumm2jMH3fTSLNxLhAjWo71GBJXfdA==
|
|
||||||
dependencies:
|
|
||||||
"@nivo/scales" "0.80.0"
|
|
||||||
"@react-spring/web" "9.4.5"
|
|
||||||
d3-format "^1.4.4"
|
|
||||||
d3-time-format "^3.0.0"
|
|
||||||
|
|
||||||
"@nivo/colors@0.80.0":
|
|
||||||
version "0.80.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/colors/-/colors-0.80.0.tgz#5b70b4979df246d9d0d69fb638bba9764dd88b52"
|
|
||||||
integrity sha512-T695Zr411FU4RPo7WDINOAn8f79DPP10SFJmDdEqELE+cbzYVTpXqLGZ7JMv88ko7EOf9qxLQgcBqY69rp9tHQ==
|
|
||||||
dependencies:
|
|
||||||
d3-color "^2.0.0"
|
|
||||||
d3-scale "^3.2.3"
|
|
||||||
d3-scale-chromatic "^2.0.0"
|
|
||||||
lodash "^4.17.21"
|
|
||||||
|
|
||||||
"@nivo/core@0.80.0":
|
|
||||||
version "0.80.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/core/-/core-0.80.0.tgz#d180cb2622158eb7bc5f984131ff07984f12297e"
|
|
||||||
integrity sha512-6caih0RavXdWWSfde+rC2pk17WrX9YQlqK26BrxIdXzv3Ydzlh5SkrC7dR2TEvMGBhunzVeLOfiC2DWT1S8CFg==
|
|
||||||
dependencies:
|
|
||||||
"@nivo/recompose" "0.80.0"
|
|
||||||
"@react-spring/web" "9.4.5"
|
|
||||||
d3-color "^2.0.0"
|
|
||||||
d3-format "^1.4.4"
|
|
||||||
d3-interpolate "^2.0.1"
|
|
||||||
d3-scale "^3.2.3"
|
|
||||||
d3-scale-chromatic "^2.0.0"
|
|
||||||
d3-shape "^1.3.5"
|
|
||||||
d3-time-format "^3.0.0"
|
|
||||||
lodash "^4.17.21"
|
|
||||||
|
|
||||||
"@nivo/legends@0.80.0":
|
|
||||||
version "0.80.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/legends/-/legends-0.80.0.tgz#49edc54000075b4df055f86794a8c32810269d06"
|
|
||||||
integrity sha512-h0IUIPGygpbKIZZZWIxkkxOw4SO0rqPrqDrykjaoQz4CvL4HtLIUS3YRA4akKOVNZfS5agmImjzvIe0s3RvqlQ==
|
|
||||||
|
|
||||||
"@nivo/line@0.80.0":
|
|
||||||
version "0.80.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/line/-/line-0.80.0.tgz#ba541b0fcfd53b3a7ce865feb43c993b7cf4a7d4"
|
|
||||||
integrity sha512-6UAD/y74qq3DDRnVb+QUPvXYojxMtwXMipGSNvCGk8omv1QZNTaUrbV+eQacvn9yh//a0yZcWipnpq0tGJyJCA==
|
|
||||||
dependencies:
|
|
||||||
"@nivo/annotations" "0.80.0"
|
|
||||||
"@nivo/axes" "0.80.0"
|
|
||||||
"@nivo/colors" "0.80.0"
|
|
||||||
"@nivo/legends" "0.80.0"
|
|
||||||
"@nivo/scales" "0.80.0"
|
|
||||||
"@nivo/tooltip" "0.80.0"
|
|
||||||
"@nivo/voronoi" "0.80.0"
|
|
||||||
"@react-spring/web" "9.4.5"
|
|
||||||
d3-shape "^1.3.5"
|
|
||||||
|
|
||||||
"@nivo/recompose@0.80.0":
|
|
||||||
version "0.80.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/recompose/-/recompose-0.80.0.tgz#572048aed793321a0bada1fd176b72df5a25282e"
|
|
||||||
integrity sha512-iL3g7j3nJGD9+mRDbwNwt/IXDXH6E29mhShY1I7SP91xrfusZV9pSFf4EzyYgruNJk/2iqMuaqn+e+TVFra44A==
|
|
||||||
dependencies:
|
|
||||||
react-lifecycles-compat "^3.0.4"
|
|
||||||
|
|
||||||
"@nivo/scales@0.80.0":
|
|
||||||
version "0.80.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/scales/-/scales-0.80.0.tgz#39313fb97c8ae9633c2aa1e17adb57cb851e8a50"
|
|
||||||
integrity sha512-4y2pQdCg+f3n4TKXC2tYuq71veZM+xPRQbOTgGYJpuBvMc7pQsXF9T5z7ryeIG9hkpXkrlyjecU6XcAG7tLSNg==
|
|
||||||
dependencies:
|
|
||||||
d3-scale "^3.2.3"
|
|
||||||
d3-time "^1.0.11"
|
|
||||||
d3-time-format "^3.0.0"
|
|
||||||
lodash "^4.17.21"
|
|
||||||
|
|
||||||
"@nivo/tooltip@0.80.0":
|
|
||||||
version "0.80.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/tooltip/-/tooltip-0.80.0.tgz#07ebef47eb708a0612bd6297d5ad156bbec19d34"
|
|
||||||
integrity sha512-qGmrreRwnCsYjn/LAuwBtxBn/tvG8y+rwgd4gkANLBAoXd3bzJyvmkSe+QJPhUG64bq57ibDK+lO2pC48a3/fw==
|
|
||||||
dependencies:
|
|
||||||
"@react-spring/web" "9.4.5"
|
|
||||||
|
|
||||||
"@nivo/voronoi@0.80.0":
|
|
||||||
version "0.80.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/voronoi/-/voronoi-0.80.0.tgz#59cc7ed253dc1a5bbcf614a5ac37d2468d561599"
|
|
||||||
integrity sha512-zaJV3I3cRu1gHpsXCIEvp6GGlGY8P7D9CwAVCjYDGrz3W/+GKN0kA7qGyHTC97zVxJtfefxSPlP/GtOdxac+qw==
|
|
||||||
dependencies:
|
|
||||||
d3-delaunay "^5.3.0"
|
|
||||||
d3-scale "^3.2.3"
|
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.5":
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||||
|
@ -2744,52 +2647,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@react-query-firebase/firestore/-/firestore-0.4.2.tgz#6ae52768715aa0a5c0d903dd4fd953ed417ba635"
|
resolved "https://registry.yarnpkg.com/@react-query-firebase/firestore/-/firestore-0.4.2.tgz#6ae52768715aa0a5c0d903dd4fd953ed417ba635"
|
||||||
integrity sha512-7eYp905+sfBRcBTdj7W7BAc3bI3V0D0kKca4/juOTnN4gyoNyaCNOCjLPY467dTq325hGs7BX0ol7Pw3JENdHA==
|
integrity sha512-7eYp905+sfBRcBTdj7W7BAc3bI3V0D0kKca4/juOTnN4gyoNyaCNOCjLPY467dTq325hGs7BX0ol7Pw3JENdHA==
|
||||||
|
|
||||||
"@react-spring/animated@~9.4.5":
|
|
||||||
version "9.4.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.4.5.tgz#dd9921c716a4f4a3ed29491e0c0c9f8ca0eb1a54"
|
|
||||||
integrity sha512-KWqrtvJSMx6Fj9nMJkhTwM9r6LIriExDRV6YHZV9HKQsaolUFppgkOXpC+rsL1JEtEvKv6EkLLmSqHTnuYjiIA==
|
|
||||||
dependencies:
|
|
||||||
"@react-spring/shared" "~9.4.5"
|
|
||||||
"@react-spring/types" "~9.4.5"
|
|
||||||
|
|
||||||
"@react-spring/core@~9.4.5":
|
|
||||||
version "9.4.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.4.5.tgz#4616e1adc18dd10f5731f100ebdbe9518b89ba3c"
|
|
||||||
integrity sha512-83u3FzfQmGMJFwZLAJSwF24/ZJctwUkWtyPD7KYtNagrFeQKUH1I05ZuhmCmqW+2w1KDW1SFWQ43RawqfXKiiQ==
|
|
||||||
dependencies:
|
|
||||||
"@react-spring/animated" "~9.4.5"
|
|
||||||
"@react-spring/rafz" "~9.4.5"
|
|
||||||
"@react-spring/shared" "~9.4.5"
|
|
||||||
"@react-spring/types" "~9.4.5"
|
|
||||||
|
|
||||||
"@react-spring/rafz@~9.4.5":
|
|
||||||
version "9.4.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.4.5.tgz#84f809f287f2a66bbfbc66195db340482f886bd7"
|
|
||||||
integrity sha512-swGsutMwvnoyTRxvqhfJBtGM8Ipx6ks0RkIpNX9F/U7XmyPvBMGd3GgX/mqxZUpdlsuI1zr/jiYw+GXZxAlLcQ==
|
|
||||||
|
|
||||||
"@react-spring/shared@~9.4.5":
|
|
||||||
version "9.4.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.4.5.tgz#4c3ad817bca547984fb1539204d752a412a6d829"
|
|
||||||
integrity sha512-JhMh3nFKsqyag0KM5IIM8BQANGscTdd0mMv3BXsUiMZrcjQTskyfnv5qxEeGWbJGGar52qr5kHuBHtCjQOzniA==
|
|
||||||
dependencies:
|
|
||||||
"@react-spring/rafz" "~9.4.5"
|
|
||||||
"@react-spring/types" "~9.4.5"
|
|
||||||
|
|
||||||
"@react-spring/types@~9.4.5":
|
|
||||||
version "9.4.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.4.5.tgz#9c71e5ff866b5484a7ef3db822bf6c10e77bdd8c"
|
|
||||||
integrity sha512-mpRIamoHwql0ogxEUh9yr4TP0xU5CWyZxVQeccGkHHF8kPMErtDXJlxyo0lj+telRF35XNihtPTWoflqtyARmg==
|
|
||||||
|
|
||||||
"@react-spring/web@9.4.5":
|
|
||||||
version "9.4.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.4.5.tgz#b92f05b87cdc0963a59ee149e677dcaff09f680e"
|
|
||||||
integrity sha512-NGAkOtKmOzDEctL7MzRlQGv24sRce++0xAY7KlcxmeVkR7LRSGkoXHaIfm9ObzxPMcPHQYQhf3+X9jepIFNHQA==
|
|
||||||
dependencies:
|
|
||||||
"@react-spring/animated" "~9.4.5"
|
|
||||||
"@react-spring/core" "~9.4.5"
|
|
||||||
"@react-spring/shared" "~9.4.5"
|
|
||||||
"@react-spring/types" "~9.4.5"
|
|
||||||
|
|
||||||
"@rushstack/eslint-patch@^1.1.3":
|
"@rushstack/eslint-patch@^1.1.3":
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz#6801033be7ff87a6b7cadaf5b337c9f366a3c4b0"
|
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz#6801033be7ff87a6b7cadaf5b337c9f366a3c4b0"
|
||||||
|
@ -5450,13 +5307,6 @@ csstype@^3.0.2, csstype@^3.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
|
||||||
integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==
|
integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==
|
||||||
|
|
||||||
d3-array@2, d3-array@^2.3.0:
|
|
||||||
version "2.12.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81"
|
|
||||||
integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==
|
|
||||||
dependencies:
|
|
||||||
internmap "^1.0.0"
|
|
||||||
|
|
||||||
"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@3.2.0:
|
"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@3.2.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.0.tgz#15bf96cd9b7333e02eb8de8053d78962eafcff14"
|
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.0.tgz#15bf96cd9b7333e02eb8de8053d78962eafcff14"
|
||||||
|
@ -5480,23 +5330,11 @@ d3-brush@3.0.0:
|
||||||
d3-selection "3"
|
d3-selection "3"
|
||||||
d3-transition "3"
|
d3-transition "3"
|
||||||
|
|
||||||
"d3-color@1 - 2", d3-color@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e"
|
|
||||||
integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==
|
|
||||||
|
|
||||||
"d3-color@1 - 3":
|
"d3-color@1 - 3":
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
|
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
|
||||||
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
|
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
|
||||||
|
|
||||||
d3-delaunay@^5.3.0:
|
|
||||||
version "5.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-5.3.0.tgz#b47f05c38f854a4e7b3cea80e0bb12e57398772d"
|
|
||||||
integrity sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==
|
|
||||||
dependencies:
|
|
||||||
delaunator "4"
|
|
||||||
|
|
||||||
"d3-dispatch@1 - 3":
|
"d3-dispatch@1 - 3":
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
|
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
|
||||||
|
@ -5515,28 +5353,11 @@ d3-delaunay@^5.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
|
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
|
||||||
integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
|
integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
|
||||||
|
|
||||||
"d3-format@1 - 2":
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767"
|
|
||||||
integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==
|
|
||||||
|
|
||||||
"d3-format@1 - 3":
|
"d3-format@1 - 3":
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
|
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
|
||||||
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
|
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
|
||||||
|
|
||||||
d3-format@^1.4.4:
|
|
||||||
version "1.4.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
|
|
||||||
integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==
|
|
||||||
|
|
||||||
"d3-interpolate@1 - 2", "d3-interpolate@1.2.0 - 2", d3-interpolate@^2.0.1:
|
|
||||||
version "2.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163"
|
|
||||||
integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==
|
|
||||||
dependencies:
|
|
||||||
d3-color "1 - 2"
|
|
||||||
|
|
||||||
"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3":
|
"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3":
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
|
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
|
||||||
|
@ -5544,24 +5365,11 @@ d3-format@^1.4.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-color "1 - 3"
|
d3-color "1 - 3"
|
||||||
|
|
||||||
d3-path@1:
|
|
||||||
version "1.0.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
|
|
||||||
integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
|
|
||||||
|
|
||||||
"d3-path@1 - 3":
|
"d3-path@1 - 3":
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e"
|
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e"
|
||||||
integrity sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==
|
integrity sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==
|
||||||
|
|
||||||
d3-scale-chromatic@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz#c13f3af86685ff91323dc2f0ebd2dabbd72d8bab"
|
|
||||||
integrity sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==
|
|
||||||
dependencies:
|
|
||||||
d3-color "1 - 2"
|
|
||||||
d3-interpolate "1 - 2"
|
|
||||||
|
|
||||||
d3-scale@4.0.2:
|
d3-scale@4.0.2:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
|
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
|
||||||
|
@ -5573,17 +5381,6 @@ d3-scale@4.0.2:
|
||||||
d3-time "2.1.1 - 3"
|
d3-time "2.1.1 - 3"
|
||||||
d3-time-format "2 - 4"
|
d3-time-format "2 - 4"
|
||||||
|
|
||||||
d3-scale@^3.2.3:
|
|
||||||
version "3.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.3.0.tgz#28c600b29f47e5b9cd2df9749c206727966203f3"
|
|
||||||
integrity sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==
|
|
||||||
dependencies:
|
|
||||||
d3-array "^2.3.0"
|
|
||||||
d3-format "1 - 2"
|
|
||||||
d3-interpolate "1.2.0 - 2"
|
|
||||||
d3-time "^2.1.1"
|
|
||||||
d3-time-format "2 - 3"
|
|
||||||
|
|
||||||
d3-selection@3, d3-selection@3.0.0:
|
d3-selection@3, d3-selection@3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
|
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
|
||||||
|
@ -5596,20 +5393,6 @@ d3-shape@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-path "1 - 3"
|
d3-path "1 - 3"
|
||||||
|
|
||||||
d3-shape@^1.3.5:
|
|
||||||
version "1.3.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
|
|
||||||
integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==
|
|
||||||
dependencies:
|
|
||||||
d3-path "1"
|
|
||||||
|
|
||||||
"d3-time-format@2 - 3", d3-time-format@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6"
|
|
||||||
integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==
|
|
||||||
dependencies:
|
|
||||||
d3-time "1 - 2"
|
|
||||||
|
|
||||||
"d3-time-format@2 - 4":
|
"d3-time-format@2 - 4":
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
|
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
|
||||||
|
@ -5617,13 +5400,6 @@ d3-shape@^1.3.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-time "1 - 3"
|
d3-time "1 - 3"
|
||||||
|
|
||||||
"d3-time@1 - 2", d3-time@^2.1.1:
|
|
||||||
version "2.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682"
|
|
||||||
integrity sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==
|
|
||||||
dependencies:
|
|
||||||
d3-array "2"
|
|
||||||
|
|
||||||
"d3-time@1 - 3", "d3-time@2.1.1 - 3":
|
"d3-time@1 - 3", "d3-time@2.1.1 - 3":
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975"
|
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975"
|
||||||
|
@ -5631,11 +5407,6 @@ d3-shape@^1.3.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-array "2 - 3"
|
d3-array "2 - 3"
|
||||||
|
|
||||||
d3-time@^1.0.11:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1"
|
|
||||||
integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==
|
|
||||||
|
|
||||||
"d3-timer@1 - 3":
|
"d3-timer@1 - 3":
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
|
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
|
||||||
|
@ -5790,11 +5561,6 @@ del@^6.0.0:
|
||||||
rimraf "^3.0.2"
|
rimraf "^3.0.2"
|
||||||
slash "^3.0.0"
|
slash "^3.0.0"
|
||||||
|
|
||||||
delaunator@4:
|
|
||||||
version "4.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-4.0.1.tgz#3d779687f57919a7a418f8ab947d3bddb6846957"
|
|
||||||
integrity sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==
|
|
||||||
|
|
||||||
delayed-stream@~1.0.0:
|
delayed-stream@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
|
@ -7855,11 +7621,6 @@ internal-slot@^1.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
|
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
|
||||||
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
||||||
|
|
||||||
internmap@^1.0.0:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95"
|
|
||||||
integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==
|
|
||||||
|
|
||||||
interpret@^1.0.0:
|
interpret@^1.0.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
|
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user