diff --git a/web/components/analytics/charts.tsx b/web/components/analytics/charts.tsx
deleted file mode 100644
index 131ce2a0..00000000
--- a/web/components/analytics/charts.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-import { Point, ResponsiveLine } from '@nivo/line'
-import clsx from 'clsx'
-import { formatPercent } from 'common/util/format'
-import dayjs from 'dayjs'
-import { zip } from 'lodash'
-import { useWindowSize } from 'web/hooks/use-window-size'
-import { Col } from '../layout/col'
-
-export function DailyCountChart(props: {
- startDate: number
- dailyCounts: number[]
- small?: boolean
-}) {
- const { dailyCounts, startDate, small } = props
- const { width } = useWindowSize()
-
- const dates = dailyCounts.map((_, i) =>
- dayjs(startDate).add(i, 'day').toDate()
- )
-
- const points = zip(dates, dailyCounts).map(([date, betCount]) => ({
- x: date,
- y: betCount,
- }))
- const data = [{ id: 'Count', data: points, color: '#11b981' }]
-
- const bottomAxisTicks = width && width < 600 ? 6 : undefined
-
- return (
-
- dayjs(date).format('MMM DD'),
- }}
- colors={{ datum: 'color' }}
- pointSize={0}
- pointBorderWidth={1}
- pointBorderColor="#fff"
- enableSlices="x"
- enableGridX={!!width && width >= 800}
- enableArea
- margin={{ top: 20, right: 28, bottom: 22, left: 40 }}
- sliceTooltip={({ slice }) => {
- const point = slice.points[0]
- return
- }}
- />
-
- )
-}
-
-export function DailyPercentChart(props: {
- startDate: number
- dailyPercent: number[]
- small?: boolean
- excludeFirstDays?: number
-}) {
- const { dailyPercent, startDate, small, excludeFirstDays } = props
- const { width } = useWindowSize()
-
- const dates = dailyPercent.map((_, i) =>
- dayjs(startDate).add(i, 'day').toDate()
- )
-
- const points = zip(dates, dailyPercent)
- .map(([date, percent]) => ({
- x: date,
- y: percent,
- }))
- .slice(excludeFirstDays ?? 0)
- const data = [{ id: 'Percent', data: points, color: '#11b981' }]
-
- const bottomAxisTicks = width && width < 600 ? 6 : undefined
-
- return (
-
- dayjs(date).format('MMM DD'),
- }}
- colors={{ datum: 'color' }}
- pointSize={0}
- pointBorderWidth={1}
- pointBorderColor="#fff"
- enableSlices="x"
- enableGridX={!!width && width >= 800}
- enableArea
- margin={{ top: 20, right: 28, bottom: 22, left: 40 }}
- sliceTooltip={({ slice }) => {
- const point = slice.points[0]
- return
- }}
- />
-
- )
-}
-
-function Tooltip(props: { point: Point; isPercent?: boolean }) {
- const { point, isPercent } = props
- return (
-
-
- {point.serieId}{' '}
- {isPercent ? formatPercent(+point.data.y) : Math.round(+point.data.y)}
-
- {dayjs(point.data.x).format('MMM DD')}
-
- )
-}
diff --git a/web/components/charts/contract/binary.tsx b/web/components/charts/contract/binary.tsx
index 7e192767..c9b3bb0b 100644
--- a/web/components/charts/contract/binary.tsx
+++ b/web/components/charts/contract/binary.tsx
@@ -1,6 +1,7 @@
import { useMemo } from 'react'
import { last, sortBy } from 'lodash'
import { scaleTime, scaleLinear } from 'd3-scale'
+import { curveStepAfter } from 'd3-shape'
import { Bet } from 'common/bet'
import { getProbability, getInitialProbability } from 'common/calculate'
@@ -76,6 +77,7 @@ export const BinaryContractChart = (props: {
yScale={yScale}
data={data}
color="#11b981"
+ curve={curveStepAfter}
onMouseOver={onMouseOver}
Tooltip={BinaryChartTooltip}
pct
diff --git a/web/components/charts/contract/choice.tsx b/web/components/charts/contract/choice.tsx
index 65279b70..99e02fa8 100644
--- a/web/components/charts/contract/choice.tsx
+++ b/web/components/charts/contract/choice.tsx
@@ -1,6 +1,7 @@
import { useMemo } from 'react'
import { last, sum, sortBy, groupBy } from 'lodash'
import { scaleTime, scaleLinear } from 'd3-scale'
+import { curveStepAfter } from 'd3-shape'
import { Bet } from 'common/bet'
import { Answer } from 'common/answer'
@@ -214,6 +215,7 @@ export const ChoiceContractChart = (props: {
yScale={yScale}
data={data}
colors={CATEGORY_COLORS}
+ curve={curveStepAfter}
onMouseOver={onMouseOver}
Tooltip={ChoiceTooltip}
pct
diff --git a/web/components/charts/contract/pseudo-numeric.tsx b/web/components/charts/contract/pseudo-numeric.tsx
index e03d4ad9..9e06b368 100644
--- a/web/components/charts/contract/pseudo-numeric.tsx
+++ b/web/components/charts/contract/pseudo-numeric.tsx
@@ -1,6 +1,7 @@
import { useMemo } from 'react'
import { last, sortBy } from 'lodash'
import { scaleTime, scaleLog, scaleLinear } from 'd3-scale'
+import { curveStepAfter } from 'd3-shape'
import { Bet } from 'common/bet'
import { DAY_MS } from 'common/util/time'
@@ -97,6 +98,7 @@ export const PseudoNumericContractChart = (props: {
xScale={xScale}
yScale={yScale}
data={data}
+ curve={curveStepAfter}
onMouseOver={onMouseOver}
Tooltip={PseudoNumericChartTooltip}
color={NUMERIC_GRAPH_COLOR}
diff --git a/web/components/charts/generic-charts.tsx b/web/components/charts/generic-charts.tsx
index 152b264c..cb930484 100644
--- a/web/components/charts/generic-charts.tsx
+++ b/web/components/charts/generic-charts.tsx
@@ -4,11 +4,11 @@ import { axisBottom, axisLeft } from 'd3-axis'
import { D3BrushEvent } from 'd3-brush'
import { ScaleTime, ScaleContinuousNumeric } from 'd3-scale'
import {
+ CurveFactory,
+ SeriesPoint,
curveLinear,
- curveStepAfter,
stack,
stackOrderReverse,
- SeriesPoint,
} from 'd3-shape'
import { range } from 'lodash'
@@ -52,10 +52,11 @@ export const DistributionChart = (props: {
color: string
xScale: ScaleContinuousNumeric
yScale: ScaleContinuousNumeric
+ curve?: CurveFactory
onMouseOver?: (p: P | undefined) => void
Tooltip?: TooltipComponent
}) => {
- const { color, data, yScale, w, h, Tooltip } = props
+ const { color, data, yScale, w, h, curve, Tooltip } = props
const [viewXScale, setViewXScale] =
useState>()
@@ -100,7 +101,7 @@ export const DistributionChart = (props: {
px={px}
py0={py0}
py1={py1}
- curve={curveLinear}
+ curve={curve ?? curveLinear}
/>
)
@@ -113,11 +114,12 @@ export const MultiValueHistoryChart =
(props: {
colors: readonly string[]
xScale: ScaleTime
yScale: ScaleContinuousNumeric
+ curve?: CurveFactory
onMouseOver?: (p: P | undefined) => void
Tooltip?: TooltipComponent
pct?: boolean
}) => {
- const { colors, data, yScale, w, h, Tooltip, pct } = props
+ const { colors, data, yScale, w, h, curve, Tooltip, pct } = props
const [viewXScale, setViewXScale] = useState>()
const xScale = viewXScale ?? props.xScale
@@ -177,7 +179,7 @@ export const MultiValueHistoryChart = (props: {
px={px}
py0={py0}
py1={py1}
- curve={curveStepAfter}
+ curve={curve ?? curveLinear}
fill={colors[i]}
/>
))}
@@ -192,11 +194,12 @@ export const SingleValueHistoryChart =
(props: {
color: string
xScale: ScaleTime
yScale: ScaleContinuousNumeric
+ curve?: CurveFactory
onMouseOver?: (p: P | undefined) => void
Tooltip?: TooltipComponent
pct?: boolean
}) => {
- const { color, data, yScale, w, h, Tooltip, pct } = props
+ const { color, data, yScale, w, h, curve, Tooltip, pct } = props
const [viewXScale, setViewXScale] = useState>()
const xScale = viewXScale ?? props.xScale
@@ -246,7 +249,7 @@ export const SingleValueHistoryChart = (props: {
px={px}
py0={py0}
py1={py1}
- curve={curveStepAfter}
+ curve={curve ?? curveLinear}
/>
)
diff --git a/web/components/charts/helpers.tsx b/web/components/charts/helpers.tsx
index 96115dc0..b40ab7db 100644
--- a/web/components/charts/helpers.tsx
+++ b/web/components/charts/helpers.tsx
@@ -10,7 +10,7 @@ import {
import { pointer, select } from 'd3-selection'
import { Axis, AxisScale } from 'd3-axis'
import { brushX, D3BrushEvent } from 'd3-brush'
-import { area, line, curveStepAfter, CurveFactory } from 'd3-shape'
+import { area, line, CurveFactory } from 'd3-shape'
import { nanoid } from 'nanoid'
import dayjs from 'dayjs'
import clsx from 'clsx'
@@ -73,11 +73,11 @@ const LinePathInternal =
(
data: P[]
px: number | ((p: P) => number)
py: number | ((p: P) => number)
- curve?: CurveFactory
+ curve: CurveFactory
} & SVGProps
) => {
const { data, px, py, curve, ...rest } = props
- const d3Line = line(px, py).curve(curve ?? curveStepAfter)
+ const d3Line = line
(px, py).curve(curve)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return
}
@@ -89,11 +89,11 @@ const AreaPathInternal =
(
px: number | ((p: P) => number)
py0: number | ((p: P) => number)
py1: number | ((p: P) => number)
- curve?: CurveFactory
+ curve: CurveFactory
} & SVGProps
) => {
const { data, px, py0, py1, curve, ...rest } = props
- const d3Area = area(px, py0, py1).curve(curve ?? curveStepAfter)
+ const d3Area = area
(px, py0, py1).curve(curve)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return
}
@@ -105,7 +105,7 @@ export const AreaWithTopStroke =
(props: {
px: number | ((p: P) => number)
py0: number | ((p: P) => number)
py1: number | ((p: P) => number)
- curve?: CurveFactory
+ curve: CurveFactory
}) => {
const { color, data, px, py0, py1, curve } = props
return (
diff --git a/web/components/charts/stats.tsx b/web/components/charts/stats.tsx
new file mode 100644
index 00000000..a630657a
--- /dev/null
+++ b/web/components/charts/stats.tsx
@@ -0,0 +1,76 @@
+import { useMemo } from 'react'
+import { scaleTime, scaleLinear } from 'd3-scale'
+import { min, max } from 'lodash'
+import dayjs from 'dayjs'
+
+import { formatPercent } from 'common/util/format'
+import { Row } from '../layout/row'
+import { HistoryPoint, SingleValueHistoryChart } from './generic-charts'
+import { TooltipProps, MARGIN_X, MARGIN_Y } from './helpers'
+import { SizedContainer } from 'web/components/sized-container'
+
+const getPoints = (startDate: number, dailyValues: number[]) => {
+ const startDateDayJs = dayjs(startDate)
+ return dailyValues.map((y, i) => ({
+ x: startDateDayJs.add(i, 'day').toDate(),
+ y: y,
+ }))
+}
+
+const DailyCountTooltip = (props: TooltipProps) => {
+ const { data, mouseX, xScale } = props
+ const d = xScale.invert(mouseX)
+ return (
+
+ {dayjs(d).format('MMM DD')}
+ {data.y}
+
+ )
+}
+
+const DailyPercentTooltip = (props: TooltipProps) => {
+ const { data, mouseX, xScale } = props
+ const d = xScale.invert(mouseX)
+ return (
+
+ {dayjs(d).format('MMM DD')}
+ {formatPercent(data.y)}
+
+ )
+}
+
+export function DailyChart(props: {
+ startDate: number
+ dailyValues: number[]
+ excludeFirstDays?: number
+ pct?: boolean
+}) {
+ const { dailyValues, startDate, excludeFirstDays, pct } = props
+
+ const data = useMemo(
+ () => getPoints(startDate, dailyValues).slice(excludeFirstDays ?? 0),
+ [startDate, dailyValues, excludeFirstDays]
+ )
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const minDate = min(data.map((d) => d.x))!
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const maxDate = max(data.map((d) => d.x))!
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const maxValue = max(data.map((d) => d.y))!
+ return (
+
+ {(width, height) => (
+
+ )}
+
+ )
+}
diff --git a/web/components/contract/contract-overview.tsx b/web/components/contract/contract-overview.tsx
index 2a6d5172..95600a8e 100644
--- a/web/components/contract/contract-overview.tsx
+++ b/web/components/contract/contract-overview.tsx
@@ -1,5 +1,3 @@
-import React, { useEffect, useRef, useState } from 'react'
-
import { tradingAllowed } from 'web/lib/firebase/contracts'
import { Col } from '../layout/col'
import { ContractChart } from 'web/components/charts/contract'
@@ -24,6 +22,7 @@ import {
BinaryContract,
} from 'common/contract'
import { ContractDetails } from './contract-details'
+import { SizedContainer } from 'web/components/sized-container'
const OverviewQuestion = (props: { text: string }) => (
@@ -49,32 +48,18 @@ const SizedContractChart = (props: {
fullHeight: number
mobileHeight: number
}) => {
- const { contract, bets, fullHeight, mobileHeight } = props
- const containerRef = useRef(null)
- const [chartWidth, setChartWidth] = useState()
- const [chartHeight, setChartHeight] = useState()
- 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])
+ const { fullHeight, mobileHeight, contract, bets } = props
return (
-
- {chartWidth != null && chartHeight != null && (
+
+ {(width, height) => (
)}
-
+
)
}
@@ -114,7 +99,11 @@ const BinaryOverview = (props: { contract: BinaryContract; bets: Bet[] }) => {
-
+
ReactNode
+}) => {
+ const { children, fullHeight, mobileHeight } = props
+ const threshold = props.mobileThreshold ?? 800
+ const containerRef = useRef(null)
+ const [width, setWidth] = useState()
+ const [height, setHeight] = useState()
+ useEffect(() => {
+ if (containerRef.current) {
+ const handleResize = () => {
+ setHeight(window.innerWidth <= threshold ? mobileHeight : fullHeight)
+ setWidth(containerRef.current?.clientWidth)
+ }
+ handleResize()
+ const resizeObserver = new ResizeObserver(handleResize)
+ resizeObserver.observe(containerRef.current)
+ window.addEventListener('resize', handleResize)
+ return () => {
+ window.removeEventListener('resize', handleResize)
+ resizeObserver.disconnect()
+ }
+ }
+ }, [threshold, fullHeight, mobileHeight])
+ return (
+
+ {width != null && height != null && children(width, height)}
+
+ )
+}
diff --git a/web/pages/stats.tsx b/web/pages/stats.tsx
index 19fab509..125af4bd 100644
--- a/web/pages/stats.tsx
+++ b/web/pages/stats.tsx
@@ -1,8 +1,5 @@
import { useEffect, useState } from 'react'
-import {
- DailyCountChart,
- DailyPercentChart,
-} from 'web/components/analytics/charts'
+import { DailyChart } from 'web/components/charts/stats'
import { Col } from 'web/components/layout/col'
import { Spacer } from 'web/components/layout/spacer'
import { Tabs } from 'web/components/layout/tabs'
@@ -96,40 +93,36 @@ export function CustomAnalytics(props: Stats) {
{
title: 'Daily',
content: (
-
),
},
{
title: 'Daily (7d avg)',
content: (
-
),
},
{
title: 'Weekly',
content: (
-
),
},
{
title: 'Monthly',
content: (
-
),
},
@@ -149,44 +142,44 @@ export function CustomAnalytics(props: Stats) {
{
title: 'D1',
content: (
-
),
},
{
title: 'D1 (7d avg)',
content: (
-
),
},
{
title: 'W1',
content: (
-
),
},
{
title: 'M1',
content: (
-
),
},
@@ -207,33 +200,33 @@ export function CustomAnalytics(props: Stats) {
{
title: 'ND1',
content: (
-
),
},
{
title: 'ND1 (7d avg)',
content: (
-
),
},
{
title: 'NW1',
content: (
-
),
},
@@ -249,41 +242,31 @@ export function CustomAnalytics(props: Stats) {
{
title: capitalize(PAST_BETS),
content: (
-
+
),
},
{
title: 'Markets created',
content: (
-
),
},
{
title: 'Comments',
content: (
-
),
},
{
title: 'Signups',
content: (
-
+
),
},
]}
@@ -304,22 +287,22 @@ export function CustomAnalytics(props: Stats) {
{
title: 'Daily',
content: (
-
),
},
{
title: 'Daily (7d avg)',
content: (
-
),
},
@@ -335,33 +318,33 @@ export function CustomAnalytics(props: Stats) {
{
title: 'Daily / Weekly',
content: (
-
),
},
{
title: 'Daily / Monthly',
content: (
-
),
},
{
title: 'Weekly / Monthly',
content: (
-
),
},
@@ -380,31 +363,19 @@ export function CustomAnalytics(props: Stats) {
{
title: 'Daily',
content: (
-
+
),
},
{
title: 'Weekly',
content: (
-
+
),
},
{
title: 'Monthly',
content: (
-
+
),
},
]}