Merge branch 'main' of https://github.com/manifoldmarkets/manifold
This commit is contained in:
		
						commit
						603201a00f
					
				|  | @ -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 ( |  | ||||||
|     <div |  | ||||||
|       className={clsx( |  | ||||||
|         'h-[250px] w-full overflow-hidden', |  | ||||||
|         !small && 'md:h-[400px]' |  | ||||||
|       )} |  | ||||||
|     > |  | ||||||
|       <ResponsiveLine |  | ||||||
|         data={data} |  | ||||||
|         yScale={{ type: 'linear', stacked: false }} |  | ||||||
|         xScale={{ |  | ||||||
|           type: 'time', |  | ||||||
|         }} |  | ||||||
|         axisBottom={{ |  | ||||||
|           tickValues: bottomAxisTicks, |  | ||||||
|           format: (date) => 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 <Tooltip point={point} /> |  | ||||||
|         }} |  | ||||||
|       /> |  | ||||||
|     </div> |  | ||||||
|   ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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 ( |  | ||||||
|     <div |  | ||||||
|       className={clsx( |  | ||||||
|         'h-[250px] w-full overflow-hidden', |  | ||||||
|         !small && 'md:h-[400px]' |  | ||||||
|       )} |  | ||||||
|     > |  | ||||||
|       <ResponsiveLine |  | ||||||
|         data={data} |  | ||||||
|         yScale={{ type: 'linear', stacked: false }} |  | ||||||
|         xScale={{ |  | ||||||
|           type: 'time', |  | ||||||
|         }} |  | ||||||
|         axisLeft={{ |  | ||||||
|           format: formatPercent, |  | ||||||
|         }} |  | ||||||
|         axisBottom={{ |  | ||||||
|           tickValues: bottomAxisTicks, |  | ||||||
|           format: (date) => 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 <Tooltip point={point} isPercent /> |  | ||||||
|         }} |  | ||||||
|       /> |  | ||||||
|     </div> |  | ||||||
|   ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function Tooltip(props: { point: Point; isPercent?: boolean }) { |  | ||||||
|   const { point, isPercent } = 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>{' '} |  | ||||||
|         {isPercent ? formatPercent(+point.data.y) : Math.round(+point.data.y)} |  | ||||||
|       </div> |  | ||||||
|       <div>{dayjs(point.data.x).format('MMM DD')}</div> |  | ||||||
|     </Col> |  | ||||||
|   ) |  | ||||||
| } |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { useMemo } 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' | ||||||
|  | import { curveStepAfter } from 'd3-shape' | ||||||
| 
 | 
 | ||||||
| import { Bet } from 'common/bet' | import { Bet } from 'common/bet' | ||||||
| import { getProbability, getInitialProbability } from 'common/calculate' | import { getProbability, getInitialProbability } from 'common/calculate' | ||||||
|  | @ -76,6 +77,7 @@ export const BinaryContractChart = (props: { | ||||||
|       yScale={yScale} |       yScale={yScale} | ||||||
|       data={data} |       data={data} | ||||||
|       color="#11b981" |       color="#11b981" | ||||||
|  |       curve={curveStepAfter} | ||||||
|       onMouseOver={onMouseOver} |       onMouseOver={onMouseOver} | ||||||
|       Tooltip={BinaryChartTooltip} |       Tooltip={BinaryChartTooltip} | ||||||
|       pct |       pct | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { useMemo } 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' | ||||||
|  | import { curveStepAfter } from 'd3-shape' | ||||||
| 
 | 
 | ||||||
| import { Bet } from 'common/bet' | import { Bet } from 'common/bet' | ||||||
| import { Answer } from 'common/answer' | import { Answer } from 'common/answer' | ||||||
|  | @ -214,6 +215,7 @@ export const ChoiceContractChart = (props: { | ||||||
|       yScale={yScale} |       yScale={yScale} | ||||||
|       data={data} |       data={data} | ||||||
|       colors={CATEGORY_COLORS} |       colors={CATEGORY_COLORS} | ||||||
|  |       curve={curveStepAfter} | ||||||
|       onMouseOver={onMouseOver} |       onMouseOver={onMouseOver} | ||||||
|       Tooltip={ChoiceTooltip} |       Tooltip={ChoiceTooltip} | ||||||
|       pct |       pct | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { useMemo } 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' | ||||||
|  | import { curveStepAfter } from 'd3-shape' | ||||||
| 
 | 
 | ||||||
| import { Bet } from 'common/bet' | import { Bet } from 'common/bet' | ||||||
| import { DAY_MS } from 'common/util/time' | import { DAY_MS } from 'common/util/time' | ||||||
|  | @ -85,11 +86,11 @@ export const PseudoNumericContractChart = (props: { | ||||||
|     Date.now() |     Date.now() | ||||||
|   ) |   ) | ||||||
|   const visibleRange = [start, rightmostDate] |   const visibleRange = [start, rightmostDate] | ||||||
|   const xScale = scaleTime(visibleRange, [0, width ?? 0 - MARGIN_X]) |   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 ?? 0 - MARGIN_Y, 0]).clamp(true) |     ? scaleLog([Math.max(min, 1), max], [height - MARGIN_Y, 0]).clamp(true) | ||||||
|     : scaleLinear([min, max], [height ?? 0 - MARGIN_Y, 0]) |     : scaleLinear([min, max], [height - MARGIN_Y, 0]) | ||||||
|   return ( |   return ( | ||||||
|     <SingleValueHistoryChart |     <SingleValueHistoryChart | ||||||
|       w={width} |       w={width} | ||||||
|  | @ -97,6 +98,7 @@ export const PseudoNumericContractChart = (props: { | ||||||
|       xScale={xScale} |       xScale={xScale} | ||||||
|       yScale={yScale} |       yScale={yScale} | ||||||
|       data={data} |       data={data} | ||||||
|  |       curve={curveStepAfter} | ||||||
|       onMouseOver={onMouseOver} |       onMouseOver={onMouseOver} | ||||||
|       Tooltip={PseudoNumericChartTooltip} |       Tooltip={PseudoNumericChartTooltip} | ||||||
|       color={NUMERIC_GRAPH_COLOR} |       color={NUMERIC_GRAPH_COLOR} | ||||||
|  |  | ||||||
|  | @ -4,11 +4,11 @@ import { axisBottom, axisLeft } from 'd3-axis' | ||||||
| import { D3BrushEvent } from 'd3-brush' | import { D3BrushEvent } from 'd3-brush' | ||||||
| import { ScaleTime, ScaleContinuousNumeric } from 'd3-scale' | import { ScaleTime, ScaleContinuousNumeric } from 'd3-scale' | ||||||
| import { | import { | ||||||
|  |   CurveFactory, | ||||||
|  |   SeriesPoint, | ||||||
|   curveLinear, |   curveLinear, | ||||||
|   curveStepAfter, |  | ||||||
|   stack, |   stack, | ||||||
|   stackOrderReverse, |   stackOrderReverse, | ||||||
|   SeriesPoint, |  | ||||||
| } from 'd3-shape' | } from 'd3-shape' | ||||||
| import { range } from 'lodash' | import { range } from 'lodash' | ||||||
| 
 | 
 | ||||||
|  | @ -52,10 +52,11 @@ export const DistributionChart = <P extends DistributionPoint>(props: { | ||||||
|   color: string |   color: string | ||||||
|   xScale: ScaleContinuousNumeric<number, number> |   xScale: ScaleContinuousNumeric<number, number> | ||||||
|   yScale: ScaleContinuousNumeric<number, number> |   yScale: ScaleContinuousNumeric<number, number> | ||||||
|  |   curve?: CurveFactory | ||||||
|   onMouseOver?: (p: P | undefined) => void |   onMouseOver?: (p: P | undefined) => void | ||||||
|   Tooltip?: TooltipComponent<number, P> |   Tooltip?: TooltipComponent<number, P> | ||||||
| }) => { | }) => { | ||||||
|   const { color, data, yScale, w, h, Tooltip } = props |   const { color, data, yScale, w, h, curve, Tooltip } = props | ||||||
| 
 | 
 | ||||||
|   const [viewXScale, setViewXScale] = |   const [viewXScale, setViewXScale] = | ||||||
|     useState<ScaleContinuousNumeric<number, number>>() |     useState<ScaleContinuousNumeric<number, number>>() | ||||||
|  | @ -100,7 +101,7 @@ export const DistributionChart = <P extends DistributionPoint>(props: { | ||||||
|         px={px} |         px={px} | ||||||
|         py0={py0} |         py0={py0} | ||||||
|         py1={py1} |         py1={py1} | ||||||
|         curve={curveLinear} |         curve={curve ?? curveLinear} | ||||||
|       /> |       /> | ||||||
|     </SVGChart> |     </SVGChart> | ||||||
|   ) |   ) | ||||||
|  | @ -113,11 +114,12 @@ export const MultiValueHistoryChart = <P extends MultiPoint>(props: { | ||||||
|   colors: readonly string[] |   colors: readonly string[] | ||||||
|   xScale: ScaleTime<number, number> |   xScale: ScaleTime<number, number> | ||||||
|   yScale: ScaleContinuousNumeric<number, number> |   yScale: ScaleContinuousNumeric<number, number> | ||||||
|  |   curve?: CurveFactory | ||||||
|   onMouseOver?: (p: P | undefined) => void |   onMouseOver?: (p: P | undefined) => void | ||||||
|   Tooltip?: TooltipComponent<Date, P> |   Tooltip?: TooltipComponent<Date, P> | ||||||
|   pct?: boolean |   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<ScaleTime<number, number>>() |   const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>() | ||||||
|   const xScale = viewXScale ?? props.xScale |   const xScale = viewXScale ?? props.xScale | ||||||
|  | @ -177,7 +179,7 @@ export const MultiValueHistoryChart = <P extends MultiPoint>(props: { | ||||||
|           px={px} |           px={px} | ||||||
|           py0={py0} |           py0={py0} | ||||||
|           py1={py1} |           py1={py1} | ||||||
|           curve={curveStepAfter} |           curve={curve ?? curveLinear} | ||||||
|           fill={colors[i]} |           fill={colors[i]} | ||||||
|         /> |         /> | ||||||
|       ))} |       ))} | ||||||
|  | @ -192,11 +194,12 @@ export const SingleValueHistoryChart = <P extends HistoryPoint>(props: { | ||||||
|   color: string |   color: string | ||||||
|   xScale: ScaleTime<number, number> |   xScale: ScaleTime<number, number> | ||||||
|   yScale: ScaleContinuousNumeric<number, number> |   yScale: ScaleContinuousNumeric<number, number> | ||||||
|  |   curve?: CurveFactory | ||||||
|   onMouseOver?: (p: P | undefined) => void |   onMouseOver?: (p: P | undefined) => void | ||||||
|   Tooltip?: TooltipComponent<Date, P> |   Tooltip?: TooltipComponent<Date, P> | ||||||
|   pct?: boolean |   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<ScaleTime<number, number>>() |   const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>() | ||||||
|   const xScale = viewXScale ?? props.xScale |   const xScale = viewXScale ?? props.xScale | ||||||
|  | @ -246,7 +249,7 @@ export const SingleValueHistoryChart = <P extends HistoryPoint>(props: { | ||||||
|         px={px} |         px={px} | ||||||
|         py0={py0} |         py0={py0} | ||||||
|         py1={py1} |         py1={py1} | ||||||
|         curve={curveStepAfter} |         curve={curve ?? curveLinear} | ||||||
|       /> |       /> | ||||||
|     </SVGChart> |     </SVGChart> | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ import { | ||||||
| import { pointer, select } from 'd3-selection' | import { pointer, select } from 'd3-selection' | ||||||
| import { Axis, AxisScale } from 'd3-axis' | import { Axis, AxisScale } from 'd3-axis' | ||||||
| import { brushX, D3BrushEvent } from 'd3-brush' | 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 { nanoid } from 'nanoid' | ||||||
| import dayjs from 'dayjs' | import dayjs from 'dayjs' | ||||||
| import clsx from 'clsx' | import clsx from 'clsx' | ||||||
|  | @ -73,11 +73,11 @@ const LinePathInternal = <P,>( | ||||||
|     data: P[] |     data: P[] | ||||||
|     px: number | ((p: P) => number) |     px: number | ((p: P) => number) | ||||||
|     py: number | ((p: P) => number) |     py: number | ((p: P) => number) | ||||||
|     curve?: CurveFactory |     curve: CurveFactory | ||||||
|   } & SVGProps<SVGPathElement> |   } & SVGProps<SVGPathElement> | ||||||
| ) => { | ) => { | ||||||
|   const { data, px, py, curve, ...rest } = props |   const { data, px, py, curve, ...rest } = props | ||||||
|   const d3Line = line<P>(px, py).curve(curve ?? curveStepAfter) |   const d3Line = line<P>(px, py).curve(curve) | ||||||
|   // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 |   // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 | ||||||
|   return <path {...rest} fill="none" d={d3Line(data)!} /> |   return <path {...rest} fill="none" d={d3Line(data)!} /> | ||||||
| } | } | ||||||
|  | @ -89,11 +89,11 @@ const AreaPathInternal = <P,>( | ||||||
|     px: number | ((p: P) => number) |     px: number | ((p: P) => number) | ||||||
|     py0: number | ((p: P) => number) |     py0: number | ((p: P) => number) | ||||||
|     py1: number | ((p: P) => number) |     py1: number | ((p: P) => number) | ||||||
|     curve?: CurveFactory |     curve: CurveFactory | ||||||
|   } & SVGProps<SVGPathElement> |   } & SVGProps<SVGPathElement> | ||||||
| ) => { | ) => { | ||||||
|   const { data, px, py0, py1, curve, ...rest } = props |   const { data, px, py0, py1, curve, ...rest } = props | ||||||
|   const d3Area = area<P>(px, py0, py1).curve(curve ?? curveStepAfter) |   const d3Area = area<P>(px, py0, py1).curve(curve) | ||||||
|   // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 |   // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 | ||||||
|   return <path {...rest} d={d3Area(data)!} /> |   return <path {...rest} d={d3Area(data)!} /> | ||||||
| } | } | ||||||
|  | @ -105,7 +105,7 @@ export const AreaWithTopStroke = <P,>(props: { | ||||||
|   px: number | ((p: P) => number) |   px: number | ((p: P) => number) | ||||||
|   py0: number | ((p: P) => number) |   py0: number | ((p: P) => number) | ||||||
|   py1: number | ((p: P) => number) |   py1: number | ((p: P) => number) | ||||||
|   curve?: CurveFactory |   curve: CurveFactory | ||||||
| }) => { | }) => { | ||||||
|   const { color, data, px, py0, py1, curve } = props |   const { color, data, px, py0, py1, curve } = props | ||||||
|   return ( |   return ( | ||||||
|  |  | ||||||
							
								
								
									
										76
									
								
								web/components/charts/stats.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								web/components/charts/stats.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -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<Date, HistoryPoint>) => { | ||||||
|  |   const { data, mouseX, xScale } = props | ||||||
|  |   const d = xScale.invert(mouseX) | ||||||
|  |   return ( | ||||||
|  |     <Row className="items-center gap-2"> | ||||||
|  |       <span className="font-semibold">{dayjs(d).format('MMM DD')}</span> | ||||||
|  |       <span className="text-greyscale-6">{data.y}</span> | ||||||
|  |     </Row> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const DailyPercentTooltip = (props: TooltipProps<Date, HistoryPoint>) => { | ||||||
|  |   const { data, mouseX, xScale } = props | ||||||
|  |   const d = xScale.invert(mouseX) | ||||||
|  |   return ( | ||||||
|  |     <Row className="items-center gap-2"> | ||||||
|  |       <span className="font-semibold">{dayjs(d).format('MMM DD')}</span> | ||||||
|  |       <span className="text-greyscale-6">{formatPercent(data.y)}</span> | ||||||
|  |     </Row> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 ( | ||||||
|  |     <SizedContainer fullHeight={250} mobileHeight={250}> | ||||||
|  |       {(width, height) => ( | ||||||
|  |         <SingleValueHistoryChart | ||||||
|  |           w={width} | ||||||
|  |           h={height} | ||||||
|  |           xScale={scaleTime([minDate, maxDate], [0, width - MARGIN_X])} | ||||||
|  |           yScale={scaleLinear([0, maxValue], [height - MARGIN_Y, 0])} | ||||||
|  |           data={data} | ||||||
|  |           Tooltip={pct ? DailyPercentTooltip : DailyCountTooltip} | ||||||
|  |           color="#11b981" | ||||||
|  |           pct={pct} | ||||||
|  |         /> | ||||||
|  |       )} | ||||||
|  |     </SizedContainer> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | @ -393,7 +393,9 @@ export function ContractCardProbChange(props: { | ||||||
|   noLinkAvatar?: boolean |   noLinkAvatar?: boolean | ||||||
|   className?: string |   className?: string | ||||||
| }) { | }) { | ||||||
|   const { contract, noLinkAvatar, className } = props |   const { noLinkAvatar, className } = props | ||||||
|  |   const contract = useContractWithPreload(props.contract) as CPMMBinaryContract | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Col |     <Col | ||||||
|       className={clsx( |       className={clsx( | ||||||
|  |  | ||||||
|  | @ -1,5 +1,3 @@ | ||||||
| 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 { ContractChart } from 'web/components/charts/contract' | import { ContractChart } from 'web/components/charts/contract' | ||||||
|  | @ -24,6 +22,7 @@ import { | ||||||
|   BinaryContract, |   BinaryContract, | ||||||
| } from 'common/contract' | } from 'common/contract' | ||||||
| import { ContractDetails } from './contract-details' | import { ContractDetails } from './contract-details' | ||||||
|  | import { SizedContainer } from 'web/components/sized-container' | ||||||
| 
 | 
 | ||||||
| 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} /> | ||||||
|  | @ -49,32 +48,18 @@ const SizedContractChart = (props: { | ||||||
|   fullHeight: number |   fullHeight: number | ||||||
|   mobileHeight: number |   mobileHeight: number | ||||||
| }) => { | }) => { | ||||||
|   const { contract, bets, fullHeight, mobileHeight } = props |   const { fullHeight, mobileHeight, contract, bets } = 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 ( |   return ( | ||||||
|     <div ref={containerRef}> |     <SizedContainer fullHeight={fullHeight} mobileHeight={mobileHeight}> | ||||||
|       {chartWidth != null && chartHeight != null && ( |       {(width, height) => ( | ||||||
|         <ContractChart |         <ContractChart | ||||||
|  |           width={width} | ||||||
|  |           height={height} | ||||||
|           contract={contract} |           contract={contract} | ||||||
|           bets={bets} |           bets={bets} | ||||||
|           width={chartWidth} |  | ||||||
|           height={chartHeight} |  | ||||||
|         /> |         /> | ||||||
|       )} |       )} | ||||||
|     </div> |     </SizedContainer> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -114,7 +99,11 @@ const BinaryOverview = (props: { contract: BinaryContract; bets: Bet[] }) => { | ||||||
|         <ContractDetails contract={contract} /> |         <ContractDetails contract={contract} /> | ||||||
|         <Row className="justify-between gap-4"> |         <Row className="justify-between gap-4"> | ||||||
|           <OverviewQuestion text={contract.question} /> |           <OverviewQuestion text={contract.question} /> | ||||||
|           <BinaryResolutionOrChance contract={contract} large /> |           <BinaryResolutionOrChance | ||||||
|  |             className="flex items-end" | ||||||
|  |             contract={contract} | ||||||
|  |             large | ||||||
|  |           /> | ||||||
|         </Row> |         </Row> | ||||||
|       </Col> |       </Col> | ||||||
|       <SizedContractChart |       <SizedContractChart | ||||||
|  |  | ||||||
|  | @ -77,13 +77,34 @@ const CommentsTabContent = memo(function CommentsTabContent(props: { | ||||||
|   const comments = useComments(contract.id) ?? props.comments |   const comments = useComments(contract.id) ?? props.comments | ||||||
|   const [sort, setSort] = useState<'Newest' | 'Best'>('Newest') |   const [sort, setSort] = useState<'Newest' | 'Best'>('Newest') | ||||||
|   const me = useUser() |   const me = useUser() | ||||||
|  | 
 | ||||||
|   if (comments == null) { |   if (comments == null) { | ||||||
|     return <LoadingIndicator /> |     return <LoadingIndicator /> | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   const tipsOrBountiesAwarded = | ||||||
|  |     Object.keys(tips).length > 0 || comments.some((c) => c.bountiesAwarded) | ||||||
|  | 
 | ||||||
|  |   const sortedComments = sortBy(comments, (c) => | ||||||
|  |     sort === 'Newest' | ||||||
|  |       ? c.createdTime | ||||||
|  |       : // Is this too magic? If there are tips/bounties, 'Best' shows your own comments made within the last 10 minutes first, then sorts by score
 | ||||||
|  |       tipsOrBountiesAwarded && | ||||||
|  |         c.createdTime > Date.now() - 10 * MINUTE_MS && | ||||||
|  |         c.userId === me?.id | ||||||
|  |       ? -Infinity | ||||||
|  |       : -((c.bountiesAwarded ?? 0) + sum(Object.values(tips[c.id] ?? []))) | ||||||
|  |   ) | ||||||
|  | 
 | ||||||
|  |   const commentsByParent = groupBy( | ||||||
|  |     sortedComments, | ||||||
|  |     (c) => c.replyToCommentId ?? '_' | ||||||
|  |   ) | ||||||
|  |   const topLevelComments = commentsByParent['_'] ?? [] | ||||||
|  |   // Top level comments are reverse-chronological, while replies are chronological
 | ||||||
|  |   if (sort === 'Newest') topLevelComments.reverse() | ||||||
|  | 
 | ||||||
|   if (contract.outcomeType === 'FREE_RESPONSE') { |   if (contract.outcomeType === 'FREE_RESPONSE') { | ||||||
|     const generalComments = comments.filter( |  | ||||||
|       (c) => c.answerOutcome === undefined && c.betId === undefined |  | ||||||
|     ) |  | ||||||
|     const sortedAnswers = sortBy( |     const sortedAnswers = sortBy( | ||||||
|       contract.answers, |       contract.answers, | ||||||
|       (a) => -getOutcomeProbability(contract, a.id) |       (a) => -getOutcomeProbability(contract, a.id) | ||||||
|  | @ -92,6 +113,9 @@ const CommentsTabContent = memo(function CommentsTabContent(props: { | ||||||
|       comments, |       comments, | ||||||
|       (c) => c.answerOutcome ?? c.betOutcome ?? '_' |       (c) => c.answerOutcome ?? c.betOutcome ?? '_' | ||||||
|     ) |     ) | ||||||
|  |     const generalTopLevelComments = topLevelComments.filter( | ||||||
|  |       (c) => c.answerOutcome === undefined && c.betId === undefined | ||||||
|  |     ) | ||||||
|     return ( |     return ( | ||||||
|       <> |       <> | ||||||
|         {sortedAnswers.map((answer) => ( |         {sortedAnswers.map((answer) => ( | ||||||
|  | @ -115,12 +139,12 @@ const CommentsTabContent = memo(function CommentsTabContent(props: { | ||||||
|           <div className="text-md mt-8 mb-2 text-left">General Comments</div> |           <div className="text-md mt-8 mb-2 text-left">General Comments</div> | ||||||
|           <div className="mb-4 w-full border-b border-gray-200" /> |           <div className="mb-4 w-full border-b border-gray-200" /> | ||||||
|           <ContractCommentInput className="mb-5" contract={contract} /> |           <ContractCommentInput className="mb-5" contract={contract} /> | ||||||
|           {generalComments.map((comment) => ( |           {generalTopLevelComments.map((comment) => ( | ||||||
|             <FeedCommentThread |             <FeedCommentThread | ||||||
|               key={comment.id} |               key={comment.id} | ||||||
|               contract={contract} |               contract={contract} | ||||||
|               parentComment={comment} |               parentComment={comment} | ||||||
|               threadComments={[]} |               threadComments={commentsByParent[comment.id] ?? []} | ||||||
|               tips={tips} |               tips={tips} | ||||||
|             /> |             /> | ||||||
|           ))} |           ))} | ||||||
|  | @ -128,24 +152,6 @@ const CommentsTabContent = memo(function CommentsTabContent(props: { | ||||||
|       </> |       </> | ||||||
|     ) |     ) | ||||||
|   } else { |   } else { | ||||||
|     const tipsOrBountiesAwarded = |  | ||||||
|       Object.keys(tips).length > 0 || comments.some((c) => c.bountiesAwarded) |  | ||||||
| 
 |  | ||||||
|     const commentsByParent = groupBy( |  | ||||||
|       sortBy(comments, (c) => |  | ||||||
|         sort === 'Newest' |  | ||||||
|           ? -c.createdTime |  | ||||||
|           : // Is this too magic? If there are tips/bounties, 'Best' shows your own comments made within the last 10 minutes first, then sorts by score
 |  | ||||||
|           tipsOrBountiesAwarded && |  | ||||||
|             c.createdTime > Date.now() - 10 * MINUTE_MS && |  | ||||||
|             c.userId === me?.id |  | ||||||
|           ? -Infinity |  | ||||||
|           : -((c.bountiesAwarded ?? 0) + sum(Object.values(tips[c.id] ?? []))) |  | ||||||
|       ), |  | ||||||
|       (c) => c.replyToCommentId ?? '_' |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     const topLevelComments = commentsByParent['_'] ?? [] |  | ||||||
|     return ( |     return ( | ||||||
|       <> |       <> | ||||||
|         <ContractCommentInput className="mb-5" contract={contract} /> |         <ContractCommentInput className="mb-5" contract={contract} /> | ||||||
|  |  | ||||||
							
								
								
									
										35
									
								
								web/components/sized-container.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								web/components/sized-container.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | ||||||
|  | import { ReactNode, useEffect, useRef, useState } from 'react' | ||||||
|  | 
 | ||||||
|  | export const SizedContainer = (props: { | ||||||
|  |   fullHeight: number | ||||||
|  |   mobileHeight: number | ||||||
|  |   mobileThreshold?: number | ||||||
|  |   children: (width: number, height: number) => ReactNode | ||||||
|  | }) => { | ||||||
|  |   const { children, fullHeight, mobileHeight } = props | ||||||
|  |   const threshold = props.mobileThreshold ?? 800 | ||||||
|  |   const containerRef = useRef<HTMLDivElement>(null) | ||||||
|  |   const [width, setWidth] = useState<number>() | ||||||
|  |   const [height, setHeight] = useState<number>() | ||||||
|  |   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 ( | ||||||
|  |     <div ref={containerRef}> | ||||||
|  |       {width != null && height != null && children(width, height)} | ||||||
|  |     </div> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | @ -11,7 +11,7 @@ const App = () => { | ||||||
|         url="/cowp" |         url="/cowp" | ||||||
|       /> |       /> | ||||||
|       <Link href="https://www.youtube.com/watch?v=FavUpD_IjVY"> |       <Link href="https://www.youtube.com/watch?v=FavUpD_IjVY"> | ||||||
|         <img src="https://i.imgur.com/Lt54IiU.png" /> |         <img src="https://i.imgur.com/Lt54IiU.png" className="cursor-pointer" /> | ||||||
|       </Link> |       </Link> | ||||||
|     </Page> |     </Page> | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ export default function LabsPage() { | ||||||
|         url="/labs" |         url="/labs" | ||||||
|       /> |       /> | ||||||
|       <Col className="px-4"> |       <Col className="px-4"> | ||||||
|         <Title className="sm:!mt-0" text="Manifold Labs" /> |         <Title className="sm:!mt-0" text="🧪 Manifold Labs" /> | ||||||
| 
 | 
 | ||||||
|         <Masonry |         <Masonry | ||||||
|           breakpointCols={{ default: 2, 768: 1 }} |           breakpointCols={{ default: 2, 768: 1 }} | ||||||
|  |  | ||||||
|  | @ -1,8 +1,5 @@ | ||||||
| import { useEffect, useState } from 'react' | import { useEffect, useState } from 'react' | ||||||
| import { | import { DailyChart } from 'web/components/charts/stats' | ||||||
|   DailyCountChart, |  | ||||||
|   DailyPercentChart, |  | ||||||
| } from 'web/components/analytics/charts' |  | ||||||
| import { Col } from 'web/components/layout/col' | import { Col } from 'web/components/layout/col' | ||||||
| import { Spacer } from 'web/components/layout/spacer' | import { Spacer } from 'web/components/layout/spacer' | ||||||
| import { Tabs } from 'web/components/layout/tabs' | import { Tabs } from 'web/components/layout/tabs' | ||||||
|  | @ -96,40 +93,36 @@ export function CustomAnalytics(props: Stats) { | ||||||
|           { |           { | ||||||
|             title: 'Daily', |             title: 'Daily', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyCountChart |               <DailyChart | ||||||
|                 dailyCounts={dailyActiveUsers} |                 dailyValues={dailyActiveUsers} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'Daily (7d avg)', |             title: 'Daily (7d avg)', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyCountChart |               <DailyChart | ||||||
|                 dailyCounts={dailyActiveUsersWeeklyAvg} |                 dailyValues={dailyActiveUsersWeeklyAvg} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'Weekly', |             title: 'Weekly', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyCountChart |               <DailyChart | ||||||
|                 dailyCounts={weeklyActiveUsers} |                 dailyValues={weeklyActiveUsers} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'Monthly', |             title: 'Monthly', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyCountChart |               <DailyChart | ||||||
|                 dailyCounts={monthlyActiveUsers} |                 dailyValues={monthlyActiveUsers} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|  | @ -149,44 +142,44 @@ export function CustomAnalytics(props: Stats) { | ||||||
|           { |           { | ||||||
|             title: 'D1', |             title: 'D1', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyPercentChart |               <DailyChart | ||||||
|                 dailyPercent={d1} |                 dailyValues={d1} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|                 excludeFirstDays={1} |                 excludeFirstDays={1} | ||||||
|  |                 pct | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'D1 (7d avg)', |             title: 'D1 (7d avg)', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyPercentChart |               <DailyChart | ||||||
|                 dailyPercent={d1WeeklyAvg} |                 dailyValues={d1WeeklyAvg} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|                 excludeFirstDays={7} |                 excludeFirstDays={7} | ||||||
|  |                 pct | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'W1', |             title: 'W1', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyPercentChart |               <DailyChart | ||||||
|                 dailyPercent={weekOnWeekRetention} |                 dailyValues={weekOnWeekRetention} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|                 excludeFirstDays={14} |                 excludeFirstDays={14} | ||||||
|  |                 pct | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'M1', |             title: 'M1', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyPercentChart |               <DailyChart | ||||||
|                 dailyPercent={monthlyRetention} |                 dailyValues={monthlyRetention} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|                 excludeFirstDays={60} |                 excludeFirstDays={60} | ||||||
|  |                 pct | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|  | @ -207,33 +200,33 @@ export function CustomAnalytics(props: Stats) { | ||||||
|           { |           { | ||||||
|             title: 'ND1', |             title: 'ND1', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyPercentChart |               <DailyChart | ||||||
|                 dailyPercent={nd1} |                 dailyValues={nd1} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 excludeFirstDays={1} |                 excludeFirstDays={1} | ||||||
|                 small |                 pct | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'ND1 (7d avg)', |             title: 'ND1 (7d avg)', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyPercentChart |               <DailyChart | ||||||
|                 dailyPercent={nd1WeeklyAvg} |                 dailyValues={nd1WeeklyAvg} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 excludeFirstDays={7} |                 excludeFirstDays={7} | ||||||
|                 small |                 pct | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'NW1', |             title: 'NW1', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyPercentChart |               <DailyChart | ||||||
|                 dailyPercent={nw1} |                 dailyValues={nw1} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 excludeFirstDays={14} |                 excludeFirstDays={14} | ||||||
|                 small |                 pct | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|  | @ -249,41 +242,31 @@ export function CustomAnalytics(props: Stats) { | ||||||
|           { |           { | ||||||
|             title: capitalize(PAST_BETS), |             title: capitalize(PAST_BETS), | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyCountChart |               <DailyChart dailyValues={dailyBetCounts} startDate={startDate} /> | ||||||
|                 dailyCounts={dailyBetCounts} |  | ||||||
|                 startDate={startDate} |  | ||||||
|                 small |  | ||||||
|               /> |  | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'Markets created', |             title: 'Markets created', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyCountChart |               <DailyChart | ||||||
|                 dailyCounts={dailyContractCounts} |                 dailyValues={dailyContractCounts} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'Comments', |             title: 'Comments', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyCountChart |               <DailyChart | ||||||
|                 dailyCounts={dailyCommentCounts} |                 dailyValues={dailyCommentCounts} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'Signups', |             title: 'Signups', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyCountChart |               <DailyChart dailyValues={dailySignups} startDate={startDate} /> | ||||||
|                 dailyCounts={dailySignups} |  | ||||||
|                 startDate={startDate} |  | ||||||
|                 small |  | ||||||
|               /> |  | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|         ]} |         ]} | ||||||
|  | @ -304,22 +287,22 @@ export function CustomAnalytics(props: Stats) { | ||||||
|           { |           { | ||||||
|             title: 'Daily', |             title: 'Daily', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyPercentChart |               <DailyChart | ||||||
|                 dailyPercent={dailyActivationRate} |                 dailyValues={dailyActivationRate} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 excludeFirstDays={1} |                 excludeFirstDays={1} | ||||||
|                 small |                 pct | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'Daily (7d avg)', |             title: 'Daily (7d avg)', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyPercentChart |               <DailyChart | ||||||
|                 dailyPercent={dailyActivationRateWeeklyAvg} |                 dailyValues={dailyActivationRateWeeklyAvg} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 excludeFirstDays={7} |                 excludeFirstDays={7} | ||||||
|                 small |                 pct | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|  | @ -335,33 +318,33 @@ export function CustomAnalytics(props: Stats) { | ||||||
|           { |           { | ||||||
|             title: 'Daily / Weekly', |             title: 'Daily / Weekly', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyPercentChart |               <DailyChart | ||||||
|                 dailyPercent={dailyDividedByWeekly} |                 dailyValues={dailyDividedByWeekly} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|                 excludeFirstDays={7} |                 excludeFirstDays={7} | ||||||
|  |                 pct | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'Daily / Monthly', |             title: 'Daily / Monthly', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyPercentChart |               <DailyChart | ||||||
|                 dailyPercent={dailyDividedByMonthly} |                 dailyValues={dailyDividedByMonthly} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|                 excludeFirstDays={30} |                 excludeFirstDays={30} | ||||||
|  |                 pct | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'Weekly / Monthly', |             title: 'Weekly / Monthly', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyPercentChart |               <DailyChart | ||||||
|                 dailyPercent={weeklyDividedByMonthly} |                 dailyValues={weeklyDividedByMonthly} | ||||||
|                 startDate={startDate} |                 startDate={startDate} | ||||||
|                 small |  | ||||||
|                 excludeFirstDays={30} |                 excludeFirstDays={30} | ||||||
|  |                 pct | ||||||
|               /> |               /> | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|  | @ -380,31 +363,19 @@ export function CustomAnalytics(props: Stats) { | ||||||
|           { |           { | ||||||
|             title: 'Daily', |             title: 'Daily', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyCountChart |               <DailyChart dailyValues={manaBet.daily} startDate={startDate} /> | ||||||
|                 dailyCounts={manaBet.daily} |  | ||||||
|                 startDate={startDate} |  | ||||||
|                 small |  | ||||||
|               /> |  | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'Weekly', |             title: 'Weekly', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyCountChart |               <DailyChart dailyValues={manaBet.weekly} startDate={startDate} /> | ||||||
|                 dailyCounts={manaBet.weekly} |  | ||||||
|                 startDate={startDate} |  | ||||||
|                 small |  | ||||||
|               /> |  | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             title: 'Monthly', |             title: 'Monthly', | ||||||
|             content: ( |             content: ( | ||||||
|               <DailyCountChart |               <DailyChart dailyValues={manaBet.monthly} startDate={startDate} /> | ||||||
|                 dailyCounts={manaBet.monthly} |  | ||||||
|                 startDate={startDate} |  | ||||||
|                 small |  | ||||||
|               /> |  | ||||||
|             ), |             ), | ||||||
|           }, |           }, | ||||||
|         ]} |         ]} | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user