fix: add liquidity ux polish (#2024)

* zoom out on intiial

* adjust initial zoom ranges

* remove unneeded reactga event

* adjust full range warning copy

* update zoom

* adjust zoom ranges and label around current price
This commit is contained in:
Justin Domingue 2021-07-13 08:23:24 -07:00 committed by GitHub
parent 2bc2a2c76e
commit bf30013b6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 52 additions and 43 deletions

@ -40,6 +40,8 @@ const Tooltip = styled.text`
// flips the handles draggers when close to the container edges // flips the handles draggers when close to the container edges
const FLIP_HANDLE_THRESHOLD_PX = 20 const FLIP_HANDLE_THRESHOLD_PX = 20
const compare = (a1: [number, number], a2: [number, number]): boolean => a1[0] !== a2[0] || a1[1] !== a2[1]
export const Brush = ({ export const Brush = ({
id, id,
xScale, xScale,
@ -49,7 +51,8 @@ export const Brush = ({
setBrushExtent, setBrushExtent,
innerWidth, innerWidth,
innerHeight, innerHeight,
colors, westHandleColor,
eastHandleColor,
}: { }: {
id: string id: string
xScale: ScaleLinear<number, number> xScale: ScaleLinear<number, number>
@ -59,10 +62,8 @@ export const Brush = ({
setBrushExtent: (extent: [number, number]) => void setBrushExtent: (extent: [number, number]) => void
innerWidth: number innerWidth: number
innerHeight: number innerHeight: number
colors: { westHandleColor: string
west: string eastHandleColor: string
east: string
}
}) => { }) => {
const brushRef = useRef<SVGGElement | null>(null) const brushRef = useRef<SVGGElement | null>(null)
const brushBehavior = useRef<BrushBehavior<SVGGElement> | null>(null) const brushBehavior = useRef<BrushBehavior<SVGGElement> | null>(null)
@ -84,13 +85,13 @@ export const Brush = ({
const scaled = (selection as [number, number]).map(xScale.invert) as [number, number] const scaled = (selection as [number, number]).map(xScale.invert) as [number, number]
// avoid infinite render loop by checking for change // avoid infinite render loop by checking for change
if (type === 'end' && (brushExtent[0] !== scaled[0] || brushExtent[1] !== scaled[1])) { if (type === 'end' && compare(brushExtent, scaled)) {
setBrushExtent(scaled) setBrushExtent(scaled)
} }
setLocalBrushExtent(scaled) setLocalBrushExtent(scaled)
}, },
[xScale.invert, brushExtent, setBrushExtent] [xScale, brushExtent, setBrushExtent]
) )
// keep local and external brush extent in sync // keep local and external brush extent in sync
@ -114,10 +115,7 @@ export const Brush = ({
brushBehavior.current(select(brushRef.current)) brushBehavior.current(select(brushRef.current))
if ( if (previousBrushExtent && compare(brushExtent, previousBrushExtent)) {
previousBrushExtent &&
(brushExtent[0] !== previousBrushExtent[0] || brushExtent[1] !== previousBrushExtent[1])
) {
select(brushRef.current) select(brushRef.current)
.transition() .transition()
.call(brushBehavior.current.move as any, brushExtent.map(xScale)) .call(brushBehavior.current.move as any, brushExtent.map(xScale))
@ -136,9 +134,7 @@ export const Brush = ({
if (!brushRef.current || !brushBehavior.current) return if (!brushRef.current || !brushBehavior.current) return
brushBehavior.current.move(select(brushRef.current) as any, brushExtent.map(xScale) as any) brushBehavior.current.move(select(brushRef.current) as any, brushExtent.map(xScale) as any)
// dependency on brushExtent would start an update loop }, [brushExtent, xScale])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [xScale])
useEffect(() => { useEffect(() => {
setShowLabels(true) setShowLabels(true)
@ -154,8 +150,8 @@ export const Brush = ({
<> <>
<defs> <defs>
<linearGradient id={`${id}-gradient-selection`} x1="0%" y1="100%" x2="100%" y2="100%"> <linearGradient id={`${id}-gradient-selection`} x1="0%" y1="100%" x2="100%" y2="100%">
<stop stopColor={colors.west} /> <stop stopColor={westHandleColor} />
<stop stopColor={colors.east} offset="1" /> <stop stopColor={eastHandleColor} offset="1" />
</linearGradient> </linearGradient>
{/* clips at exactly the svg area */} {/* clips at exactly the svg area */}
@ -186,7 +182,7 @@ export const Brush = ({
}, 1)`} }, 1)`}
> >
<g clipPath={`url(#${id}-handles-clip)`}> <g clipPath={`url(#${id}-handles-clip)`}>
<Handle color={colors.west} d={brushHandlePath(innerHeight)} /> <Handle color={westHandleColor} d={brushHandlePath(innerHeight)} />
<HandleAccent d={brushHandleAccentPath()} /> <HandleAccent d={brushHandleAccentPath()} />
</g> </g>
@ -208,7 +204,7 @@ export const Brush = ({
}, 1)`} }, 1)`}
> >
<g clipPath={`url(#${id}-handles-clip)`}> <g clipPath={`url(#${id}-handles-clip)`}>
<Handle color={colors.east} d={brushHandlePath(innerHeight)} /> <Handle color={eastHandleColor} d={brushHandlePath(innerHeight)} />
<HandleAccent d={brushHandleAccentPath()} /> <HandleAccent d={brushHandleAccentPath()} />
</g> </g>
@ -228,8 +224,7 @@ export const Brush = ({
), ),
[ [
brushLabelValue, brushLabelValue,
colors.east, eastHandleColor,
colors.west,
flipEastHandle, flipEastHandle,
flipWestHandle, flipWestHandle,
hovering, hovering,
@ -238,6 +233,7 @@ export const Brush = ({
innerWidth, innerWidth,
localBrushExtent, localBrushExtent,
showLabels, showLabels,
westHandleColor,
xScale, xScale,
] ]
) )

@ -24,7 +24,7 @@ export function Chart({
}: LiquidityChartRangeInputProps) { }: LiquidityChartRangeInputProps) {
const svgRef = useRef<SVGSVGElement | null>(null) const svgRef = useRef<SVGSVGElement | null>(null)
const [zoom, setZoom] = useState<ZoomTransform>() const [zoom, setZoom] = useState<ZoomTransform | null>(null)
const [innerHeight, innerWidth] = useMemo( const [innerHeight, innerWidth] = useMemo(
() => [height - margins.top - margins.bottom, width - margins.left - margins.right], () => [height - margins.top - margins.bottom, width - margins.left - margins.right],
@ -34,7 +34,7 @@ export function Chart({
const { xScale, yScale } = useMemo(() => { const { xScale, yScale } = useMemo(() => {
const scales = { const scales = {
xScale: scaleLinear() xScale: scaleLinear()
.domain([(1 - zoomLevels.initial) * current, (1 + zoomLevels.initial) * current] as number[]) .domain([current * zoomLevels.initialMin, current * zoomLevels.initialMax] as number[])
.range([0, innerWidth]), .range([0, innerWidth]),
yScale: scaleLinear() yScale: scaleLinear()
.domain([0, max(series, yAccessor)] as number[]) .domain([0, max(series, yAccessor)] as number[])
@ -47,7 +47,12 @@ export function Chart({
} }
return scales return scales
}, [zoomLevels.initial, current, innerWidth, series, innerHeight, zoom]) }, [current, zoomLevels.initialMin, zoomLevels.initialMax, innerWidth, series, innerHeight, zoom])
useEffect(() => {
// reset zoom as necessary
setZoom(null)
}, [zoomLevels])
useEffect(() => { useEffect(() => {
if (!brushDomain) { if (!brushDomain) {
@ -121,10 +126,8 @@ export function Chart({
innerWidth={innerWidth} innerWidth={innerWidth}
innerHeight={innerHeight} innerHeight={innerHeight}
setBrushExtent={onBrushDomainChange} setBrushExtent={onBrushDomainChange}
colors={{ westHandleColor={styles.brush.handle.west}
west: styles.brush.handle.west, eastHandleColor={styles.brush.handle.east}
east: styles.brush.handle.east,
}}
/> />
</g> </g>
</svg> </svg>

@ -45,7 +45,7 @@ export default function Zoom({
}) { }) {
const zoomBehavior = useRef<ZoomBehavior<Element, unknown>>() const zoomBehavior = useRef<ZoomBehavior<Element, unknown>>()
const [zoomIn, zoomOut, reset] = useMemo( const [zoomIn, zoomOut, reset, initial] = useMemo(
() => [ () => [
() => () =>
svg && svg &&
@ -65,6 +65,12 @@ export default function Zoom({
select(svg as Element) select(svg as Element)
.transition() .transition()
.call(zoomBehavior.current.scaleTo, 1), .call(zoomBehavior.current.scaleTo, 1),
() =>
svg &&
zoomBehavior.current &&
select(svg as Element)
.transition()
.call(zoomBehavior.current.scaleTo, 0.5),
], ],
[svg, zoomBehavior] [svg, zoomBehavior]
) )
@ -72,7 +78,6 @@ export default function Zoom({
useEffect(() => { useEffect(() => {
if (!svg) return if (!svg) return
// zoom
zoomBehavior.current = zoom() zoomBehavior.current = zoom()
.scaleExtent([zoomLevels.min, zoomLevels.max]) .scaleExtent([zoomLevels.min, zoomLevels.max])
.translateExtent([ .translateExtent([
@ -88,7 +93,12 @@ export default function Zoom({
select(svg as Element) select(svg as Element)
.call(zoomBehavior.current) .call(zoomBehavior.current)
.on('mousedown.zoom', null) .on('mousedown.zoom', null)
}, [innerHeight, innerWidth, setZoom, svg, xScale, zoomBehavior, zoomLevels.max, zoomLevels.min]) }, [innerHeight, innerWidth, setZoom, svg, xScale, zoomBehavior, zoomLevels, zoomLevels.max, zoomLevels.min])
useEffect(() => {
// reset zoom to initial on zoomLevel chang
initial()
}, [initial, zoomLevels])
return ( return (
<Wrapper count={showClear ? 3 : 2}> <Wrapper count={showClear ? 3 : 2}>

@ -20,17 +20,20 @@ import { ZoomLevels } from './types'
const ZOOM_LEVELS: Record<FeeAmount, ZoomLevels> = { const ZOOM_LEVELS: Record<FeeAmount, ZoomLevels> = {
[FeeAmount.LOW]: { [FeeAmount.LOW]: {
initial: 0.002, initialMin: 0.999,
initialMax: 1.001,
min: 0.001, min: 0.001,
max: 2, max: 1.5,
}, },
[FeeAmount.MEDIUM]: { [FeeAmount.MEDIUM]: {
initial: 0.3, initialMin: 0.5,
initialMax: 2,
min: 0.01, min: 0.01,
max: 20, max: 20,
}, },
[FeeAmount.HIGH]: { [FeeAmount.HIGH]: {
initial: 0.3, initialMin: 0.5,
initialMax: 2,
min: 0.01, min: 0.01,
max: 20, max: 20,
}, },
@ -95,11 +98,6 @@ export default function LiquidityChartRangeInput({
let leftRangeValue = Number(domain[0]) let leftRangeValue = Number(domain[0])
const rightRangeValue = Number(domain[1]) const rightRangeValue = Number(domain[1])
ReactGA.event({
category: 'Liquidity',
action: 'Chart brushed',
})
if (leftRangeValue <= 0) { if (leftRangeValue <= 0) {
leftRangeValue = 1 / 10 ** 6 leftRangeValue = 1 / 10 ** 6
} }
@ -133,7 +131,9 @@ export default function LiquidityChartRangeInput({
if (d === 'w' && ticksAtLimit[Bound.LOWER]) return '0' if (d === 'w' && ticksAtLimit[Bound.LOWER]) return '0'
if (d === 'e' && ticksAtLimit[Bound.UPPER]) return '∞' if (d === 'e' && ticksAtLimit[Bound.UPPER]) return '∞'
const percent = (((x < price ? -1 : 1) * (Math.max(x, price) - Math.min(x, price))) / Math.min(x, price)) * 100 //const percent = (((x < price ? -1 : 1) * (Math.max(x, price) - Math.min(x, price))) / Math.min(x, price)) * 100
const percent = (x < price ? -1 : 1) * ((Math.max(x, price) - Math.min(x, price)) / price) * 100
return price ? `${format(Math.abs(percent) > 1 ? '.2~s' : '.2~f')(percent)}%` : '' return price ? `${format(Math.abs(percent) > 1 ? '.2~s' : '.2~f')(percent)}%` : ''
}, },

@ -16,7 +16,8 @@ export interface Margins {
} }
export interface ZoomLevels { export interface ZoomLevels {
initial: number initialMin: number
initialMax: number
min: number min: number
max: number max: number
} }

@ -842,8 +842,7 @@ export default function AddLiquidity({
<RowFixed> <RowFixed>
<TYPE.yellow ml="12px" fontSize="13px" margin={0} fontWeight={400}> <TYPE.yellow ml="12px" fontSize="13px" margin={0} fontWeight={400}>
<Trans> <Trans>
On Uniswap V3, setting a range across all prices like V2 is less capital efficient Full range positions may earn less fees than concentrated positions. Learn more{' '}
than a concentrated one. Learn more{' '}
<ExternalLink <ExternalLink
style={{ color: theme.yellow3, textDecoration: 'underline' }} style={{ color: theme.yellow3, textDecoration: 'underline' }}
href={''} href={''}