|
|
import { useCallback, useMemo } from 'react'; |
|
|
import * as d3 from 'd3'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const useViewportCulling = (positions, viewportBounds, padding = 200) => { |
|
|
|
|
|
|
|
|
const getVisibleGlyphs = useCallback((positions, bounds, padding) => { |
|
|
if (!positions.length || !bounds) return positions; |
|
|
|
|
|
const { x, y, width, height, scale } = bounds; |
|
|
|
|
|
|
|
|
const visibleBounds = { |
|
|
minX: (x - padding) / scale, |
|
|
maxX: (x + width + padding) / scale, |
|
|
minY: (y - padding) / scale, |
|
|
maxY: (y + height + padding) / scale |
|
|
}; |
|
|
|
|
|
|
|
|
return positions.filter(position => { |
|
|
return position.x >= visibleBounds.minX && |
|
|
position.x <= visibleBounds.maxX && |
|
|
position.y >= visibleBounds.minY && |
|
|
position.y <= visibleBounds.maxY; |
|
|
}); |
|
|
}, []); |
|
|
|
|
|
|
|
|
const visiblePositions = useMemo(() => { |
|
|
return getVisibleGlyphs(positions, viewportBounds, padding); |
|
|
}, [positions, viewportBounds, padding, getVisibleGlyphs]); |
|
|
|
|
|
|
|
|
const getViewportBounds = useCallback((svg, viewportGroup) => { |
|
|
if (!svg || !viewportGroup) return null; |
|
|
|
|
|
const transform = d3.zoomTransform(svg.node()); |
|
|
const svgRect = svg.node().getBoundingClientRect(); |
|
|
|
|
|
return { |
|
|
x: -transform.x / transform.k, |
|
|
y: -transform.y / transform.k, |
|
|
width: svgRect.width / transform.k, |
|
|
height: svgRect.height / transform.k, |
|
|
scale: transform.k |
|
|
}; |
|
|
}, []); |
|
|
|
|
|
return { |
|
|
visiblePositions, |
|
|
getViewportBounds, |
|
|
totalCount: positions.length, |
|
|
visibleCount: visiblePositions.length |
|
|
}; |
|
|
}; |
|
|
|