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

View File

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

View File

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

View File

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

View File

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

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 { Col } from '../layout/col'
import {
BinaryContractChart,
NumericContractChart,
PseudoNumericContractChart,
ChoiceContractChart,
} from 'web/components/charts/contract'
import { ContractChart } from 'web/components/charts/contract'
import { useUser } from 'web/hooks/use-user'
import { Row } from '../layout/row'
import { Linkify } from '../linkify'
@ -48,8 +43,43 @@ const BetWidget = (props: { contract: CPMMContract }) => {
)
}
const NumericOverview = (props: { contract: NumericContract }) => {
const { contract } = props
const SizedContractChart = (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 (
<Col className="gap-1 md:gap-2">
<Col className="gap-3 px-2 sm:gap-4">
@ -66,7 +96,12 @@ const NumericOverview = (props: { contract: NumericContract }) => {
contract={contract}
/>
</Col>
<NumericContractChart contract={contract} />
<SizedContractChart
contract={contract}
bets={bets}
fullHeight={250}
mobileHeight={150}
/>
</Col>
)
}
@ -86,7 +121,12 @@ const BinaryOverview = (props: { contract: BinaryContract; bets: Bet[] }) => {
/>
</Row>
</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">
{tradingAllowed(contract) && (
<BinaryMobileBetting contract={contract} />
@ -111,9 +151,12 @@ const ChoiceOverview = (props: {
<FreeResponseResolutionOrChance contract={contract} truncate="none" />
)}
</Col>
<Col className={'mb-1 gap-y-2'}>
<ChoiceContractChart contract={contract} bets={bets} />
</Col>
<SizedContractChart
contract={contract}
bets={bets}
fullHeight={350}
mobileHeight={250}
/>
</Col>
)
}
@ -139,7 +182,12 @@ const PseudoNumericOverview = (props: {
{tradingAllowed(contract) && <BetWidget contract={contract} />}
</Row>
</Col>
<PseudoNumericContractChart contract={contract} bets={bets} />
<SizedContractChart
contract={contract}
bets={bets}
fullHeight={250}
mobileHeight={150}
/>
</Col>
)
}
@ -153,7 +201,7 @@ export const ContractOverview = (props: {
case 'BINARY':
return <BinaryOverview contract={contract} bets={bets} />
case 'NUMERIC':
return <NumericOverview contract={contract} />
return <NumericOverview contract={contract} bets={bets} />
case 'PSEUDO_NUMERIC':
return <PseudoNumericOverview contract={contract} bets={bets} />
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 { setElem, height: graphHeight } = useMeasureSize()
const { setElem, width: graphWidth, height: graphHeight } = useMeasureSize()
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}>
<ContractChart contract={contract} bets={bets} height={graphHeight} />
{graphWidth != null && graphHeight != null && (
<ContractChart
contract={contract}
bets={bets}
width={graphWidth}
height={graphHeight}
/>
)}
</div>
</Col>
)