diff --git a/src/components/Asset/AssetContent/TransactionHistoryBrushVisualization.tsx b/src/components/Asset/AssetContent/TransactionHistoryBrushVisualization.tsx new file mode 100644 index 0000000000..8cad927a39 --- /dev/null +++ b/src/components/Asset/AssetContent/TransactionHistoryBrushVisualization.tsx @@ -0,0 +1,188 @@ +// import React, { useRef, useState, useMemo } from 'react' +// import { scaleTime, scaleLinear } from '@visx/scale' +// import { Brush } from '@visx/brush' +// import { Bounds } from '@visx/brush/lib/types' +// import BaseBrush, { +// BaseBrushState, +// UpdateBrush +// } from '@visx/brush/lib/BaseBrush' +// import { PatternLines } from '@visx/pattern' +// import { Group } from '@visx/group' +// import { LinearGradient } from '@visx/gradient' +// import { max, extent } from 'd3-array' +// import { BrushHandleRenderProps } from '@visx/brush/src/BrushHandle' +// import TransactionHistoryVisualization from './TransactionHistoryVisualization' +// import { useAsset } from '@context/Asset' + +// // Initialize some variables +// const brushMargin = { top: 10, bottom: 15, left: 50, right: 20 } +// const chartSeparation = 30 +// const PATTERN_ID = 'brush_pattern' +// const GRADIENT_ID = 'brush_gradient' +// export const accentColor = '#f6acc8' +// export const background = '#584153' +// export const background2 = '#af8baf' +// const selectedBrushStyle = { +// fill: `url(#${PATTERN_ID})`, +// stroke: 'white' +// } + +// export type BrushProps = { +// width: number +// height: number +// events?: boolean +// margin?: { top: number; right: number; bottom: number; left: number } +// compact?: boolean +// } + +// function BrushChart({ +// width, +// height, +// events = false, +// margin = { +// top: 20, +// left: 50, +// bottom: 20, +// right: 50 +// }, +// compact = false +// }: BrushProps) { +// const brushRef = useRef(null) +// const { asset } = useAsset() +// const txs: TransactionHistory = asset.transactionHistory +// const orders = asset.transactionHistory?.token?.orders +// ?.slice() +// .sort((a, b) => a.createdTimestamp - b.createdTimestamp) + +// // const onBrushChange = (domain: Bounds | null) => { +// // if (!domain) return +// // const { x0, x1, y0, y1 } = domain +// // const stockCopy = stock.filter((s) => { +// // const x = getDate(s).getTime() +// // const y = getStockValue(s) +// // return x > x0 && x < x1 && y > y0 && y < y1 +// // }) +// // setFilteredStock(stockCopy) +// // } + +// const innerHeight = height - margin.top - margin.bottom +// const topChartBottomMargin = compact +// ? chartSeparation / 2 +// : chartSeparation + 10 +// const topChartHeight = 0.8 * innerHeight - topChartBottomMargin +// const bottomChartHeight = innerHeight - topChartHeight - chartSeparation + +// // bounds +// const xMax = Math.max(width - margin.left - margin.right, 0) +// const yMax = Math.max(topChartHeight, 0) +// const xBrushMax = Math.max(width - brushMargin.left - brushMargin.right, 0) +// const yBrushMax = Math.max( +// bottomChartHeight - brushMargin.top - brushMargin.bottom, +// 0 +// ) + +// // accessors +// const getDate = (d: Order) => new Date(d.createdTimestamp * 1000) +// const getY = (d: Order) => parseFloat(d.amount) + +// // scales +// const brushDateScale = useMemo( +// () => +// scaleTime({ +// range: [50, xBrushMax - 50], +// domain: [ +// new Date(orders?.length && orders[0]?.createdTimestamp * 1000), +// new Date() +// ] +// }), +// [xBrushMax, orders] +// ) +// const brushStockScale = useMemo( +// () => +// scaleLinear({ +// range: [yMax, 0], +// nice: true +// }), +// [yMax] +// ) + +// // const initialBrushPosition = useMemo( +// // () => ({ +// // start: { x: brushDateScale(getDate(txs?.token?.orders[0])) }, +// // end: { x: brushDateScale(getDate(txs?.token?.orders[0])) } +// // // end: { +// // // x: brushDateScale(getDate(orders?.length && orders[orders?.length - 1])) +// // // } +// // }), +// // [brushDateScale, orders] +// // ) + +// // // event handlers +// // const handleClearClick = () => { +// // if (brushRef?.current) { +// // setFilteredStock(stock) +// // brushRef.current.reset() +// // } +// // } + +// // const handleResetClick = () => { +// // if (brushRef?.current) { +// // const updater: UpdateBrush = (prevBrush) => { +// // const newExtent = brushRef.current!.getExtent( +// // initialBrushPosition.start, +// // initialBrushPosition.end +// // ) + +// // const newState: BaseBrushState = { +// // ...prevBrush, +// // start: { y: newExtent.y0, x: newExtent.x0 }, +// // end: { y: newExtent.y1, x: newExtent.x1 }, +// // extent: newExtent +// // } + +// // return newState +// // } +// // brushRef.current.updateBrush(updater) +// // } +// // } + +// return ( +// +// +// +// +// +// +// +// +// ) +// } +// // // We need to manually offset the handles for them to be rendered at the right position +// // const BrushHandle = ({ x, height, isBrushActive }: BrushHandleRenderProps) => { +// // const pathWidth = 8 +// // const pathHeight = 15 +// // if (!isBrushActive) { +// // return null +// // } +// // return ( +// // +// // +// // +// // ) +// // } + +// export default BrushChart diff --git a/src/components/Asset/AssetContent/TransactionHistoryVisualization.tsx b/src/components/Asset/AssetContent/TransactionHistoryVisualization.tsx new file mode 100644 index 0000000000..df44d89366 --- /dev/null +++ b/src/components/Asset/AssetContent/TransactionHistoryVisualization.tsx @@ -0,0 +1,251 @@ +import React, { useMemo, useRef, useState } from 'react' +import { LinearGradient } from '@visx/gradient' +import { scaleLinear, scaleTime } from '@visx/scale' +import { Group } from '@visx/group' +import { useAsset } from '@context/Asset' +import { + withTooltip, + Tooltip, + TooltipWithBounds, + defaultStyles +} from '@visx/tooltip' +import { WithTooltipProvidedProps } from '@visx/tooltip/lib/enhancers/withTooltip' +import { AxisBottom } from '@visx/axis' +import { Bounds } from '@visx/brush/lib/types' +import { extent } from 'd3-array' +import { filter } from 'lodash' +import BaseBrush from '@visx/brush/lib/BaseBrush' + +// tooltip +const tooltipStyles = { + ...defaultStyles, + background: '#3b6978', + border: '1px solid white', + color: 'white' +} + +const axisBottomTickLabelProps = { + dx: '-0.25em', + dy: '0.25em', + fontFamily: 'Arial', + fontSize: 10, + textAnchor: 'end' as const, + fill: '#ffffff' +} + +export type TimelineProps = { + width: number + height: number + events?: boolean +} + +export default withTooltip( + ({ + width, + height, + events = false, + showTooltip, + hideTooltip, + tooltipData, + tooltipTop = 0, + tooltipLeft = 0 + }: TimelineProps & WithTooltipProvidedProps) => { + const brushRef = useRef(null) + const { asset } = useAsset() + const txs: TransactionHistory = asset.transactionHistory + const orders = asset.transactionHistory?.token?.orders + .slice() + .sort((a, b) => a.createdTimestamp - b.createdTimestamp) + + const [filteredOrders, setFilteredOrders] = useState(orders?.slice()) + + // bounds + const xMax = width - 30 + const yMax = height - 120 + + // accessors + const getDate = (d: Order) => new Date(d?.createdTimestamp * 1000) + const getY = (d: Order) => parseFloat(d?.amount) + + // const onBrushChange = (domain: Bounds | null) => { + // if (!domain) return + // const { x0, x1, y0, y1 } = domain + // const ordersCopy = + // orders?.length && + // orders?.filter((order) => { + // const x = getDate(order).getTime() + // const y = getY(order) + // return x > x0 && x < x1 && y > y0 && y < y1 + // }) + // setFilteredOrders(ordersCopy) + // setFilteredOrders(orders) + // } + + // scales + // const xScale = useMemo( + // () => + // scaleTime({ + // range: [50, xMax - 50], + // domain: [ + // new Date(orders?.length && orders[0]?.createdTimestamp * 1000), + // new Date() + // ] + // }), + // [xMax, orders] + // ) + const xScale = useMemo( + () => + scaleTime({ + range: [50, xMax - 50], + domain: [ + new Date( + filteredOrders?.length && + filteredOrders[0]?.createdTimestamp * 1000 + ), + new Date() + ] + }), + [xMax, filteredOrders] + ) + // const xScale = useMemo( + // () => + // scaleTime({ + // range: [50, xMax - 50], + // domain: [new Date(orders?.length && getDate(orders[0])), new Date()] + // }), + // [xMax, orders] + // ) + // const xScale = useMemo( + // () => + // scaleTime({ + // range: [50, xMax - 50], + // domain: extent(orders, getDate) as [Date, Date] + // }), + // [xMax, orders] + // ) + // const xScale = useMemo( + // () => + // scaleTime({ + // range: [50, xMax - 50], + // domain: [ + // new Date( + // filteredOrders?.length && + // filteredOrders[0]?.createdTimestamp * 1000 + // ), + // new Date( + // filteredOrders?.length && + // filteredOrders[filteredOrders?.length]?.createdTimestamp * 1000 + // ) || new Date() + // ] + // }), + // [xMax, filteredOrders] + // ) + const yScale = useMemo( + () => + scaleLinear({ + range: [yMax, 0], + nice: true + }), + [yMax] + ) + + // event handlers + const handleClearClick = () => { + if (brushRef?.current) { + setFilteredOrders(orders) + brushRef.current.reset() + } + } + + return ( +
+ + + + {txs?.token?.orders?.map((order: Order, i) => { + return ( + + { + const top = height / 2 - yScale(getY(order)) + 10 + const left = xScale(getDate(order)) + showTooltip({ + tooltipData: order, + tooltipLeft: left, + tooltipTop: top + }) + }} + onMouseLeave={() => hideTooltip()} + /> + + ) + })} + axisBottomTickLabelProps} + /> + + {tooltipData && ( +
+ { + showTooltip({ + tooltipData, + tooltipLeft: 500, + tooltipTop: 200 + }) + }} + > +
+ Tx Id: {`${tooltipData.tx.slice(0, 10)}...`} +
+
+ Payer Id:{' '} + {`${tooltipData.payer.id.slice(0, 8)}...`} +
+
+ Consumer Id:{' '} + {`${tooltipData.consumer.id.slice(0, 8)}...`} +
+
+ Amount: {tooltipData.amount} +
+
+ USD Value: {tooltipData.estimatedUSDValue} +
+
+ Time: + {new Date(tooltipData.createdTimestamp * 1000).toDateString()} +
+
+ +
+ )} +
+ ) + } +) diff --git a/src/components/Asset/AssetContent/index.tsx b/src/components/Asset/AssetContent/index.tsx index c32cfa417d..5c38cae917 100644 --- a/src/components/Asset/AssetContent/index.tsx +++ b/src/components/Asset/AssetContent/index.tsx @@ -16,6 +16,8 @@ import content from '../../../../content/purgatory.json' import Web3 from 'web3' import Button from '@shared/atoms/Button' import IndividualUseVisualization from './IndividualUseVisualization' +import TransactionHistoryVisualization from './TransactionHistoryVisualization' +import TransactionHistoryBrushVisualization from './TransactionHistoryBrushVisualization' export default function AssetContent({ asset @@ -48,10 +50,32 @@ export default function AssetContent({
-

Transaction History Visualization

- +

Transaction History Brush Visualization

+ {/*
+ +
*/} +
+ +
+ {/*
+

Transaction History Visualization

+ +
*/} +