Adjustments to chart time range management
This commit is contained in:
parent
7718c8b910
commit
6375f7cf2c
|
@ -10,20 +10,18 @@ import { MARGIN_X, MARGIN_Y, getDateRange } from '../helpers'
|
|||
import { SingleValueHistoryChart } from '../generic-charts'
|
||||
import { useElementWidth } from 'web/hooks/use-element-width'
|
||||
|
||||
const getChartData = (
|
||||
contract: BinaryContract,
|
||||
bets: Bet[],
|
||||
start: Date,
|
||||
end: Date
|
||||
) => {
|
||||
const sortedBets = sortBy(bets, (b) => b.createdTime)
|
||||
const startProb = getInitialProbability(contract)
|
||||
const endProb = getProbability(contract)
|
||||
return [
|
||||
[start, startProb] as const,
|
||||
...sortedBets.map((b) => [new Date(b.createdTime), b.probAfter] as const),
|
||||
[end, endProb] as const,
|
||||
]
|
||||
const getBetPoints = (bets: Bet[]) => {
|
||||
return sortBy(bets, (b) => b.createdTime).map(
|
||||
(b) => [new Date(b.createdTime), b.probAfter] as const
|
||||
)
|
||||
}
|
||||
|
||||
const getStartPoint = (contract: BinaryContract, start: Date) => {
|
||||
return [start, getInitialProbability(contract)] as const
|
||||
}
|
||||
|
||||
const getEndPoint = (contract: BinaryContract, end: Date) => {
|
||||
return [end, getProbability(contract)] as const
|
||||
}
|
||||
|
||||
export const BinaryContractChart = (props: {
|
||||
|
@ -32,10 +30,15 @@ export const BinaryContractChart = (props: {
|
|||
height?: number
|
||||
}) => {
|
||||
const { contract, bets } = props
|
||||
const [start, end] = useMemo(() => getDateRange(contract), [contract])
|
||||
const [start, end] = getDateRange(contract)
|
||||
const betPoints = useMemo(() => getBetPoints(bets), [bets])
|
||||
const data = useMemo(
|
||||
() => getChartData(contract, bets, start, end),
|
||||
[contract, bets, start, end]
|
||||
() => [
|
||||
getStartPoint(contract, start),
|
||||
...betPoints,
|
||||
getEndPoint(contract, end),
|
||||
],
|
||||
[contract, betPoints, start, end]
|
||||
)
|
||||
const isMobile = useIsMobile(800)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
|
|
@ -3,6 +3,7 @@ import { sum, sortBy, groupBy } from 'lodash'
|
|||
import { scaleTime, scaleLinear } from 'd3'
|
||||
|
||||
import { Bet } from 'common/bet'
|
||||
import { Answer } from 'common/answer'
|
||||
import { FreeResponseContract, MultipleChoiceContract } from 'common/contract'
|
||||
import { getOutcomeProbability } from 'common/calculate'
|
||||
import { useIsMobile } from 'web/hooks/use-is-mobile'
|
||||
|
@ -68,35 +69,45 @@ const CATEGORY_COLORS = [
|
|||
'#70a560',
|
||||
]
|
||||
|
||||
const getMultiChartData = (
|
||||
const getTrackedAnswers = (
|
||||
contract: FreeResponseContract | MultipleChoiceContract,
|
||||
bets: Bet[],
|
||||
start: Date,
|
||||
end: Date,
|
||||
topN: number
|
||||
) => {
|
||||
const { answers, totalBets, outcomeType } = contract
|
||||
|
||||
const sortedBets = sortBy(bets, (b) => b.createdTime)
|
||||
const betsByOutcome = groupBy(sortedBets, (bet) => bet.outcome)
|
||||
const { answers, outcomeType, totalBets } = contract
|
||||
const validAnswers = answers.filter((answer) => {
|
||||
return (
|
||||
(answer.id !== '0' || outcomeType === 'MULTIPLE_CHOICE') &&
|
||||
totalBets[answer.id] > 0.000000001
|
||||
)
|
||||
})
|
||||
|
||||
const trackedAnswers = sortBy(
|
||||
return sortBy(
|
||||
validAnswers,
|
||||
(answer) => -1 * getOutcomeProbability(contract, answer.id)
|
||||
).slice(0, topN)
|
||||
}
|
||||
|
||||
const points: MultiPoint[] = []
|
||||
const getStartPoint = (answers: Answer[], start: Date) => {
|
||||
return [start, answers.map((_) => 0)] as const
|
||||
}
|
||||
|
||||
const getEndPoint = (
|
||||
answers: Answer[],
|
||||
contract: FreeResponseContract | MultipleChoiceContract,
|
||||
end: Date
|
||||
) => {
|
||||
return [
|
||||
end,
|
||||
answers.map((a) => getOutcomeProbability(contract, a.id)),
|
||||
] as const
|
||||
}
|
||||
|
||||
const getBetPoints = (answers: Answer[], bets: Bet[]) => {
|
||||
const sortedBets = sortBy(bets, (b) => b.createdTime)
|
||||
const betsByOutcome = groupBy(sortedBets, (bet) => bet.outcome)
|
||||
const sharesByOutcome = Object.fromEntries(
|
||||
Object.keys(betsByOutcome).map((outcome) => [outcome, 0])
|
||||
)
|
||||
|
||||
const points: MultiPoint[] = []
|
||||
for (const bet of sortedBets) {
|
||||
const { outcome, shares } = bet
|
||||
sharesByOutcome[outcome] += shares
|
||||
|
@ -106,26 +117,10 @@ const getMultiChartData = (
|
|||
)
|
||||
points.push([
|
||||
new Date(bet.createdTime),
|
||||
trackedAnswers.map(
|
||||
(answer) => sharesByOutcome[answer.id] ** 2 / sharesSquared
|
||||
),
|
||||
answers.map((answer) => sharesByOutcome[answer.id] ** 2 / sharesSquared),
|
||||
])
|
||||
}
|
||||
|
||||
const allPoints: MultiPoint[] = [
|
||||
[start, trackedAnswers.map((_) => 0)],
|
||||
...points,
|
||||
[
|
||||
end,
|
||||
trackedAnswers.map((answer) =>
|
||||
getOutcomeProbability(contract, answer.id)
|
||||
),
|
||||
],
|
||||
]
|
||||
return {
|
||||
points: allPoints,
|
||||
labels: trackedAnswers.map((answer) => answer.text),
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
export const ChoiceContractChart = (props: {
|
||||
|
@ -134,10 +129,19 @@ export const ChoiceContractChart = (props: {
|
|||
height?: number
|
||||
}) => {
|
||||
const { contract, bets } = props
|
||||
const [start, end] = useMemo(() => getDateRange(contract), [contract])
|
||||
const [start, end] = getDateRange(contract)
|
||||
const answers = useMemo(
|
||||
() => getTrackedAnswers(contract, CATEGORY_COLORS.length),
|
||||
[contract]
|
||||
)
|
||||
const betPoints = useMemo(() => getBetPoints(answers, bets), [answers, bets])
|
||||
const data = useMemo(
|
||||
() => getMultiChartData(contract, bets, start, end, CATEGORY_COLORS.length),
|
||||
[contract, bets, start, end]
|
||||
() => [
|
||||
getStartPoint(answers, start),
|
||||
...betPoints,
|
||||
getEndPoint(answers, contract, end),
|
||||
],
|
||||
[answers, contract, betPoints, start, end]
|
||||
)
|
||||
const isMobile = useIsMobile(800)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
@ -153,9 +157,9 @@ export const ChoiceContractChart = (props: {
|
|||
h={height}
|
||||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
data={data.points}
|
||||
data={data}
|
||||
colors={CATEGORY_COLORS}
|
||||
labels={data.labels}
|
||||
labels={answers.map((answer) => answer.text)}
|
||||
pct
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -15,29 +15,25 @@ import { useElementWidth } from 'web/hooks/use-element-width'
|
|||
// contracts. the values are stored "linearly" and can include zero.
|
||||
// as a result, we have to do some weird-looking stuff in this code
|
||||
|
||||
const getChartData = (
|
||||
contract: PseudoNumericContract,
|
||||
bets: Bet[],
|
||||
start: Date,
|
||||
end: Date
|
||||
) => {
|
||||
const getY = (p: number, contract: PseudoNumericContract) => {
|
||||
const { min, max, isLogScale } = contract
|
||||
const getY = (p: number) =>
|
||||
isLogScale
|
||||
? 10 ** (p * Math.log10(contract.max - contract.min + 1)) +
|
||||
contract.min -
|
||||
1
|
||||
: p * (max - min) + min
|
||||
const sortedBets = sortBy(bets, (b) => b.createdTime)
|
||||
const startProb = getInitialProbability(contract)
|
||||
const endProb = getProbability(contract)
|
||||
return [
|
||||
[start, getY(startProb)] as const,
|
||||
...sortedBets.map(
|
||||
(b) => [new Date(b.createdTime), getY(b.probAfter)] as const
|
||||
),
|
||||
[end, getY(endProb)] as const,
|
||||
]
|
||||
return isLogScale
|
||||
? 10 ** (p * Math.log10(max - min + 1)) + min - 1
|
||||
: p * (max - min) + min
|
||||
}
|
||||
|
||||
const getBetPoints = (contract: PseudoNumericContract, bets: Bet[]) => {
|
||||
return sortBy(bets, (b) => b.createdTime).map(
|
||||
(b) => [new Date(b.createdTime), getY(b.probAfter, contract)] as const
|
||||
)
|
||||
}
|
||||
|
||||
const getStartPoint = (contract: PseudoNumericContract, start: Date) => {
|
||||
return [start, getY(getInitialProbability(contract), contract)] as const
|
||||
}
|
||||
|
||||
const getEndPoint = (contract: PseudoNumericContract, end: Date) => {
|
||||
return [end, getY(getProbability(contract), contract)] as const
|
||||
}
|
||||
|
||||
export const PseudoNumericContractChart = (props: {
|
||||
|
@ -46,10 +42,18 @@ export const PseudoNumericContractChart = (props: {
|
|||
height?: number
|
||||
}) => {
|
||||
const { contract, bets } = props
|
||||
const [start, end] = useMemo(() => getDateRange(contract), [contract])
|
||||
const [start, end] = getDateRange(contract)
|
||||
const betPoints = useMemo(
|
||||
() => getBetPoints(contract, bets),
|
||||
[contract, bets]
|
||||
)
|
||||
const data = useMemo(
|
||||
() => getChartData(contract, bets, start, end),
|
||||
[contract, bets, start, end]
|
||||
() => [
|
||||
getStartPoint(contract, start),
|
||||
...betPoints,
|
||||
getEndPoint(contract, end),
|
||||
],
|
||||
[contract, betPoints, start, end]
|
||||
)
|
||||
const isMobile = useIsMobile(800)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
|
|
@ -110,9 +110,11 @@ export const SingleValueDistributionChart = (props: {
|
|||
|
||||
// note that we have to type this funkily in order to succesfully store
|
||||
// a function inside of useState
|
||||
const [xScale, setXScale] = useState(() => props.xScale)
|
||||
const [viewXScale, setViewXScale] =
|
||||
useState<ScaleContinuousNumeric<number, number>>()
|
||||
const [mouseState, setMouseState] =
|
||||
useState<PositionValue<DistributionPoint>>()
|
||||
const xScale = viewXScale ?? props.xScale
|
||||
|
||||
const px = useCallback((p: DistributionPoint) => xScale(p[0]), [xScale])
|
||||
const py0 = yScale(yScale.domain()[0])
|
||||
|
@ -130,12 +132,12 @@ export const SingleValueDistributionChart = (props: {
|
|||
const onSelect = useEvent((ev: D3BrushEvent<DistributionPoint>) => {
|
||||
if (ev.selection) {
|
||||
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
||||
setXScale(() =>
|
||||
setViewXScale(() =>
|
||||
xScale.copy().domain([xScale.invert(mouseX0), xScale.invert(mouseX1)])
|
||||
)
|
||||
setMouseState(undefined)
|
||||
} else {
|
||||
setXScale(() => props.xScale)
|
||||
setViewXScale(undefined)
|
||||
setMouseState(undefined)
|
||||
}
|
||||
})
|
||||
|
@ -194,10 +196,9 @@ export const MultiValueHistoryChart = (props: {
|
|||
}) => {
|
||||
const { colors, data, yScale, labels, w, h, pct } = props
|
||||
|
||||
// note that we have to type this funkily in order to succesfully store
|
||||
// a function inside of useState
|
||||
const [xScale, setXScale] = useState(() => props.xScale)
|
||||
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||
const [mouseState, setMouseState] = useState<PositionValue<MultiPoint>>()
|
||||
const xScale = viewXScale ?? props.xScale
|
||||
|
||||
type SP = SeriesPoint<MultiPoint>
|
||||
const px = useCallback((p: SP) => xScale(p.data[0]), [xScale])
|
||||
|
@ -231,12 +232,12 @@ export const MultiValueHistoryChart = (props: {
|
|||
const onSelect = useEvent((ev: D3BrushEvent<MultiPoint>) => {
|
||||
if (ev.selection) {
|
||||
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
||||
setXScale(() =>
|
||||
setViewXScale(() =>
|
||||
xScale.copy().domain([xScale.invert(mouseX0), xScale.invert(mouseX1)])
|
||||
)
|
||||
setMouseState(undefined)
|
||||
} else {
|
||||
setXScale(() => props.xScale)
|
||||
setViewXScale(undefined)
|
||||
setMouseState(undefined)
|
||||
}
|
||||
})
|
||||
|
@ -303,16 +304,15 @@ export const SingleValueHistoryChart = (props: {
|
|||
w: number
|
||||
h: number
|
||||
color: string
|
||||
xScale: d3.ScaleTime<number, number>
|
||||
yScale: d3.ScaleContinuousNumeric<number, number>
|
||||
xScale: ScaleTime<number, number>
|
||||
yScale: ScaleContinuousNumeric<number, number>
|
||||
pct?: boolean
|
||||
}) => {
|
||||
const { color, data, pct, yScale, w, h } = props
|
||||
|
||||
// note that we have to type this funkily in order to succesfully store
|
||||
// a function inside of useState
|
||||
const [xScale, setXScale] = useState(() => props.xScale)
|
||||
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||
const [mouseState, setMouseState] = useState<PositionValue<HistoryPoint>>()
|
||||
const xScale = viewXScale ?? props.xScale
|
||||
|
||||
const px = useCallback((p: HistoryPoint) => xScale(p[0]), [xScale])
|
||||
const py0 = yScale(yScale.domain()[0])
|
||||
|
@ -336,12 +336,12 @@ export const SingleValueHistoryChart = (props: {
|
|||
const onSelect = useEvent((ev: D3BrushEvent<HistoryPoint>) => {
|
||||
if (ev.selection) {
|
||||
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
||||
setXScale(() =>
|
||||
setViewXScale(() =>
|
||||
xScale.copy().domain([xScale.invert(mouseX0), xScale.invert(mouseX1)])
|
||||
)
|
||||
setMouseState(undefined)
|
||||
} else {
|
||||
setXScale(() => props.xScale)
|
||||
setViewXScale(undefined)
|
||||
setMouseState(undefined)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
select,
|
||||
} from 'd3'
|
||||
import { nanoid } from 'nanoid'
|
||||
import dayjs from 'dayjs'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { Contract } from 'common/contract'
|
||||
|
@ -202,9 +201,5 @@ export const getDateRange = (contract: Contract) => {
|
|||
const now = Date.now()
|
||||
const isClosed = !!closeTime && now > closeTime
|
||||
const endDate = resolutionTime ?? (isClosed ? closeTime : now)
|
||||
// the graph should be minimum an hour wide
|
||||
const adjustedEndDate = dayjs(createdTime).add(1, 'hour').isAfter(endDate)
|
||||
? dayjs(endDate).add(1, 'hours')
|
||||
: dayjs(endDate)
|
||||
return [new Date(createdTime), adjustedEndDate.toDate()] as const
|
||||
return [new Date(createdTime), new Date(endDate)] as const
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user