Skip to content

Commit

Permalink
Fit Nodes in the Scene on Initialization (#94)
Browse files Browse the repository at this point in the history
* Fit view on initialization config

- `fitViewOnInit`
- `fitViewDelay`
- `fitViewByNodesInRect`

* 1.5.1
  • Loading branch information
Stukova authored Dec 21, 2023
1 parent 8fcf08b commit df0613e
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 11 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cosmograph/cosmos",
"version": "1.4.2",
"version": "1.5.1",
"description": "GPU-based force graph layout and rendering",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
20 changes: 20 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,23 @@ export interface GraphConfigInterface<N extends CosmosInputNode, L extends Cosmo
* Default: `false`
*/
disableZoom?: boolean;
/**
* Whether to center and zoom the view to fit all nodes in the scene on initialization or not.
* Default: `true`
*/
fitViewOnInit?: boolean;
/**
* Delay in milliseconds before fitting the view.
* Useful if you want the layout to stabilize a bit before fitting.
* Default: `250`
*/
fitViewDelay?: number;
/**
* When `fitViewOnInit` is set to `true`, fits the view to show the nodes within a rectangle
* defined by its two corner coordinates `[[left, bottom], [right, top]]` in the scene space.
* Default: `undefined`
*/
fitViewByNodesInRect?: [[number, number], [number, number]] | [number, number][];
/**
* Providing a `randomSeed` value allows you to control
* the randomness of the layout across different simulation runs.
Expand Down Expand Up @@ -444,6 +461,9 @@ export class GraphConfig<N extends CosmosInputNode, L extends CosmosInputLink> i
public scaleNodesOnZoom = defaultConfigValues.scaleNodesOnZoom
public initialZoomLevel = defaultConfigValues.initialZoomLevel
public disableZoom = defaultConfigValues.disableZoom
public fitViewOnInit = defaultConfigValues.fitViewOnInit
public fitViewDelay = defaultConfigValues.fitViewDelay
public fitViewByNodesInRect = undefined

public randomSeed = undefined
public nodeSamplingDistance = defaultConfigValues.nodeSamplingDistance
Expand Down
19 changes: 17 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ export class Graph<N extends CosmosInputNode, L extends CosmosInputLink> {
* If the mouse is not on the Canvas, the `findHoveredPoint` method will not be executed.
*/
private _isMouseOnCanvas = false
/**
* After setting data at a first time, the fit logic will run
* */
private _isFirstDataAfterInit = true
private _fitViewOnInitTimeoutID: number | undefined

public constructor (canvas: HTMLCanvasElement, config?: GraphConfigInterface<N, L>) {
if (config) this.config.init(config)
Expand Down Expand Up @@ -212,6 +217,7 @@ export class Graph<N extends CosmosInputNode, L extends CosmosInputLink> {
* @param runSimulation When set to `false`, the simulation won't be started automatically (`true` by default).
*/
public setData (nodes: N[], links: L[], runSimulation = true): void {
const { fitViewOnInit, fitViewDelay, fitViewByNodesInRect } = this.config
if (!nodes.length && !links.length) {
this.destroyParticleSystem()
this.reglInstance.clear({
Expand All @@ -222,6 +228,14 @@ export class Graph<N extends CosmosInputNode, L extends CosmosInputLink> {
return
}
this.graph.setData(nodes, links)
if (this._isFirstDataAfterInit && fitViewOnInit) {
this._fitViewOnInitTimeoutID = window.setTimeout(() => {
if (fitViewByNodesInRect) this.setZoomTransformByNodePositions(fitViewByNodesInRect, undefined, undefined, 0)
else this.fitView()
}, fitViewDelay)
}
this._isFirstDataAfterInit = false

this.update(runSimulation)
}

Expand Down Expand Up @@ -608,6 +622,7 @@ export class Graph<N extends CosmosInputNode, L extends CosmosInputLink> {
* Destroy this Cosmos instance.
*/
public destroy (): void {
window.clearTimeout(this._fitViewOnInitTimeoutID)
this.stopFrames()
this.destroyParticleSystem()
this.fpsMonitor?.destroy()
Expand Down Expand Up @@ -803,9 +818,9 @@ export class Graph<N extends CosmosInputNode, L extends CosmosInputLink> {
}
}

private setZoomTransformByNodePositions (positions: [number, number][], duration = 250, scale?: number): void {
private setZoomTransformByNodePositions (positions: [number, number][], duration = 250, scale?: number, padding?: number): void {
this.resizeCanvas()
const transform = this.zoomInstance.getTransform(positions, scale)
const transform = this.zoomInstance.getTransform(positions, scale, padding)
this.canvasD3Selection
.transition()
.ease(easeQuadInOut)
Expand Down
13 changes: 7 additions & 6 deletions src/modules/Zoom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,18 @@ export class Zoom <N extends CosmosInputNode, L extends CosmosInputLink> {
this.config = config
}

public getTransform (positions: [number, number][], scale?: number): ZoomTransform {
public getTransform (positions: [number, number][], scale?: number, padding = this.store.maxPointSize / 2): ZoomTransform {
if (positions.length === 0) return this.eventTransform
const { store: { screenSize, maxPointSize } } = this
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] - maxPointSize / 2)
xExtent[1] = this.store.scaleX(xExtent[1] + maxPointSize / 2)
yExtent[0] = this.store.scaleY(yExtent[0] - maxPointSize / 2)
yExtent[1] = this.store.scaleY(yExtent[1] + maxPointSize / 2)
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)

const xScale = width / (xExtent[1] - xExtent[0])
const yScale = height / (yExtent[0] - yExtent[1])
const clampedScale = clamp(scale ?? Math.min(xScale, yScale), ...this.behavior.scaleExtent())
Expand Down
2 changes: 2 additions & 0 deletions src/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export const defaultConfigValues = {
scaleNodesOnZoom: true,
initialZoomLevel: 1,
disableZoom: false,
fitViewOnInit: true,
fitViewDelay: 250,
nodeSamplingDistance: 150,
}

Expand Down

0 comments on commit df0613e

Please sign in to comment.