Clean up chart sizing code (#977)

* Clean up chart sizing code

* Do all the chart sizing work in same batch
This commit is contained in:
Marshall Polaris 2022-09-30 16:57:48 -07:00 committed by GitHub
parent 38b7c898f6
commit 89e26d077e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 136 additions and 135 deletions

View File

@ -1,4 +1,4 @@
import { useMemo, useRef } from 'react' import { useMemo } from 'react'
import { last, sortBy } from 'lodash' import { last, sortBy } from 'lodash'
import { scaleTime, scaleLinear } from 'd3-scale' import { scaleTime, scaleLinear } from 'd3-scale'
@ -6,7 +6,6 @@ import { Bet } from 'common/bet'
import { getProbability, getInitialProbability } from 'common/calculate' import { getProbability, getInitialProbability } from 'common/calculate'
import { BinaryContract } from 'common/contract' import { BinaryContract } from 'common/contract'
import { DAY_MS } from 'common/util/time' import { DAY_MS } from 'common/util/time'
import { useIsMobile } from 'web/hooks/use-is-mobile'
import { import {
TooltipProps, TooltipProps,
MARGIN_X, MARGIN_X,
@ -17,7 +16,6 @@ import {
formatPct, formatPct,
} from '../helpers' } from '../helpers'
import { HistoryPoint, SingleValueHistoryChart } from '../generic-charts' import { HistoryPoint, SingleValueHistoryChart } from '../generic-charts'
import { useElementWidth } from 'web/hooks/use-element-width'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { Avatar } from 'web/components/avatar' import { Avatar } from 'web/components/avatar'
@ -45,10 +43,11 @@ const BinaryChartTooltip = (props: TooltipProps<Date, HistoryPoint<Bet>>) => {
export const BinaryContractChart = (props: { export const BinaryContractChart = (props: {
contract: BinaryContract contract: BinaryContract
bets: Bet[] bets: Bet[]
height?: number width: number
height: number
onMouseOver?: (p: HistoryPoint<Bet> | undefined) => void onMouseOver?: (p: HistoryPoint<Bet> | undefined) => void
}) => { }) => {
const { contract, bets, onMouseOver } = props const { contract, bets, width, height, onMouseOver } = props
const [start, end] = getDateRange(contract) const [start, end] = getDateRange(contract)
const startP = getInitialProbability(contract) const startP = getInitialProbability(contract)
const endP = getProbability(contract) const endP = getProbability(contract)
@ -67,16 +66,9 @@ export const BinaryContractChart = (props: {
Date.now() Date.now()
) )
const visibleRange = [start, rightmostDate] const visibleRange = [start, rightmostDate]
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 xScale = scaleTime(visibleRange, [0, width - MARGIN_X])
const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0]) const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0])
return ( return (
<div ref={containerRef}>
{width > 0 && (
<SingleValueHistoryChart <SingleValueHistoryChart
w={width} w={width}
h={height} h={height}
@ -88,7 +80,5 @@ export const BinaryContractChart = (props: {
Tooltip={BinaryChartTooltip} Tooltip={BinaryChartTooltip}
pct pct
/> />
)}
</div>
) )
} }

View File

@ -1,4 +1,4 @@
import { useMemo, useRef } from 'react' import { useMemo } from 'react'
import { last, sum, sortBy, groupBy } from 'lodash' import { last, sum, sortBy, groupBy } from 'lodash'
import { scaleTime, scaleLinear } from 'd3-scale' import { scaleTime, scaleLinear } from 'd3-scale'
@ -6,7 +6,6 @@ import { Bet } from 'common/bet'
import { Answer } from 'common/answer' import { Answer } from 'common/answer'
import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' import { FreeResponseContract, MultipleChoiceContract } from 'common/contract'
import { getOutcomeProbability } from 'common/calculate' import { getOutcomeProbability } from 'common/calculate'
import { useIsMobile } from 'web/hooks/use-is-mobile'
import { DAY_MS } from 'common/util/time' import { DAY_MS } from 'common/util/time'
import { import {
TooltipProps, TooltipProps,
@ -18,7 +17,6 @@ import {
formatDateInRange, formatDateInRange,
} from '../helpers' } from '../helpers'
import { MultiPoint, MultiValueHistoryChart } from '../generic-charts' import { MultiPoint, MultiValueHistoryChart } from '../generic-charts'
import { useElementWidth } from 'web/hooks/use-element-width'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { Avatar } from 'web/components/avatar' import { Avatar } from 'web/components/avatar'
@ -146,10 +144,11 @@ const Legend = (props: { className?: string; items: LegendItem[] }) => {
export const ChoiceContractChart = (props: { export const ChoiceContractChart = (props: {
contract: FreeResponseContract | MultipleChoiceContract contract: FreeResponseContract | MultipleChoiceContract
bets: Bet[] bets: Bet[]
height?: number width: number
height: number
onMouseOver?: (p: MultiPoint<Bet> | undefined) => void onMouseOver?: (p: MultiPoint<Bet> | undefined) => void
}) => { }) => {
const { contract, bets, onMouseOver } = props const { contract, bets, width, height, onMouseOver } = props
const [start, end] = getDateRange(contract) const [start, end] = getDateRange(contract)
const answers = useMemo( const answers = useMemo(
() => getTrackedAnswers(contract, CATEGORY_COLORS.length), () => getTrackedAnswers(contract, CATEGORY_COLORS.length),
@ -173,10 +172,6 @@ export const ChoiceContractChart = (props: {
Date.now() Date.now()
) )
const visibleRange = [start, rightmostDate] const visibleRange = [start, rightmostDate]
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 xScale = scaleTime(visibleRange, [0, width - MARGIN_X])
const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0]) const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0])
@ -212,8 +207,6 @@ export const ChoiceContractChart = (props: {
) )
return ( return (
<div ref={containerRef}>
{width > 0 && (
<MultiValueHistoryChart <MultiValueHistoryChart
w={width} w={width}
h={height} h={height}
@ -225,7 +218,5 @@ export const ChoiceContractChart = (props: {
Tooltip={ChoiceTooltip} Tooltip={ChoiceTooltip}
pct pct
/> />
)}
</div>
) )
} }

View File

@ -8,7 +8,8 @@ import { NumericContractChart } from './numeric'
export const ContractChart = (props: { export const ContractChart = (props: {
contract: Contract contract: Contract
bets: Bet[] bets: Bet[]
height?: number width: number
height: number
}) => { }) => {
const { contract } = props const { contract } = props
switch (contract.outcomeType) { switch (contract.outcomeType) {

View File

@ -1,4 +1,4 @@
import { useMemo, useRef } from 'react' import { useMemo } from 'react'
import { range } from 'lodash' import { range } from 'lodash'
import { scaleLinear } from 'd3-scale' import { scaleLinear } from 'd3-scale'
@ -6,10 +6,8 @@ import { formatLargeNumber } from 'common/util/format'
import { getDpmOutcomeProbabilities } from 'common/calculate-dpm' import { getDpmOutcomeProbabilities } from 'common/calculate-dpm'
import { NumericContract } from 'common/contract' import { NumericContract } from 'common/contract'
import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants' import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants'
import { useIsMobile } from 'web/hooks/use-is-mobile'
import { TooltipProps, MARGIN_X, MARGIN_Y, formatPct } from '../helpers' import { TooltipProps, MARGIN_X, MARGIN_Y, formatPct } from '../helpers'
import { DistributionPoint, DistributionChart } from '../generic-charts' import { DistributionPoint, DistributionChart } from '../generic-charts'
import { useElementWidth } from 'web/hooks/use-element-width'
const getNumericChartData = (contract: NumericContract) => { const getNumericChartData = (contract: NumericContract) => {
const { totalShares, bucketCount, min, max } = contract const { totalShares, bucketCount, min, max } = contract
@ -36,22 +34,17 @@ const NumericChartTooltip = (
export const NumericContractChart = (props: { export const NumericContractChart = (props: {
contract: NumericContract contract: NumericContract
height?: number width: number
height: number
onMouseOver?: (p: DistributionPoint | undefined) => void onMouseOver?: (p: DistributionPoint | undefined) => void
}) => { }) => {
const { contract, onMouseOver } = props const { contract, width, height, onMouseOver } = props
const { min, max } = contract const { min, max } = contract
const data = useMemo(() => getNumericChartData(contract), [contract]) 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 = Math.max(...data.map((d) => d.y)) const maxY = Math.max(...data.map((d) => d.y))
const xScale = scaleLinear([min, max], [0, width - MARGIN_X]) const xScale = scaleLinear([min, max], [0, width - MARGIN_X])
const yScale = scaleLinear([0, maxY], [height - MARGIN_Y, 0]) const yScale = scaleLinear([0, maxY], [height - MARGIN_Y, 0])
return ( return (
<div ref={containerRef}>
{width > 0 && (
<DistributionChart <DistributionChart
w={width} w={width}
h={height} h={height}
@ -62,7 +55,5 @@ export const NumericContractChart = (props: {
onMouseOver={onMouseOver} onMouseOver={onMouseOver}
Tooltip={NumericChartTooltip} Tooltip={NumericChartTooltip}
/> />
)}
</div>
) )
} }

View File

@ -1,4 +1,4 @@
import { useMemo, useRef } from 'react' import { useMemo } from 'react'
import { last, sortBy } from 'lodash' import { last, sortBy } from 'lodash'
import { scaleTime, scaleLog, scaleLinear } from 'd3-scale' import { scaleTime, scaleLog, scaleLinear } from 'd3-scale'
@ -8,7 +8,6 @@ import { getInitialProbability, getProbability } from 'common/calculate'
import { formatLargeNumber } from 'common/util/format' import { formatLargeNumber } from 'common/util/format'
import { PseudoNumericContract } from 'common/contract' import { PseudoNumericContract } from 'common/contract'
import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants' import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants'
import { useIsMobile } from 'web/hooks/use-is-mobile'
import { import {
TooltipProps, TooltipProps,
MARGIN_X, MARGIN_X,
@ -18,7 +17,6 @@ import {
formatDateInRange, formatDateInRange,
} from '../helpers' } from '../helpers'
import { HistoryPoint, SingleValueHistoryChart } from '../generic-charts' import { HistoryPoint, SingleValueHistoryChart } from '../generic-charts'
import { useElementWidth } from 'web/hooks/use-element-width'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { Avatar } from 'web/components/avatar' import { Avatar } from 'web/components/avatar'
@ -59,10 +57,11 @@ const PseudoNumericChartTooltip = (
export const PseudoNumericContractChart = (props: { export const PseudoNumericContractChart = (props: {
contract: PseudoNumericContract contract: PseudoNumericContract
bets: Bet[] bets: Bet[]
height?: number width: number
height: number
onMouseOver?: (p: HistoryPoint<Bet> | undefined) => void onMouseOver?: (p: HistoryPoint<Bet> | undefined) => void
}) => { }) => {
const { contract, bets, onMouseOver } = props const { contract, bets, width, height, onMouseOver } = props
const { min, max, isLogScale } = contract const { min, max, isLogScale } = contract
const [start, end] = getDateRange(contract) const [start, end] = getDateRange(contract)
const scaleP = useMemo( const scaleP = useMemo(
@ -86,19 +85,12 @@ export const PseudoNumericContractChart = (props: {
Date.now() Date.now()
) )
const visibleRange = [start, rightmostDate] const visibleRange = [start, rightmostDate]
const isMobile = useIsMobile(800) const xScale = scaleTime(visibleRange, [0, width ?? 0 - MARGIN_X])
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])
// clamp log scale to make sure zeroes go to the bottom // clamp log scale to make sure zeroes go to the bottom
const yScale = isLogScale const yScale = isLogScale
? scaleLog([Math.max(min, 1), max], [height - MARGIN_Y, 0]).clamp(true) ? scaleLog([Math.max(min, 1), max], [height ?? 0 - MARGIN_Y, 0]).clamp(true)
: scaleLinear([min, max], [height - MARGIN_Y, 0]) : scaleLinear([min, max], [height ?? 0 - MARGIN_Y, 0])
return ( return (
<div ref={containerRef}>
{width > 0 && (
<SingleValueHistoryChart <SingleValueHistoryChart
w={width} w={width}
h={height} h={height}
@ -109,7 +101,5 @@ export const PseudoNumericContractChart = (props: {
Tooltip={PseudoNumericChartTooltip} Tooltip={PseudoNumericChartTooltip}
color={NUMERIC_GRAPH_COLOR} color={NUMERIC_GRAPH_COLOR}
/> />
)}
</div>
) )
} }

View File

@ -1,13 +1,8 @@
import React from 'react' import React, { useEffect, useRef, useState } 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 { import { ContractChart } from 'web/components/charts/contract'
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'
@ -48,8 +43,43 @@ const BetWidget = (props: { contract: CPMMContract }) => {
) )
} }
const NumericOverview = (props: { contract: NumericContract }) => { const SizedContractChart = (props: {
const { contract } = props contract: Contract
bets: Bet[]
fullHeight: number
mobileHeight: number
}) => {
const { contract, bets, fullHeight, mobileHeight } = props
const containerRef = useRef<HTMLDivElement>(null)
const [chartWidth, setChartWidth] = useState<number>()
const [chartHeight, setChartHeight] = useState<number>()
useEffect(() => {
const handleResize = () => {
setChartHeight(window.innerWidth < 800 ? mobileHeight : fullHeight)
setChartWidth(containerRef.current?.clientWidth)
}
handleResize()
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [fullHeight, mobileHeight])
return (
<div ref={containerRef}>
{chartWidth != null && chartHeight != null && (
<ContractChart
contract={contract}
bets={bets}
width={chartWidth}
height={chartHeight}
/>
)}
</div>
)
}
const NumericOverview = (props: { contract: NumericContract; bets: Bet[] }) => {
const { contract, bets } = props
return ( return (
<Col className="gap-1 md:gap-2"> <Col className="gap-1 md:gap-2">
<Col className="gap-3 px-2 sm:gap-4"> <Col className="gap-3 px-2 sm:gap-4">
@ -66,7 +96,12 @@ const NumericOverview = (props: { contract: NumericContract }) => {
contract={contract} contract={contract}
/> />
</Col> </Col>
<NumericContractChart contract={contract} /> <SizedContractChart
contract={contract}
bets={bets}
fullHeight={250}
mobileHeight={150}
/>
</Col> </Col>
) )
} }
@ -86,7 +121,12 @@ const BinaryOverview = (props: { contract: BinaryContract; bets: Bet[] }) => {
/> />
</Row> </Row>
</Col> </Col>
<BinaryContractChart contract={contract} bets={bets} /> <SizedContractChart
contract={contract}
bets={bets}
fullHeight={250}
mobileHeight={150}
/>
<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} />
@ -111,9 +151,12 @@ const ChoiceOverview = (props: {
<FreeResponseResolutionOrChance contract={contract} truncate="none" /> <FreeResponseResolutionOrChance contract={contract} truncate="none" />
)} )}
</Col> </Col>
<Col className={'mb-1 gap-y-2'}> <SizedContractChart
<ChoiceContractChart contract={contract} bets={bets} /> contract={contract}
</Col> bets={bets}
fullHeight={350}
mobileHeight={250}
/>
</Col> </Col>
) )
} }
@ -139,7 +182,12 @@ const PseudoNumericOverview = (props: {
{tradingAllowed(contract) && <BetWidget contract={contract} />} {tradingAllowed(contract) && <BetWidget contract={contract} />}
</Row> </Row>
</Col> </Col>
<PseudoNumericContractChart contract={contract} bets={bets} /> <SizedContractChart
contract={contract}
bets={bets}
fullHeight={250}
mobileHeight={150}
/>
</Col> </Col>
) )
} }
@ -153,7 +201,7 @@ export const ContractOverview = (props: {
case 'BINARY': case 'BINARY':
return <BinaryOverview contract={contract} bets={bets} /> return <BinaryOverview contract={contract} bets={bets} />
case 'NUMERIC': case 'NUMERIC':
return <NumericOverview contract={contract} /> return <NumericOverview contract={contract} bets={bets} />
case 'PSEUDO_NUMERIC': case 'PSEUDO_NUMERIC':
return <PseudoNumericOverview contract={contract} bets={bets} /> return <PseudoNumericOverview contract={contract} bets={bets} />
case 'FREE_RESPONSE': case 'FREE_RESPONSE':

View File

@ -1,17 +0,0 @@
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
}

View File

@ -79,7 +79,7 @@ export function ContractEmbed(props: { contract: Contract; bets: Bet[] }) {
const href = `https://${DOMAIN}${contractPath(contract)}` const href = `https://${DOMAIN}${contractPath(contract)}`
const { setElem, height: graphHeight } = useMeasureSize() const { setElem, width: graphWidth, height: graphHeight } = useMeasureSize()
const [betPanelOpen, setBetPanelOpen] = useState(false) const [betPanelOpen, setBetPanelOpen] = useState(false)
@ -132,7 +132,14 @@ 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}>
<ContractChart contract={contract} bets={bets} height={graphHeight} /> {graphWidth != null && graphHeight != null && (
<ContractChart
contract={contract}
bets={bets}
width={graphWidth}
height={graphHeight}
/>
)}
</div> </div>
</Col> </Col>
) )