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