From 31c6cb7739f2f149dc7a57201c636cda50bc97d6 Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Tue, 4 Oct 2022 01:18:22 -0700 Subject: [PATCH] Rewrite portfolio graphs with new machinery (#986) * Fix chart `onMouseOver` propagation * Make generic charts support money on y-axis * Fix somewhat ridiculous `formatMoney` to work with negative amounts * Make margins on charts configurable * Implement color as function of point on SingleValueHistoryChart * Rewrite portfolio history graphs with new graph machinery * Toast Nivo --- common/util/format.ts | 4 +- web/components/charts/contract/binary.tsx | 9 +- web/components/charts/contract/choice.tsx | 9 +- web/components/charts/contract/numeric.tsx | 7 +- .../charts/contract/pseudo-numeric.tsx | 7 +- web/components/charts/generic-charts.tsx | 103 ++++++-- web/components/charts/helpers.tsx | 72 ++++-- web/components/charts/stats.tsx | 9 +- .../portfolio/portfolio-value-graph.tsx | 215 ++++++---------- .../portfolio/portfolio-value-section.tsx | 95 +++---- web/package.json | 3 - yarn.lock | 239 ------------------ 12 files changed, 270 insertions(+), 502 deletions(-) diff --git a/common/util/format.ts b/common/util/format.ts index ee59d3e7..fd3e7551 100644 --- a/common/util/format.ts +++ b/common/util/format.ts @@ -13,7 +13,9 @@ export function formatMoney(amount: number) { Math.round(amount) === 0 ? 0 : // Handle 499.9999999999999 case - Math.floor(amount + 0.00000000001 * Math.sign(amount)) + (amount > 0 ? Math.floor : Math.ceil)( + amount + 0.00000000001 * Math.sign(amount) + ) return ENV_CONFIG.moneyMoniker + formatter.format(newAmount).replace('$', '') } diff --git a/web/components/charts/contract/binary.tsx b/web/components/charts/contract/binary.tsx index c9b3bb0b..b7dc9e18 100644 --- a/web/components/charts/contract/binary.tsx +++ b/web/components/charts/contract/binary.tsx @@ -9,8 +9,6 @@ import { BinaryContract } from 'common/contract' import { DAY_MS } from 'common/util/time' import { TooltipProps, - MARGIN_X, - MARGIN_Y, getDateRange, getRightmostVisibleDate, formatDateInRange, @@ -20,6 +18,10 @@ import { HistoryPoint, SingleValueHistoryChart } from '../generic-charts' import { Row } from 'web/components/layout/row' import { Avatar } from 'web/components/avatar' +const MARGIN = { top: 20, right: 10, bottom: 20, left: 40 } +const MARGIN_X = MARGIN.left + MARGIN.right +const MARGIN_Y = MARGIN.top + MARGIN.bottom + const getBetPoints = (bets: Bet[]) => { return sortBy(bets, (b) => b.createdTime).map((b) => ({ x: new Date(b.createdTime), @@ -73,14 +75,15 @@ export const BinaryContractChart = (props: { ) } diff --git a/web/components/charts/contract/choice.tsx b/web/components/charts/contract/choice.tsx index 99e02fa8..513e020e 100644 --- a/web/components/charts/contract/choice.tsx +++ b/web/components/charts/contract/choice.tsx @@ -10,8 +10,6 @@ import { getOutcomeProbability } from 'common/calculate' import { DAY_MS } from 'common/util/time' import { TooltipProps, - MARGIN_X, - MARGIN_Y, getDateRange, getRightmostVisibleDate, formatPct, @@ -79,6 +77,10 @@ const CATEGORY_COLORS = [ '#70a560', ] +const MARGIN = { top: 20, right: 10, bottom: 20, left: 40 } +const MARGIN_X = MARGIN.left + MARGIN.right +const MARGIN_Y = MARGIN.top + MARGIN.bottom + const getTrackedAnswers = ( contract: FreeResponseContract | MultipleChoiceContract, topN: number @@ -211,14 +213,15 @@ export const ChoiceContractChart = (props: { ) } diff --git a/web/components/charts/contract/numeric.tsx b/web/components/charts/contract/numeric.tsx index 2d62cb11..f634b56d 100644 --- a/web/components/charts/contract/numeric.tsx +++ b/web/components/charts/contract/numeric.tsx @@ -6,9 +6,13 @@ 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 { TooltipProps, MARGIN_X, MARGIN_Y, formatPct } from '../helpers' +import { TooltipProps, formatPct } from '../helpers' import { DistributionPoint, DistributionChart } from '../generic-charts' +const MARGIN = { top: 20, right: 10, bottom: 20, left: 40 } +const MARGIN_X = MARGIN.left + MARGIN.right +const MARGIN_Y = MARGIN.top + MARGIN.bottom + const getNumericChartData = (contract: NumericContract) => { const { totalShares, bucketCount, min, max } = contract const step = (max - min) / bucketCount @@ -48,6 +52,7 @@ export const NumericContractChart = (props: { = Point export type HistoryPoint = Point export type DistributionPoint = Point +export type ValueKind = 'm$' | 'percent' | 'amount' const getTickValues = (min: number, max: number, n: number) => { const step = (max - min) / (n - 1) @@ -50,13 +55,14 @@ export const DistributionChart =

(props: { w: number h: number color: string + margin: Margin xScale: ScaleContinuousNumeric yScale: ScaleContinuousNumeric curve?: CurveFactory onMouseOver?: (p: P | undefined) => void Tooltip?: TooltipComponent }) => { - const { color, data, yScale, w, h, curve, Tooltip } = props + const { data, w, h, color, margin, yScale, curve, Tooltip } = props const [viewXScale, setViewXScale] = useState>() @@ -72,7 +78,12 @@ export const DistributionChart =

(props: { return { xAxis, yAxis } }, [w, xScale, yScale]) - const onMouseOver = useEvent(betAtPointSelector(data, xScale)) + const selector = betAtPointSelector(data, xScale) + const onMouseOver = useEvent((mouseX: number) => { + const p = selector(mouseX) + props.onMouseOver?.(p) + return p + }) const onSelect = useEvent((ev: D3BrushEvent

) => { if (ev.selection) { @@ -89,6 +100,7 @@ export const DistributionChart =

(props: { (props: { w: number h: number colors: readonly string[] + margin: Margin xScale: ScaleTime yScale: ScaleContinuousNumeric + yKind?: ValueKind curve?: CurveFactory onMouseOver?: (p: P | undefined) => void Tooltip?: TooltipComponent - pct?: boolean }) => { - const { colors, data, yScale, w, h, curve, Tooltip, pct } = props + const { data, w, h, colors, margin, yScale, yKind, curve, Tooltip } = props const [viewXScale, setViewXScale] = useState>() const xScale = viewXScale ?? props.xScale @@ -131,25 +144,36 @@ export const MultiValueHistoryChart =

(props: { const { xAxis, yAxis } = useMemo(() => { const [min, max] = yScale.domain() - const pctTickValues = getTickValues(min, max, h < 200 ? 3 : 5) + const nTicks = h < 200 ? 3 : 5 + const pctTickValues = getTickValues(min, max, nTicks) const xAxis = axisBottom(xScale).ticks(w / 100) - const yAxis = pct - ? axisLeft(yScale) - .tickValues(pctTickValues) - .tickFormat((n) => formatPct(n)) - : axisLeft(yScale) + const yAxis = + yKind === 'percent' + ? axisLeft(yScale) + .tickValues(pctTickValues) + .tickFormat((n) => formatPct(n)) + : yKind === 'm$' + ? axisLeft(yScale) + .ticks(nTicks) + .tickFormat((n) => formatMoney(n)) + : axisLeft(yScale).ticks(nTicks) return { xAxis, yAxis } - }, [w, h, pct, xScale, yScale]) + }, [w, h, yKind, xScale, yScale]) const series = useMemo(() => { const d3Stack = stack() .keys(range(0, Math.max(...data.map(({ y }) => y.length)))) - .value(({ y }, o) => y[o]) + .value(({ y }, k) => y[k]) .order(stackOrderReverse) return d3Stack(data) }, [data]) - const onMouseOver = useEvent(betAtPointSelector(data, xScale)) + const selector = betAtPointSelector(data, xScale) + const onMouseOver = useEvent((mouseX: number) => { + const p = selector(mouseX) + props.onMouseOver?.(p) + return p + }) const onSelect = useEvent((ev: D3BrushEvent

) => { if (ev.selection) { @@ -166,6 +190,7 @@ export const MultiValueHistoryChart =

(props: { (props: { data: P[] w: number h: number - color: string + color: string | ((p: P) => string) + margin: Margin xScale: ScaleTime yScale: ScaleContinuousNumeric + yKind?: ValueKind curve?: CurveFactory onMouseOver?: (p: P | undefined) => void Tooltip?: TooltipComponent pct?: boolean }) => { - const { color, data, yScale, w, h, curve, Tooltip, pct } = props + const { data, w, h, color, margin, yScale, yKind, curve, Tooltip } = props const [viewXScale, setViewXScale] = useState>() const xScale = viewXScale ?? props.xScale @@ -210,17 +237,28 @@ export const SingleValueHistoryChart =

(props: { const { xAxis, yAxis } = useMemo(() => { const [min, max] = yScale.domain() - const pctTickValues = getTickValues(min, max, h < 200 ? 3 : 5) + const nTicks = h < 200 ? 3 : 5 + const pctTickValues = getTickValues(min, max, nTicks) const xAxis = axisBottom(xScale).ticks(w / 100) - const yAxis = pct - ? axisLeft(yScale) - .tickValues(pctTickValues) - .tickFormat((n) => formatPct(n)) - : axisLeft(yScale) + const yAxis = + yKind === 'percent' + ? axisLeft(yScale) + .tickValues(pctTickValues) + .tickFormat((n) => formatPct(n)) + : yKind === 'm$' + ? axisLeft(yScale) + .ticks(nTicks) + .tickFormat((n) => formatMoney(n)) + : axisLeft(yScale).ticks(nTicks) return { xAxis, yAxis } - }, [w, h, pct, xScale, yScale]) + }, [w, h, yKind, xScale, yScale]) - const onMouseOver = useEvent(betAtPointSelector(data, xScale)) + const selector = betAtPointSelector(data, xScale) + const onMouseOver = useEvent((mouseX: number) => { + const p = selector(mouseX) + props.onMouseOver?.(p) + return p + }) const onSelect = useEvent((ev: D3BrushEvent

) => { if (ev.selection) { @@ -233,18 +271,35 @@ export const SingleValueHistoryChart =

(props: { } }) + const gradientId = useMemo(() => nanoid(), []) + const stops = useMemo( + () => + typeof color !== 'string' ? computeColorStops(data, color, px) : null, + [color, data, px] + ) + return ( + {stops && ( + + + {stops.map((s, i) => ( + + ))} + + + )} extends AxisScale { export type XScale

= P extends Point ? AxisScale : never export type YScale

= P extends Point ? AxisScale : never -export const MARGIN = { top: 20, right: 10, bottom: 20, left: 40 } -export const MARGIN_X = MARGIN.right + MARGIN.left -export const MARGIN_Y = MARGIN.top + MARGIN.bottom -const MARGIN_STYLE = `${MARGIN.top}px ${MARGIN.right}px ${MARGIN.bottom}px ${MARGIN.left}px` -const MARGIN_XFORM = `translate(${MARGIN.left}, ${MARGIN.top})` +export type Margin = { + top: number + right: number + bottom: number + left: number +} export const XAxis = (props: { w: number; h: number; axis: Axis }) => { const { h, axis } = props @@ -55,8 +56,6 @@ export const YAxis = (props: { w: number; h: number; axis: Axis }) => { useEffect(() => { if (axisRef.current != null) { select(axisRef.current) - .transition() - .duration(250) .call(axis) .call((g) => g.selectAll('.tick line').attr('x2', w).attr('stroke-opacity', 0.1) @@ -100,14 +99,14 @@ const AreaPathInternal = ( export const AreaPath = memo(AreaPathInternal) as typeof AreaPathInternal export const AreaWithTopStroke = (props: { - color: string data: P[] + color: string px: number | ((p: P) => number) py0: number | ((p: P) => number) py1: number | ((p: P) => number) curve: CurveFactory }) => { - const { color, data, px, py0, py1, curve } = props + const { data, color, px, py0, py1, curve } = props return ( (props: { children: ReactNode w: number h: number + margin: Margin xAxis: Axis yAxis: Axis onSelect?: (ev: D3BrushEvent) => void onMouseOver?: (mouseX: number, mouseY: number) => TT | undefined Tooltip?: TooltipComponent }) => { - const { children, w, h, xAxis, yAxis, onMouseOver, onSelect, Tooltip } = props + const { + children, + w, + h, + margin, + xAxis, + yAxis, + onMouseOver, + onSelect, + Tooltip, + } = props const [mouse, setMouse] = useState<{ x: number; y: number; data: TT }>() const tooltipMeasure = useMeasureSize() const overlayRef = useRef(null) - const innerW = w - MARGIN_X - const innerH = h - MARGIN_Y + const innerW = w - (margin.left + margin.right) + const innerH = h - (margin.top + margin.bottom) const clipPathId = useMemo(() => nanoid(), []) const justSelected = useRef(false) @@ -194,6 +204,7 @@ export const SVGChart = (props: { {mouse && Tooltip && ( (props: { - + {children} @@ -275,10 +286,11 @@ export type TooltipComponent = React.ComponentType> export const TooltipContainer = (props: { setElem: (e: HTMLElement | null) => void pos: TooltipPosition + margin: Margin className?: string children: React.ReactNode }) => { - const { setElem, pos, className, children } = props + const { setElem, pos, margin, className, children } = props return (

{children}
) } +export const computeColorStops = ( + data: P[], + pc: (p: P) => string, + px: (p: P) => number +) => { + const segments: { x: number; color: string }[] = [] + let currOffset = 0 + let currColor = pc(data[0]) + for (const p of data) { + const c = pc(p) + if (c !== currColor) { + segments.push({ x: currOffset, color: currColor }) + currOffset = px(p) + currColor = c + } + } + segments.push({ x: currOffset, color: currColor }) + + const stops: { x: number; color: string }[] = [] + stops.push({ x: segments[0].x, color: segments[0].color }) + for (const s of segments.slice(1)) { + stops.push({ x: s.x, color: stops[stops.length - 1].color }) + stops.push({ x: s.x, color: s.color }) + } + return stops +} + export const getDateRange = (contract: Contract) => { const { createdTime, closeTime, resolutionTime } = contract const isClosed = !!closeTime && Date.now() > closeTime diff --git a/web/components/charts/stats.tsx b/web/components/charts/stats.tsx index a630657a..eb3999d8 100644 --- a/web/components/charts/stats.tsx +++ b/web/components/charts/stats.tsx @@ -6,9 +6,13 @@ 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 { TooltipProps } from './helpers' import { SizedContainer } from 'web/components/sized-container' +const MARGIN = { top: 20, right: 10, bottom: 20, left: 40 } +const MARGIN_X = MARGIN.left + MARGIN.right +const MARGIN_Y = MARGIN.top + MARGIN.bottom + const getPoints = (startDate: number, dailyValues: number[]) => { const startDateDayJs = dayjs(startDate) return dailyValues.map((y, i) => ({ @@ -63,12 +67,13 @@ export function DailyChart(props: { )} diff --git a/web/components/portfolio/portfolio-value-graph.tsx b/web/components/portfolio/portfolio-value-graph.tsx index e329457d..62692ae5 100644 --- a/web/components/portfolio/portfolio-value-graph.tsx +++ b/web/components/portfolio/portfolio-value-graph.tsx @@ -1,155 +1,84 @@ -import { ResponsiveLine } from '@nivo/line' -import { PortfolioMetrics } from 'common/user' -import { filterDefined } from 'common/util/array' -import { formatMoney } from 'common/util/format' +import { useMemo } from 'react' +import { scaleTime, scaleLinear } from 'd3-scale' +import { curveStepAfter } from 'd3-shape' +import { min, max } from 'lodash' import dayjs from 'dayjs' -import { last } from 'lodash' -import { memo } from 'react' -import { useWindowSize } from 'web/hooks/use-window-size' +import { PortfolioMetrics } from 'common/user' import { Col } from '../layout/col' +import { TooltipProps } from 'web/components/charts/helpers' +import { + HistoryPoint, + SingleValueHistoryChart, +} from 'web/components/charts/generic-charts' -export const PortfolioValueGraph = memo(function PortfolioValueGraph(props: { - portfolioHistory: PortfolioMetrics[] - mode: 'value' | 'profit' - handleGraphDisplayChange: (arg0: string | number | null) => void - height?: number -}) { - const { portfolioHistory, height, mode, handleGraphDisplayChange } = props - const { width } = useWindowSize() +const MARGIN = { top: 20, right: 10, bottom: 20, left: 70 } +const MARGIN_X = MARGIN.left + MARGIN.right +const MARGIN_Y = MARGIN.top + MARGIN.bottom - const valuePoints = getPoints('value', portfolioHistory) - const posProfitPoints = getPoints('posProfit', portfolioHistory) - const negProfitPoints = getPoints('negProfit', portfolioHistory) +export type GraphMode = 'profit' | 'value' - const valuePointsY = valuePoints.map((p) => p.y) - const posProfitPointsY = posProfitPoints.map((p) => p.y) - const negProfitPointsY = negProfitPoints.map((p) => p.y) +export const PortfolioTooltip = (props: TooltipProps) => { + const { mouseX, xScale } = props + const d = dayjs(xScale.invert(mouseX)) + return ( + +
{d.format('MMM/D/YY')}
+
+ {d.format('h:mm A')} +
+ + ) +} - let data +const getY = (mode: GraphMode, p: PortfolioMetrics) => + p.balance + p.investmentValue - (mode === 'profit' ? p.totalDeposits : 0) - if (mode === 'value') { - data = [{ id: 'value', data: valuePoints, color: '#4f46e5' }] - } else { - data = [ - { - id: 'negProfit', - data: negProfitPoints, - color: '#dc2626', - }, - { - id: 'posProfit', - data: posProfitPoints, - color: '#14b8a6', - }, - ] - } - const numYTickValues = 2 - const endDate = last(data[0].data)?.x +export function getPoints(mode: GraphMode, history: PortfolioMetrics[]) { + return history.map((p) => ({ + x: new Date(p.timestamp), + y: getY(mode, p), + obj: p, + })) +} - const yMin = - mode === 'value' - ? Math.min(...filterDefined(valuePointsY)) - : Math.min( - ...filterDefined(negProfitPointsY), - ...filterDefined(posProfitPointsY) - ) - - const yMax = - mode === 'value' - ? Math.max(...filterDefined(valuePointsY)) - : Math.max( - ...filterDefined(negProfitPointsY), - ...filterDefined(posProfitPointsY) - ) +export const PortfolioGraph = (props: { + mode: 'profit' | 'value' + history: PortfolioMetrics[] + width: number + height: number + onMouseOver?: (p: HistoryPoint | undefined) => void +}) => { + const { mode, history, onMouseOver, width, height } = props + const { data, minDate, maxDate, minValue, maxValue } = useMemo(() => { + const data = getPoints(mode, history) + // 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 minValue = min(data.map((d) => d.y))! + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const maxValue = max(data.map((d) => d.y))! + return { data, minDate, maxDate, minValue, maxValue } + }, [mode, history]) return ( -
= 800 ? 200 : 100) }} - onMouseLeave={() => handleGraphDisplayChange(null)} - > - 100 ? 0 : 6} - axisLeft={{ - tickValues: numYTickValues, - format: '.3s', - }} - enableGridX={false} - enableGridY={true} - gridYValues={numYTickValues} - enableSlices="x" - animate={false} - yFormat={(value) => formatMoney(+value)} - enableArea={true} - areaOpacity={0.1} - sliceTooltip={({ slice }) => { - handleGraphDisplayChange(slice.points[0].data.yFormatted) - return ( -
-
- -
- {dayjs(slice.points[0].data.xFormatted).format('MMM/D/YY')} -
-
- {dayjs(slice.points[0].data.xFormatted).format('h:mm A')} -
- -
- {/* ))} */} -
- ) - }} - >
-
+ (p.y >= 0 ? '#14b8a6' : '#f00') + } + /> ) -}) - -export function getPoints( - line: 'value' | 'posProfit' | 'negProfit', - portfolioHistory: PortfolioMetrics[] -) { - const points = portfolioHistory.map((p) => { - const { timestamp, balance, investmentValue, totalDeposits } = p - const value = balance + investmentValue - - const profit = value - totalDeposits - let posProfit = null - let negProfit = null - if (profit < 0) { - negProfit = profit - } else { - posProfit = profit - } - - return { - x: new Date(timestamp), - y: - line === 'value' ? value : line === 'posProfit' ? posProfit : negProfit, - } - }) - return points } diff --git a/web/components/portfolio/portfolio-value-section.tsx b/web/components/portfolio/portfolio-value-section.tsx index d7fff6ef..cc9fb197 100644 --- a/web/components/portfolio/portfolio-value-section.tsx +++ b/web/components/portfolio/portfolio-value-section.tsx @@ -1,34 +1,29 @@ import clsx from 'clsx' import { formatMoney } from 'common/util/format' import { last } from 'lodash' -import { memo, useRef, useState } from 'react' +import { memo, useState } from 'react' import { usePortfolioHistory } from 'web/hooks/use-portfolio-history' -import { Period } from 'web/lib/firebase/users' import { Col } from '../layout/col' import { Row } from '../layout/row' -import { PortfolioValueGraph } from './portfolio-value-graph' +import { GraphMode, PortfolioGraph } from './portfolio-value-graph' +import { SizedContainer } from 'web/components/sized-container' export const PortfolioValueSection = memo( function PortfolioValueSection(props: { userId: string }) { const { userId } = props - const [portfolioPeriod, setPortfolioPeriod] = useState('weekly') - const portfolioHistory = usePortfolioHistory(userId, portfolioPeriod) - const [graphMode, setGraphMode] = useState<'profit' | 'value'>('profit') + const portfolioHistory = usePortfolioHistory(userId, 'allTime') + const [graphMode, setGraphMode] = useState('profit') const [graphDisplayNumber, setGraphDisplayNumber] = useState< number | string | null >(null) - const handleGraphDisplayChange = (num: string | number | null) => { - setGraphDisplayNumber(num) + const handleGraphDisplayChange = (p: { y: number } | undefined) => { + console.log(p) + setGraphDisplayNumber(p != null ? formatMoney(p.y) : null) } - // Remember the last defined portfolio history. - const portfolioRef = useRef(portfolioHistory) - if (portfolioHistory) portfolioRef.current = portfolioHistory - const currPortfolioHistory = portfolioRef.current - - const lastPortfolioMetrics = last(currPortfolioHistory) - if (!currPortfolioHistory || !lastPortfolioMetrics) { + const lastPortfolioMetrics = last(portfolioHistory) + if (!portfolioHistory || !lastPortfolioMetrics) { return <> } @@ -46,7 +41,10 @@ export const PortfolioValueSection = memo( ? 'cursor-pointer opacity-40 hover:opacity-80' : '' )} - onClick={() => setGraphMode('profit')} + onClick={() => { + setGraphMode('profit') + setGraphDisplayNumber(null) + }} >
Profit
setGraphMode('value')} + onClick={() => { + setGraphMode('value') + setGraphDisplayNumber(null) + }} >
Portfolio value @@ -93,56 +94,18 @@ export const PortfolioValueSection = memo( - - + + {(width, height) => ( + + )} + ) } ) - -export function PortfolioPeriodSelection(props: { - setPortfolioPeriod: (string: any) => void - portfolioPeriod: string - className?: string - selectClassName?: string -}) { - const { setPortfolioPeriod, portfolioPeriod, className, selectClassName } = - props - return ( - - - - - - - ) -} diff --git a/web/package.json b/web/package.json index a5fa8ced..3fba7f9d 100644 --- a/web/package.json +++ b/web/package.json @@ -23,9 +23,6 @@ "@floating-ui/react-dom-interactions": "0.9.2", "@headlessui/react": "1.6.1", "@heroicons/react": "1.0.6", - "@nivo/core": "0.80.0", - "@nivo/line": "0.80.0", - "@nivo/tooltip": "0.80.0", "@react-query-firebase/firestore": "0.4.2", "@tiptap/core": "2.0.0-beta.182", "@tiptap/extension-character-count": "2.0.0-beta.31", diff --git a/yarn.lock b/yarn.lock index be247b89..5f0abdf4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2553,103 +2553,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.1.tgz#27d71a95247a9eaee03d47adee7e3bd594514136" integrity sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA== -"@nivo/annotations@0.80.0": - version "0.80.0" - resolved "https://registry.yarnpkg.com/@nivo/annotations/-/annotations-0.80.0.tgz#127e4801fff7370dcfb9acfe1e335781dd65cfd5" - integrity sha512-bC9z0CLjU07LULTMWsqpjovRtHxP7n8oJjqBQBLmHOGB4IfiLbrryBfu9+aEZH3VN2jXHhdpWUz+HxeZzOzsLg== - dependencies: - "@nivo/colors" "0.80.0" - "@react-spring/web" "9.4.5" - lodash "^4.17.21" - -"@nivo/axes@0.80.0": - version "0.80.0" - resolved "https://registry.yarnpkg.com/@nivo/axes/-/axes-0.80.0.tgz#22788855ddc45bb6a619dcd03d62d4bd8c0fc35f" - integrity sha512-AsUyaSHGwQVSEK8QXpsn8X+poZxvakLMYW7crKY1xTGPNw+SU4SSBohPVumm2jMH3fTSLNxLhAjWo71GBJXfdA== - dependencies: - "@nivo/scales" "0.80.0" - "@react-spring/web" "9.4.5" - d3-format "^1.4.4" - d3-time-format "^3.0.0" - -"@nivo/colors@0.80.0": - version "0.80.0" - resolved "https://registry.yarnpkg.com/@nivo/colors/-/colors-0.80.0.tgz#5b70b4979df246d9d0d69fb638bba9764dd88b52" - integrity sha512-T695Zr411FU4RPo7WDINOAn8f79DPP10SFJmDdEqELE+cbzYVTpXqLGZ7JMv88ko7EOf9qxLQgcBqY69rp9tHQ== - dependencies: - d3-color "^2.0.0" - d3-scale "^3.2.3" - d3-scale-chromatic "^2.0.0" - lodash "^4.17.21" - -"@nivo/core@0.80.0": - version "0.80.0" - resolved "https://registry.yarnpkg.com/@nivo/core/-/core-0.80.0.tgz#d180cb2622158eb7bc5f984131ff07984f12297e" - integrity sha512-6caih0RavXdWWSfde+rC2pk17WrX9YQlqK26BrxIdXzv3Ydzlh5SkrC7dR2TEvMGBhunzVeLOfiC2DWT1S8CFg== - dependencies: - "@nivo/recompose" "0.80.0" - "@react-spring/web" "9.4.5" - d3-color "^2.0.0" - d3-format "^1.4.4" - d3-interpolate "^2.0.1" - d3-scale "^3.2.3" - d3-scale-chromatic "^2.0.0" - d3-shape "^1.3.5" - d3-time-format "^3.0.0" - lodash "^4.17.21" - -"@nivo/legends@0.80.0": - version "0.80.0" - resolved "https://registry.yarnpkg.com/@nivo/legends/-/legends-0.80.0.tgz#49edc54000075b4df055f86794a8c32810269d06" - integrity sha512-h0IUIPGygpbKIZZZWIxkkxOw4SO0rqPrqDrykjaoQz4CvL4HtLIUS3YRA4akKOVNZfS5agmImjzvIe0s3RvqlQ== - -"@nivo/line@0.80.0": - version "0.80.0" - resolved "https://registry.yarnpkg.com/@nivo/line/-/line-0.80.0.tgz#ba541b0fcfd53b3a7ce865feb43c993b7cf4a7d4" - integrity sha512-6UAD/y74qq3DDRnVb+QUPvXYojxMtwXMipGSNvCGk8omv1QZNTaUrbV+eQacvn9yh//a0yZcWipnpq0tGJyJCA== - dependencies: - "@nivo/annotations" "0.80.0" - "@nivo/axes" "0.80.0" - "@nivo/colors" "0.80.0" - "@nivo/legends" "0.80.0" - "@nivo/scales" "0.80.0" - "@nivo/tooltip" "0.80.0" - "@nivo/voronoi" "0.80.0" - "@react-spring/web" "9.4.5" - d3-shape "^1.3.5" - -"@nivo/recompose@0.80.0": - version "0.80.0" - resolved "https://registry.yarnpkg.com/@nivo/recompose/-/recompose-0.80.0.tgz#572048aed793321a0bada1fd176b72df5a25282e" - integrity sha512-iL3g7j3nJGD9+mRDbwNwt/IXDXH6E29mhShY1I7SP91xrfusZV9pSFf4EzyYgruNJk/2iqMuaqn+e+TVFra44A== - dependencies: - react-lifecycles-compat "^3.0.4" - -"@nivo/scales@0.80.0": - version "0.80.0" - resolved "https://registry.yarnpkg.com/@nivo/scales/-/scales-0.80.0.tgz#39313fb97c8ae9633c2aa1e17adb57cb851e8a50" - integrity sha512-4y2pQdCg+f3n4TKXC2tYuq71veZM+xPRQbOTgGYJpuBvMc7pQsXF9T5z7ryeIG9hkpXkrlyjecU6XcAG7tLSNg== - dependencies: - d3-scale "^3.2.3" - d3-time "^1.0.11" - d3-time-format "^3.0.0" - lodash "^4.17.21" - -"@nivo/tooltip@0.80.0": - version "0.80.0" - resolved "https://registry.yarnpkg.com/@nivo/tooltip/-/tooltip-0.80.0.tgz#07ebef47eb708a0612bd6297d5ad156bbec19d34" - integrity sha512-qGmrreRwnCsYjn/LAuwBtxBn/tvG8y+rwgd4gkANLBAoXd3bzJyvmkSe+QJPhUG64bq57ibDK+lO2pC48a3/fw== - dependencies: - "@react-spring/web" "9.4.5" - -"@nivo/voronoi@0.80.0": - version "0.80.0" - resolved "https://registry.yarnpkg.com/@nivo/voronoi/-/voronoi-0.80.0.tgz#59cc7ed253dc1a5bbcf614a5ac37d2468d561599" - integrity sha512-zaJV3I3cRu1gHpsXCIEvp6GGlGY8P7D9CwAVCjYDGrz3W/+GKN0kA7qGyHTC97zVxJtfefxSPlP/GtOdxac+qw== - dependencies: - d3-delaunay "^5.3.0" - d3-scale "^3.2.3" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2744,52 +2647,6 @@ resolved "https://registry.yarnpkg.com/@react-query-firebase/firestore/-/firestore-0.4.2.tgz#6ae52768715aa0a5c0d903dd4fd953ed417ba635" integrity sha512-7eYp905+sfBRcBTdj7W7BAc3bI3V0D0kKca4/juOTnN4gyoNyaCNOCjLPY467dTq325hGs7BX0ol7Pw3JENdHA== -"@react-spring/animated@~9.4.5": - version "9.4.5" - resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.4.5.tgz#dd9921c716a4f4a3ed29491e0c0c9f8ca0eb1a54" - integrity sha512-KWqrtvJSMx6Fj9nMJkhTwM9r6LIriExDRV6YHZV9HKQsaolUFppgkOXpC+rsL1JEtEvKv6EkLLmSqHTnuYjiIA== - dependencies: - "@react-spring/shared" "~9.4.5" - "@react-spring/types" "~9.4.5" - -"@react-spring/core@~9.4.5": - version "9.4.5" - resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.4.5.tgz#4616e1adc18dd10f5731f100ebdbe9518b89ba3c" - integrity sha512-83u3FzfQmGMJFwZLAJSwF24/ZJctwUkWtyPD7KYtNagrFeQKUH1I05ZuhmCmqW+2w1KDW1SFWQ43RawqfXKiiQ== - dependencies: - "@react-spring/animated" "~9.4.5" - "@react-spring/rafz" "~9.4.5" - "@react-spring/shared" "~9.4.5" - "@react-spring/types" "~9.4.5" - -"@react-spring/rafz@~9.4.5": - version "9.4.5" - resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.4.5.tgz#84f809f287f2a66bbfbc66195db340482f886bd7" - integrity sha512-swGsutMwvnoyTRxvqhfJBtGM8Ipx6ks0RkIpNX9F/U7XmyPvBMGd3GgX/mqxZUpdlsuI1zr/jiYw+GXZxAlLcQ== - -"@react-spring/shared@~9.4.5": - version "9.4.5" - resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.4.5.tgz#4c3ad817bca547984fb1539204d752a412a6d829" - integrity sha512-JhMh3nFKsqyag0KM5IIM8BQANGscTdd0mMv3BXsUiMZrcjQTskyfnv5qxEeGWbJGGar52qr5kHuBHtCjQOzniA== - dependencies: - "@react-spring/rafz" "~9.4.5" - "@react-spring/types" "~9.4.5" - -"@react-spring/types@~9.4.5": - version "9.4.5" - resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.4.5.tgz#9c71e5ff866b5484a7ef3db822bf6c10e77bdd8c" - integrity sha512-mpRIamoHwql0ogxEUh9yr4TP0xU5CWyZxVQeccGkHHF8kPMErtDXJlxyo0lj+telRF35XNihtPTWoflqtyARmg== - -"@react-spring/web@9.4.5": - version "9.4.5" - resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.4.5.tgz#b92f05b87cdc0963a59ee149e677dcaff09f680e" - integrity sha512-NGAkOtKmOzDEctL7MzRlQGv24sRce++0xAY7KlcxmeVkR7LRSGkoXHaIfm9ObzxPMcPHQYQhf3+X9jepIFNHQA== - dependencies: - "@react-spring/animated" "~9.4.5" - "@react-spring/core" "~9.4.5" - "@react-spring/shared" "~9.4.5" - "@react-spring/types" "~9.4.5" - "@rushstack/eslint-patch@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz#6801033be7ff87a6b7cadaf5b337c9f366a3c4b0" @@ -5450,13 +5307,6 @@ csstype@^3.0.2, csstype@^3.1.0: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2" integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== -d3-array@2, d3-array@^2.3.0: - version "2.12.1" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81" - integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ== - dependencies: - internmap "^1.0.0" - "d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.0.tgz#15bf96cd9b7333e02eb8de8053d78962eafcff14" @@ -5480,23 +5330,11 @@ d3-brush@3.0.0: d3-selection "3" d3-transition "3" -"d3-color@1 - 2", d3-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e" - integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ== - "d3-color@1 - 3": version "3.1.0" resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== -d3-delaunay@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-5.3.0.tgz#b47f05c38f854a4e7b3cea80e0bb12e57398772d" - integrity sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w== - dependencies: - delaunator "4" - "d3-dispatch@1 - 3": version "3.0.1" resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" @@ -5515,28 +5353,11 @@ d3-delaunay@^5.3.0: resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== -"d3-format@1 - 2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767" - integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA== - "d3-format@1 - 3": version "3.1.0" resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== -d3-format@^1.4.4: - version "1.4.5" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4" - integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ== - -"d3-interpolate@1 - 2", "d3-interpolate@1.2.0 - 2", d3-interpolate@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163" - integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ== - dependencies: - d3-color "1 - 2" - "d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3": version "3.0.1" resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" @@ -5544,24 +5365,11 @@ d3-format@^1.4.4: dependencies: d3-color "1 - 3" -d3-path@1: - version "1.0.9" - resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" - integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== - "d3-path@1 - 3": version "3.0.1" resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e" integrity sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w== -d3-scale-chromatic@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz#c13f3af86685ff91323dc2f0ebd2dabbd72d8bab" - integrity sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA== - dependencies: - d3-color "1 - 2" - d3-interpolate "1 - 2" - d3-scale@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" @@ -5573,17 +5381,6 @@ d3-scale@4.0.2: d3-time "2.1.1 - 3" d3-time-format "2 - 4" -d3-scale@^3.2.3: - version "3.3.0" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.3.0.tgz#28c600b29f47e5b9cd2df9749c206727966203f3" - integrity sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ== - dependencies: - d3-array "^2.3.0" - d3-format "1 - 2" - d3-interpolate "1.2.0 - 2" - d3-time "^2.1.1" - d3-time-format "2 - 3" - d3-selection@3, d3-selection@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" @@ -5596,20 +5393,6 @@ d3-shape@3.1.0: dependencies: d3-path "1 - 3" -d3-shape@^1.3.5: - version "1.3.7" - resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" - integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== - dependencies: - d3-path "1" - -"d3-time-format@2 - 3", d3-time-format@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6" - integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag== - dependencies: - d3-time "1 - 2" - "d3-time-format@2 - 4": version "4.1.0" resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" @@ -5617,13 +5400,6 @@ d3-shape@^1.3.5: dependencies: d3-time "1 - 3" -"d3-time@1 - 2", d3-time@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682" - integrity sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ== - dependencies: - d3-array "2" - "d3-time@1 - 3", "d3-time@2.1.1 - 3": version "3.0.0" resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975" @@ -5631,11 +5407,6 @@ d3-shape@^1.3.5: dependencies: d3-array "2 - 3" -d3-time@^1.0.11: - version "1.1.0" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1" - integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA== - "d3-timer@1 - 3": version "3.0.1" resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" @@ -5790,11 +5561,6 @@ del@^6.0.0: rimraf "^3.0.2" slash "^3.0.0" -delaunator@4: - version "4.0.1" - resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-4.0.1.tgz#3d779687f57919a7a418f8ab947d3bddb6846957" - integrity sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag== - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -7855,11 +7621,6 @@ internal-slot@^1.0.3: resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== -internmap@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" - integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== - interpret@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"