Refactor to invert control of chart tooltip display
This commit is contained in:
parent
0df34473fe
commit
9fe3c24bae
|
@ -12,8 +12,13 @@ import {
|
|||
MAX_DATE,
|
||||
getDateRange,
|
||||
getRightmostVisibleDate,
|
||||
formatDateInRange,
|
||||
formatPct,
|
||||
} from '../helpers'
|
||||
import { SingleValueHistoryChart } from '../generic-charts'
|
||||
import {
|
||||
SingleValueHistoryTooltipProps,
|
||||
SingleValueHistoryChart,
|
||||
} from '../generic-charts'
|
||||
import { useElementWidth } from 'web/hooks/use-element-width'
|
||||
|
||||
const getBetPoints = (bets: Bet[]) => {
|
||||
|
@ -24,6 +29,16 @@ const getBetPoints = (bets: Bet[]) => {
|
|||
}))
|
||||
}
|
||||
|
||||
const BinaryChartTooltip = (props: SingleValueHistoryTooltipProps<Bet>) => {
|
||||
const { x, y, xScale } = props
|
||||
const [start, end] = xScale.domain()
|
||||
return (
|
||||
<span className="text-sm">
|
||||
<strong>{formatPct(y)}</strong> {formatDateInRange(x, start, end)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export const BinaryContractChart = (props: {
|
||||
contract: BinaryContract
|
||||
bets: Bet[]
|
||||
|
@ -55,6 +70,7 @@ export const BinaryContractChart = (props: {
|
|||
const height = props.height ?? (isMobile ? 250 : 350)
|
||||
const xScale = scaleTime(visibleRange, [0, width - MARGIN_X]).clamp(true)
|
||||
const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0])
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
{width > 0 && (
|
||||
|
@ -65,6 +81,7 @@ export const BinaryContractChart = (props: {
|
|||
yScale={yScale}
|
||||
data={data}
|
||||
color="#11b981"
|
||||
Tooltip={BinaryChartTooltip}
|
||||
pct
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -13,8 +13,15 @@ import {
|
|||
MAX_DATE,
|
||||
getDateRange,
|
||||
getRightmostVisibleDate,
|
||||
formatPct,
|
||||
formatDateInRange,
|
||||
} from '../helpers'
|
||||
import { MultiPoint, MultiValueHistoryChart } from '../generic-charts'
|
||||
import {
|
||||
Legend,
|
||||
MultiPoint,
|
||||
MultiValueHistoryChart,
|
||||
MultiValueHistoryTooltipProps,
|
||||
} from '../generic-charts'
|
||||
import { useElementWidth } from 'web/hooks/use-element-width'
|
||||
|
||||
// thanks to https://observablehq.com/@jonhelfman/optimal-orders-for-choosing-categorical-colors
|
||||
|
@ -150,6 +157,30 @@ export const ChoiceContractChart = (props: {
|
|||
const height = props.height ?? (isMobile ? 150 : 250)
|
||||
const xScale = scaleTime(visibleRange, [0, width - MARGIN_X]).clamp(true)
|
||||
const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0])
|
||||
|
||||
const ChoiceTooltip = useMemo(
|
||||
() => (props: MultiValueHistoryTooltipProps<Bet>) => {
|
||||
const { x, y, xScale } = props
|
||||
const [start, end] = xScale.domain()
|
||||
const legendItems = sortBy(
|
||||
y.map((p, i) => ({
|
||||
color: CATEGORY_COLORS[i],
|
||||
label: answers[i].text,
|
||||
value: formatPct(p),
|
||||
p,
|
||||
})),
|
||||
(item) => -item.p
|
||||
).slice(0, 10)
|
||||
return (
|
||||
<div>
|
||||
<p>{formatDateInRange(x, start, end)}</p>
|
||||
<Legend className="max-w-xs text-sm" items={legendItems} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
[answers]
|
||||
)
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
{width > 0 && (
|
||||
|
@ -161,6 +192,7 @@ export const ChoiceContractChart = (props: {
|
|||
data={data}
|
||||
colors={CATEGORY_COLORS}
|
||||
labels={answers.map((answer) => answer.text)}
|
||||
Tooltip={ChoiceTooltip}
|
||||
pct
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -2,12 +2,16 @@ import { useMemo, useRef } from 'react'
|
|||
import { range } from 'lodash'
|
||||
import { scaleLinear } from 'd3-scale'
|
||||
|
||||
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 { MARGIN_X, MARGIN_Y } from '../helpers'
|
||||
import { SingleValueDistributionChart } from '../generic-charts'
|
||||
import { MARGIN_X, MARGIN_Y, formatPct } from '../helpers'
|
||||
import {
|
||||
SingleValueDistributionChart,
|
||||
SingleValueDistributionTooltipProps,
|
||||
} from '../generic-charts'
|
||||
import { useElementWidth } from 'web/hooks/use-element-width'
|
||||
|
||||
const getNumericChartData = (contract: NumericContract) => {
|
||||
|
@ -20,6 +24,15 @@ const getNumericChartData = (contract: NumericContract) => {
|
|||
}))
|
||||
}
|
||||
|
||||
const NumericChartTooltip = (props: SingleValueDistributionTooltipProps) => {
|
||||
const { x, y } = props
|
||||
return (
|
||||
<span className="text-sm">
|
||||
<strong>{formatPct(y, 2)}</strong> {formatLargeNumber(x)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export const NumericContractChart = (props: {
|
||||
contract: NumericContract
|
||||
height?: number
|
||||
|
@ -44,6 +57,7 @@ export const NumericContractChart = (props: {
|
|||
yScale={yScale}
|
||||
data={data}
|
||||
color={NUMERIC_GRAPH_COLOR}
|
||||
Tooltip={NumericChartTooltip}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { scaleTime, scaleLog, scaleLinear } from 'd3-scale'
|
|||
|
||||
import { Bet } from 'common/bet'
|
||||
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'
|
||||
|
@ -13,8 +14,12 @@ import {
|
|||
MAX_DATE,
|
||||
getDateRange,
|
||||
getRightmostVisibleDate,
|
||||
formatDateInRange,
|
||||
} from '../helpers'
|
||||
import { SingleValueHistoryChart } from '../generic-charts'
|
||||
import {
|
||||
SingleValueHistoryChart,
|
||||
SingleValueHistoryTooltipProps,
|
||||
} from '../generic-charts'
|
||||
import { useElementWidth } from 'web/hooks/use-element-width'
|
||||
|
||||
// mqp: note that we have an idiosyncratic version of 'log scale'
|
||||
|
@ -36,6 +41,16 @@ const getBetPoints = (bets: Bet[], scaleP: (p: number) => number) => {
|
|||
}))
|
||||
}
|
||||
|
||||
const PseudoNumericChartTooltip = (props: SingleValueHistoryTooltipProps) => {
|
||||
const { x, y, xScale } = props
|
||||
const [start, end] = xScale.domain()
|
||||
return (
|
||||
<span className="text-sm">
|
||||
<strong>{formatLargeNumber(y)}</strong> {formatDateInRange(x, start, end)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export const PseudoNumericContractChart = (props: {
|
||||
contract: PseudoNumericContract
|
||||
bets: Bet[]
|
||||
|
@ -84,6 +99,7 @@ export const PseudoNumericContractChart = (props: {
|
|||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
data={data}
|
||||
Tooltip={PseudoNumericChartTooltip}
|
||||
color={NUMERIC_GRAPH_COLOR}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -11,69 +11,26 @@ import {
|
|||
stackOrderReverse,
|
||||
SeriesPoint,
|
||||
} from 'd3-shape'
|
||||
import { range, sortBy } from 'lodash'
|
||||
import dayjs from 'dayjs'
|
||||
import { range } from 'lodash'
|
||||
|
||||
import {
|
||||
SVGChart,
|
||||
AreaPath,
|
||||
AreaWithTopStroke,
|
||||
ChartTooltip,
|
||||
TooltipContent,
|
||||
TooltipContainer,
|
||||
TooltipPosition,
|
||||
formatPct,
|
||||
} from './helpers'
|
||||
import { formatLargeNumber } from 'common/util/format'
|
||||
import { useEvent } from 'web/hooks/use-event'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
|
||||
export type MultiPoint<T = unknown> = { x: Date; y: number[]; datum?: T }
|
||||
export type HistoryPoint<T = unknown> = { x: Date; y: number; datum?: T }
|
||||
export type DistributionPoint<T = unknown> = { x: number; y: number; datum?: T }
|
||||
export type MultiPoint<T = never> = { x: Date; y: number[]; datum?: T }
|
||||
export type HistoryPoint<T = never> = { x: Date; y: number; datum?: T }
|
||||
export type DistributionPoint<T = never> = { x: number; y: number; datum?: T }
|
||||
|
||||
type PositionValue<P> = TooltipPosition & { p: P }
|
||||
|
||||
const formatPct = (n: number, digits?: number) => {
|
||||
return `${(n * 100).toFixed(digits ?? 0)}%`
|
||||
}
|
||||
|
||||
const formatDate = (
|
||||
date: Date,
|
||||
opts: { includeYear: boolean; includeHour: boolean; includeMinute: boolean }
|
||||
) => {
|
||||
const { includeYear, includeHour, includeMinute } = opts
|
||||
const d = dayjs(date)
|
||||
const now = Date.now()
|
||||
if (
|
||||
d.add(1, 'minute').isAfter(now) &&
|
||||
d.subtract(1, 'minute').isBefore(now)
|
||||
) {
|
||||
return 'Now'
|
||||
} else {
|
||||
const dayName = d.isSame(now, 'day')
|
||||
? 'Today'
|
||||
: d.add(1, 'day').isSame(now, 'day')
|
||||
? 'Yesterday'
|
||||
: null
|
||||
let format = dayName ? `[${dayName}]` : 'MMM D'
|
||||
if (includeMinute) {
|
||||
format += ', h:mma'
|
||||
} else if (includeHour) {
|
||||
format += ', ha'
|
||||
} else if (includeYear) {
|
||||
format += ', YYYY'
|
||||
}
|
||||
return d.format(format)
|
||||
}
|
||||
}
|
||||
|
||||
const getFormatterForDateRange = (start: Date, end: Date) => {
|
||||
const opts = {
|
||||
includeYear: !dayjs(start).isSame(end, 'year'),
|
||||
includeHour: dayjs(start).add(8, 'day').isAfter(end),
|
||||
includeMinute: dayjs(end).diff(start, 'hours') < 2,
|
||||
}
|
||||
return (d: Date) => formatDate(d, opts)
|
||||
}
|
||||
|
||||
const getTickValues = (min: number, max: number, n: number) => {
|
||||
const step = (max - min) / (n - 1)
|
||||
return [min, ...range(1, n - 1).map((i) => min + step * i), max]
|
||||
|
@ -81,7 +38,7 @@ const getTickValues = (min: number, max: number, n: number) => {
|
|||
|
||||
type LegendItem = { color: string; label: string; value?: string }
|
||||
|
||||
const Legend = (props: { className?: string; items: LegendItem[] }) => {
|
||||
export const Legend = (props: { className?: string; items: LegendItem[] }) => {
|
||||
const { items, className } = props
|
||||
return (
|
||||
<ol className={className}>
|
||||
|
@ -101,18 +58,17 @@ const Legend = (props: { className?: string; items: LegendItem[] }) => {
|
|||
)
|
||||
}
|
||||
|
||||
export const SingleValueDistributionChart = <T = unknown,>(props: {
|
||||
export const SingleValueDistributionChart = <T,>(props: {
|
||||
data: DistributionPoint<T>[]
|
||||
w: number
|
||||
h: number
|
||||
color: string
|
||||
xScale: ScaleContinuousNumeric<number, number>
|
||||
yScale: ScaleContinuousNumeric<number, number>
|
||||
Tooltip?: TooltipContent<SingleValueDistributionTooltipProps<T>>
|
||||
}) => {
|
||||
const { color, data, yScale, w, h } = props
|
||||
const { color, data, yScale, w, h, Tooltip } = props
|
||||
|
||||
// note that we have to type this funkily in order to succesfully store
|
||||
// a function inside of useState
|
||||
const [viewXScale, setViewXScale] =
|
||||
useState<ScaleContinuousNumeric<number, number>>()
|
||||
const [mouseState, setMouseState] =
|
||||
|
@ -124,12 +80,10 @@ export const SingleValueDistributionChart = <T = unknown,>(props: {
|
|||
const py1 = useCallback((p: DistributionPoint<T>) => yScale(p.y), [yScale])
|
||||
const xBisector = bisector((p: DistributionPoint<T>) => p.x)
|
||||
|
||||
const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => {
|
||||
const fmtX = (n: number) => formatLargeNumber(n)
|
||||
const fmtY = (n: number) => formatPct(n, 2)
|
||||
const { xAxis, yAxis } = useMemo(() => {
|
||||
const xAxis = axisBottom<number>(xScale).ticks(w / 100)
|
||||
const yAxis = axisLeft<number>(yScale).tickFormat(fmtY)
|
||||
return { fmtX, fmtY, xAxis, yAxis }
|
||||
const yAxis = axisLeft<number>(yScale).tickFormat((n) => formatPct(n, 2))
|
||||
return { xAxis, yAxis }
|
||||
}, [w, xScale, yScale])
|
||||
|
||||
const onSelect = useEvent((ev: D3BrushEvent<DistributionPoint<T>>) => {
|
||||
|
@ -166,10 +120,10 @@ export const SingleValueDistributionChart = <T = unknown,>(props: {
|
|||
|
||||
return (
|
||||
<div className="relative">
|
||||
{mouseState && (
|
||||
<ChartTooltip className="text-sm" {...mouseState}>
|
||||
<strong>{fmtY(mouseState.p.y)}</strong> {fmtX(mouseState.p.x)}
|
||||
</ChartTooltip>
|
||||
{mouseState && Tooltip && (
|
||||
<TooltipContainer className="text-sm" {...mouseState}>
|
||||
<Tooltip xScale={xScale} {...mouseState.p} />
|
||||
</TooltipContainer>
|
||||
)}
|
||||
<SVGChart
|
||||
w={w}
|
||||
|
@ -193,7 +147,14 @@ export const SingleValueDistributionChart = <T = unknown,>(props: {
|
|||
)
|
||||
}
|
||||
|
||||
export const MultiValueHistoryChart = <T = unknown,>(props: {
|
||||
export type SingleValueDistributionTooltipProps<T = unknown> =
|
||||
DistributionPoint<T> & {
|
||||
xScale: React.ComponentProps<
|
||||
typeof SingleValueDistributionChart<T>
|
||||
>['xScale']
|
||||
}
|
||||
|
||||
export const MultiValueHistoryChart = <T,>(props: {
|
||||
data: MultiPoint<T>[]
|
||||
w: number
|
||||
h: number
|
||||
|
@ -201,9 +162,10 @@ export const MultiValueHistoryChart = <T = unknown,>(props: {
|
|||
colors: readonly string[]
|
||||
xScale: ScaleTime<number, number>
|
||||
yScale: ScaleContinuousNumeric<number, number>
|
||||
Tooltip?: TooltipContent<MultiValueHistoryTooltipProps<T>>
|
||||
pct?: boolean
|
||||
}) => {
|
||||
const { colors, data, yScale, labels, w, h, pct } = props
|
||||
const { colors, data, yScale, labels, w, h, Tooltip, pct } = props
|
||||
|
||||
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||
const [mouseState, setMouseState] = useState<PositionValue<MultiPoint<T>>>()
|
||||
|
@ -215,19 +177,15 @@ export const MultiValueHistoryChart = <T = unknown,>(props: {
|
|||
const py1 = useCallback((p: SP) => yScale(p[1]), [yScale])
|
||||
const xBisector = bisector((p: MultiPoint<T>) => p.x)
|
||||
|
||||
const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => {
|
||||
const [start, end] = xScale.domain()
|
||||
const fmtX = getFormatterForDateRange(start, end)
|
||||
const fmtY = (n: number) => (pct ? formatPct(n, 0) : formatLargeNumber(n))
|
||||
|
||||
const { xAxis, yAxis } = useMemo(() => {
|
||||
const [min, max] = yScale.domain()
|
||||
const pctTickValues = getTickValues(min, max, h < 200 ? 3 : 5)
|
||||
const xAxis = axisBottom<Date>(xScale).ticks(w / 100)
|
||||
const yAxis = pct
|
||||
? axisLeft<number>(yScale).tickValues(pctTickValues).tickFormat(fmtY)
|
||||
? axisLeft<number>(yScale).tickValues(pctTickValues).tickFormat(formatPct)
|
||||
: axisLeft<number>(yScale)
|
||||
|
||||
return { fmtX, fmtY, xAxis, yAxis }
|
||||
return { xAxis, yAxis }
|
||||
}, [w, h, pct, xScale, yScale])
|
||||
|
||||
const series = useMemo(() => {
|
||||
|
@ -270,24 +228,12 @@ export const MultiValueHistoryChart = <T = unknown,>(props: {
|
|||
setMouseState(undefined)
|
||||
})
|
||||
|
||||
const mouseProbs = mouseState?.p.y ?? []
|
||||
const legendItems = sortBy(
|
||||
mouseProbs.map((p, i) => ({
|
||||
color: colors[i],
|
||||
label: labels[i],
|
||||
value: fmtY(p),
|
||||
p,
|
||||
})),
|
||||
(item) => -item.p
|
||||
).slice(0, 10)
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{mouseState && (
|
||||
<ChartTooltip {...mouseState}>
|
||||
{fmtX(mouseState.p.x)}
|
||||
<Legend className="max-w-xs text-sm" items={legendItems} />
|
||||
</ChartTooltip>
|
||||
{mouseState && Tooltip && (
|
||||
<TooltipContainer top={mouseState.top} left={mouseState.left}>
|
||||
<Tooltip xScale={xScale} {...mouseState.p} />
|
||||
</TooltipContainer>
|
||||
)}
|
||||
<SVGChart
|
||||
w={w}
|
||||
|
@ -314,16 +260,21 @@ export const MultiValueHistoryChart = <T = unknown,>(props: {
|
|||
)
|
||||
}
|
||||
|
||||
export const SingleValueHistoryChart = <T = unknown,>(props: {
|
||||
export type MultiValueHistoryTooltipProps<T = unknown> = MultiPoint<T> & {
|
||||
xScale: React.ComponentProps<typeof MultiValueHistoryChart<T>>['xScale']
|
||||
}
|
||||
|
||||
export const SingleValueHistoryChart = <T,>(props: {
|
||||
data: HistoryPoint<T>[]
|
||||
w: number
|
||||
h: number
|
||||
color: string
|
||||
xScale: ScaleTime<number, number>
|
||||
yScale: ScaleContinuousNumeric<number, number>
|
||||
Tooltip?: TooltipContent<SingleValueHistoryTooltipProps<T>>
|
||||
pct?: boolean
|
||||
}) => {
|
||||
const { color, data, pct, yScale, w, h } = props
|
||||
const { color, data, pct, yScale, w, h, Tooltip } = props
|
||||
|
||||
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||
const [mouseState, setMouseState] = useState<PositionValue<HistoryPoint<T>>>()
|
||||
|
@ -334,18 +285,14 @@ export const SingleValueHistoryChart = <T = unknown,>(props: {
|
|||
const py1 = useCallback((p: HistoryPoint<T>) => yScale(p.y), [yScale])
|
||||
const xBisector = bisector((p: HistoryPoint<T>) => p.x)
|
||||
|
||||
const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => {
|
||||
const [start, end] = xScale.domain()
|
||||
const fmtX = getFormatterForDateRange(start, end)
|
||||
const fmtY = (n: number) => (pct ? formatPct(n, 0) : formatLargeNumber(n))
|
||||
|
||||
const { xAxis, yAxis } = useMemo(() => {
|
||||
const [min, max] = yScale.domain()
|
||||
const pctTickValues = getTickValues(min, max, h < 200 ? 3 : 5)
|
||||
const xAxis = axisBottom<Date>(xScale).ticks(w / 100)
|
||||
const yAxis = pct
|
||||
? axisLeft<number>(yScale).tickValues(pctTickValues).tickFormat(fmtY)
|
||||
? axisLeft<number>(yScale).tickValues(pctTickValues).tickFormat(formatPct)
|
||||
: axisLeft<number>(yScale)
|
||||
return { fmtX, fmtY, xAxis, yAxis }
|
||||
return { xAxis, yAxis }
|
||||
}, [w, h, pct, xScale, yScale])
|
||||
|
||||
const onSelect = useEvent((ev: D3BrushEvent<HistoryPoint<T>>) => {
|
||||
|
@ -382,10 +329,10 @@ export const SingleValueHistoryChart = <T = unknown,>(props: {
|
|||
|
||||
return (
|
||||
<div className="relative">
|
||||
{mouseState && (
|
||||
<ChartTooltip className="text-sm" {...mouseState}>
|
||||
<strong>{fmtY(mouseState.p.y)}</strong> {fmtX(mouseState.p.x)}{' '}
|
||||
</ChartTooltip>
|
||||
{mouseState && Tooltip && (
|
||||
<TooltipContainer top={mouseState.top} left={mouseState.left}>
|
||||
<Tooltip xScale={xScale} {...mouseState.p} />
|
||||
</TooltipContainer>
|
||||
)}
|
||||
<SVGChart
|
||||
w={w}
|
||||
|
@ -408,3 +355,7 @@ export const SingleValueHistoryChart = <T = unknown,>(props: {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export type SingleValueHistoryTooltipProps<T = unknown> = HistoryPoint<T> & {
|
||||
xScale: React.ComponentProps<typeof SingleValueHistoryChart<T>>['xScale']
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Axis } from 'd3-axis'
|
|||
import { brushX, D3BrushEvent } from 'd3-brush'
|
||||
import { area, line, curveStepAfter, CurveFactory } from 'd3-shape'
|
||||
import { nanoid } from 'nanoid'
|
||||
import dayjs from 'dayjs'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { Contract } from 'common/contract'
|
||||
|
@ -182,7 +183,7 @@ export const SVGChart = <X, Y>(props: {
|
|||
|
||||
export type TooltipPosition = { top: number; left: number }
|
||||
|
||||
export const ChartTooltip = (
|
||||
export const TooltipContainer = (
|
||||
props: TooltipPosition & { className?: string; children: React.ReactNode }
|
||||
) => {
|
||||
const { top, left, className, children } = props
|
||||
|
@ -199,6 +200,8 @@ export const ChartTooltip = (
|
|||
)
|
||||
}
|
||||
|
||||
export type TooltipContent<P> = React.ComponentType<P>
|
||||
|
||||
export const getDateRange = (contract: Contract) => {
|
||||
const { createdTime, closeTime, resolutionTime } = contract
|
||||
const isClosed = !!closeTime && Date.now() > closeTime
|
||||
|
@ -220,3 +223,46 @@ export const getRightmostVisibleDate = (
|
|||
return now
|
||||
}
|
||||
}
|
||||
|
||||
export const formatPct = (n: number, digits?: number) => {
|
||||
return `${(n * 100).toFixed(digits ?? 0)}%`
|
||||
}
|
||||
|
||||
export const formatDate = (
|
||||
date: Date,
|
||||
opts: { includeYear: boolean; includeHour: boolean; includeMinute: boolean }
|
||||
) => {
|
||||
const { includeYear, includeHour, includeMinute } = opts
|
||||
const d = dayjs(date)
|
||||
const now = Date.now()
|
||||
if (
|
||||
d.add(1, 'minute').isAfter(now) &&
|
||||
d.subtract(1, 'minute').isBefore(now)
|
||||
) {
|
||||
return 'Now'
|
||||
} else {
|
||||
const dayName = d.isSame(now, 'day')
|
||||
? 'Today'
|
||||
: d.add(1, 'day').isSame(now, 'day')
|
||||
? 'Yesterday'
|
||||
: null
|
||||
let format = dayName ? `[${dayName}]` : 'MMM D'
|
||||
if (includeMinute) {
|
||||
format += ', h:mma'
|
||||
} else if (includeHour) {
|
||||
format += ', ha'
|
||||
} else if (includeYear) {
|
||||
format += ', YYYY'
|
||||
}
|
||||
return d.format(format)
|
||||
}
|
||||
}
|
||||
|
||||
export const formatDateInRange = (d: Date, start: Date, end: Date) => {
|
||||
const opts = {
|
||||
includeYear: !dayjs(start).isSame(end, 'year'),
|
||||
includeHour: dayjs(start).add(8, 'day').isAfter(end),
|
||||
includeMinute: dayjs(end).diff(start, 'hours') < 2,
|
||||
}
|
||||
return formatDate(d, opts)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user