Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to Canvas Resizing, View Fitting, and Memory Management #105

Merged
merged 3 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,19 +363,21 @@ export class Graph<N extends CosmosInputNode, L extends CosmosInputLink> {
/**
* Center and zoom in/out the view to fit all nodes in the scene.
* @param duration Duration of the center and zoom in/out animation in milliseconds (`250` by default).
* @param padding Padding around the viewport in percentage
*/
public fitView (duration = 250): void {
this.setZoomTransformByNodePositions(this.getNodePositionsArray(), duration)
public fitView (duration = 250, padding = 0.1): void {
this.setZoomTransformByNodePositions(this.getNodePositionsArray(), duration, undefined, padding)
}

/**
* Center and zoom in/out the view to fit nodes by their ids in the scene.
* @param duration Duration of the center and zoom in/out animation in milliseconds (`250` by default).
* @param padding Padding around the viewport in percentage
*/
public fitViewByNodeIds (ids: string[], duration = 250): void {
public fitViewByNodeIds (ids: string[], duration = 250, padding = 0.1): void {
const positionsMap = this.getNodePositionsMap()
const positions = ids.map(id => positionsMap.get(id)).filter((d): d is [number, number] => d !== undefined)
this.setZoomTransformByNodePositions(positions, duration)
this.setZoomTransformByNodePositions(positions, duration, undefined, padding)
}

/** Select nodes inside a rectangular area.
Expand Down Expand Up @@ -778,15 +780,9 @@ export class Graph<N extends CosmosInputNode, L extends CosmosInputLink> {

private updateMousePosition (event: MouseEvent): void {
if (!event || event.offsetX === undefined || event.offsetY === undefined) return
const { x, y, k } = this.zoomInstance.eventTransform
const h = this.canvas.clientHeight
const mouseX = event.offsetX
const mouseY = event.offsetY
const invertedX = (mouseX - x) / k
const invertedY = (mouseY - y) / k
this.store.mousePosition = [invertedX, (h - invertedY)]
this.store.mousePosition[0] -= (this.store.screenSize[0] - this.store.adjustedSpaceSize) / 2
this.store.mousePosition[1] -= (this.store.screenSize[1] - this.store.adjustedSpaceSize) / 2
this.store.mousePosition = this.zoomInstance.convertScreenToSpacePosition([mouseX, mouseY])
this.store.screenMousePosition = [mouseX, (this.store.screenSize[1] - mouseY)]
}

Expand All @@ -813,12 +809,16 @@ export class Graph<N extends CosmosInputNode, L extends CosmosInputLink> {
const h = this.canvas.clientHeight

if (forceResize || prevWidth !== w * this.config.pixelRatio || prevHeight !== h * this.config.pixelRatio) {
const [prevW, prevH] = this.store.screenSize
const { k } = this.zoomInstance.eventTransform
const centerPosition = this.zoomInstance.convertScreenToSpacePosition([prevW / 2, prevH / 2])

this.store.updateScreenSize(w, h)
this.canvas.width = w * this.config.pixelRatio
this.canvas.height = h * this.config.pixelRatio
this.reglInstance.poll()
this.canvasD3Selection
.call(this.zoomInstance.behavior.transform, this.zoomInstance.eventTransform)
.call(this.zoomInstance.behavior.transform, this.zoomInstance.getTransform([centerPosition], k))
this.points.updateSampledNodesGrid()
}
}
Expand Down
9 changes: 5 additions & 4 deletions src/modules/Points/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import regl from 'regl'
import { scaleLinear } from 'd3-scale'
import { extent } from 'd3-array'
import { CoreModule } from '@/graph/modules/core-module'
import { defaultConfigValues } from '@/graph/variables'
import { createColorBuffer, createGreyoutStatusBuffer } from '@/graph/modules/Points/color-buffer'
Expand Down Expand Up @@ -466,10 +467,10 @@ export class Points<N extends CosmosInputNode, L extends CosmosInputLink> extend
if (xs.length === 0) return
const ys = nodes.map(n => n.y).filter((n): n is number => n !== undefined)
if (ys.length === 0) return
const minX = Math.min(...xs)
const maxX = Math.max(...xs)
const minY = Math.min(...ys)
const maxY = Math.max(...ys)
const [minX, maxX] = extent(xs)
if (minX === undefined || maxX === undefined) return
const [minY, maxY] = extent(ys)
if (minY === undefined || maxY === undefined) return
const w = maxX - minX
const h = maxY - minY

Expand Down
33 changes: 26 additions & 7 deletions src/modules/Zoom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,27 @@ export class Zoom <N extends CosmosInputNode, L extends CosmosInputLink> {
this.config = config
}

public getTransform (positions: [number, number][], scale?: number, padding = this.store.maxPointSize / 2): ZoomTransform {
/**
* Get the zoom transform that will fit the given node positions into the viewport
*
* @param positions An array of node positions in the form `[x, y]`
* @param scale An optional scale factor to apply to the transform
* @param padding Padding around the viewport in percentage
*/
public getTransform (positions: [number, number][], scale?: number, padding = 0.1): ZoomTransform {
if (positions.length === 0) return this.eventTransform
const { store: { screenSize } } = this
const width = screenSize[0]
const height = screenSize[1]
const xExtent = extent(positions.map(d => d[0])) as [number, number]
const yExtent = extent(positions.map(d => d[1])) as [number, number]
xExtent[0] = this.store.scaleX(xExtent[0] - padding)
xExtent[1] = this.store.scaleX(xExtent[1] + padding)
yExtent[0] = this.store.scaleY(yExtent[0] - padding)
yExtent[1] = this.store.scaleY(yExtent[1] + padding)
xExtent[0] = this.store.scaleX(xExtent[0])
xExtent[1] = this.store.scaleX(xExtent[1])
yExtent[0] = this.store.scaleY(yExtent[0])
yExtent[1] = this.store.scaleY(yExtent[1])

const xScale = width / (xExtent[1] - xExtent[0])
const yScale = height / (yExtent[0] - yExtent[1])
const xScale = (width * (1 - padding * 2)) / (xExtent[1] - xExtent[0])
const yScale = (height * (1 - padding * 2)) / (yExtent[0] - yExtent[1])
const clampedScale = clamp(scale ?? Math.min(xScale, yScale), ...this.behavior.scaleExtent())
const xCenter = (xExtent[1] + xExtent[0]) / 2
const yCenter = (yExtent[1] + yExtent[0]) / 2
Expand Down Expand Up @@ -101,6 +108,18 @@ export class Zoom <N extends CosmosInputNode, L extends CosmosInputLink> {
.scale(scale)
}

public convertScreenToSpacePosition (screenPosition: [number, number]): [number, number] {
const { eventTransform: { x, y, k }, store: { screenSize } } = this
const w = screenSize[0]
const h = screenSize[1]
const invertedX = (screenPosition[0] - x) / k
const invertedY = (screenPosition[1] - y) / k
const spacePosition = [invertedX, (h - invertedY)] as [number, number]
spacePosition[0] -= (w - this.store.adjustedSpaceSize) / 2
spacePosition[1] -= (h - this.store.adjustedSpaceSize) / 2
return spacePosition
}

public convertSpaceToScreenPosition (spacePosition: [number, number]): [number, number] {
const screenPointX = this.eventTransform.applyX(this.store.scaleX(spacePosition[0]))
const screenPointY = this.eventTransform.applyY(this.store.scaleY(spacePosition[1]))
Expand Down
Loading