From e0d9b4d3354ad082df0dca90483c158df8393d6a Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Tue, 27 Sep 2022 20:24:42 -0700 Subject: [PATCH 01/16] Rewrite contract graphs (#935) * Fiddle around with everything, WIP FR charts * Implement numeric chart * Reorganize everything into neat little files * Add `AreaWithTopStroke` helper * Tidying, don't gratuitously use d3.format * Remove duplicate code * Better tooltip bisection * `NumericPoint` -> `DistributionPoint` * Add numeric market tooltip * Make numeric chart bucket points less wrong * Clean up numeric bucket computation * Clean up a bunch of tooltip stuff, add FR legend tooltips * Fix a dumb bug * Implement basic time selection * Fix fishy Date.now inconsistency bugs * Might as well show all the FR outcomes now * Make tooltips accurate on curveStepAfter charts * Make log scale PN charts work properly * Adjust x-axis tick count * Display tooltip on charts only for mouse * Fix up deps * Tighter chart tooltips * Adjustments to chart time range management * Better date formatting * Continue tweaking time selection handling to be perfect * Make FR charts taller by default --- web/components/answers/answers-graph.tsx | 238 --------- web/components/charts/contract/binary.tsx | 65 +++ web/components/charts/contract/choice.tsx | 170 ++++++ web/components/charts/contract/index.tsx | 34 ++ web/components/charts/contract/numeric.tsx | 52 ++ .../charts/contract/pseudo-numeric.tsx | 86 +++ web/components/charts/generic-charts.tsx | 393 ++++++++++++++ web/components/charts/helpers.tsx | 207 ++++++++ web/components/contract/contract-overview.tsx | 17 +- .../contract/contract-prob-graph.tsx | 203 ------- web/components/contract/numeric-graph.tsx | 99 ---- web/hooks/use-element-width.tsx | 17 + web/package.json | 4 +- web/pages/embed/[username]/[contractSlug].tsx | 21 +- yarn.lock | 500 +++++++++++++++++- 15 files changed, 1533 insertions(+), 573 deletions(-) delete mode 100644 web/components/answers/answers-graph.tsx create mode 100644 web/components/charts/contract/binary.tsx create mode 100644 web/components/charts/contract/choice.tsx create mode 100644 web/components/charts/contract/index.tsx create mode 100644 web/components/charts/contract/numeric.tsx create mode 100644 web/components/charts/contract/pseudo-numeric.tsx create mode 100644 web/components/charts/generic-charts.tsx create mode 100644 web/components/charts/helpers.tsx delete mode 100644 web/components/contract/contract-prob-graph.tsx delete mode 100644 web/components/contract/numeric-graph.tsx create mode 100644 web/hooks/use-element-width.tsx diff --git a/web/components/answers/answers-graph.tsx b/web/components/answers/answers-graph.tsx deleted file mode 100644 index e4167d11..00000000 --- a/web/components/answers/answers-graph.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import { DatumValue } from '@nivo/core' -import { ResponsiveLine } from '@nivo/line' -import dayjs from 'dayjs' -import { groupBy, sortBy, sumBy } from 'lodash' -import { memo } from 'react' - -import { Bet } from 'common/bet' -import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' -import { getOutcomeProbability } from 'common/calculate' -import { useWindowSize } from 'web/hooks/use-window-size' - -const NUM_LINES = 6 - -export const AnswersGraph = memo(function AnswersGraph(props: { - contract: FreeResponseContract | MultipleChoiceContract - bets: Bet[] - height?: number -}) { - const { contract, bets, height } = props - const { createdTime, resolutionTime, closeTime, answers } = contract - const now = Date.now() - - const { probsByOutcome, sortedOutcomes } = computeProbsByOutcome( - bets, - contract - ) - - const isClosed = !!closeTime && now > closeTime - const latestTime = dayjs( - resolutionTime && isClosed - ? Math.min(resolutionTime, closeTime) - : isClosed - ? closeTime - : resolutionTime ?? now - ) - - const { width } = useWindowSize() - - const isLargeWidth = !width || width > 800 - const labelLength = isLargeWidth ? 50 : 20 - - // Add a fake datapoint so the line continues to the right - const endTime = latestTime.valueOf() - - const times = sortBy([ - createdTime, - ...bets.map((bet) => bet.createdTime), - endTime, - ]) - const dateTimes = times.map((time) => new Date(time)) - - const data = sortedOutcomes.map((outcome) => { - const betProbs = probsByOutcome[outcome] - // Add extra point for contract start and end. - const probs = [0, ...betProbs, betProbs[betProbs.length - 1]] - - const points = probs.map((prob, i) => ({ - x: dateTimes[i], - y: Math.round(prob * 100), - })) - - const answer = - answers?.find((answer) => answer.id === outcome)?.text ?? 'None' - const answerText = - answer.slice(0, labelLength) + (answer.length > labelLength ? '...' : '') - - return { id: answerText, data: points } - }) - - data.reverse() - - const yTickValues = [0, 25, 50, 75, 100] - - const numXTickValues = isLargeWidth ? 5 : 2 - const startDate = dayjs(contract.createdTime) - const endDate = startDate.add(1, 'hour').isAfter(latestTime) - ? latestTime.add(1, 'hours') - : latestTime - const includeMinute = endDate.diff(startDate, 'hours') < 2 - - const multiYear = !startDate.isSame(latestTime, 'year') - const lessThanAWeek = startDate.add(1, 'week').isAfter(latestTime) - - return ( -
- - formatTime(now, +d.valueOf(), multiYear, lessThanAWeek, lessThanAWeek) - } - axisBottom={{ - tickValues: numXTickValues, - format: (time) => - formatTime(now, +time, multiYear, lessThanAWeek, includeMinute), - }} - colors={[ - '#fca5a5', // red-300 - '#a5b4fc', // indigo-300 - '#86efac', // green-300 - '#fef08a', // yellow-200 - '#fdba74', // orange-300 - '#c084fc', // purple-400 - ]} - pointSize={0} - curve="stepAfter" - enableSlices="x" - enableGridX={!!width && width >= 800} - enableArea - areaOpacity={1} - margin={{ top: 20, right: 20, bottom: 25, left: 40 }} - legends={[ - { - anchor: 'top-left', - direction: 'column', - justify: false, - translateX: isLargeWidth ? 5 : 2, - translateY: 0, - itemsSpacing: 0, - itemTextColor: 'black', - itemDirection: 'left-to-right', - itemWidth: isLargeWidth ? 288 : 138, - itemHeight: 20, - itemBackground: 'white', - itemOpacity: 0.9, - symbolSize: 12, - effects: [ - { - on: 'hover', - style: { - itemBackground: 'rgba(255, 255, 255, 1)', - itemOpacity: 1, - }, - }, - ], - }, - ]} - /> -
- ) -}) - -function formatPercent(y: DatumValue) { - return `${Math.round(+y.toString())}%` -} - -function formatTime( - now: number, - time: number, - includeYear: boolean, - includeHour: boolean, - includeMinute: boolean -) { - const d = dayjs(time) - if (d.add(1, 'minute').isAfter(now) && d.subtract(1, 'minute').isBefore(now)) - return 'Now' - - let format: string - if (d.isSame(now, 'day')) { - format = '[Today]' - } else if (d.add(1, 'day').isSame(now, 'day')) { - format = '[Yesterday]' - } else { - format = 'MMM D' - } - - if (includeMinute) { - format += ', h:mma' - } else if (includeHour) { - format += ', ha' - } else if (includeYear) { - format += ', YYYY' - } - - return d.format(format) -} - -const computeProbsByOutcome = ( - bets: Bet[], - contract: FreeResponseContract | MultipleChoiceContract -) => { - const { totalBets, outcomeType } = contract - - const betsByOutcome = groupBy(bets, (bet) => bet.outcome) - const outcomes = Object.keys(betsByOutcome).filter((outcome) => { - const maxProb = Math.max( - ...betsByOutcome[outcome].map((bet) => bet.probAfter) - ) - return ( - (outcome !== '0' || outcomeType === 'MULTIPLE_CHOICE') && - maxProb > 0.02 && - totalBets[outcome] > 0.000000001 - ) - }) - - const trackedOutcomes = sortBy( - outcomes, - (outcome) => -1 * getOutcomeProbability(contract, outcome) - ).slice(0, NUM_LINES) - - const probsByOutcome = Object.fromEntries( - trackedOutcomes.map((outcome) => [outcome, [] as number[]]) - ) - const sharesByOutcome = Object.fromEntries( - Object.keys(betsByOutcome).map((outcome) => [outcome, 0]) - ) - - for (const bet of bets) { - const { outcome, shares } = bet - sharesByOutcome[outcome] += shares - - const sharesSquared = sumBy( - Object.values(sharesByOutcome).map((shares) => shares ** 2) - ) - - for (const outcome of trackedOutcomes) { - probsByOutcome[outcome].push( - sharesByOutcome[outcome] ** 2 / sharesSquared - ) - } - } - - return { probsByOutcome, sortedOutcomes: trackedOutcomes } -} diff --git a/web/components/charts/contract/binary.tsx b/web/components/charts/contract/binary.tsx new file mode 100644 index 00000000..f7ed406b --- /dev/null +++ b/web/components/charts/contract/binary.tsx @@ -0,0 +1,65 @@ +import { useMemo, useRef } from 'react' +import { sortBy } from 'lodash' +import { scaleTime, scaleLinear } from 'd3' + +import { Bet } from 'common/bet' +import { getInitialProbability, getProbability } from 'common/calculate' +import { BinaryContract } from 'common/contract' +import { useIsMobile } from 'web/hooks/use-is-mobile' +import { MARGIN_X, MARGIN_Y, MAX_DATE, getDateRange } from '../helpers' +import { SingleValueHistoryChart } from '../generic-charts' +import { useElementWidth } from 'web/hooks/use-element-width' + +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: { + contract: BinaryContract + bets: Bet[] + height?: number +}) => { + const { contract, bets } = props + const [contractStart, contractEnd] = getDateRange(contract) + const betPoints = useMemo(() => getBetPoints(bets), [bets]) + const data = useMemo( + () => [ + getStartPoint(contract, contractStart), + ...betPoints, + getEndPoint(contract, contractEnd ?? MAX_DATE), + ], + [contract, betPoints, contractStart, contractEnd] + ) + const visibleRange = [contractStart, contractEnd ?? Date.now()] + const isMobile = useIsMobile(800) + const containerRef = useRef(null) + const width = useElementWidth(containerRef) ?? 0 + const height = props.height ?? (isMobile ? 250 : 350) + const xScale = scaleTime(visibleRange, [0, width - MARGIN_X]) + const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0]) + return ( +
+ {width && ( + + )} +
+ ) +} diff --git a/web/components/charts/contract/choice.tsx b/web/components/charts/contract/choice.tsx new file mode 100644 index 00000000..4b4fa34d --- /dev/null +++ b/web/components/charts/contract/choice.tsx @@ -0,0 +1,170 @@ +import { useMemo, useRef } from 'react' +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' +import { MARGIN_X, MARGIN_Y, MAX_DATE, getDateRange } from '../helpers' +import { MultiPoint, MultiValueHistoryChart } from '../generic-charts' +import { useElementWidth } from 'web/hooks/use-element-width' + +// thanks to https://observablehq.com/@jonhelfman/optimal-orders-for-choosing-categorical-colors +const CATEGORY_COLORS = [ + '#00b8dd', + '#eecafe', + '#874c62', + '#6457ca', + '#f773ba', + '#9c6bbc', + '#a87744', + '#af8a04', + '#bff9aa', + '#f3d89d', + '#c9a0f5', + '#ff00e5', + '#9dc6f7', + '#824475', + '#d973cc', + '#bc6808', + '#056e70', + '#677932', + '#00b287', + '#c8ab6c', + '#a2fb7a', + '#f8db68', + '#14675a', + '#8288f4', + '#fe1ca0', + '#ad6aff', + '#786306', + '#9bfbaf', + '#b00cf7', + '#2f7ec5', + '#4b998b', + '#42fa0e', + '#5b80a1', + '#962d9d', + '#3385ff', + '#48c5ab', + '#b2c873', + '#4cf9a4', + '#00ffff', + '#3cca73', + '#99ae17', + '#7af5cf', + '#52af45', + '#fbb80f', + '#29971b', + '#187c9a', + '#00d539', + '#bbfa1a', + '#61f55c', + '#cabc03', + '#ff9000', + '#779100', + '#bcfd6f', + '#70a560', +] + +const getTrackedAnswers = ( + contract: FreeResponseContract | MultipleChoiceContract, + topN: number +) => { + const { answers, outcomeType, totalBets } = contract + const validAnswers = answers.filter((answer) => { + return ( + (answer.id !== '0' || outcomeType === 'MULTIPLE_CHOICE') && + totalBets[answer.id] > 0.000000001 + ) + }) + return sortBy( + validAnswers, + (answer) => -1 * getOutcomeProbability(contract, answer.id) + ).slice(0, topN) +} + +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 + + const sharesSquared = sum( + Object.values(sharesByOutcome).map((shares) => shares ** 2) + ) + points.push([ + new Date(bet.createdTime), + answers.map((answer) => sharesByOutcome[answer.id] ** 2 / sharesSquared), + ]) + } + return points +} + +export const ChoiceContractChart = (props: { + contract: FreeResponseContract | MultipleChoiceContract + bets: Bet[] + height?: number +}) => { + const { contract, bets } = props + const [contractStart, contractEnd] = getDateRange(contract) + const answers = useMemo( + () => getTrackedAnswers(contract, CATEGORY_COLORS.length), + [contract] + ) + const betPoints = useMemo(() => getBetPoints(answers, bets), [answers, bets]) + const data = useMemo( + () => [ + getStartPoint(answers, contractStart), + ...betPoints, + getEndPoint(answers, contract, contractEnd ?? MAX_DATE), + ], + [answers, contract, betPoints, contractStart, contractEnd] + ) + const visibleRange = [contractStart, contractEnd ?? Date.now()] + + const isMobile = useIsMobile(800) + const containerRef = useRef(null) + const width = useElementWidth(containerRef) ?? 0 + const height = props.height ?? (isMobile ? 150 : 250) + const xScale = scaleTime(visibleRange, [0, width - MARGIN_X]) + const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0]) + return ( +
+ {width && ( + answer.text)} + pct + /> + )} +
+ ) +} diff --git a/web/components/charts/contract/index.tsx b/web/components/charts/contract/index.tsx new file mode 100644 index 00000000..1f580bae --- /dev/null +++ b/web/components/charts/contract/index.tsx @@ -0,0 +1,34 @@ +import { Contract } from 'common/contract' +import { Bet } from 'common/bet' +import { BinaryContractChart } from './binary' +import { PseudoNumericContractChart } from './pseudo-numeric' +import { ChoiceContractChart } from './choice' +import { NumericContractChart } from './numeric' + +export const ContractChart = (props: { + contract: Contract + bets: Bet[] + height?: number +}) => { + const { contract } = props + switch (contract.outcomeType) { + case 'BINARY': + return + case 'PSEUDO_NUMERIC': + return + case 'FREE_RESPONSE': + case 'MULTIPLE_CHOICE': + return + case 'NUMERIC': + return + default: + return null + } +} + +export { + BinaryContractChart, + PseudoNumericContractChart, + ChoiceContractChart, + NumericContractChart, +} diff --git a/web/components/charts/contract/numeric.tsx b/web/components/charts/contract/numeric.tsx new file mode 100644 index 00000000..6adc52f0 --- /dev/null +++ b/web/components/charts/contract/numeric.tsx @@ -0,0 +1,52 @@ +import { useMemo, useRef } from 'react' +import { max, range } from 'lodash' +import { scaleLinear } from 'd3' + +import { getDpmOutcomeProbabilities } from 'common/calculate-dpm' +import { NumericContract } from 'common/contract' +import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants' +import { useIsMobile } from 'web/hooks/use-is-mobile' +import { MARGIN_X, MARGIN_Y } from '../helpers' +import { SingleValueDistributionChart } from '../generic-charts' +import { useElementWidth } from 'web/hooks/use-element-width' + +const getNumericChartData = (contract: NumericContract) => { + const { totalShares, bucketCount, min, max } = contract + const step = (max - min) / bucketCount + const bucketProbs = getDpmOutcomeProbabilities(totalShares) + return range(bucketCount).map( + (i) => [min + step * (i + 0.5), bucketProbs[`${i}`]] as const + ) +} + +export const NumericContractChart = (props: { + contract: NumericContract + height?: number +}) => { + const { contract } = props + const data = useMemo(() => getNumericChartData(contract), [contract]) + const isMobile = useIsMobile(800) + const containerRef = useRef(null) + const width = useElementWidth(containerRef) ?? 0 + const height = props.height ?? (isMobile ? 150 : 250) + const maxY = max(data.map((d) => d[1])) as number + const xScale = scaleLinear( + [contract.min, contract.max], + [0, width - MARGIN_X] + ) + const yScale = scaleLinear([0, maxY], [height - MARGIN_Y, 0]) + return ( +
+ {width && ( + + )} +
+ ) +} diff --git a/web/components/charts/contract/pseudo-numeric.tsx b/web/components/charts/contract/pseudo-numeric.tsx new file mode 100644 index 00000000..9b67a169 --- /dev/null +++ b/web/components/charts/contract/pseudo-numeric.tsx @@ -0,0 +1,86 @@ +import { useMemo, useRef } from 'react' +import { sortBy } from 'lodash' +import { scaleTime, scaleLog, scaleLinear } from 'd3' + +import { Bet } from 'common/bet' +import { getInitialProbability, getProbability } from 'common/calculate' +import { PseudoNumericContract } from 'common/contract' +import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants' +import { useIsMobile } from 'web/hooks/use-is-mobile' +import { MARGIN_X, MARGIN_Y, MAX_DATE, getDateRange } from '../helpers' +import { SingleValueHistoryChart } from '../generic-charts' +import { useElementWidth } from 'web/hooks/use-element-width' + +// mqp: note that we have an idiosyncratic version of 'log scale' +// 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 getY = (p: number, contract: PseudoNumericContract) => { + const { min, max, isLogScale } = contract + 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: { + contract: PseudoNumericContract + bets: Bet[] + height?: number +}) => { + const { contract, bets } = props + const [contractStart, contractEnd] = getDateRange(contract) + const betPoints = useMemo( + () => getBetPoints(contract, bets), + [contract, bets] + ) + const data = useMemo( + () => [ + getStartPoint(contract, contractStart), + ...betPoints, + getEndPoint(contract, contractEnd ?? MAX_DATE), + ], + [contract, betPoints, contractStart, contractEnd] + ) + const visibleRange = [contractStart, contractEnd ?? Date.now()] + + const isMobile = useIsMobile(800) + const containerRef = useRef(null) + const width = useElementWidth(containerRef) ?? 0 + const height = props.height ?? (isMobile ? 150 : 250) + const xScale = scaleTime(visibleRange, [0, width - MARGIN_X]) + const yScale = contract.isLogScale + ? scaleLog( + [Math.max(contract.min, 1), contract.max], + [height - MARGIN_Y, 0] + ).clamp(true) // make sure zeroes go to the bottom + : scaleLinear([contract.min, contract.max], [height - MARGIN_Y, 0]) + + return ( +
+ {width && ( + + )} +
+ ) +} diff --git a/web/components/charts/generic-charts.tsx b/web/components/charts/generic-charts.tsx new file mode 100644 index 00000000..2bb5e089 --- /dev/null +++ b/web/components/charts/generic-charts.tsx @@ -0,0 +1,393 @@ +import { useCallback, useMemo, useState } from 'react' +import { + axisBottom, + axisLeft, + bisector, + curveLinear, + curveStepAfter, + pointer, + stack, + stackOrderReverse, + D3BrushEvent, + ScaleTime, + ScaleContinuousNumeric, + SeriesPoint, +} from 'd3' +import { range, sortBy } from 'lodash' +import dayjs from 'dayjs' + +import { + SVGChart, + AreaPath, + AreaWithTopStroke, + ChartTooltip, + TooltipPosition, +} from './helpers' +import { formatLargeNumber } from 'common/util/format' +import { useEvent } from 'web/hooks/use-event' +import { Row } from 'web/components/layout/row' + +export type MultiPoint = readonly [Date, number[]] // [time, [ordered outcome probs]] +export type HistoryPoint = readonly [Date, number] // [time, number or percentage] +export type DistributionPoint = readonly [number, number] // [outcome amount, prob] +export type PositionValue

= TooltipPosition & { p: P } + +const formatPct = (n: number, digits?: number) => { + return `${(n * 100).toFixed(digits ?? 0)}%` +} + +const formatDate = ( + date: Date, + opts: { includeYear: boolean; includeHour: boolean; includeMinute: boolean } +) => { + const { includeYear, includeHour, includeMinute } = opts + const d = dayjs(date) + const now = Date.now() + if ( + d.add(1, 'minute').isAfter(now) && + d.subtract(1, 'minute').isBefore(now) + ) { + return 'Now' + } else { + const dayName = d.isSame(now, 'day') + ? 'Today' + : d.add(1, 'day').isSame(now, 'day') + ? 'Yesterday' + : null + let format = dayName ? `[${dayName}]` : 'MMM D' + if (includeMinute) { + format += ', h:mma' + } else if (includeHour) { + format += ', ha' + } else if (includeYear) { + format += ', YYYY' + } + return d.format(format) + } +} + +const getFormatterForDateRange = (start: Date, end: Date) => { + const opts = { + includeYear: !dayjs(start).isSame(end, 'year'), + includeHour: dayjs(start).add(8, 'day').isAfter(end), + includeMinute: dayjs(end).diff(start, 'hours') < 2, + } + return (d: Date) => formatDate(d, opts) +} + +const getTickValues = (min: number, max: number, n: number) => { + const step = (max - min) / (n - 1) + return [min, ...range(1, n - 1).map((i) => min + step * i), max] +} + +type LegendItem = { color: string; label: string; value?: string } + +const Legend = (props: { className?: string; items: LegendItem[] }) => { + const { items, className } = props + return ( +

    + {items.map((item) => ( +
  1. + + + {item.label} + + {item.value} +
  2. + ))} +
+ ) +} + +export const SingleValueDistributionChart = (props: { + data: DistributionPoint[] + w: number + h: number + color: string + xScale: ScaleContinuousNumeric + yScale: ScaleContinuousNumeric +}) => { + const { color, data, yScale, w, h } = props + + // note that we have to type this funkily in order to succesfully store + // a function inside of useState + const [viewXScale, setViewXScale] = + useState>() + const [mouseState, setMouseState] = + useState>() + const xScale = viewXScale ?? props.xScale + + const px = useCallback((p: DistributionPoint) => xScale(p[0]), [xScale]) + const py0 = yScale(yScale.domain()[0]) + const py1 = useCallback((p: DistributionPoint) => yScale(p[1]), [yScale]) + const xBisector = bisector((p: DistributionPoint) => p[0]) + + const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => { + const fmtX = (n: number) => formatLargeNumber(n) + const fmtY = (n: number) => formatPct(n, 2) + const xAxis = axisBottom(xScale).ticks(w / 100) + const yAxis = axisLeft(yScale).tickFormat(fmtY) + return { fmtX, fmtY, xAxis, yAxis } + }, [w, xScale, yScale]) + + const onSelect = useEvent((ev: D3BrushEvent) => { + if (ev.selection) { + const [mouseX0, mouseX1] = ev.selection as [number, number] + setViewXScale(() => + xScale.copy().domain([xScale.invert(mouseX0), xScale.invert(mouseX1)]) + ) + setMouseState(undefined) + } else { + setViewXScale(undefined) + setMouseState(undefined) + } + }) + + const onMouseOver = useEvent((ev: React.PointerEvent) => { + if (ev.pointerType === 'mouse') { + const [mouseX, mouseY] = pointer(ev) + const queryX = xScale.invert(mouseX) + const [_x, y] = data[xBisector.center(data, queryX)] + setMouseState({ top: mouseY - 10, left: mouseX + 60, p: [queryX, y] }) + } + }) + + const onMouseLeave = useEvent(() => { + setMouseState(undefined) + }) + + return ( +
+ {mouseState && ( + + {fmtY(mouseState.p[1])} {fmtX(mouseState.p[0])} + + )} + + + +
+ ) +} + +export const MultiValueHistoryChart = (props: { + data: MultiPoint[] + w: number + h: number + labels: readonly string[] + colors: readonly string[] + xScale: ScaleTime + yScale: ScaleContinuousNumeric + pct?: boolean +}) => { + const { colors, data, yScale, labels, w, h, pct } = props + + const [viewXScale, setViewXScale] = useState>() + const [mouseState, setMouseState] = useState>() + const xScale = viewXScale ?? props.xScale + + type SP = SeriesPoint + const px = useCallback((p: SP) => xScale(p.data[0]), [xScale]) + const py0 = useCallback((p: SP) => yScale(p[0]), [yScale]) + const py1 = useCallback((p: SP) => yScale(p[1]), [yScale]) + const xBisector = bisector((p: MultiPoint) => p[0]) + + const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => { + const [start, end] = xScale.domain() + const fmtX = getFormatterForDateRange(start, end) + const fmtY = (n: number) => (pct ? formatPct(n, 0) : formatLargeNumber(n)) + + const [min, max] = yScale.domain() + const pctTickValues = getTickValues(min, max, h < 200 ? 3 : 5) + const xAxis = axisBottom(xScale).ticks(w / 100) + const yAxis = pct + ? axisLeft(yScale).tickValues(pctTickValues).tickFormat(fmtY) + : axisLeft(yScale) + + return { fmtX, fmtY, xAxis, yAxis } + }, [w, h, pct, xScale, yScale]) + + const series = useMemo(() => { + const d3Stack = stack() + .keys(range(0, labels.length)) + .value(([_date, probs], o) => probs[o]) + .order(stackOrderReverse) + return d3Stack(data) + }, [data, labels.length]) + + const onSelect = useEvent((ev: D3BrushEvent) => { + if (ev.selection) { + const [mouseX0, mouseX1] = ev.selection as [number, number] + setViewXScale(() => + xScale.copy().domain([xScale.invert(mouseX0), xScale.invert(mouseX1)]) + ) + setMouseState(undefined) + } else { + setViewXScale(undefined) + setMouseState(undefined) + } + }) + + const onMouseOver = useEvent((ev: React.PointerEvent) => { + if (ev.pointerType === 'mouse') { + const [mouseX, mouseY] = pointer(ev) + const queryX = xScale.invert(mouseX) + const [_x, ys] = data[xBisector.left(data, queryX) - 1] + setMouseState({ top: mouseY - 10, left: mouseX + 60, p: [queryX, ys] }) + } + }) + + const onMouseLeave = useEvent(() => { + setMouseState(undefined) + }) + + const mouseProbs = mouseState?.p[1] ?? [] + const legendItems = sortBy( + mouseProbs.map((p, i) => ({ + color: colors[i], + label: labels[i], + value: fmtY(p), + p, + })), + (item) => -item.p + ).slice(0, 10) + + return ( +
+ {mouseState && ( + + {fmtX(mouseState.p[0])} + + + )} + + {series.map((s, i) => ( + + ))} + +
+ ) +} + +export const SingleValueHistoryChart = (props: { + data: HistoryPoint[] + w: number + h: number + color: string + xScale: ScaleTime + yScale: ScaleContinuousNumeric + pct?: boolean +}) => { + const { color, data, pct, yScale, w, h } = props + + const [viewXScale, setViewXScale] = useState>() + const [mouseState, setMouseState] = useState>() + const xScale = viewXScale ?? props.xScale + + const px = useCallback((p: HistoryPoint) => xScale(p[0]), [xScale]) + const py0 = yScale(yScale.domain()[0]) + const py1 = useCallback((p: HistoryPoint) => yScale(p[1]), [yScale]) + const xBisector = bisector((p: HistoryPoint) => p[0]) + + const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => { + const [start, end] = xScale.domain() + const fmtX = getFormatterForDateRange(start, end) + const fmtY = (n: number) => (pct ? formatPct(n, 0) : formatLargeNumber(n)) + + const [min, max] = yScale.domain() + const pctTickValues = getTickValues(min, max, h < 200 ? 3 : 5) + const xAxis = axisBottom(xScale).ticks(w / 100) + const yAxis = pct + ? axisLeft(yScale).tickValues(pctTickValues).tickFormat(fmtY) + : axisLeft(yScale) + return { fmtX, fmtY, xAxis, yAxis } + }, [w, h, pct, xScale, yScale]) + + const onSelect = useEvent((ev: D3BrushEvent) => { + if (ev.selection) { + const [mouseX0, mouseX1] = ev.selection as [number, number] + setViewXScale(() => + xScale.copy().domain([xScale.invert(mouseX0), xScale.invert(mouseX1)]) + ) + setMouseState(undefined) + } else { + setViewXScale(undefined) + setMouseState(undefined) + } + }) + + const onMouseOver = useEvent((ev: React.PointerEvent) => { + if (ev.pointerType === 'mouse') { + const [mouseX, mouseY] = pointer(ev) + const queryX = xScale.invert(mouseX) + const [_x, y] = data[xBisector.left(data, queryX) - 1] + setMouseState({ top: mouseY - 10, left: mouseX + 60, p: [queryX, y] }) + } + }) + + const onMouseLeave = useEvent(() => { + setMouseState(undefined) + }) + + return ( +
+ {mouseState && ( + + {fmtY(mouseState.p[1])} {fmtX(mouseState.p[0])} + + )} + + + +
+ ) +} diff --git a/web/components/charts/helpers.tsx b/web/components/charts/helpers.tsx new file mode 100644 index 00000000..20231bc9 --- /dev/null +++ b/web/components/charts/helpers.tsx @@ -0,0 +1,207 @@ +import { ReactNode, SVGProps, memo, useRef, useEffect, useMemo } from 'react' +import { + Axis, + CurveFactory, + D3BrushEvent, + area, + brushX, + curveStepAfter, + line, + select, +} from 'd3' +import { nanoid } from 'nanoid' +import clsx from 'clsx' + +import { Contract } from 'common/contract' + +export const MARGIN = { top: 20, right: 10, bottom: 20, left: 40 } +export const MARGIN_X = MARGIN.right + MARGIN.left +export const MARGIN_Y = MARGIN.top + MARGIN.bottom + +export const MAX_TIMESTAMP = 8640000000000000 +export const MAX_DATE = new Date(MAX_TIMESTAMP) + +export const XAxis = (props: { w: number; h: number; axis: Axis }) => { + const { h, axis } = props + const axisRef = useRef(null) + useEffect(() => { + if (axisRef.current != null) { + select(axisRef.current) + .transition() + .duration(250) + .call(axis) + .select('.domain') + .attr('stroke-width', 0) + } + }, [h, axis]) + return +} + +export const YAxis = (props: { w: number; h: number; axis: Axis }) => { + const { w, h, axis } = props + const axisRef = useRef(null) + useEffect(() => { + if (axisRef.current != null) { + select(axisRef.current) + .transition() + .duration(250) + .call(axis) + .call((g) => + g.selectAll('.tick line').attr('x2', w).attr('stroke-opacity', 0.1) + ) + .select('.domain') + .attr('stroke-width', 0) + } + }, [w, h, axis]) + return +} + +const LinePathInternal = ( + props: { + data: P[] + px: number | ((p: P) => number) + py: number | ((p: P) => number) + curve?: CurveFactory + } & SVGProps +) => { + const { data, px, py, curve, ...rest } = props + const d3Line = line

(px, py).curve(curve ?? curveStepAfter) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return +} +export const LinePath = memo(LinePathInternal) as typeof LinePathInternal + +const AreaPathInternal = ( + props: { + data: P[] + px: number | ((p: P) => number) + py0: number | ((p: P) => number) + py1: number | ((p: P) => number) + curve?: CurveFactory + } & SVGProps +) => { + const { data, px, py0, py1, curve, ...rest } = props + const d3Area = area

(px, py0, py1).curve(curve ?? curveStepAfter) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return +} +export const AreaPath = memo(AreaPathInternal) as typeof AreaPathInternal + +export const AreaWithTopStroke = (props: { + color: string + data: P[] + px: number | ((p: P) => number) + py0: number | ((p: P) => number) + py1: number | ((p: P) => number) + curve?: CurveFactory +}) => { + const { color, data, px, py0, py1, curve } = props + return ( + + + + + ) +} + +export const SVGChart = (props: { + children: ReactNode + w: number + h: number + xAxis: Axis + yAxis: Axis + onSelect?: (ev: D3BrushEvent) => void + onMouseOver?: (ev: React.PointerEvent) => void + onMouseLeave?: (ev: React.PointerEvent) => void + pct?: boolean +}) => { + const { children, w, h, xAxis, yAxis, onMouseOver, onMouseLeave, onSelect } = + props + const overlayRef = useRef(null) + const innerW = w - MARGIN_X + const innerH = h - MARGIN_Y + const clipPathId = useMemo(() => nanoid(), []) + + const justSelected = useRef(false) + useEffect(() => { + if (onSelect != null && overlayRef.current) { + const brush = brushX().extent([ + [0, 0], + [innerW, innerH], + ]) + brush.on('end', (ev) => { + // when we clear the brush after a selection, that would normally cause + // another 'end' event, so we have to suppress it with this flag + if (!justSelected.current) { + justSelected.current = true + onSelect(ev) + if (overlayRef.current) { + select(overlayRef.current).call(brush.clear) + } + } else { + justSelected.current = false + } + }) + select(overlayRef.current).call(brush) + } + }, [innerW, innerH, onSelect]) + + return ( + + + + + + + + {children} + + + + ) +} + +export type TooltipPosition = { top: number; left: number } + +export const ChartTooltip = ( + props: TooltipPosition & { className?: string; children: React.ReactNode } +) => { + const { top, left, className, children } = props + return ( +

+ {children} +
+ ) +} + +export const getDateRange = (contract: Contract) => { + const { createdTime, closeTime, resolutionTime } = contract + const isClosed = !!closeTime && Date.now() > closeTime + const endDate = resolutionTime ?? (isClosed ? closeTime : null) + return [new Date(createdTime), endDate ? new Date(endDate) : null] as const +} diff --git a/web/components/contract/contract-overview.tsx b/web/components/contract/contract-overview.tsx index 139b30fe..add9ba48 100644 --- a/web/components/contract/contract-overview.tsx +++ b/web/components/contract/contract-overview.tsx @@ -2,7 +2,12 @@ import React from 'react' import { tradingAllowed } from 'web/lib/firebase/contracts' import { Col } from '../layout/col' -import { ContractProbGraph } from './contract-prob-graph' +import { + BinaryContractChart, + NumericContractChart, + PseudoNumericContractChart, + ChoiceContractChart, +} from 'web/components/charts/contract' import { useUser } from 'web/hooks/use-user' import { Row } from '../layout/row' import { Linkify } from '../linkify' @@ -14,7 +19,6 @@ import { } from './contract-card' import { Bet } from 'common/bet' import BetButton, { BinaryMobileBetting } from '../bet-button' -import { AnswersGraph } from '../answers/answers-graph' import { Contract, CPMMContract, @@ -25,7 +29,6 @@ import { BinaryContract, } from 'common/contract' import { ContractDetails } from './contract-details' -import { NumericGraph } from './numeric-graph' const OverviewQuestion = (props: { text: string }) => ( @@ -63,7 +66,7 @@ const NumericOverview = (props: { contract: NumericContract }) => { contract={contract} /> - + ) } @@ -83,7 +86,7 @@ const BinaryOverview = (props: { contract: BinaryContract; bets: Bet[] }) => { /> - + {tradingAllowed(contract) && ( @@ -109,7 +112,7 @@ const ChoiceOverview = (props: { )} - + ) @@ -136,7 +139,7 @@ const PseudoNumericOverview = (props: { {tradingAllowed(contract) && } - + ) } diff --git a/web/components/contract/contract-prob-graph.tsx b/web/components/contract/contract-prob-graph.tsx deleted file mode 100644 index 60ef85b5..00000000 --- a/web/components/contract/contract-prob-graph.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import { DatumValue } from '@nivo/core' -import { ResponsiveLine, SliceTooltipProps } from '@nivo/line' -import { BasicTooltip } from '@nivo/tooltip' -import dayjs from 'dayjs' -import { memo } from 'react' -import { Bet } from 'common/bet' -import { getInitialProbability } from 'common/calculate' -import { BinaryContract, PseudoNumericContract } from 'common/contract' -import { useWindowSize } from 'web/hooks/use-window-size' -import { formatLargeNumber } from 'common/util/format' - -export const ContractProbGraph = memo(function ContractProbGraph(props: { - contract: BinaryContract | PseudoNumericContract - bets: Bet[] - height?: number -}) { - const { contract, height } = props - const { resolutionTime, closeTime, outcomeType } = contract - const now = Date.now() - const isBinary = outcomeType === 'BINARY' - const isLogScale = outcomeType === 'PSEUDO_NUMERIC' && contract.isLogScale - - const bets = props.bets.filter((bet) => !bet.isAnte && !bet.isRedemption) - - const startProb = getInitialProbability(contract) - - const times = [contract.createdTime, ...bets.map((bet) => bet.createdTime)] - - const f: (p: number) => number = isBinary - ? (p) => p - : isLogScale - ? (p) => p * Math.log10(contract.max - contract.min + 1) - : (p) => p * (contract.max - contract.min) + contract.min - - const probs = [startProb, ...bets.map((bet) => bet.probAfter)].map(f) - - const isClosed = !!closeTime && now > closeTime - const latestTime = dayjs( - resolutionTime && isClosed - ? Math.min(resolutionTime, closeTime) - : isClosed - ? closeTime - : resolutionTime ?? now - ) - - // Add a fake datapoint so the line continues to the right - times.push(latestTime.valueOf()) - probs.push(probs[probs.length - 1]) - - const { width } = useWindowSize() - - const quartiles = !width || width < 800 ? [0, 50, 100] : [0, 25, 50, 75, 100] - - const yTickValues = isBinary - ? quartiles - : quartiles.map((x) => x / 100).map(f) - - const numXTickValues = !width || width < 800 ? 2 : 5 - const startDate = dayjs(times[0]) - const endDate = startDate.add(1, 'hour').isAfter(latestTime) - ? latestTime.add(1, 'hours') - : latestTime - const includeMinute = endDate.diff(startDate, 'hours') < 2 - - // Minimum number of points for the graph to have. For smooth tooltip movement - // If we aren't actually loading any data yet, skip adding extra points to let page load faster - // This fn runs again once DOM is finished loading - const totalPoints = width && bets.length ? (width > 800 ? 300 : 50) : 1 - - const timeStep: number = latestTime.diff(startDate, 'ms') / totalPoints - - const points: { x: Date; y: number }[] = [] - const s = isBinary ? 100 : 1 - - for (let i = 0; i < times.length - 1; i++) { - const p = probs[i] - const d0 = times[i] - const d1 = times[i + 1] - const msDiff = d1 - d0 - const numPoints = Math.floor(msDiff / timeStep) - points.push({ x: new Date(times[i]), y: s * p }) - if (numPoints > 1) { - const thisTimeStep: number = msDiff / numPoints - for (let n = 1; n < numPoints; n++) { - points.push({ x: new Date(d0 + thisTimeStep * n), y: s * p }) - } - } - } - - const data = [ - { id: 'Yes', data: points, color: isBinary ? '#11b981' : '#5fa5f9' }, - ] - - const multiYear = !startDate.isSame(latestTime, 'year') - const lessThanAWeek = startDate.add(8, 'day').isAfter(latestTime) - - const formatter = isBinary - ? formatPercent - : isLogScale - ? (x: DatumValue) => - formatLargeNumber(10 ** +x.valueOf() + contract.min - 1) - : (x: DatumValue) => formatLargeNumber(+x.valueOf()) - - return ( -
= 800 ? 250 : 150) }} - > - - formatTime(now, +d.valueOf(), multiYear, lessThanAWeek, lessThanAWeek) - } - axisBottom={{ - tickValues: numXTickValues, - format: (time) => - formatTime(now, +time, multiYear, lessThanAWeek, includeMinute), - }} - colors={{ datum: 'color' }} - curve="stepAfter" - enablePoints={false} - pointBorderWidth={1} - pointBorderColor="#fff" - enableSlices="x" - enableGridX={false} - enableArea - areaBaselineValue={isBinary || isLogScale ? 0 : contract.min} - margin={{ top: 20, right: 20, bottom: 25, left: 40 }} - animate={false} - sliceTooltip={SliceTooltip} - /> -
- ) -}) - -const SliceTooltip = ({ slice }: SliceTooltipProps) => { - return ( - [ - - {point.data[`yFormatted`]} {point.data['xFormatted']} - , - ])} - /> - ) -} - -function formatPercent(y: DatumValue) { - return `${Math.round(+y.toString())}%` -} - -function formatTime( - now: number, - time: number, - includeYear: boolean, - includeHour: boolean, - includeMinute: boolean -) { - const d = dayjs(time) - if (d.add(1, 'minute').isAfter(now) && d.subtract(1, 'minute').isBefore(now)) - return 'Now' - - let format: string - if (d.isSame(now, 'day')) { - format = '[Today]' - } else if (d.add(1, 'day').isSame(now, 'day')) { - format = '[Yesterday]' - } else { - format = 'MMM D' - } - - if (includeMinute) { - format += ', h:mma' - } else if (includeHour) { - format += ', ha' - } else if (includeYear) { - format += ', YYYY' - } - - return d.format(format) -} diff --git a/web/components/contract/numeric-graph.tsx b/web/components/contract/numeric-graph.tsx deleted file mode 100644 index f6532b9b..00000000 --- a/web/components/contract/numeric-graph.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { DatumValue } from '@nivo/core' -import { Point, ResponsiveLine } from '@nivo/line' -import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants' -import { memo } from 'react' -import { range } from 'lodash' -import { getDpmOutcomeProbabilities } from '../../../common/calculate-dpm' -import { NumericContract } from '../../../common/contract' -import { useWindowSize } from '../../hooks/use-window-size' -import { Col } from '../layout/col' -import { formatLargeNumber } from 'common/util/format' - -export const NumericGraph = memo(function NumericGraph(props: { - contract: NumericContract - height?: number -}) { - const { contract, height } = props - const { totalShares, bucketCount, min, max } = contract - - const bucketProbs = getDpmOutcomeProbabilities(totalShares) - - const xs = range(bucketCount).map( - (i) => min + ((max - min) * i) / bucketCount - ) - const probs = range(bucketCount).map((i) => bucketProbs[`${i}`] * 100) - const points = probs.map((prob, i) => ({ x: xs[i], y: prob })) - const maxProb = Math.max(...probs) - const data = [{ id: 'Probability', data: points, color: NUMERIC_GRAPH_COLOR }] - - const yTickValues = [ - 0, - 0.25 * maxProb, - 0.5 & maxProb, - 0.75 * maxProb, - maxProb, - ] - - const { width } = useWindowSize() - - const numXTickValues = !width || width < 800 ? 2 : 5 - - return ( -
= 800 ? 350 : 250) }} - > - `${formatLargeNumber(+d, 3)}`} - axisBottom={{ - tickValues: numXTickValues, - format: (d) => `${formatLargeNumber(+d, 3)}`, - }} - colors={{ datum: 'color' }} - pointSize={0} - enableSlices="x" - sliceTooltip={({ slice }) => { - const point = slice.points[0] - return - }} - enableGridX={!!width && width >= 800} - enableArea - margin={{ top: 20, right: 28, bottom: 22, left: 50 }} - /> -
- ) -}) - -function formatPercent(y: DatumValue) { - const p = Math.round(+y * 100) / 100 - return `${p}%` -} - -function Tooltip(props: { point: Point }) { - const { point } = props - return ( - -
- {point.serieId} {point.data.yFormatted} -
-
{formatLargeNumber(+point.data.x)}
- - ) -} diff --git a/web/hooks/use-element-width.tsx b/web/hooks/use-element-width.tsx new file mode 100644 index 00000000..1c373839 --- /dev/null +++ b/web/hooks/use-element-width.tsx @@ -0,0 +1,17 @@ +import { RefObject, useState, useEffect } from 'react' + +// todo: consider consolidation with use-measure-size +export const useElementWidth = (ref: RefObject) => { + const [width, setWidth] = useState() + useEffect(() => { + const handleResize = () => { + setWidth(ref.current?.clientWidth) + } + handleResize() + window.addEventListener('resize', handleResize) + return () => { + window.removeEventListener('resize', handleResize) + } + }, [ref]) + return width +} diff --git a/web/package.json b/web/package.json index 3ccbc96c..570139f4 100644 --- a/web/package.json +++ b/web/package.json @@ -39,6 +39,7 @@ "browser-image-compression": "2.0.0", "clsx": "1.1.1", "cors": "2.8.5", + "d3": "7.6.1", "daisyui": "1.16.4", "dayjs": "1.10.7", "firebase": "9.9.3", @@ -56,9 +57,9 @@ "react-expanding-textarea": "2.3.5", "react-hot-toast": "2.2.0", "react-instantsearch-hooks-web": "6.24.1", + "react-masonry-css": "1.0.16", "react-query": "3.39.0", "react-twitter-embed": "4.0.4", - "react-masonry-css": "1.0.16", "string-similarity": "^4.0.4", "tippy.js": "6.3.7" }, @@ -66,6 +67,7 @@ "@tailwindcss/forms": "0.4.0", "@tailwindcss/line-clamp": "^0.3.1", "@tailwindcss/typography": "^0.5.1", + "@types/d3": "7.4.0", "@types/lodash": "4.14.178", "@types/node": "16.11.11", "@types/react": "17.0.43", diff --git a/web/pages/embed/[username]/[contractSlug].tsx b/web/pages/embed/[username]/[contractSlug].tsx index 75a9ad05..e925a1f6 100644 --- a/web/pages/embed/[username]/[contractSlug].tsx +++ b/web/pages/embed/[username]/[contractSlug].tsx @@ -2,7 +2,6 @@ import { Bet } from 'common/bet' import { Contract } from 'common/contract' import { DOMAIN } from 'common/envs/constants' import { useState } from 'react' -import { AnswersGraph } from 'web/components/answers/answers-graph' import { BetInline } from 'web/components/bet-inline' import { Button } from 'web/components/button' import { @@ -12,8 +11,7 @@ import { PseudoNumericResolutionOrExpectation, } from 'web/components/contract/contract-card' import { MarketSubheader } from 'web/components/contract/contract-details' -import { ContractProbGraph } from 'web/components/contract/contract-prob-graph' -import { NumericGraph } from 'web/components/contract/numeric-graph' +import { ContractChart } from 'web/components/charts/contract' import { Col } from 'web/components/layout/col' import { Row } from 'web/components/layout/row' import { Spacer } from 'web/components/layout/spacer' @@ -134,22 +132,7 @@ export function ContractEmbed(props: { contract: Contract; bets: Bet[] }) { )}
- {(isBinary || isPseudoNumeric) && ( - - )} - - {(outcomeType === 'FREE_RESPONSE' || - outcomeType === 'MULTIPLE_CHOICE') && ( - - )} - - {outcomeType === 'NUMERIC' && ( - - )} +
) diff --git a/yarn.lock b/yarn.lock index 9829f0b1..17a065f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3250,6 +3250,216 @@ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== +"@types/d3-array@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.3.tgz#87d990bf504d14ad6b16766979d04e943c046dac" + integrity sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ== + +"@types/d3-axis@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.1.tgz#6afc20744fa5cc0cbc3e2bd367b140a79ed3e7a8" + integrity sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-brush@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.1.tgz#ae5f17ce391935ca88b29000e60ee20452c6357c" + integrity sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-chord@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.1.tgz#54c8856c19c8e4ab36a53f73ba737de4768ad248" + integrity sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw== + +"@types/d3-color@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.0.tgz#6594da178ded6c7c3842f3cc0ac84b156f12f2d4" + integrity sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA== + +"@types/d3-contour@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.1.tgz#9ff4e2fd2a3910de9c5097270a7da8a6ef240017" + integrity sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ== + dependencies: + "@types/d3-array" "*" + "@types/geojson" "*" + +"@types/d3-delaunay@*": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz#006b7bd838baec1511270cb900bf4fc377bbbf41" + integrity sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ== + +"@types/d3-dispatch@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz#a1b18ae5fa055a6734cb3bd3cbc6260ef19676e3" + integrity sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw== + +"@types/d3-drag@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.1.tgz#fb1e3d5cceeee4d913caa59dedf55c94cb66e80f" + integrity sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-dsv@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.0.tgz#f3c61fb117bd493ec0e814856feb804a14cfc311" + integrity sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A== + +"@types/d3-ease@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.0.tgz#c29926f8b596f9dadaeca062a32a45365681eae0" + integrity sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA== + +"@types/d3-fetch@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.1.tgz#f9fa88b81aa2eea5814f11aec82ecfddbd0b8fe0" + integrity sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw== + dependencies: + "@types/d3-dsv" "*" + +"@types/d3-force@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.3.tgz#76cb20d04ae798afede1ea6e41750763ff5a9c82" + integrity sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA== + +"@types/d3-format@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.1.tgz#194f1317a499edd7e58766f96735bdc0216bb89d" + integrity sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg== + +"@types/d3-geo@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.0.2.tgz#e7ec5f484c159b2c404c42d260e6d99d99f45d9a" + integrity sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ== + dependencies: + "@types/geojson" "*" + +"@types/d3-hierarchy@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz#4561bb7ace038f247e108295ef77b6a82193ac25" + integrity sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ== + +"@types/d3-interpolate@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz#e7d17fa4a5830ad56fe22ce3b4fac8541a9572dc" + integrity sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.0.0.tgz#939e3a784ae4f80b1fde8098b91af1776ff1312b" + integrity sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg== + +"@types/d3-polygon@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.0.tgz#5200a3fa793d7736fa104285fa19b0dbc2424b93" + integrity sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw== + +"@types/d3-quadtree@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz#433112a178eb7df123aab2ce11c67f51cafe8ff5" + integrity sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw== + +"@types/d3-random@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.1.tgz#5c8d42b36cd4c80b92e5626a252f994ca6bfc953" + integrity sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ== + +"@types/d3-scale-chromatic@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#103124777e8cdec85b20b51fd3397c682ee1e954" + integrity sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw== + +"@types/d3-scale@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.2.tgz#41be241126af4630524ead9cb1008ab2f0f26e69" + integrity sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA== + dependencies: + "@types/d3-time" "*" + +"@types/d3-selection@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.3.tgz#57be7da68e7d9c9b29efefd8ea5a9ef1171e42ba" + integrity sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA== + +"@types/d3-shape@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.0.tgz#1d87a6ddcf28285ef1e5c278ca4bdbc0658f3505" + integrity sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time-format@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.0.tgz#ee7b6e798f8deb2d9640675f8811d0253aaa1946" + integrity sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw== + +"@types/d3-time@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.0.tgz#e1ac0f3e9e195135361fa1a1d62f795d87e6e819" + integrity sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg== + +"@types/d3-timer@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.0.tgz#e2505f1c21ec08bda8915238e397fb71d2fc54ce" + integrity sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g== + +"@types/d3-transition@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.2.tgz#393dc3e3d55009a43cc6f252e73fccab6d78a8a4" + integrity sha512-jo5o/Rf+/u6uerJ/963Dc39NI16FQzqwOc54bwvksGAdVfvDrqDpVeq95bEvPtBwLCVZutAEyAtmSyEMxN7vxQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-zoom@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.1.tgz#4bfc7e29625c4f79df38e2c36de52ec3e9faf826" + integrity sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ== + dependencies: + "@types/d3-interpolate" "*" + "@types/d3-selection" "*" + +"@types/d3@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.0.tgz#fc5cac5b1756fc592a3cf1f3dc881bf08225f515" + integrity sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA== + dependencies: + "@types/d3-array" "*" + "@types/d3-axis" "*" + "@types/d3-brush" "*" + "@types/d3-chord" "*" + "@types/d3-color" "*" + "@types/d3-contour" "*" + "@types/d3-delaunay" "*" + "@types/d3-dispatch" "*" + "@types/d3-drag" "*" + "@types/d3-dsv" "*" + "@types/d3-ease" "*" + "@types/d3-fetch" "*" + "@types/d3-force" "*" + "@types/d3-format" "*" + "@types/d3-geo" "*" + "@types/d3-hierarchy" "*" + "@types/d3-interpolate" "*" + "@types/d3-path" "*" + "@types/d3-polygon" "*" + "@types/d3-quadtree" "*" + "@types/d3-random" "*" + "@types/d3-scale" "*" + "@types/d3-scale-chromatic" "*" + "@types/d3-selection" "*" + "@types/d3-shape" "*" + "@types/d3-time" "*" + "@types/d3-time-format" "*" + "@types/d3-timer" "*" + "@types/d3-transition" "*" + "@types/d3-zoom" "*" + "@types/eslint-scope@^3.7.3": version "3.7.3" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" @@ -3299,6 +3509,11 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" +"@types/geojson@*": + version "7946.0.10" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249" + integrity sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA== + "@types/google.maps@^3.45.3": version "3.49.0" resolved "https://registry.yarnpkg.com/@types/google.maps/-/google.maps-3.49.0.tgz#26fcf3d86ecbc6545db0e6691a434ec8132df48b" @@ -4834,6 +5049,11 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== +commander@7, commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -4844,11 +5064,6 @@ commander@^5.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - commander@^8.0.0, commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" @@ -5242,11 +5457,60 @@ d3-array@2, d3-array@^2.3.0: dependencies: internmap "^1.0.0" +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.0.tgz#15bf96cd9b7333e02eb8de8053d78962eafcff14" + integrity sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g== + dependencies: + internmap "1 - 2" + +d3-axis@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" + integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== + +d3-brush@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" + integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "3" + d3-transition "3" + +d3-chord@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" + integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== + dependencies: + d3-path "1 - 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@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-contour@4: + version "4.0.0" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.0.tgz#5a1337c6da0d528479acdb5db54bc81a0ff2ec6b" + integrity sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw== + dependencies: + d3-array "^3.2.0" + +d3-delaunay@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.2.tgz#7fd3717ad0eade2fc9939f4260acfb503f984e92" + integrity sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ== + dependencies: + delaunator "5" + d3-delaunay@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-5.3.0.tgz#b47f05c38f854a4e7b3cea80e0bb12e57398772d" @@ -5254,16 +5518,76 @@ d3-delaunay@^5.3.0: dependencies: delaunator "4" +"d3-dispatch@1 - 3", d3-dispatch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3", d3-drag@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-dsv@1 - 3", d3-dsv@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" + integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== + dependencies: + commander "7" + iconv-lite "0.6" + rw "1" + +"d3-ease@1 - 3", d3-ease@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +d3-fetch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" + integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== + dependencies: + d3-dsv "1 - 3" + +d3-force@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" + integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== + dependencies: + d3-dispatch "1 - 3" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + "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@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + 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-geo@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.0.1.tgz#4f92362fd8685d93e3b1fae0fd97dc8980b1ed7e" + integrity sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA== + dependencies: + d3-array "2.5.0 - 3" + +d3-hierarchy@3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== + "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" @@ -5271,11 +5595,46 @@ d3-format@^1.4.4: dependencies: d3-color "1 - 2" +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + 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@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e" + integrity sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w== + +d3-polygon@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" + integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== + +"d3-quadtree@1 - 3", d3-quadtree@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + +d3-random@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" + integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== + +d3-scale-chromatic@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a" + integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + 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" @@ -5284,6 +5643,17 @@ d3-scale-chromatic@^2.0.0: d3-color "1 - 2" d3-interpolate "1 - 2" +d3-scale@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + 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" @@ -5295,6 +5665,18 @@ d3-scale@^3.2.3: d3-time "^2.1.1" d3-time-format "2 - 3" +"d3-selection@2 - 3", d3-selection@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +d3-shape@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.1.0.tgz#c8a495652d83ea6f524e482fca57aa3f8bc32556" + integrity sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ== + dependencies: + 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" @@ -5309,6 +5691,13 @@ d3-shape@^1.3.5: dependencies: d3-time "1 - 2" +"d3-time-format@2 - 4", d3-time-format@4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + 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" @@ -5316,11 +5705,81 @@ d3-shape@^1.3.5: dependencies: d3-array "2" +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975" + integrity sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ== + dependencies: + 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@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +"d3-transition@2 - 3", d3-transition@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +d3@7.6.1: + version "7.6.1" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.6.1.tgz#b21af9563485ed472802f8c611cc43be6c37c40c" + integrity sha512-txMTdIHFbcpLx+8a0IFhZsbp+PfBBPt8yfbmukZTQFroKuFqIwqswF0qE5JXWefylaAVpSXFoKm3yP+jpNLFLw== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + daisyui@1.16.4: version "1.16.4" resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-1.16.4.tgz#52773401c0962e37ef40507d29f0e513c7f2856f" @@ -5464,6 +5923,13 @@ delaunator@4: resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-4.0.1.tgz#3d779687f57919a7a418f8ab947d3bddb6846957" integrity sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag== +delaunator@5: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b" + integrity sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw== + dependencies: + robust-predicates "^3.0.0" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -7381,6 +7847,13 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@0.6: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" @@ -7519,6 +7992,11 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + 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" @@ -10672,6 +11150,11 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +robust-predicates@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a" + integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g== + rope-sequence@^1.3.0: version "1.3.3" resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.3.tgz#3f67fc106288b84b71532b4a5fd9d4881e4457f0" @@ -10699,6 +11182,11 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rw@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== + rxjs@^6.6.3: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" @@ -10723,7 +11211,7 @@ safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== From c16adb9ec9fceeed1921957e4273b02f765b1888 Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Tue, 27 Sep 2022 21:18:22 -0700 Subject: [PATCH 02/16] Fix potential clock sync issues with graph updating (#947) --- web/components/charts/contract/binary.tsx | 17 ++++++++++++++--- web/components/charts/contract/choice.tsx | 18 ++++++++++++++---- .../charts/contract/pseudo-numeric.tsx | 18 ++++++++++++++---- web/components/charts/helpers.tsx | 15 +++++++++++++++ 4 files changed, 57 insertions(+), 11 deletions(-) diff --git a/web/components/charts/contract/binary.tsx b/web/components/charts/contract/binary.tsx index f7ed406b..8cd4bcb4 100644 --- a/web/components/charts/contract/binary.tsx +++ b/web/components/charts/contract/binary.tsx @@ -1,12 +1,18 @@ import { useMemo, useRef } from 'react' -import { sortBy } from 'lodash' +import { last, sortBy } from 'lodash' import { scaleTime, scaleLinear } from 'd3' import { Bet } from 'common/bet' import { getInitialProbability, getProbability } from 'common/calculate' import { BinaryContract } from 'common/contract' import { useIsMobile } from 'web/hooks/use-is-mobile' -import { MARGIN_X, MARGIN_Y, MAX_DATE, getDateRange } from '../helpers' +import { + MARGIN_X, + MARGIN_Y, + MAX_DATE, + getDateRange, + getRightmostVisibleDate, +} from '../helpers' import { SingleValueHistoryChart } from '../generic-charts' import { useElementWidth } from 'web/hooks/use-element-width' @@ -40,7 +46,12 @@ export const BinaryContractChart = (props: { ], [contract, betPoints, contractStart, contractEnd] ) - const visibleRange = [contractStart, contractEnd ?? Date.now()] + const rightmostDate = getRightmostVisibleDate( + contractEnd, + last(betPoints)?.[0], + new Date(Date.now()) + ) + const visibleRange = [contractStart, rightmostDate] const isMobile = useIsMobile(800) const containerRef = useRef(null) const width = useElementWidth(containerRef) ?? 0 diff --git a/web/components/charts/contract/choice.tsx b/web/components/charts/contract/choice.tsx index 4b4fa34d..ce87c382 100644 --- a/web/components/charts/contract/choice.tsx +++ b/web/components/charts/contract/choice.tsx @@ -1,5 +1,5 @@ import { useMemo, useRef } from 'react' -import { sum, sortBy, groupBy } from 'lodash' +import { last, sum, sortBy, groupBy } from 'lodash' import { scaleTime, scaleLinear } from 'd3' import { Bet } from 'common/bet' @@ -7,7 +7,13 @@ import { Answer } from 'common/answer' import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' import { getOutcomeProbability } from 'common/calculate' import { useIsMobile } from 'web/hooks/use-is-mobile' -import { MARGIN_X, MARGIN_Y, MAX_DATE, getDateRange } from '../helpers' +import { + MARGIN_X, + MARGIN_Y, + MAX_DATE, + getDateRange, + getRightmostVisibleDate, +} from '../helpers' import { MultiPoint, MultiValueHistoryChart } from '../generic-charts' import { useElementWidth } from 'web/hooks/use-element-width' @@ -143,8 +149,12 @@ export const ChoiceContractChart = (props: { ], [answers, contract, betPoints, contractStart, contractEnd] ) - const visibleRange = [contractStart, contractEnd ?? Date.now()] - + const rightmostDate = getRightmostVisibleDate( + contractEnd, + last(betPoints)?.[0], + new Date(Date.now()) + ) + const visibleRange = [contractStart, rightmostDate] const isMobile = useIsMobile(800) const containerRef = useRef(null) const width = useElementWidth(containerRef) ?? 0 diff --git a/web/components/charts/contract/pseudo-numeric.tsx b/web/components/charts/contract/pseudo-numeric.tsx index 9b67a169..8b0319f6 100644 --- a/web/components/charts/contract/pseudo-numeric.tsx +++ b/web/components/charts/contract/pseudo-numeric.tsx @@ -1,5 +1,5 @@ import { useMemo, useRef } from 'react' -import { sortBy } from 'lodash' +import { last, sortBy } from 'lodash' import { scaleTime, scaleLog, scaleLinear } from 'd3' import { Bet } from 'common/bet' @@ -7,7 +7,13 @@ import { getInitialProbability, getProbability } from 'common/calculate' import { PseudoNumericContract } from 'common/contract' import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants' import { useIsMobile } from 'web/hooks/use-is-mobile' -import { MARGIN_X, MARGIN_Y, MAX_DATE, getDateRange } from '../helpers' +import { + MARGIN_X, + MARGIN_Y, + MAX_DATE, + getDateRange, + getRightmostVisibleDate, +} from '../helpers' import { SingleValueHistoryChart } from '../generic-charts' import { useElementWidth } from 'web/hooks/use-element-width' @@ -55,8 +61,12 @@ export const PseudoNumericContractChart = (props: { ], [contract, betPoints, contractStart, contractEnd] ) - const visibleRange = [contractStart, contractEnd ?? Date.now()] - + const rightmostDate = getRightmostVisibleDate( + contractEnd, + last(betPoints)?.[0], + new Date(Date.now()) + ) + const visibleRange = [contractStart, rightmostDate] const isMobile = useIsMobile(800) const containerRef = useRef(null) const width = useElementWidth(containerRef) ?? 0 diff --git a/web/components/charts/helpers.tsx b/web/components/charts/helpers.tsx index 20231bc9..658fcbc9 100644 --- a/web/components/charts/helpers.tsx +++ b/web/components/charts/helpers.tsx @@ -205,3 +205,18 @@ export const getDateRange = (contract: Contract) => { const endDate = resolutionTime ?? (isClosed ? closeTime : null) return [new Date(createdTime), endDate ? new Date(endDate) : null] as const } + +export const getRightmostVisibleDate = ( + contractEnd: Date | null | undefined, + lastActivity: Date | null | undefined, + now: Date +) => { + if (contractEnd != null) { + return contractEnd + } else if (lastActivity != null) { + // client-DB clock divergence may cause last activity to be later than now + return new Date(Math.max(lastActivity.getTime(), now.getTime())) + } else { + return now + } +} From a5b943965c32ecde87bfd3db99c0ef8a9f709e43 Mon Sep 17 00:00:00 2001 From: Pico2x Date: Wed, 28 Sep 2022 00:59:24 -0400 Subject: [PATCH 03/16] Create cowp.tsx --- web/pages/cowp.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 web/pages/cowp.tsx diff --git a/web/pages/cowp.tsx b/web/pages/cowp.tsx new file mode 100644 index 00000000..6e5059d9 --- /dev/null +++ b/web/pages/cowp.tsx @@ -0,0 +1,11 @@ +import { Page } from 'web/components/page' + +const App = () => { + return ( + + + + ) +} + +export default App From 95f26044796a25b29325087969eb9ce1c9e19dd3 Mon Sep 17 00:00:00 2001 From: Pico2x Date: Wed, 28 Sep 2022 01:04:38 -0400 Subject: [PATCH 04/16] Cowp SEO friendly --- web/pages/cowp.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/web/pages/cowp.tsx b/web/pages/cowp.tsx index 6e5059d9..4d7d81e9 100644 --- a/web/pages/cowp.tsx +++ b/web/pages/cowp.tsx @@ -1,9 +1,18 @@ +import Link from 'next/link' import { Page } from 'web/components/page' +import { SEO } from 'web/components/SEO' const App = () => { return ( - + + + + ) } From f52127237ee1ee1715e370beceac168b3499da07 Mon Sep 17 00:00:00 2001 From: Pico2x Date: Wed, 28 Sep 2022 01:21:38 -0400 Subject: [PATCH 05/16] COWP for cows --- web/pages/cowp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/pages/cowp.tsx b/web/pages/cowp.tsx index 4d7d81e9..21494c37 100644 --- a/web/pages/cowp.tsx +++ b/web/pages/cowp.tsx @@ -10,7 +10,7 @@ const App = () => { description="A picture of a cowpy cowp copwer cowp saying 'salutations'" url="/cowp" /> - + From 5b54e7d468d1112fb4a4ef4ae9d6ebd2441cce84 Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Tue, 27 Sep 2022 22:25:37 -0700 Subject: [PATCH 06/16] Limit max width of FR legend tooltip labels (#948) --- web/components/charts/generic-charts.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/components/charts/generic-charts.tsx b/web/components/charts/generic-charts.tsx index 2bb5e089..05c26a42 100644 --- a/web/components/charts/generic-charts.tsx +++ b/web/components/charts/generic-charts.tsx @@ -88,12 +88,12 @@ const Legend = (props: { className?: string; items: LegendItem[] }) => {
    {items.map((item) => (
  1. - + - {item.label} + {item.label} {item.value}
  2. @@ -275,7 +275,7 @@ export const MultiValueHistoryChart = (props: { {mouseState && ( {fmtX(mouseState.p[0])} - + )} Date: Wed, 28 Sep 2022 00:56:43 -0700 Subject: [PATCH 07/16] Fix an edge case with chart mouseover tooltips (#949) --- web/components/charts/generic-charts.tsx | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/web/components/charts/generic-charts.tsx b/web/components/charts/generic-charts.tsx index 05c26a42..cb12ea45 100644 --- a/web/components/charts/generic-charts.tsx +++ b/web/components/charts/generic-charts.tsx @@ -150,7 +150,13 @@ export const SingleValueDistributionChart = (props: { if (ev.pointerType === 'mouse') { const [mouseX, mouseY] = pointer(ev) const queryX = xScale.invert(mouseX) - const [_x, y] = data[xBisector.center(data, queryX)] + const item = data[xBisector.left(data, queryX) - 1] + if (item == null) { + // this can happen if you are on the very left or right edge of the chart, + // so your queryX is out of bounds + return + } + const [_x, y] = item setMouseState({ top: mouseY - 10, left: mouseX + 60, p: [queryX, y] }) } }) @@ -250,7 +256,13 @@ export const MultiValueHistoryChart = (props: { if (ev.pointerType === 'mouse') { const [mouseX, mouseY] = pointer(ev) const queryX = xScale.invert(mouseX) - const [_x, ys] = data[xBisector.left(data, queryX) - 1] + const item = data[xBisector.left(data, queryX) - 1] + if (item == null) { + // this can happen if you are on the very left or right edge of the chart, + // so your queryX is out of bounds + return + } + const [_x, ys] = item setMouseState({ top: mouseY - 10, left: mouseX + 60, p: [queryX, ys] }) } }) @@ -354,7 +366,13 @@ export const SingleValueHistoryChart = (props: { if (ev.pointerType === 'mouse') { const [mouseX, mouseY] = pointer(ev) const queryX = xScale.invert(mouseX) - const [_x, y] = data[xBisector.left(data, queryX) - 1] + const item = data[xBisector.left(data, queryX) - 1] + if (item == null) { + // this can happen if you are on the very left or right edge of the chart, + // so your queryX is out of bounds + return + } + const [_x, y] = item setMouseState({ top: mouseY - 10, left: mouseX + 60, p: [queryX, y] }) } }) From 925a9e850f9e835f7350e4d9ccad4b7609d6ac42 Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Wed, 28 Sep 2022 00:58:51 -0700 Subject: [PATCH 08/16] Hack up brush rendering to fix possible Chrome bug (#950) --- web/components/charts/helpers.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web/components/charts/helpers.tsx b/web/components/charts/helpers.tsx index 658fcbc9..b4fbac58 100644 --- a/web/components/charts/helpers.tsx +++ b/web/components/charts/helpers.tsx @@ -150,7 +150,13 @@ export const SVGChart = (props: { justSelected.current = false } }) - select(overlayRef.current).call(brush) + // mqp: shape-rendering null overrides the default d3-brush shape-rendering + // of `crisp-edges`, which seems to cause graphical glitches on Chrome + // (i.e. the bug where the area fill flickers white) + select(overlayRef.current) + .call(brush) + .select('.selection') + .attr('shape-rendering', 'null') } }, [innerW, innerH, onSelect]) From 9238b20242477166ce01ecc2e668d00dba5a79d5 Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Wed, 28 Sep 2022 01:00:39 -0700 Subject: [PATCH 09/16] Modularize d3 imports (#951) --- web/components/charts/contract/binary.tsx | 2 +- web/components/charts/contract/choice.tsx | 2 +- web/components/charts/contract/numeric.tsx | 2 +- .../charts/contract/pseudo-numeric.tsx | 2 +- web/components/charts/generic-charts.tsx | 14 +- web/components/charts/helpers.tsx | 14 +- web/package.json | 7 +- yarn.lock | 198 ++---------------- 8 files changed, 43 insertions(+), 198 deletions(-) diff --git a/web/components/charts/contract/binary.tsx b/web/components/charts/contract/binary.tsx index 8cd4bcb4..372577c4 100644 --- a/web/components/charts/contract/binary.tsx +++ b/web/components/charts/contract/binary.tsx @@ -1,6 +1,6 @@ import { useMemo, useRef } from 'react' import { last, sortBy } from 'lodash' -import { scaleTime, scaleLinear } from 'd3' +import { scaleTime, scaleLinear } from 'd3-scale' import { Bet } from 'common/bet' import { getInitialProbability, getProbability } from 'common/calculate' diff --git a/web/components/charts/contract/choice.tsx b/web/components/charts/contract/choice.tsx index ce87c382..5786b7bb 100644 --- a/web/components/charts/contract/choice.tsx +++ b/web/components/charts/contract/choice.tsx @@ -1,6 +1,6 @@ import { useMemo, useRef } from 'react' import { last, sum, sortBy, groupBy } from 'lodash' -import { scaleTime, scaleLinear } from 'd3' +import { scaleTime, scaleLinear } from 'd3-scale' import { Bet } from 'common/bet' import { Answer } from 'common/answer' diff --git a/web/components/charts/contract/numeric.tsx b/web/components/charts/contract/numeric.tsx index 6adc52f0..6b574f15 100644 --- a/web/components/charts/contract/numeric.tsx +++ b/web/components/charts/contract/numeric.tsx @@ -1,6 +1,6 @@ import { useMemo, useRef } from 'react' import { max, range } from 'lodash' -import { scaleLinear } from 'd3' +import { scaleLinear } from 'd3-scale' import { getDpmOutcomeProbabilities } from 'common/calculate-dpm' import { NumericContract } from 'common/contract' diff --git a/web/components/charts/contract/pseudo-numeric.tsx b/web/components/charts/contract/pseudo-numeric.tsx index 8b0319f6..987a7fea 100644 --- a/web/components/charts/contract/pseudo-numeric.tsx +++ b/web/components/charts/contract/pseudo-numeric.tsx @@ -1,6 +1,6 @@ import { useMemo, useRef } from 'react' import { last, sortBy } from 'lodash' -import { scaleTime, scaleLog, scaleLinear } from 'd3' +import { scaleTime, scaleLog, scaleLinear } from 'd3-scale' import { Bet } from 'common/bet' import { getInitialProbability, getProbability } from 'common/calculate' diff --git a/web/components/charts/generic-charts.tsx b/web/components/charts/generic-charts.tsx index cb12ea45..0d262e17 100644 --- a/web/components/charts/generic-charts.tsx +++ b/web/components/charts/generic-charts.tsx @@ -1,18 +1,16 @@ import { useCallback, useMemo, useState } from 'react' +import { bisector } from 'd3-array' +import { axisBottom, axisLeft } from 'd3-axis' +import { D3BrushEvent } from 'd3-brush' +import { ScaleTime, ScaleContinuousNumeric } from 'd3-scale' +import { pointer } from 'd3-selection' import { - axisBottom, - axisLeft, - bisector, curveLinear, curveStepAfter, - pointer, stack, stackOrderReverse, - D3BrushEvent, - ScaleTime, - ScaleContinuousNumeric, SeriesPoint, -} from 'd3' +} from 'd3-shape' import { range, sortBy } from 'lodash' import dayjs from 'dayjs' diff --git a/web/components/charts/helpers.tsx b/web/components/charts/helpers.tsx index b4fbac58..644a421c 100644 --- a/web/components/charts/helpers.tsx +++ b/web/components/charts/helpers.tsx @@ -1,14 +1,8 @@ import { ReactNode, SVGProps, memo, useRef, useEffect, useMemo } from 'react' -import { - Axis, - CurveFactory, - D3BrushEvent, - area, - brushX, - curveStepAfter, - line, - select, -} from 'd3' +import { select } from 'd3-selection' +import { Axis } from 'd3-axis' +import { brushX, D3BrushEvent } from 'd3-brush' +import { area, line, curveStepAfter, CurveFactory } from 'd3-shape' import { nanoid } from 'nanoid' import clsx from 'clsx' diff --git a/web/package.json b/web/package.json index 570139f4..a3ec9aaa 100644 --- a/web/package.json +++ b/web/package.json @@ -39,7 +39,12 @@ "browser-image-compression": "2.0.0", "clsx": "1.1.1", "cors": "2.8.5", - "d3": "7.6.1", + "d3-array": "3.2.0", + "d3-axis": "3.0.0", + "d3-brush": "3.0.0", + "d3-scale": "4.0.2", + "d3-shape": "3.1.0", + "d3-selection": "3.0.0", "daisyui": "1.16.4", "dayjs": "1.10.7", "firebase": "9.9.3", diff --git a/yarn.lock b/yarn.lock index 17a065f7..d0ef33f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5049,11 +5049,6 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== -commander@7, commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -5064,6 +5059,11 @@ commander@^5.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commander@^8.0.0, commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" @@ -5457,19 +5457,19 @@ d3-array@2, d3-array@^2.3.0: dependencies: internmap "^1.0.0" -"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@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" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.0.tgz#15bf96cd9b7333e02eb8de8053d78962eafcff14" integrity sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g== dependencies: internmap "1 - 2" -d3-axis@3: +d3-axis@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== -d3-brush@3: +d3-brush@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== @@ -5480,37 +5480,16 @@ d3-brush@3: d3-selection "3" d3-transition "3" -d3-chord@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" - integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== - dependencies: - d3-path "1 - 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@3: +"d3-color@1 - 3": version "3.1.0" resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== -d3-contour@4: - version "4.0.0" - resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.0.tgz#5a1337c6da0d528479acdb5db54bc81a0ff2ec6b" - integrity sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw== - dependencies: - d3-array "^3.2.0" - -d3-delaunay@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.2.tgz#7fd3717ad0eade2fc9939f4260acfb503f984e92" - integrity sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ== - dependencies: - delaunator "5" - d3-delaunay@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-5.3.0.tgz#b47f05c38f854a4e7b3cea80e0bb12e57398772d" @@ -5518,12 +5497,12 @@ d3-delaunay@^5.3.0: dependencies: delaunator "4" -"d3-dispatch@1 - 3", d3-dispatch@3: +"d3-dispatch@1 - 3": version "3.0.1" resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== -"d3-drag@2 - 3", d3-drag@3: +"d3-drag@2 - 3": version "3.0.0" resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== @@ -5531,42 +5510,17 @@ d3-delaunay@^5.3.0: d3-dispatch "1 - 3" d3-selection "3" -"d3-dsv@1 - 3", d3-dsv@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" - integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== - dependencies: - commander "7" - iconv-lite "0.6" - rw "1" - -"d3-ease@1 - 3", d3-ease@3: +"d3-ease@1 - 3": version "3.0.1" resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== -d3-fetch@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" - integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== - dependencies: - d3-dsv "1 - 3" - -d3-force@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" - integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== - dependencies: - d3-dispatch "1 - 3" - d3-quadtree "1 - 3" - d3-timer "1 - 3" - "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@3: +"d3-format@1 - 3": version "3.1.0" resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== @@ -5576,18 +5530,6 @@ d3-format@^1.4.4: resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4" integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ== -d3-geo@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.0.1.tgz#4f92362fd8685d93e3b1fae0fd97dc8980b1ed7e" - integrity sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA== - dependencies: - d3-array "2.5.0 - 3" - -d3-hierarchy@3: - version "3.1.2" - resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" - integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== - "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" @@ -5595,7 +5537,7 @@ d3-hierarchy@3: dependencies: d3-color "1 - 2" -"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3": version "3.0.1" resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== @@ -5607,34 +5549,11 @@ d3-path@1: 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@3: +"d3-path@1 - 3": version "3.0.1" resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e" integrity sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w== -d3-polygon@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" - integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== - -"d3-quadtree@1 - 3", d3-quadtree@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" - integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== - -d3-random@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" - integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== - -d3-scale-chromatic@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a" - integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g== - dependencies: - d3-color "1 - 3" - d3-interpolate "1 - 3" - 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" @@ -5643,7 +5562,7 @@ d3-scale-chromatic@^2.0.0: d3-color "1 - 2" d3-interpolate "1 - 2" -d3-scale@4: +d3-scale@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== @@ -5665,12 +5584,12 @@ d3-scale@^3.2.3: d3-time "^2.1.1" d3-time-format "2 - 3" -"d3-selection@2 - 3", d3-selection@3: +d3-selection@3, d3-selection@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== -d3-shape@3: +d3-shape@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.1.0.tgz#c8a495652d83ea6f524e482fca57aa3f8bc32556" integrity sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ== @@ -5691,7 +5610,7 @@ d3-shape@^1.3.5: dependencies: d3-time "1 - 2" -"d3-time-format@2 - 4", d3-time-format@4: +"d3-time-format@2 - 4": version "4.1.0" resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== @@ -5705,7 +5624,7 @@ d3-shape@^1.3.5: dependencies: d3-array "2" -"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: +"d3-time@1 - 3", "d3-time@2.1.1 - 3": version "3.0.0" resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975" integrity sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ== @@ -5717,12 +5636,12 @@ d3-time@^1.0.11: 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@3: +"d3-timer@1 - 3": version "3.0.1" resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== -"d3-transition@2 - 3", d3-transition@3: +d3-transition@3: version "3.0.1" resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== @@ -5733,53 +5652,6 @@ d3-time@^1.0.11: d3-interpolate "1 - 3" d3-timer "1 - 3" -d3-zoom@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" - integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== - dependencies: - d3-dispatch "1 - 3" - d3-drag "2 - 3" - d3-interpolate "1 - 3" - d3-selection "2 - 3" - d3-transition "2 - 3" - -d3@7.6.1: - version "7.6.1" - resolved "https://registry.yarnpkg.com/d3/-/d3-7.6.1.tgz#b21af9563485ed472802f8c611cc43be6c37c40c" - integrity sha512-txMTdIHFbcpLx+8a0IFhZsbp+PfBBPt8yfbmukZTQFroKuFqIwqswF0qE5JXWefylaAVpSXFoKm3yP+jpNLFLw== - dependencies: - d3-array "3" - d3-axis "3" - d3-brush "3" - d3-chord "3" - d3-color "3" - d3-contour "4" - d3-delaunay "6" - d3-dispatch "3" - d3-drag "3" - d3-dsv "3" - d3-ease "3" - d3-fetch "3" - d3-force "3" - d3-format "3" - d3-geo "3" - d3-hierarchy "3" - d3-interpolate "3" - d3-path "3" - d3-polygon "3" - d3-quadtree "3" - d3-random "3" - d3-scale "4" - d3-scale-chromatic "3" - d3-selection "3" - d3-shape "3" - d3-time "3" - d3-time-format "4" - d3-timer "3" - d3-transition "3" - d3-zoom "3" - daisyui@1.16.4: version "1.16.4" resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-1.16.4.tgz#52773401c0962e37ef40507d29f0e513c7f2856f" @@ -5923,13 +5795,6 @@ delaunator@4: resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-4.0.1.tgz#3d779687f57919a7a418f8ab947d3bddb6846957" integrity sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag== -delaunator@5: - version "5.0.0" - resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b" - integrity sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw== - dependencies: - robust-predicates "^3.0.0" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -7847,13 +7712,6 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@0.6: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" @@ -11150,11 +11008,6 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" -robust-predicates@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a" - integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g== - rope-sequence@^1.3.0: version "1.3.3" resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.3.tgz#3f67fc106288b84b71532b4a5fd9d4881e4457f0" @@ -11182,11 +11035,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rw@1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" - integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== - rxjs@^6.6.3: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" @@ -11211,7 +11059,7 @@ safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== From 89c3ea559c166669b618fbd4e5ba8bde7250e479 Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Wed, 28 Sep 2022 01:18:11 -0700 Subject: [PATCH 10/16] Clamp time range in history chart scales (#952) --- web/components/charts/contract/binary.tsx | 2 +- web/components/charts/contract/choice.tsx | 2 +- web/components/charts/contract/pseudo-numeric.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/components/charts/contract/binary.tsx b/web/components/charts/contract/binary.tsx index 372577c4..6d906998 100644 --- a/web/components/charts/contract/binary.tsx +++ b/web/components/charts/contract/binary.tsx @@ -56,7 +56,7 @@ export const BinaryContractChart = (props: { const containerRef = useRef(null) const width = useElementWidth(containerRef) ?? 0 const height = props.height ?? (isMobile ? 250 : 350) - const xScale = scaleTime(visibleRange, [0, width - MARGIN_X]) + const xScale = scaleTime(visibleRange, [0, width - MARGIN_X]).clamp(true) const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0]) return (
    diff --git a/web/components/charts/contract/choice.tsx b/web/components/charts/contract/choice.tsx index 5786b7bb..7cf3e5ed 100644 --- a/web/components/charts/contract/choice.tsx +++ b/web/components/charts/contract/choice.tsx @@ -159,7 +159,7 @@ export const ChoiceContractChart = (props: { const containerRef = useRef(null) const width = useElementWidth(containerRef) ?? 0 const height = props.height ?? (isMobile ? 150 : 250) - const xScale = scaleTime(visibleRange, [0, width - MARGIN_X]) + const xScale = scaleTime(visibleRange, [0, width - MARGIN_X]).clamp(true) const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0]) return (
    diff --git a/web/components/charts/contract/pseudo-numeric.tsx b/web/components/charts/contract/pseudo-numeric.tsx index 987a7fea..0e2aaad0 100644 --- a/web/components/charts/contract/pseudo-numeric.tsx +++ b/web/components/charts/contract/pseudo-numeric.tsx @@ -71,7 +71,7 @@ export const PseudoNumericContractChart = (props: { const containerRef = useRef(null) const width = useElementWidth(containerRef) ?? 0 const height = props.height ?? (isMobile ? 150 : 250) - const xScale = scaleTime(visibleRange, [0, width - MARGIN_X]) + const xScale = scaleTime(visibleRange, [0, width - MARGIN_X]).clamp(true) const yScale = contract.isLogScale ? scaleLog( [Math.max(contract.min, 1), contract.max], From 513cf7b290bf84b2c98bed69fe957a9cd2da9829 Mon Sep 17 00:00:00 2001 From: ingawei Date: Wed, 28 Sep 2022 06:45:32 -0700 Subject: [PATCH 11/16] added order book --- web/components/bet-button.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/components/bet-button.tsx b/web/components/bet-button.tsx index 808b450f..622192c7 100644 --- a/web/components/bet-button.tsx +++ b/web/components/bet-button.tsx @@ -17,6 +17,7 @@ import { BetSignUpPrompt } from './sign-up-prompt' import { User } from 'web/lib/firebase/users' import { SellRow } from './sell-row' import { useUnfilledBets } from 'web/hooks/use-bets' +import { LimitBets, OrderBookButton } from './limit-bets' /** Button that opens BetPanel in a new modal */ export default function BetButton(props: { @@ -115,6 +116,11 @@ export function SignedInBinaryMobileBetting(props: { 'border-greyscale-3 bg-greyscale-1 rounded-md border-2 px-4 py-2' } /> + ) From e0e6838711131419a352a1be66f2b18d38d9bb12 Mon Sep 17 00:00:00 2001 From: ingawei Date: Wed, 28 Sep 2022 13:46:41 +0000 Subject: [PATCH 12/16] Auto-remove unused imports --- web/components/bet-button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/components/bet-button.tsx b/web/components/bet-button.tsx index 622192c7..e51fc527 100644 --- a/web/components/bet-button.tsx +++ b/web/components/bet-button.tsx @@ -17,7 +17,7 @@ import { BetSignUpPrompt } from './sign-up-prompt' import { User } from 'web/lib/firebase/users' import { SellRow } from './sell-row' import { useUnfilledBets } from 'web/hooks/use-bets' -import { LimitBets, OrderBookButton } from './limit-bets' +import { LimitBets } from './limit-bets' /** Button that opens BetPanel in a new modal */ export default function BetButton(props: { From 7c8e977d60f6f2ca49631c717823e3c58253ea3e Mon Sep 17 00:00:00 2001 From: ingawei <46611122+ingawei@users.noreply.github.com> Date: Wed, 28 Sep 2022 09:04:47 -0500 Subject: [PATCH 13/16] order book things (#953) Adding order book to limit orders in mobile modal. This is pretty ugly and just a quick fix because people are complaining. --- web/components/bet-button.tsx | 6 ------ web/components/bet-panel.tsx | 7 ++++++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/web/components/bet-button.tsx b/web/components/bet-button.tsx index e51fc527..808b450f 100644 --- a/web/components/bet-button.tsx +++ b/web/components/bet-button.tsx @@ -17,7 +17,6 @@ import { BetSignUpPrompt } from './sign-up-prompt' import { User } from 'web/lib/firebase/users' import { SellRow } from './sell-row' import { useUnfilledBets } from 'web/hooks/use-bets' -import { LimitBets } from './limit-bets' /** Button that opens BetPanel in a new modal */ export default function BetButton(props: { @@ -116,11 +115,6 @@ export function SignedInBinaryMobileBetting(props: { 'border-greyscale-3 bg-greyscale-1 rounded-md border-2 px-4 py-2' } /> - ) diff --git a/web/components/bet-panel.tsx b/web/components/bet-panel.tsx index 90918283..5d908937 100644 --- a/web/components/bet-panel.tsx +++ b/web/components/bet-panel.tsx @@ -419,7 +419,7 @@ export function BuyPanel(props: { open={seeLimit} setOpen={setSeeLimit} position="center" - className="rounded-lg bg-white px-4 pb-8" + className="rounded-lg bg-white px-4 pb-4" > <LimitOrderPanel @@ -428,6 +428,11 @@ export function BuyPanel(props: { user={user} unfilledBets={unfilledBets} /> + <LimitBets + contract={contract} + bets={unfilledBets as LimitBet[]} + className="mt-4" + /> </Modal> </Col> </Col> From dba938032f96d46bb0aa63b1ffcf89212270d02c Mon Sep 17 00:00:00 2001 From: James Grugett <jahooma@gmail.com> Date: Wed, 28 Sep 2022 09:52:16 -0400 Subject: [PATCH 14/16] Listen for updates on daily mover contract --- web/components/contract/prob-change-table.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/components/contract/prob-change-table.tsx b/web/components/contract/prob-change-table.tsx index 07b7c659..f54ad915 100644 --- a/web/components/contract/prob-change-table.tsx +++ b/web/components/contract/prob-change-table.tsx @@ -7,6 +7,7 @@ import { SiteLink } from '../site-link' import { Col } from '../layout/col' import { Row } from '../layout/row' import { LoadingIndicator } from '../loading-indicator' +import { useContractWithPreload } from 'web/hooks/use-contract' export function ProbChangeTable(props: { changes: CPMMContract[] | undefined @@ -59,7 +60,9 @@ export function ProbChangeRow(props: { contract: CPMMContract className?: string }) { - const { contract, className } = props + const { className } = props + const contract = + (useContractWithPreload(props.contract) as CPMMContract) ?? props.contract return ( <Row className={clsx( From eb762d9b9ee557ffddffc005b8931dff9e66f476 Mon Sep 17 00:00:00 2001 From: James Grugett <jahooma@gmail.com> Date: Wed, 28 Sep 2022 12:28:39 -0400 Subject: [PATCH 15/16] Make loading more sequential for updateMetrics to prevent firebase error. --- functions/src/update-metrics.ts | 51 ++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/functions/src/update-metrics.ts b/functions/src/update-metrics.ts index d2b5f9b2..12f41453 100644 --- a/functions/src/update-metrics.ts +++ b/functions/src/update-metrics.ts @@ -17,7 +17,8 @@ import { computeVolume, } from '../../common/calculate-metrics' import { getProbability } from '../../common/calculate' -import { Group } from 'common/group' +import { Group } from '../../common/group' +import { batchedWaitAll } from '../../common/util/promise' const firestore = admin.firestore() @@ -27,28 +28,46 @@ export const updateMetrics = functions .onRun(updateMetricsCore) export async function updateMetricsCore() { - const [users, contracts, bets, allPortfolioHistories, groups] = - await Promise.all([ - getValues<User>(firestore.collection('users')), - getValues<Contract>(firestore.collection('contracts')), - getValues<Bet>(firestore.collectionGroup('bets')), - getValues<PortfolioMetrics>( - firestore - .collectionGroup('portfolioHistory') - .where('timestamp', '>', Date.now() - 31 * DAY_MS) // so it includes just over a month ago - ), - getValues<Group>(firestore.collection('groups')), - ]) + console.log('Loading users') + const users = await getValues<User>(firestore.collection('users')) + console.log('Loading contracts') + const contracts = await getValues<Contract>(firestore.collection('contracts')) + + console.log('Loading portfolio history') + const allPortfolioHistories = await getValues<PortfolioMetrics>( + firestore + .collectionGroup('portfolioHistory') + .where('timestamp', '>', Date.now() - 31 * DAY_MS) // so it includes just over a month ago + ) + + console.log('Loading groups') + const groups = await getValues<Group>(firestore.collection('groups')) + + console.log('Loading bets') + const contractBets = await batchedWaitAll( + contracts + .filter((c) => c.id) + .map( + (c) => () => + getValues<Bet>( + firestore.collection('contracts').doc(c.id).collection('bets') + ) + ), + 100 + ) + const bets = contractBets.flat() + + console.log('Loading group contracts') const contractsByGroup = await Promise.all( - groups.map((group) => { - return getValues( + groups.map((group) => + getValues( firestore .collection('groups') .doc(group.id) .collection('groupContracts') ) - }) + ) ) log( `Loaded ${users.length} users, ${contracts.length} contracts, and ${bets.length} bets.` From d55cedb36c46fa3ad9c9fd33f076b418d3f40c50 Mon Sep 17 00:00:00 2001 From: James Grugett <jahooma@gmail.com> Date: Wed, 28 Sep 2022 13:11:26 -0400 Subject: [PATCH 16/16] Load comments via static props --- web/components/contract/contract-tabs.tsx | 9 +++++--- web/lib/firebase/comments.ts | 2 +- web/pages/[username]/[contractSlug].tsx | 27 ++++++++++++++++++----- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/web/components/contract/contract-tabs.tsx b/web/components/contract/contract-tabs.tsx index bd3204ed..33a3c05a 100644 --- a/web/components/contract/contract-tabs.tsx +++ b/web/components/contract/contract-tabs.tsx @@ -23,13 +23,15 @@ import { HOUSE_LIQUIDITY_PROVIDER_ID, } from 'common/antes' import { buildArray } from 'common/util/array' +import { ContractComment } from 'common/comment' export function ContractTabs(props: { contract: Contract bets: Bet[] userBets: Bet[] + comments: ContractComment[] }) { - const { contract, bets, userBets } = props + const { contract, bets, userBets, comments } = props const yourTrades = ( <div> @@ -42,7 +44,7 @@ export function ContractTabs(props: { const tabs = buildArray( { title: 'Comments', - content: <CommentsTabContent contract={contract} />, + content: <CommentsTabContent contract={contract} comments={comments} />, }, { title: capitalize(PAST_BETS), @@ -61,10 +63,11 @@ export function ContractTabs(props: { const CommentsTabContent = memo(function CommentsTabContent(props: { contract: Contract + comments: ContractComment[] }) { const { contract } = props const tips = useTipTxns({ contractId: contract.id }) - const comments = useComments(contract.id) + const comments = useComments(contract.id) ?? props.comments if (comments == null) { return <LoadingIndicator /> } diff --git a/web/lib/firebase/comments.ts b/web/lib/firebase/comments.ts index db4e8ede..733a1e06 100644 --- a/web/lib/firebase/comments.ts +++ b/web/lib/firebase/comments.ts @@ -131,7 +131,7 @@ function getCommentsOnPostCollection(postId: string) { } export async function listAllComments(contractId: string) { - return await getValues<Comment>( + return await getValues<ContractComment>( query(getCommentsCollection(contractId), orderBy('createdTime', 'desc')) ) } diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx index 1dde2f95..93b53447 100644 --- a/web/pages/[username]/[contractSlug].tsx +++ b/web/pages/[username]/[contractSlug].tsx @@ -46,6 +46,8 @@ import { BetSignUpPrompt } from 'web/components/sign-up-prompt' import { PlayMoneyDisclaimer } from 'web/components/play-money-disclaimer' import BetButton from 'web/components/bet-button' import { BetsSummary } from 'web/components/bet-summary' +import { listAllComments } from 'web/lib/firebase/comments' +import { ContractComment } from 'common/comment' export const getStaticProps = fromPropz(getStaticPropz) export async function getStaticPropz(props: { @@ -55,10 +57,15 @@ export async function getStaticPropz(props: { const contract = (await getContractFromSlug(contractSlug)) || null const contractId = contract?.id const bets = contractId ? await listAllBets(contractId) : [] + const comments = contractId ? await listAllComments(contractId) : [] return { - // Limit the data sent to the client. Client will still load all bets directly. - props: { contract, bets: bets.slice(0, 5000) }, + props: { + contract, + // Limit the data sent to the client. Client will still load all bets/comments directly. + bets: bets.slice(0, 5000), + comments: comments.slice(0, 1000), + }, revalidate: 5, // regenerate after five seconds } } @@ -70,9 +77,14 @@ export async function getStaticPaths() { export default function ContractPage(props: { contract: Contract | null bets: Bet[] + comments: ContractComment[] backToHome?: () => void }) { - props = usePropz(props, getStaticPropz) ?? { contract: null, bets: [] } + props = usePropz(props, getStaticPropz) ?? { + contract: null, + bets: [], + comments: [], + } const inIframe = useIsIframe() if (inIframe) { @@ -147,7 +159,7 @@ export function ContractPageContent( contract: Contract } ) { - const { backToHome } = props + const { backToHome, comments } = props const contract = useContractWithPreload(props.contract) ?? props.contract const user = useUser() usePrefetch(user?.id) @@ -258,7 +270,12 @@ export function ContractPageContent( userBets={userBets} /> - <ContractTabs contract={contract} bets={bets} userBets={userBets} /> + <ContractTabs + contract={contract} + bets={bets} + userBets={userBets} + comments={comments} + /> {!user ? ( <Col className="mt-4 max-w-sm items-center xl:hidden">