Skip to content
This repository has been archived by the owner on Jun 24, 2022. It is now read-only.

Commit

Permalink
fix: remove chart clamping and show indicator when handle is off scre…
Browse files Browse the repository at this point in the history
…en (#2064)

* initial off screen indicator

* adjust offscreen indicator

* add off screen handle indicator

* hide reset until we get a better behavior

* add svg.tsx
  • Loading branch information
Justin Domingue authored Jul 15, 2021
1 parent a184afa commit a00432c
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 73 deletions.
114 changes: 68 additions & 46 deletions src/components/LiquidityChartRangeInput/Brush.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { BrushBehavior, brushX, D3BrushEvent, ScaleLinear, select } from 'd3'
import styled from 'styled-components/macro'
import { brushHandleAccentPath, brushHandlePath } from 'components/LiquidityChartRangeInput/svg'
import { brushHandleAccentPath, brushHandlePath, OffScreenHandle } from 'components/LiquidityChartRangeInput/svg'
import usePrevious from 'hooks/usePrevious'

const Handle = styled.path<{ color: string }>`
cursor: ew-resize;
pointer-events: none;
stroke-width: 4;
stroke-width: 3;
stroke: ${({ color }) => color};
fill: ${({ color }) => color};
`
Expand Down Expand Up @@ -40,6 +40,9 @@ const Tooltip = styled.text`
// flips the handles draggers when close to the container edges
const FLIP_HANDLE_THRESHOLD_PX = 20

// margin to prevent tick snapping from putting the brush off screen
const BRUSH_EXTENT_MARGIN_PX = 2

const compare = (a1: [number, number], a2: [number, number]): boolean => a1[0] !== a2[0] || a1[1] !== a2[1]

export const Brush = ({
Expand Down Expand Up @@ -106,8 +109,8 @@ export const Brush = ({

brushBehavior.current = brushX<SVGGElement>()
.extent([
[Math.max(0, xScale(0)), 0],
[innerWidth, innerHeight],
[Math.max(0 + BRUSH_EXTENT_MARGIN_PX, xScale(0)), 0],
[innerWidth - BRUSH_EXTENT_MARGIN_PX, innerHeight],
])
.handleSize(30)
.filter(() => interactive)
Expand Down Expand Up @@ -136,15 +139,26 @@ export const Brush = ({
brushBehavior.current.move(select(brushRef.current) as any, brushExtent.map(xScale) as any)
}, [brushExtent, xScale])

// show labels when local brush changes
useEffect(() => {
setShowLabels(true)
const timeout = setTimeout(() => setShowLabels(false), 1500)
return () => clearTimeout(timeout)
}, [localBrushExtent])

// variables to help render the SVGs
const flipWestHandle = localBrushExtent && xScale(localBrushExtent[0]) > FLIP_HANDLE_THRESHOLD_PX
const flipEastHandle = localBrushExtent && xScale(localBrushExtent[1]) > innerWidth - FLIP_HANDLE_THRESHOLD_PX

const showWestArrow = localBrushExtent && (xScale(localBrushExtent[0]) < 0 || xScale(localBrushExtent[1]) < 0)
const showEastArrow =
localBrushExtent && (xScale(localBrushExtent[0]) > innerWidth || xScale(localBrushExtent[1]) > innerWidth)

const westHandleInView =
localBrushExtent && xScale(localBrushExtent[0]) >= 0 && xScale(localBrushExtent[0]) <= innerWidth
const eastHandleInView =
localBrushExtent && xScale(localBrushExtent[1]) >= 0 && xScale(localBrushExtent[1]) <= innerWidth

return useMemo(
() => (
<>
Expand All @@ -156,11 +170,7 @@ export const Brush = ({

{/* clips at exactly the svg area */}
<clipPath id={`${id}-brush-clip`}>
<rect x="0" y="0" width={innerWidth} height="100%" />
</clipPath>

<clipPath id={`${id}-handles-clip`}>
<rect x="0" y="0" width="100%" height="100%" />
<rect x="0" y="0" width={innerWidth} height={innerHeight} />
</clipPath>
</defs>

Expand All @@ -176,64 +186,76 @@ export const Brush = ({
{localBrushExtent && (
<>
{/* west handle */}
<g
transform={`translate(${Math.max(0, xScale(localBrushExtent[0]))}, 0), scale(${
flipWestHandle ? '-1' : '1'
}, 1)`}
>
<g clipPath={`url(#${id}-handles-clip)`}>
<Handle color={westHandleColor} d={brushHandlePath(innerHeight)} />
<HandleAccent d={brushHandleAccentPath()} />
</g>

<LabelGroup
transform={`translate(50,0), scale(${flipWestHandle ? '1' : '-1'}, 1)`}
visible={showLabels || hovering}
{westHandleInView ? (
<g
transform={`translate(${Math.max(0, xScale(localBrushExtent[0]))}, 0), scale(${
flipWestHandle ? '-1' : '1'
}, 1)`}
>
<TooltipBackground y="0" x="-30" height="30" width="60" rx="8" />
<Tooltip transform={`scale(-1, 1)`} y="15" dominantBaseline="middle">
{brushLabelValue('w', localBrushExtent[0])}
</Tooltip>
</LabelGroup>
</g>
<g>
<Handle color={westHandleColor} d={brushHandlePath(innerHeight)} />
<HandleAccent d={brushHandleAccentPath()} />
</g>

<LabelGroup
transform={`translate(50,0), scale(${flipWestHandle ? '1' : '-1'}, 1)`}
visible={showLabels || hovering}
>
<TooltipBackground y="0" x="-30" height="30" width="60" rx="8" />
<Tooltip transform={`scale(-1, 1)`} y="15" dominantBaseline="middle">
{brushLabelValue('w', localBrushExtent[0])}
</Tooltip>
</LabelGroup>
</g>
) : null}

{/* east handle */}
<g
transform={`translate(${Math.min(xScale(localBrushExtent[1]), innerWidth)}, 0), scale(${
flipEastHandle ? '-1' : '1'
}, 1)`}
>
<g clipPath={`url(#${id}-handles-clip)`}>
<Handle color={eastHandleColor} d={brushHandlePath(innerHeight)} />
<HandleAccent d={brushHandleAccentPath()} />
{eastHandleInView ? (
<g transform={`translate(${xScale(localBrushExtent[1])}, 0), scale(${flipEastHandle ? '-1' : '1'}, 1)`}>
<g>
<Handle color={eastHandleColor} d={brushHandlePath(innerHeight)} />
<HandleAccent d={brushHandleAccentPath()} />
</g>

<LabelGroup
transform={`translate(50,0), scale(${flipEastHandle ? '-1' : '1'}, 1)`}
visible={showLabels || hovering}
>
<TooltipBackground y="0" x="-30" height="30" width="60" rx="8" />
<Tooltip y="15" dominantBaseline="middle">
{brushLabelValue('e', localBrushExtent[1])}
</Tooltip>
</LabelGroup>
</g>
) : null}

<LabelGroup
transform={`translate(50,0), scale(${flipEastHandle ? '-1' : '1'}, 1)`}
visible={showLabels || hovering}
>
<TooltipBackground y="0" x="-30" height="30" width="60" rx="8" />
<Tooltip y="15" dominantBaseline="middle">
{brushLabelValue('e', localBrushExtent[1])}
</Tooltip>
</LabelGroup>
</g>
{showWestArrow && <OffScreenHandle color={westHandleColor} />}

{showEastArrow && (
<g transform={`translate(${innerWidth}, 0) scale(-1, 1)`}>
<OffScreenHandle color={eastHandleColor} />
</g>
)}
</>
)}
</>
),
[
brushLabelValue,
eastHandleColor,
eastHandleInView,
flipEastHandle,
flipWestHandle,
hovering,
id,
innerHeight,
innerWidth,
localBrushExtent,
showEastArrow,
showLabels,
showWestArrow,
westHandleColor,
westHandleInView,
xScale,
]
)
Expand Down
20 changes: 11 additions & 9 deletions src/components/LiquidityChartRangeInput/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function Chart({
onBrushDomainChange,
zoomLevels,
}: LiquidityChartRangeInputProps) {
const svgRef = useRef<SVGSVGElement | null>(null)
const zoomRef = useRef<SVGRectElement | null>(null)

const [zoom, setZoom] = useState<ZoomTransform | null>(null)

Expand Down Expand Up @@ -60,21 +60,21 @@ export function Chart({
}
}, [brushDomain, onBrushDomainChange, xScale])

// ensures the brush remains in view and adapts to zooms
xScale.clamp(true)

return (
<>
<Zoom
svg={svgRef.current}
svg={zoomRef.current}
xScale={xScale}
setZoom={setZoom}
innerWidth={innerWidth}
innerHeight={innerHeight}
showClear={Boolean(zoom && zoom.k !== 1)}
width={innerWidth}
height={
// allow zooming inside the x-axis
height
}
showClear={false}
zoomLevels={zoomLevels}
/>
<svg ref={svgRef} width="100%" height="100%" viewBox={`0 0 ${width} ${height}`} style={{ overflow: 'visible' }}>
<svg width="100%" height="100%" viewBox={`0 0 ${width} ${height}`} style={{ overflow: 'visible' }}>
<defs>
<clipPath id={`${id}-chart-clip`}>
<rect x="0" y="0" width={innerWidth} height={height} />
Expand Down Expand Up @@ -117,6 +117,8 @@ export function Chart({
<AxisBottom xScale={xScale} innerHeight={innerHeight} />
</g>

<rect width={innerWidth} height={height} fill="transparent" ref={zoomRef} cursor="grab" />

<Brush
id={id}
xScale={xScale}
Expand Down
24 changes: 9 additions & 15 deletions src/components/LiquidityChartRangeInput/Zoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ export default function Zoom({
svg,
xScale,
setZoom,
innerWidth,
innerHeight,
width,
height,
showClear,
zoomLevels,
}: {
svg: SVGSVGElement | null
svg: SVGElement | null
xScale: ScaleLinear<number, number>
setZoom: (transform: ZoomTransform) => void
innerWidth: number
innerHeight: number
width: number
height: number
showClear: boolean
zoomLevels: ZoomLevels
}) {
Expand Down Expand Up @@ -80,23 +80,17 @@ export default function Zoom({

zoomBehavior.current = zoom()
.scaleExtent([zoomLevels.min, zoomLevels.max])
.translateExtent([
[0, 0],
[innerWidth, innerHeight],
])
.extent([
[0, 0],
[innerWidth, innerHeight],
[width, height],
])
.on('zoom', ({ transform }: { transform: ZoomTransform }) => setZoom(transform))

select(svg as Element)
.call(zoomBehavior.current)
.on('mousedown.zoom', null)
}, [innerHeight, innerWidth, setZoom, svg, xScale, zoomBehavior, zoomLevels, zoomLevels.max, zoomLevels.min])
select(svg as Element).call(zoomBehavior.current)
}, [height, width, setZoom, svg, xScale, zoomBehavior, zoomLevels, zoomLevels.max, zoomLevels.min])

useEffect(() => {
// reset zoom to initial on zoomLevel chang
// reset zoom to initial on zoomLevel change
initial()
}, [initial, zoomLevels])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const brushHandlePath = (height: number) =>
`v ${height}`, // vertical line
'm 1 0', // move 1px to the right
`V 0`, // second vertical line
`M 0 2`, // move to origin
`M 0 1`, // move to origin

// head
'h 12', // horizontal line
Expand All @@ -33,10 +33,29 @@ export const brushHandlePath = (height: number) =>

export const brushHandleAccentPath = () =>
[
'm 6 8', // move to first accent
'm 5 7', // move to first accent
'v 14', // vertical line
'M 0 0', // move to origin
'm 10 8', // move to second accent
'm 9 7', // move to second accent
'v 14', // vertical line
'z',
].join(' ')

export const OffScreenHandle = ({
color,
size = 10,
margin = 10,
}: {
color: string
size?: number
margin?: number
}) => (
<polygon
points={`0 0, ${size} ${size}, 0 ${size}`}
transform={` translate(${size + margin}, ${margin}) rotate(45) `}
fill={color}
stroke={color}
strokeWidth="4"
strokeLinejoin="round"
/>
)

0 comments on commit a00432c

Please sign in to comment.