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
This commit is contained in:
parent
9dc0d1696e
commit
e0d9b4d335
|
@ -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 (
|
|
||||||
<div
|
|
||||||
className="w-full"
|
|
||||||
style={{ height: height ?? (isLargeWidth ? 350 : 250) }}
|
|
||||||
>
|
|
||||||
<ResponsiveLine
|
|
||||||
data={data}
|
|
||||||
yScale={{ min: 0, max: 100, type: 'linear', stacked: true }}
|
|
||||||
yFormat={formatPercent}
|
|
||||||
gridYValues={yTickValues}
|
|
||||||
axisLeft={{
|
|
||||||
tickValues: yTickValues,
|
|
||||||
format: formatPercent,
|
|
||||||
}}
|
|
||||||
xScale={{
|
|
||||||
type: 'time',
|
|
||||||
min: startDate.toDate(),
|
|
||||||
max: endDate.toDate(),
|
|
||||||
}}
|
|
||||||
xFormat={(d) =>
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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 }
|
|
||||||
}
|
|
65
web/components/charts/contract/binary.tsx
Normal file
65
web/components/charts/contract/binary.tsx
Normal file
|
@ -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<HTMLDivElement>(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 (
|
||||||
|
<div ref={containerRef}>
|
||||||
|
{width && (
|
||||||
|
<SingleValueHistoryChart
|
||||||
|
w={width}
|
||||||
|
h={height}
|
||||||
|
xScale={xScale}
|
||||||
|
yScale={yScale}
|
||||||
|
data={data}
|
||||||
|
color="#11b981"
|
||||||
|
pct
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
170
web/components/charts/contract/choice.tsx
Normal file
170
web/components/charts/contract/choice.tsx
Normal file
|
@ -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<HTMLDivElement>(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 (
|
||||||
|
<div ref={containerRef}>
|
||||||
|
{width && (
|
||||||
|
<MultiValueHistoryChart
|
||||||
|
w={width}
|
||||||
|
h={height}
|
||||||
|
xScale={xScale}
|
||||||
|
yScale={yScale}
|
||||||
|
data={data}
|
||||||
|
colors={CATEGORY_COLORS}
|
||||||
|
labels={answers.map((answer) => answer.text)}
|
||||||
|
pct
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
34
web/components/charts/contract/index.tsx
Normal file
34
web/components/charts/contract/index.tsx
Normal file
|
@ -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 <BinaryContractChart {...{ ...props, contract }} />
|
||||||
|
case 'PSEUDO_NUMERIC':
|
||||||
|
return <PseudoNumericContractChart {...{ ...props, contract }} />
|
||||||
|
case 'FREE_RESPONSE':
|
||||||
|
case 'MULTIPLE_CHOICE':
|
||||||
|
return <ChoiceContractChart {...{ ...props, contract }} />
|
||||||
|
case 'NUMERIC':
|
||||||
|
return <NumericContractChart {...{ ...props, contract }} />
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
BinaryContractChart,
|
||||||
|
PseudoNumericContractChart,
|
||||||
|
ChoiceContractChart,
|
||||||
|
NumericContractChart,
|
||||||
|
}
|
52
web/components/charts/contract/numeric.tsx
Normal file
52
web/components/charts/contract/numeric.tsx
Normal file
|
@ -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<HTMLDivElement>(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 (
|
||||||
|
<div ref={containerRef}>
|
||||||
|
{width && (
|
||||||
|
<SingleValueDistributionChart
|
||||||
|
w={width}
|
||||||
|
h={height}
|
||||||
|
xScale={xScale}
|
||||||
|
yScale={yScale}
|
||||||
|
data={data}
|
||||||
|
color={NUMERIC_GRAPH_COLOR}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
86
web/components/charts/contract/pseudo-numeric.tsx
Normal file
86
web/components/charts/contract/pseudo-numeric.tsx
Normal file
|
@ -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<HTMLDivElement>(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 (
|
||||||
|
<div ref={containerRef}>
|
||||||
|
{width && (
|
||||||
|
<SingleValueHistoryChart
|
||||||
|
w={width}
|
||||||
|
h={height}
|
||||||
|
xScale={xScale}
|
||||||
|
yScale={yScale}
|
||||||
|
data={data}
|
||||||
|
color={NUMERIC_GRAPH_COLOR}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
393
web/components/charts/generic-charts.tsx
Normal file
393
web/components/charts/generic-charts.tsx
Normal file
|
@ -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<P> = 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 (
|
||||||
|
<ol className={className}>
|
||||||
|
{items.map((item) => (
|
||||||
|
<li key={item.label} className="flex flex-row justify-between">
|
||||||
|
<Row className="mr-4 items-center">
|
||||||
|
<span
|
||||||
|
className="mr-2 h-4 w-4"
|
||||||
|
style={{ backgroundColor: item.color }}
|
||||||
|
></span>
|
||||||
|
{item.label}
|
||||||
|
</Row>
|
||||||
|
{item.value}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SingleValueDistributionChart = (props: {
|
||||||
|
data: DistributionPoint[]
|
||||||
|
w: number
|
||||||
|
h: number
|
||||||
|
color: string
|
||||||
|
xScale: ScaleContinuousNumeric<number, number>
|
||||||
|
yScale: ScaleContinuousNumeric<number, number>
|
||||||
|
}) => {
|
||||||
|
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<ScaleContinuousNumeric<number, number>>()
|
||||||
|
const [mouseState, setMouseState] =
|
||||||
|
useState<PositionValue<DistributionPoint>>()
|
||||||
|
const xScale = viewXScale ?? props.xScale
|
||||||
|
|
||||||
|
const px = useCallback((p: DistributionPoint) => xScale(p[0]), [xScale])
|
||||||
|
const py0 = yScale(yScale.domain()[0])
|
||||||
|
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<number>(xScale).ticks(w / 100)
|
||||||
|
const yAxis = axisLeft<number>(yScale).tickFormat(fmtY)
|
||||||
|
return { fmtX, fmtY, xAxis, yAxis }
|
||||||
|
}, [w, xScale, yScale])
|
||||||
|
|
||||||
|
const onSelect = useEvent((ev: D3BrushEvent<DistributionPoint>) => {
|
||||||
|
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 (
|
||||||
|
<div className="relative">
|
||||||
|
{mouseState && (
|
||||||
|
<ChartTooltip className="text-sm" {...mouseState}>
|
||||||
|
<strong>{fmtY(mouseState.p[1])}</strong> {fmtX(mouseState.p[0])}
|
||||||
|
</ChartTooltip>
|
||||||
|
)}
|
||||||
|
<SVGChart
|
||||||
|
w={w}
|
||||||
|
h={h}
|
||||||
|
xAxis={xAxis}
|
||||||
|
yAxis={yAxis}
|
||||||
|
onSelect={onSelect}
|
||||||
|
onMouseOver={onMouseOver}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
>
|
||||||
|
<AreaWithTopStroke
|
||||||
|
color={color}
|
||||||
|
data={data}
|
||||||
|
px={px}
|
||||||
|
py0={py0}
|
||||||
|
py1={py1}
|
||||||
|
curve={curveLinear}
|
||||||
|
/>
|
||||||
|
</SVGChart>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MultiValueHistoryChart = (props: {
|
||||||
|
data: MultiPoint[]
|
||||||
|
w: number
|
||||||
|
h: number
|
||||||
|
labels: readonly string[]
|
||||||
|
colors: readonly string[]
|
||||||
|
xScale: ScaleTime<number, number>
|
||||||
|
yScale: ScaleContinuousNumeric<number, number>
|
||||||
|
pct?: boolean
|
||||||
|
}) => {
|
||||||
|
const { colors, data, yScale, labels, w, h, pct } = props
|
||||||
|
|
||||||
|
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||||
|
const [mouseState, setMouseState] = useState<PositionValue<MultiPoint>>()
|
||||||
|
const xScale = viewXScale ?? props.xScale
|
||||||
|
|
||||||
|
type SP = SeriesPoint<MultiPoint>
|
||||||
|
const px = useCallback((p: SP) => xScale(p.data[0]), [xScale])
|
||||||
|
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<Date>(xScale).ticks(w / 100)
|
||||||
|
const yAxis = pct
|
||||||
|
? axisLeft<number>(yScale).tickValues(pctTickValues).tickFormat(fmtY)
|
||||||
|
: axisLeft<number>(yScale)
|
||||||
|
|
||||||
|
return { fmtX, fmtY, xAxis, yAxis }
|
||||||
|
}, [w, h, pct, xScale, yScale])
|
||||||
|
|
||||||
|
const series = useMemo(() => {
|
||||||
|
const d3Stack = stack<MultiPoint, number>()
|
||||||
|
.keys(range(0, labels.length))
|
||||||
|
.value(([_date, probs], o) => probs[o])
|
||||||
|
.order(stackOrderReverse)
|
||||||
|
return d3Stack(data)
|
||||||
|
}, [data, labels.length])
|
||||||
|
|
||||||
|
const onSelect = useEvent((ev: D3BrushEvent<MultiPoint>) => {
|
||||||
|
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 (
|
||||||
|
<div className="relative">
|
||||||
|
{mouseState && (
|
||||||
|
<ChartTooltip {...mouseState}>
|
||||||
|
{fmtX(mouseState.p[0])}
|
||||||
|
<Legend className="text-sm" items={legendItems} />
|
||||||
|
</ChartTooltip>
|
||||||
|
)}
|
||||||
|
<SVGChart
|
||||||
|
w={w}
|
||||||
|
h={h}
|
||||||
|
xAxis={xAxis}
|
||||||
|
yAxis={yAxis}
|
||||||
|
onSelect={onSelect}
|
||||||
|
onMouseOver={onMouseOver}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
>
|
||||||
|
{series.map((s, i) => (
|
||||||
|
<AreaPath
|
||||||
|
key={i}
|
||||||
|
data={s}
|
||||||
|
px={px}
|
||||||
|
py0={py0}
|
||||||
|
py1={py1}
|
||||||
|
curve={curveStepAfter}
|
||||||
|
fill={colors[i]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SVGChart>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SingleValueHistoryChart = (props: {
|
||||||
|
data: HistoryPoint[]
|
||||||
|
w: number
|
||||||
|
h: number
|
||||||
|
color: string
|
||||||
|
xScale: ScaleTime<number, number>
|
||||||
|
yScale: ScaleContinuousNumeric<number, number>
|
||||||
|
pct?: boolean
|
||||||
|
}) => {
|
||||||
|
const { color, data, pct, yScale, w, h } = props
|
||||||
|
|
||||||
|
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||||
|
const [mouseState, setMouseState] = useState<PositionValue<HistoryPoint>>()
|
||||||
|
const xScale = viewXScale ?? props.xScale
|
||||||
|
|
||||||
|
const px = useCallback((p: HistoryPoint) => xScale(p[0]), [xScale])
|
||||||
|
const py0 = yScale(yScale.domain()[0])
|
||||||
|
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<Date>(xScale).ticks(w / 100)
|
||||||
|
const yAxis = pct
|
||||||
|
? axisLeft<number>(yScale).tickValues(pctTickValues).tickFormat(fmtY)
|
||||||
|
: axisLeft<number>(yScale)
|
||||||
|
return { fmtX, fmtY, xAxis, yAxis }
|
||||||
|
}, [w, h, pct, xScale, yScale])
|
||||||
|
|
||||||
|
const onSelect = useEvent((ev: D3BrushEvent<HistoryPoint>) => {
|
||||||
|
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 (
|
||||||
|
<div className="relative">
|
||||||
|
{mouseState && (
|
||||||
|
<ChartTooltip className="text-sm" {...mouseState}>
|
||||||
|
<strong>{fmtY(mouseState.p[1])}</strong> {fmtX(mouseState.p[0])}
|
||||||
|
</ChartTooltip>
|
||||||
|
)}
|
||||||
|
<SVGChart
|
||||||
|
w={w}
|
||||||
|
h={h}
|
||||||
|
xAxis={xAxis}
|
||||||
|
yAxis={yAxis}
|
||||||
|
onSelect={onSelect}
|
||||||
|
onMouseOver={onMouseOver}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
>
|
||||||
|
<AreaWithTopStroke
|
||||||
|
color={color}
|
||||||
|
data={data}
|
||||||
|
px={px}
|
||||||
|
py0={py0}
|
||||||
|
py1={py1}
|
||||||
|
curve={curveStepAfter}
|
||||||
|
/>
|
||||||
|
</SVGChart>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
207
web/components/charts/helpers.tsx
Normal file
207
web/components/charts/helpers.tsx
Normal file
|
@ -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 = <X,>(props: { w: number; h: number; axis: Axis<X> }) => {
|
||||||
|
const { h, axis } = props
|
||||||
|
const axisRef = useRef<SVGGElement>(null)
|
||||||
|
useEffect(() => {
|
||||||
|
if (axisRef.current != null) {
|
||||||
|
select(axisRef.current)
|
||||||
|
.transition()
|
||||||
|
.duration(250)
|
||||||
|
.call(axis)
|
||||||
|
.select('.domain')
|
||||||
|
.attr('stroke-width', 0)
|
||||||
|
}
|
||||||
|
}, [h, axis])
|
||||||
|
return <g ref={axisRef} transform={`translate(0, ${h})`} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const YAxis = <Y,>(props: { w: number; h: number; axis: Axis<Y> }) => {
|
||||||
|
const { w, h, axis } = props
|
||||||
|
const axisRef = useRef<SVGGElement>(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 <g ref={axisRef} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const LinePathInternal = <P,>(
|
||||||
|
props: {
|
||||||
|
data: P[]
|
||||||
|
px: number | ((p: P) => number)
|
||||||
|
py: number | ((p: P) => number)
|
||||||
|
curve?: CurveFactory
|
||||||
|
} & SVGProps<SVGPathElement>
|
||||||
|
) => {
|
||||||
|
const { data, px, py, curve, ...rest } = props
|
||||||
|
const d3Line = line<P>(px, py).curve(curve ?? curveStepAfter)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
return <path {...rest} fill="none" d={d3Line(data)!} />
|
||||||
|
}
|
||||||
|
export const LinePath = memo(LinePathInternal) as typeof LinePathInternal
|
||||||
|
|
||||||
|
const AreaPathInternal = <P,>(
|
||||||
|
props: {
|
||||||
|
data: P[]
|
||||||
|
px: number | ((p: P) => number)
|
||||||
|
py0: number | ((p: P) => number)
|
||||||
|
py1: number | ((p: P) => number)
|
||||||
|
curve?: CurveFactory
|
||||||
|
} & SVGProps<SVGPathElement>
|
||||||
|
) => {
|
||||||
|
const { data, px, py0, py1, curve, ...rest } = props
|
||||||
|
const d3Area = area<P>(px, py0, py1).curve(curve ?? curveStepAfter)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
return <path {...rest} d={d3Area(data)!} />
|
||||||
|
}
|
||||||
|
export const AreaPath = memo(AreaPathInternal) as typeof AreaPathInternal
|
||||||
|
|
||||||
|
export const AreaWithTopStroke = <P,>(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 (
|
||||||
|
<g>
|
||||||
|
<AreaPath
|
||||||
|
data={data}
|
||||||
|
px={px}
|
||||||
|
py0={py0}
|
||||||
|
py1={py1}
|
||||||
|
curve={curve}
|
||||||
|
fill={color}
|
||||||
|
opacity={0.3}
|
||||||
|
/>
|
||||||
|
<LinePath data={data} px={px} py={py1} curve={curve} stroke={color} />
|
||||||
|
</g>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SVGChart = <X, Y>(props: {
|
||||||
|
children: ReactNode
|
||||||
|
w: number
|
||||||
|
h: number
|
||||||
|
xAxis: Axis<X>
|
||||||
|
yAxis: Axis<Y>
|
||||||
|
onSelect?: (ev: D3BrushEvent<any>) => 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<SVGGElement>(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 (
|
||||||
|
<svg className="w-full" width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
|
||||||
|
<clipPath id={clipPathId}>
|
||||||
|
<rect x={0} y={0} width={innerW} height={innerH} />
|
||||||
|
</clipPath>
|
||||||
|
<g transform={`translate(${MARGIN.left}, ${MARGIN.top})`}>
|
||||||
|
<XAxis axis={xAxis} w={innerW} h={innerH} />
|
||||||
|
<YAxis axis={yAxis} w={innerW} h={innerH} />
|
||||||
|
<g clipPath={`url(#${clipPathId})`}>{children}</g>
|
||||||
|
<g
|
||||||
|
ref={overlayRef}
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width={innerW}
|
||||||
|
height={innerH}
|
||||||
|
fill="none"
|
||||||
|
pointerEvents="all"
|
||||||
|
onPointerEnter={onMouseOver}
|
||||||
|
onPointerMove={onMouseOver}
|
||||||
|
onPointerLeave={onMouseLeave}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TooltipPosition = { top: number; left: number }
|
||||||
|
|
||||||
|
export const ChartTooltip = (
|
||||||
|
props: TooltipPosition & { className?: string; children: React.ReactNode }
|
||||||
|
) => {
|
||||||
|
const { top, left, className, children } = props
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
className,
|
||||||
|
'pointer-events-none absolute z-10 whitespace-pre rounded border-2 border-black bg-white/90 p-2'
|
||||||
|
)}
|
||||||
|
style={{ top, left }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
|
@ -2,7 +2,12 @@ import React from 'react'
|
||||||
|
|
||||||
import { tradingAllowed } from 'web/lib/firebase/contracts'
|
import { tradingAllowed } from 'web/lib/firebase/contracts'
|
||||||
import { Col } from '../layout/col'
|
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 { useUser } from 'web/hooks/use-user'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { Linkify } from '../linkify'
|
import { Linkify } from '../linkify'
|
||||||
|
@ -14,7 +19,6 @@ import {
|
||||||
} from './contract-card'
|
} from './contract-card'
|
||||||
import { Bet } from 'common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import BetButton, { BinaryMobileBetting } from '../bet-button'
|
import BetButton, { BinaryMobileBetting } from '../bet-button'
|
||||||
import { AnswersGraph } from '../answers/answers-graph'
|
|
||||||
import {
|
import {
|
||||||
Contract,
|
Contract,
|
||||||
CPMMContract,
|
CPMMContract,
|
||||||
|
@ -25,7 +29,6 @@ import {
|
||||||
BinaryContract,
|
BinaryContract,
|
||||||
} from 'common/contract'
|
} from 'common/contract'
|
||||||
import { ContractDetails } from './contract-details'
|
import { ContractDetails } from './contract-details'
|
||||||
import { NumericGraph } from './numeric-graph'
|
|
||||||
|
|
||||||
const OverviewQuestion = (props: { text: string }) => (
|
const OverviewQuestion = (props: { text: string }) => (
|
||||||
<Linkify className="text-lg text-indigo-700 sm:text-2xl" text={props.text} />
|
<Linkify className="text-lg text-indigo-700 sm:text-2xl" text={props.text} />
|
||||||
|
@ -63,7 +66,7 @@ const NumericOverview = (props: { contract: NumericContract }) => {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<NumericGraph contract={contract} />
|
<NumericContractChart contract={contract} />
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -83,7 +86,7 @@ const BinaryOverview = (props: { contract: BinaryContract; bets: Bet[] }) => {
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
<ContractProbGraph contract={contract} bets={[...bets].reverse()} />
|
<BinaryContractChart contract={contract} bets={bets} />
|
||||||
<Row className="items-center justify-between gap-4 xl:hidden">
|
<Row className="items-center justify-between gap-4 xl:hidden">
|
||||||
{tradingAllowed(contract) && (
|
{tradingAllowed(contract) && (
|
||||||
<BinaryMobileBetting contract={contract} />
|
<BinaryMobileBetting contract={contract} />
|
||||||
|
@ -109,7 +112,7 @@ const ChoiceOverview = (props: {
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
<Col className={'mb-1 gap-y-2'}>
|
<Col className={'mb-1 gap-y-2'}>
|
||||||
<AnswersGraph contract={contract} bets={[...bets].reverse()} />
|
<ChoiceContractChart contract={contract} bets={bets} />
|
||||||
</Col>
|
</Col>
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
|
@ -136,7 +139,7 @@ const PseudoNumericOverview = (props: {
|
||||||
{tradingAllowed(contract) && <BetWidget contract={contract} />}
|
{tradingAllowed(contract) && <BetWidget contract={contract} />}
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
<ContractProbGraph contract={contract} bets={[...bets].reverse()} />
|
<PseudoNumericContractChart contract={contract} bets={bets} />
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (
|
|
||||||
<div
|
|
||||||
className="w-full overflow-visible"
|
|
||||||
style={{ height: height ?? (!width || width >= 800 ? 250 : 150) }}
|
|
||||||
>
|
|
||||||
<ResponsiveLine
|
|
||||||
data={data}
|
|
||||||
yScale={
|
|
||||||
isBinary
|
|
||||||
? { min: 0, max: 100, type: 'linear' }
|
|
||||||
: isLogScale
|
|
||||||
? {
|
|
||||||
min: 0,
|
|
||||||
max: Math.log10(contract.max - contract.min + 1),
|
|
||||||
type: 'linear',
|
|
||||||
}
|
|
||||||
: { min: contract.min, max: contract.max, type: 'linear' }
|
|
||||||
}
|
|
||||||
yFormat={formatter}
|
|
||||||
gridYValues={yTickValues}
|
|
||||||
axisLeft={{
|
|
||||||
tickValues: yTickValues,
|
|
||||||
format: formatter,
|
|
||||||
}}
|
|
||||||
xScale={{
|
|
||||||
type: 'time',
|
|
||||||
min: startDate.toDate(),
|
|
||||||
max: endDate.toDate(),
|
|
||||||
}}
|
|
||||||
xFormat={(d) =>
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const SliceTooltip = ({ slice }: SliceTooltipProps) => {
|
|
||||||
return (
|
|
||||||
<BasicTooltip
|
|
||||||
id={slice.points.map((point) => [
|
|
||||||
<span key="date">
|
|
||||||
<strong>{point.data[`yFormatted`]}</strong> {point.data['xFormatted']}
|
|
||||||
</span>,
|
|
||||||
])}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
|
@ -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 (
|
|
||||||
<div
|
|
||||||
className="w-full overflow-hidden"
|
|
||||||
style={{ height: height ?? (!width || width >= 800 ? 350 : 250) }}
|
|
||||||
>
|
|
||||||
<ResponsiveLine
|
|
||||||
data={data}
|
|
||||||
yScale={{ min: 0, max: maxProb, type: 'linear' }}
|
|
||||||
yFormat={formatPercent}
|
|
||||||
axisLeft={{
|
|
||||||
tickValues: yTickValues,
|
|
||||||
format: formatPercent,
|
|
||||||
}}
|
|
||||||
xScale={{
|
|
||||||
type: 'linear',
|
|
||||||
min: min,
|
|
||||||
max: max,
|
|
||||||
}}
|
|
||||||
xFormat={(d) => `${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 <Tooltip point={point} />
|
|
||||||
}}
|
|
||||||
enableGridX={!!width && width >= 800}
|
|
||||||
enableArea
|
|
||||||
margin={{ top: 20, right: 28, bottom: 22, left: 50 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
function formatPercent(y: DatumValue) {
|
|
||||||
const p = Math.round(+y * 100) / 100
|
|
||||||
return `${p}%`
|
|
||||||
}
|
|
||||||
|
|
||||||
function Tooltip(props: { point: Point }) {
|
|
||||||
const { point } = props
|
|
||||||
return (
|
|
||||||
<Col className="border border-gray-300 bg-white py-2 px-3">
|
|
||||||
<div
|
|
||||||
className="pb-1"
|
|
||||||
style={{
|
|
||||||
color: point.serieColor,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<strong>{point.serieId}</strong> {point.data.yFormatted}
|
|
||||||
</div>
|
|
||||||
<div>{formatLargeNumber(+point.data.x)}</div>
|
|
||||||
</Col>
|
|
||||||
)
|
|
||||||
}
|
|
17
web/hooks/use-element-width.tsx
Normal file
17
web/hooks/use-element-width.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { RefObject, useState, useEffect } from 'react'
|
||||||
|
|
||||||
|
// todo: consider consolidation with use-measure-size
|
||||||
|
export const useElementWidth = <T extends Element>(ref: RefObject<T>) => {
|
||||||
|
const [width, setWidth] = useState<number>()
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setWidth(ref.current?.clientWidth)
|
||||||
|
}
|
||||||
|
handleResize()
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
}
|
||||||
|
}, [ref])
|
||||||
|
return width
|
||||||
|
}
|
|
@ -39,6 +39,7 @@
|
||||||
"browser-image-compression": "2.0.0",
|
"browser-image-compression": "2.0.0",
|
||||||
"clsx": "1.1.1",
|
"clsx": "1.1.1",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
|
"d3": "7.6.1",
|
||||||
"daisyui": "1.16.4",
|
"daisyui": "1.16.4",
|
||||||
"dayjs": "1.10.7",
|
"dayjs": "1.10.7",
|
||||||
"firebase": "9.9.3",
|
"firebase": "9.9.3",
|
||||||
|
@ -56,9 +57,9 @@
|
||||||
"react-expanding-textarea": "2.3.5",
|
"react-expanding-textarea": "2.3.5",
|
||||||
"react-hot-toast": "2.2.0",
|
"react-hot-toast": "2.2.0",
|
||||||
"react-instantsearch-hooks-web": "6.24.1",
|
"react-instantsearch-hooks-web": "6.24.1",
|
||||||
|
"react-masonry-css": "1.0.16",
|
||||||
"react-query": "3.39.0",
|
"react-query": "3.39.0",
|
||||||
"react-twitter-embed": "4.0.4",
|
"react-twitter-embed": "4.0.4",
|
||||||
"react-masonry-css": "1.0.16",
|
|
||||||
"string-similarity": "^4.0.4",
|
"string-similarity": "^4.0.4",
|
||||||
"tippy.js": "6.3.7"
|
"tippy.js": "6.3.7"
|
||||||
},
|
},
|
||||||
|
@ -66,6 +67,7 @@
|
||||||
"@tailwindcss/forms": "0.4.0",
|
"@tailwindcss/forms": "0.4.0",
|
||||||
"@tailwindcss/line-clamp": "^0.3.1",
|
"@tailwindcss/line-clamp": "^0.3.1",
|
||||||
"@tailwindcss/typography": "^0.5.1",
|
"@tailwindcss/typography": "^0.5.1",
|
||||||
|
"@types/d3": "7.4.0",
|
||||||
"@types/lodash": "4.14.178",
|
"@types/lodash": "4.14.178",
|
||||||
"@types/node": "16.11.11",
|
"@types/node": "16.11.11",
|
||||||
"@types/react": "17.0.43",
|
"@types/react": "17.0.43",
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { Bet } from 'common/bet'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { DOMAIN } from 'common/envs/constants'
|
import { DOMAIN } from 'common/envs/constants'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { AnswersGraph } from 'web/components/answers/answers-graph'
|
|
||||||
import { BetInline } from 'web/components/bet-inline'
|
import { BetInline } from 'web/components/bet-inline'
|
||||||
import { Button } from 'web/components/button'
|
import { Button } from 'web/components/button'
|
||||||
import {
|
import {
|
||||||
|
@ -12,8 +11,7 @@ import {
|
||||||
PseudoNumericResolutionOrExpectation,
|
PseudoNumericResolutionOrExpectation,
|
||||||
} from 'web/components/contract/contract-card'
|
} from 'web/components/contract/contract-card'
|
||||||
import { MarketSubheader } from 'web/components/contract/contract-details'
|
import { MarketSubheader } from 'web/components/contract/contract-details'
|
||||||
import { ContractProbGraph } from 'web/components/contract/contract-prob-graph'
|
import { ContractChart } from 'web/components/charts/contract'
|
||||||
import { NumericGraph } from 'web/components/contract/numeric-graph'
|
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { Spacer } from 'web/components/layout/spacer'
|
import { Spacer } from 'web/components/layout/spacer'
|
||||||
|
@ -134,22 +132,7 @@ export function ContractEmbed(props: { contract: Contract; bets: Bet[] }) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mx-1 mb-2 min-h-0 flex-1" ref={setElem}>
|
<div className="mx-1 mb-2 min-h-0 flex-1" ref={setElem}>
|
||||||
{(isBinary || isPseudoNumeric) && (
|
<ContractChart contract={contract} bets={bets} height={graphHeight} />
|
||||||
<ContractProbGraph
|
|
||||||
contract={contract}
|
|
||||||
bets={[...bets].reverse()}
|
|
||||||
height={graphHeight}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(outcomeType === 'FREE_RESPONSE' ||
|
|
||||||
outcomeType === 'MULTIPLE_CHOICE') && (
|
|
||||||
<AnswersGraph contract={contract} bets={bets} height={graphHeight} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{outcomeType === 'NUMERIC' && (
|
|
||||||
<NumericGraph contract={contract} height={graphHeight} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
|
|
500
yarn.lock
500
yarn.lock
|
@ -3250,6 +3250,216 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
|
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
|
||||||
integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==
|
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":
|
"@types/eslint-scope@^3.7.3":
|
||||||
version "3.7.3"
|
version "3.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224"
|
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/express-serve-static-core" "*"
|
||||||
"@types/serve-static" "*"
|
"@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":
|
"@types/google.maps@^3.45.3":
|
||||||
version "3.49.0"
|
version "3.49.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/google.maps/-/google.maps-3.49.0.tgz#26fcf3d86ecbc6545db0e6691a434ec8132df48b"
|
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"
|
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea"
|
||||||
integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==
|
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:
|
commander@^2.20.0:
|
||||||
version "2.20.3"
|
version "2.20.3"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
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"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
|
||||||
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
|
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:
|
commander@^8.0.0, commander@^8.3.0:
|
||||||
version "8.3.0"
|
version "8.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
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:
|
dependencies:
|
||||||
internmap "^1.0.0"
|
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:
|
"d3-color@1 - 2", d3-color@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e"
|
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e"
|
||||||
integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==
|
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:
|
d3-delaunay@^5.3.0:
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-5.3.0.tgz#b47f05c38f854a4e7b3cea80e0bb12e57398772d"
|
resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-5.3.0.tgz#b47f05c38f854a4e7b3cea80e0bb12e57398772d"
|
||||||
|
@ -5254,16 +5518,76 @@ d3-delaunay@^5.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
delaunator "4"
|
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":
|
"d3-format@1 - 2":
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767"
|
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767"
|
||||||
integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==
|
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:
|
d3-format@^1.4.4:
|
||||||
version "1.4.5"
|
version "1.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
|
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
|
||||||
integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==
|
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:
|
"d3-interpolate@1 - 2", "d3-interpolate@1.2.0 - 2", d3-interpolate@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163"
|
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163"
|
||||||
|
@ -5271,11 +5595,46 @@ d3-format@^1.4.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-color "1 - 2"
|
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:
|
d3-path@1:
|
||||||
version "1.0.9"
|
version "1.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
|
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
|
||||||
integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
|
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:
|
d3-scale-chromatic@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz#c13f3af86685ff91323dc2f0ebd2dabbd72d8bab"
|
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-color "1 - 2"
|
||||||
d3-interpolate "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:
|
d3-scale@^3.2.3:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.3.0.tgz#28c600b29f47e5b9cd2df9749c206727966203f3"
|
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 "^2.1.1"
|
||||||
d3-time-format "2 - 3"
|
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:
|
d3-shape@^1.3.5:
|
||||||
version "1.3.7"
|
version "1.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
|
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
|
||||||
|
@ -5309,6 +5691,13 @@ d3-shape@^1.3.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-time "1 - 2"
|
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:
|
"d3-time@1 - 2", d3-time@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682"
|
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682"
|
||||||
|
@ -5316,11 +5705,81 @@ d3-shape@^1.3.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-array "2"
|
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:
|
d3-time@^1.0.11:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1"
|
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1"
|
||||||
integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==
|
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:
|
daisyui@1.16.4:
|
||||||
version "1.16.4"
|
version "1.16.4"
|
||||||
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-1.16.4.tgz#52773401c0962e37ef40507d29f0e513c7f2856f"
|
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"
|
resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-4.0.1.tgz#3d779687f57919a7a418f8ab947d3bddb6846957"
|
||||||
integrity sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==
|
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:
|
delayed-stream@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
|
@ -7381,6 +7847,13 @@ iconv-lite@0.4.24:
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer ">= 2.1.2 < 3"
|
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:
|
icss-utils@^5.0.0, icss-utils@^5.1.0:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
|
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"
|
has "^1.0.3"
|
||||||
side-channel "^1.0.4"
|
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:
|
internmap@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95"
|
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:
|
dependencies:
|
||||||
glob "^7.1.3"
|
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:
|
rope-sequence@^1.3.0:
|
||||||
version "1.3.3"
|
version "1.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.3.tgz#3f67fc106288b84b71532b4a5fd9d4881e4457f0"
|
resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.3.tgz#3f67fc106288b84b71532b4a5fd9d4881e4457f0"
|
||||||
|
@ -10699,6 +11182,11 @@ run-parallel@^1.1.9:
|
||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask "^1.2.2"
|
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:
|
rxjs@^6.6.3:
|
||||||
version "6.6.7"
|
version "6.6.7"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
|
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"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
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"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
Loading…
Reference in New Issue
Block a user