Carry bet data down into charts
This commit is contained in:
parent
c7221d1026
commit
0df34473fe
|
@ -20,6 +20,7 @@ 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),
|
||||||
y: b.probAfter,
|
y: b.probAfter,
|
||||||
|
datum: b,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,28 +92,13 @@ const getTrackedAnswers = (
|
||||||
).slice(0, topN)
|
).slice(0, topN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStartPoint = (answers: Answer[], start: Date) => {
|
|
||||||
return { x: start, y: answers.map((_) => 0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
const getEndPoint = (
|
|
||||||
answers: Answer[],
|
|
||||||
contract: FreeResponseContract | MultipleChoiceContract,
|
|
||||||
end: Date
|
|
||||||
) => {
|
|
||||||
return {
|
|
||||||
x: end,
|
|
||||||
y: answers.map((a) => getOutcomeProbability(contract, a.id)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getBetPoints = (answers: Answer[], bets: Bet[]) => {
|
const getBetPoints = (answers: Answer[], bets: Bet[]) => {
|
||||||
const sortedBets = sortBy(bets, (b) => b.createdTime)
|
const sortedBets = sortBy(bets, (b) => b.createdTime)
|
||||||
const betsByOutcome = groupBy(sortedBets, (bet) => bet.outcome)
|
const betsByOutcome = groupBy(sortedBets, (bet) => bet.outcome)
|
||||||
const sharesByOutcome = Object.fromEntries(
|
const sharesByOutcome = Object.fromEntries(
|
||||||
Object.keys(betsByOutcome).map((outcome) => [outcome, 0])
|
Object.keys(betsByOutcome).map((outcome) => [outcome, 0])
|
||||||
)
|
)
|
||||||
const points: MultiPoint[] = []
|
const points: MultiPoint<Bet>[] = []
|
||||||
for (const bet of sortedBets) {
|
for (const bet of sortedBets) {
|
||||||
const { outcome, shares } = bet
|
const { outcome, shares } = bet
|
||||||
sharesByOutcome[outcome] += shares
|
sharesByOutcome[outcome] += shares
|
||||||
|
@ -124,6 +109,7 @@ const getBetPoints = (answers: Answer[], bets: Bet[]) => {
|
||||||
points.push({
|
points.push({
|
||||||
x: new Date(bet.createdTime),
|
x: new Date(bet.createdTime),
|
||||||
y: answers.map((a) => sharesByOutcome[a.id] ** 2 / sharesSquared),
|
y: answers.map((a) => sharesByOutcome[a.id] ** 2 / sharesSquared),
|
||||||
|
datum: bet,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return points
|
return points
|
||||||
|
@ -135,7 +121,7 @@ export const ChoiceContractChart = (props: {
|
||||||
height?: number
|
height?: number
|
||||||
}) => {
|
}) => {
|
||||||
const { contract, bets } = props
|
const { contract, bets } = props
|
||||||
const [contractStart, contractEnd] = getDateRange(contract)
|
const [start, end] = getDateRange(contract)
|
||||||
const answers = useMemo(
|
const answers = useMemo(
|
||||||
() => getTrackedAnswers(contract, CATEGORY_COLORS.length),
|
() => getTrackedAnswers(contract, CATEGORY_COLORS.length),
|
||||||
[contract]
|
[contract]
|
||||||
|
@ -143,18 +129,21 @@ export const ChoiceContractChart = (props: {
|
||||||
const betPoints = useMemo(() => getBetPoints(answers, bets), [answers, bets])
|
const betPoints = useMemo(() => getBetPoints(answers, bets), [answers, bets])
|
||||||
const data = useMemo(
|
const data = useMemo(
|
||||||
() => [
|
() => [
|
||||||
getStartPoint(answers, contractStart),
|
{ x: start, y: answers.map((_) => 0) },
|
||||||
...betPoints,
|
...betPoints,
|
||||||
getEndPoint(answers, contract, contractEnd ?? MAX_DATE),
|
{
|
||||||
|
x: end ?? MAX_DATE,
|
||||||
|
y: answers.map((a) => getOutcomeProbability(contract, a.id)),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[answers, contract, betPoints, contractStart, contractEnd]
|
[answers, contract, betPoints, start, end]
|
||||||
)
|
)
|
||||||
const rightmostDate = getRightmostVisibleDate(
|
const rightmostDate = getRightmostVisibleDate(
|
||||||
contractEnd,
|
end,
|
||||||
last(betPoints)?.x,
|
last(betPoints)?.x,
|
||||||
new Date(Date.now())
|
new Date(Date.now())
|
||||||
)
|
)
|
||||||
const visibleRange = [contractStart, rightmostDate]
|
const visibleRange = [start, rightmostDate]
|
||||||
const isMobile = useIsMobile(800)
|
const isMobile = useIsMobile(800)
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
const width = useElementWidth(containerRef) ?? 0
|
const width = useElementWidth(containerRef) ?? 0
|
||||||
|
|
|
@ -32,6 +32,7 @@ const getBetPoints = (bets: Bet[], scaleP: (p: number) => number) => {
|
||||||
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),
|
||||||
y: scaleP(b.probAfter),
|
y: scaleP(b.probAfter),
|
||||||
|
datum: b,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,11 @@ import { formatLargeNumber } from 'common/util/format'
|
||||||
import { useEvent } from 'web/hooks/use-event'
|
import { useEvent } from 'web/hooks/use-event'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
|
|
||||||
export type MultiPoint = { x: Date; y: number[] }
|
export type MultiPoint<T = unknown> = { x: Date; y: number[]; datum?: T }
|
||||||
export type HistoryPoint = { x: Date; y: number }
|
export type HistoryPoint<T = unknown> = { x: Date; y: number; datum?: T }
|
||||||
export type DistributionPoint = { x: number; y: number }
|
export type DistributionPoint<T = unknown> = { x: number; y: number; datum?: T }
|
||||||
export type PositionValue<P> = TooltipPosition & { p: P }
|
|
||||||
|
type PositionValue<P> = TooltipPosition & { p: P }
|
||||||
|
|
||||||
const formatPct = (n: number, digits?: number) => {
|
const formatPct = (n: number, digits?: number) => {
|
||||||
return `${(n * 100).toFixed(digits ?? 0)}%`
|
return `${(n * 100).toFixed(digits ?? 0)}%`
|
||||||
|
@ -100,8 +101,8 @@ const Legend = (props: { className?: string; items: LegendItem[] }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SingleValueDistributionChart = (props: {
|
export const SingleValueDistributionChart = <T = unknown,>(props: {
|
||||||
data: DistributionPoint[]
|
data: DistributionPoint<T>[]
|
||||||
w: number
|
w: number
|
||||||
h: number
|
h: number
|
||||||
color: string
|
color: string
|
||||||
|
@ -115,13 +116,13 @@ export const SingleValueDistributionChart = (props: {
|
||||||
const [viewXScale, setViewXScale] =
|
const [viewXScale, setViewXScale] =
|
||||||
useState<ScaleContinuousNumeric<number, number>>()
|
useState<ScaleContinuousNumeric<number, number>>()
|
||||||
const [mouseState, setMouseState] =
|
const [mouseState, setMouseState] =
|
||||||
useState<PositionValue<DistributionPoint>>()
|
useState<PositionValue<DistributionPoint<T>>>()
|
||||||
const xScale = viewXScale ?? props.xScale
|
const xScale = viewXScale ?? props.xScale
|
||||||
|
|
||||||
const px = useCallback((p: DistributionPoint) => xScale(p.x), [xScale])
|
const px = useCallback((p: DistributionPoint<T>) => xScale(p.x), [xScale])
|
||||||
const py0 = yScale(yScale.domain()[0])
|
const py0 = yScale(yScale.domain()[0])
|
||||||
const py1 = useCallback((p: DistributionPoint) => yScale(p.y), [yScale])
|
const py1 = useCallback((p: DistributionPoint<T>) => yScale(p.y), [yScale])
|
||||||
const xBisector = bisector((p: DistributionPoint) => p.x)
|
const xBisector = bisector((p: DistributionPoint<T>) => p.x)
|
||||||
|
|
||||||
const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => {
|
const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => {
|
||||||
const fmtX = (n: number) => formatLargeNumber(n)
|
const fmtX = (n: number) => formatLargeNumber(n)
|
||||||
|
@ -131,7 +132,7 @@ export const SingleValueDistributionChart = (props: {
|
||||||
return { fmtX, fmtY, xAxis, yAxis }
|
return { fmtX, fmtY, xAxis, yAxis }
|
||||||
}, [w, xScale, yScale])
|
}, [w, xScale, yScale])
|
||||||
|
|
||||||
const onSelect = useEvent((ev: D3BrushEvent<DistributionPoint>) => {
|
const onSelect = useEvent((ev: D3BrushEvent<DistributionPoint<T>>) => {
|
||||||
if (ev.selection) {
|
if (ev.selection) {
|
||||||
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
||||||
setViewXScale(() =>
|
setViewXScale(() =>
|
||||||
|
@ -154,7 +155,7 @@ export const SingleValueDistributionChart = (props: {
|
||||||
// so your queryX is out of bounds
|
// so your queryX is out of bounds
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const p = { x: queryX, y: item.y }
|
const p = { x: queryX, y: item.y, datum: item.datum }
|
||||||
setMouseState({ top: mouseY - 10, left: mouseX + 60, p })
|
setMouseState({ top: mouseY - 10, left: mouseX + 60, p })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -192,8 +193,8 @@ export const SingleValueDistributionChart = (props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MultiValueHistoryChart = (props: {
|
export const MultiValueHistoryChart = <T = unknown,>(props: {
|
||||||
data: MultiPoint[]
|
data: MultiPoint<T>[]
|
||||||
w: number
|
w: number
|
||||||
h: number
|
h: number
|
||||||
labels: readonly string[]
|
labels: readonly string[]
|
||||||
|
@ -205,14 +206,14 @@ export const MultiValueHistoryChart = (props: {
|
||||||
const { colors, data, yScale, labels, w, h, pct } = props
|
const { colors, data, yScale, labels, w, h, pct } = props
|
||||||
|
|
||||||
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||||
const [mouseState, setMouseState] = useState<PositionValue<MultiPoint>>()
|
const [mouseState, setMouseState] = useState<PositionValue<MultiPoint<T>>>()
|
||||||
const xScale = viewXScale ?? props.xScale
|
const xScale = viewXScale ?? props.xScale
|
||||||
|
|
||||||
type SP = SeriesPoint<MultiPoint>
|
type SP = SeriesPoint<MultiPoint<T>>
|
||||||
const px = useCallback((p: SP) => xScale(p.data.x), [xScale])
|
const px = useCallback((p: SP) => xScale(p.data.x), [xScale])
|
||||||
const py0 = useCallback((p: SP) => yScale(p[0]), [yScale])
|
const py0 = useCallback((p: SP) => yScale(p[0]), [yScale])
|
||||||
const py1 = useCallback((p: SP) => yScale(p[1]), [yScale])
|
const py1 = useCallback((p: SP) => yScale(p[1]), [yScale])
|
||||||
const xBisector = bisector((p: MultiPoint) => p.x)
|
const xBisector = bisector((p: MultiPoint<T>) => p.x)
|
||||||
|
|
||||||
const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => {
|
const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => {
|
||||||
const [start, end] = xScale.domain()
|
const [start, end] = xScale.domain()
|
||||||
|
@ -230,14 +231,14 @@ export const MultiValueHistoryChart = (props: {
|
||||||
}, [w, h, pct, xScale, yScale])
|
}, [w, h, pct, xScale, yScale])
|
||||||
|
|
||||||
const series = useMemo(() => {
|
const series = useMemo(() => {
|
||||||
const d3Stack = stack<MultiPoint, number>()
|
const d3Stack = stack<MultiPoint<T>, number>()
|
||||||
.keys(range(0, labels.length))
|
.keys(range(0, labels.length))
|
||||||
.value(({ y }, o) => y[o])
|
.value(({ y }, o) => y[o])
|
||||||
.order(stackOrderReverse)
|
.order(stackOrderReverse)
|
||||||
return d3Stack(data)
|
return d3Stack(data)
|
||||||
}, [data, labels.length])
|
}, [data, labels.length])
|
||||||
|
|
||||||
const onSelect = useEvent((ev: D3BrushEvent<MultiPoint>) => {
|
const onSelect = useEvent((ev: D3BrushEvent<MultiPoint<T>>) => {
|
||||||
if (ev.selection) {
|
if (ev.selection) {
|
||||||
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
||||||
setViewXScale(() =>
|
setViewXScale(() =>
|
||||||
|
@ -260,7 +261,7 @@ export const MultiValueHistoryChart = (props: {
|
||||||
// so your queryX is out of bounds
|
// so your queryX is out of bounds
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const p = { x: queryX, y: item.y }
|
const p = { x: queryX, y: item.y, datum: item.datum }
|
||||||
setMouseState({ top: mouseY - 10, left: mouseX + 60, p })
|
setMouseState({ top: mouseY - 10, left: mouseX + 60, p })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -313,8 +314,8 @@ export const MultiValueHistoryChart = (props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SingleValueHistoryChart = (props: {
|
export const SingleValueHistoryChart = <T = unknown,>(props: {
|
||||||
data: HistoryPoint[]
|
data: HistoryPoint<T>[]
|
||||||
w: number
|
w: number
|
||||||
h: number
|
h: number
|
||||||
color: string
|
color: string
|
||||||
|
@ -325,13 +326,13 @@ export const SingleValueHistoryChart = (props: {
|
||||||
const { color, data, pct, yScale, w, h } = props
|
const { color, data, pct, yScale, w, h } = props
|
||||||
|
|
||||||
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||||
const [mouseState, setMouseState] = useState<PositionValue<HistoryPoint>>()
|
const [mouseState, setMouseState] = useState<PositionValue<HistoryPoint<T>>>()
|
||||||
const xScale = viewXScale ?? props.xScale
|
const xScale = viewXScale ?? props.xScale
|
||||||
|
|
||||||
const px = useCallback((p: HistoryPoint) => xScale(p.x), [xScale])
|
const px = useCallback((p: HistoryPoint<T>) => xScale(p.x), [xScale])
|
||||||
const py0 = yScale(yScale.domain()[0])
|
const py0 = yScale(yScale.domain()[0])
|
||||||
const py1 = useCallback((p: HistoryPoint) => yScale(p.y), [yScale])
|
const py1 = useCallback((p: HistoryPoint<T>) => yScale(p.y), [yScale])
|
||||||
const xBisector = bisector((p: HistoryPoint) => p.x)
|
const xBisector = bisector((p: HistoryPoint<T>) => p.x)
|
||||||
|
|
||||||
const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => {
|
const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => {
|
||||||
const [start, end] = xScale.domain()
|
const [start, end] = xScale.domain()
|
||||||
|
@ -347,7 +348,7 @@ export const SingleValueHistoryChart = (props: {
|
||||||
return { fmtX, fmtY, xAxis, yAxis }
|
return { fmtX, fmtY, xAxis, yAxis }
|
||||||
}, [w, h, pct, xScale, yScale])
|
}, [w, h, pct, xScale, yScale])
|
||||||
|
|
||||||
const onSelect = useEvent((ev: D3BrushEvent<HistoryPoint>) => {
|
const onSelect = useEvent((ev: D3BrushEvent<HistoryPoint<T>>) => {
|
||||||
if (ev.selection) {
|
if (ev.selection) {
|
||||||
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
||||||
setViewXScale(() =>
|
setViewXScale(() =>
|
||||||
|
@ -370,7 +371,7 @@ export const SingleValueHistoryChart = (props: {
|
||||||
// so your queryX is out of bounds
|
// so your queryX is out of bounds
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const p = { x: queryX, y: item.y }
|
const p = { x: queryX, y: item.y, datum: item.datum }
|
||||||
setMouseState({ top: mouseY - 10, left: mouseX + 60, p })
|
setMouseState({ top: mouseY - 10, left: mouseX + 60, p })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -383,7 +384,7 @@ export const SingleValueHistoryChart = (props: {
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{mouseState && (
|
{mouseState && (
|
||||||
<ChartTooltip className="text-sm" {...mouseState}>
|
<ChartTooltip className="text-sm" {...mouseState}>
|
||||||
<strong>{fmtY(mouseState.p.y)}</strong> {fmtX(mouseState.p.x)}
|
<strong>{fmtY(mouseState.p.y)}</strong> {fmtX(mouseState.p.x)}{' '}
|
||||||
</ChartTooltip>
|
</ChartTooltip>
|
||||||
)}
|
)}
|
||||||
<SVGChart
|
<SVGChart
|
||||||
|
|
Loading…
Reference in New Issue
Block a user