From 3b6d84f5461fb1692fe68c763f6af90000f6d86a Mon Sep 17 00:00:00 2001 From: Abhinav Singh Parmar Date: Mon, 31 Jul 2023 18:11:03 +0530 Subject: [PATCH 1/6] New: Added support for keyboard interaction --- docs/view-default.md | 22 +++++- src/renderer/canvas/canvas-renderer.ts | 41 ++++++++++ src/renderer/shared.ts | 10 +++ src/renderer/webgl/webgl-renderer.ts | 24 ++++++ src/views/orb-view.ts | 102 ++++++++++++++++++++++++- 5 files changed, 196 insertions(+), 3 deletions(-) diff --git a/docs/view-default.md b/docs/view-default.md index 598dae6..86cf991 100644 --- a/docs/view-default.md +++ b/docs/view-default.md @@ -84,6 +84,12 @@ interface IOrbViewSettings { interaction: { isDragEnabled: boolean; isZoomEnabled: boolean; + keyboard: { + isKeyboardEnabled: boolean; + zoomInFactor: number; + zoomOutFactor: number; + draggingFactor: number; + }; }; // Other default view parameters zoomFitTransitionMs: number; @@ -157,6 +163,12 @@ const defaultSettings = { interaction: { isDragEnabled: true; isZoomEnabled: true; + keyboard: { + isKeyboardEnabled: true, + zoomInFactor: 1.2, + zoomOutFactor: 0.8, + draggingFactor: 25 + } }, zoomFitTransitionMs: 200, isOutOfBoundsDragEnabled: false, @@ -323,13 +335,19 @@ orb.events.on(OrbEventType.MOUSE_CLICK, (event) => { ### Property `interaction` -The optional property `interaction` has two properties that you can enable/disable: +The `interaction` property controls the interactivity options available to users when interacting with the graph visualization. * `isDragEnabled` - property controls the dragging behavior within the application. When it is set to `true`, dragging is enabled, allowing users to interact with nodes and edges by dragging them to different positions within the graph. On the other hand, when `isDragEnabled`` is set to false, dragging functionality is disabled, preventing users from moving or repositioning nodes and edges through dragging interactions. * `isZoomEnabled` - This property controls the zooming behavior within the application. Setting it to `true` enables zooming, allowing users to interactively zoom in and out of the graph. Setting it to `false` disables zooming, restricting the user's ability to change the zoom level. -These properties provide a straightforward way to enable or disable dragging and zooming features based on the needs and requirements of your application. By toggling the values of isDragEnabled and isZoomEnabled, you can easily control the interactivity options available to users. e.g: +* `keyboard` - This property allows you to define keyboard interaction settings: + * `isKeyboardEnabled` (boolean): Set this property to `true` to enable keyboard interaction within the graph visualization. When enabled, users can use keyboard shortcuts for certain interactions. + * `zoomInFactor` (number): The zoom-in factor determines the extent to which the graph will zoom in when the user presses the zoom-in keyboard shortcut. + * `zoomOutFactor` (number): The zoom-out factor determines the extent to which the graph will zoom out when the user presses the zoom-out keyboard shortcut. + * `draggingFactor` (number): The dragging factor specifies the speed at which nodes and edges will move when the user interacts with them using keyboard shortcuts for dragging. + +By customizing the interaction property, you can enable or disable dragging and zooming features based on the needs and requirements of your graph visualization application. Additionally, you can also choose to enable keyboard interactions for further user control and convenience. e.g: ```typescript // Disable default drag interaction and enable zooming diff --git a/src/renderer/canvas/canvas-renderer.ts b/src/renderer/canvas/canvas-renderer.ts index 0625c9b..b893ce9 100644 --- a/src/renderer/canvas/canvas-renderer.ts +++ b/src/renderer/canvas/canvas-renderer.ts @@ -245,6 +245,47 @@ export class CanvasRenderer extends Em return zoomIdentity.translate(newX, newY).scale(newZoom); } + getZoomTransform(zoomFactor: number): ZoomTransform { + const previousZoom = this.transform.k; + const newZoom = Math.max( + Math.min(zoomFactor * previousZoom, this._settings.maxZoom), + this._settings.minZoom + ); + return zoomIdentity.scale(newZoom); + } + + getDragLeftTransform(draggingFactor: number): ZoomTransform { + const currentTransform = this.transform; + const newX = currentTransform.x + draggingFactor; + return zoomIdentity + .translate(newX, currentTransform.y) + .scale(currentTransform.k); + } + + getDragRightTransform(draggingFactor: number): ZoomTransform { + const currentTransform = this.transform; + const newX = currentTransform.x - draggingFactor; + return zoomIdentity + .translate(newX, currentTransform.y) + .scale(currentTransform.k); + } + + getDragUpTransform(draggingFactor: number): ZoomTransform { + const currentTransform = this.transform; + const newY = currentTransform.y + draggingFactor; + return zoomIdentity + .translate(currentTransform.x, newY) + .scale(currentTransform.k); + } + + getDragDownTransform(draggingFactor: number): ZoomTransform { + const currentTransform = this.transform; + const newY = currentTransform.y - draggingFactor; + return zoomIdentity + .translate(currentTransform.x, newY) + .scale(currentTransform.k); + } + getSimulationPosition(canvasPoint: IPosition): IPosition { // By default, the canvas is translated by (width/2, height/2) to center the graph. // The simulation is not, it's starting coordinates are at (0, 0). diff --git a/src/renderer/shared.ts b/src/renderer/shared.ts index fe945b0..371675c 100644 --- a/src/renderer/shared.ts +++ b/src/renderer/shared.ts @@ -59,6 +59,16 @@ export interface IRenderer extends IEm getFitZoomTransform(graph: IGraph): ZoomTransform; + getZoomTransform(zoomFactor: number): ZoomTransform; + + getDragLeftTransform(draggingFactor: number): ZoomTransform; + + getDragRightTransform(draggingFactor: number): ZoomTransform; + + getDragUpTransform(draggingFactor: number): ZoomTransform; + + getDragDownTransform(draggingFactor: number): ZoomTransform; + getSimulationPosition(canvasPoint: IPosition): IPosition; /** diff --git a/src/renderer/webgl/webgl-renderer.ts b/src/renderer/webgl/webgl-renderer.ts index 9f7115f..f711dbb 100644 --- a/src/renderer/webgl/webgl-renderer.ts +++ b/src/renderer/webgl/webgl-renderer.ts @@ -66,6 +66,30 @@ export class WebGLRenderer extends Emi throw new Error('Method not implemented.'); } + getZoomTransform(zoomFactor: number): ZoomTransform { + console.log("zoomFactor:", zoomFactor); + throw new Error("Method not implemented."); + } + getDragLeftTransform(draggingFactor: number): ZoomTransform { + console.log("draggingFactor:", draggingFactor); + throw new Error("Method not implemented."); + } + + getDragRightTransform(draggingFactor: number): ZoomTransform { + console.log("draggingFactor:", draggingFactor); + throw new Error("Method not implemented."); + } + + getDragUpTransform(draggingFactor: number): ZoomTransform { + console.log("draggingFactor:", draggingFactor); + throw new Error("Method not implemented."); + } + + getDragDownTransform(draggingFactor: number): ZoomTransform { + console.log("draggingFactor:", draggingFactor); + throw new Error("Method not implemented."); + } + getSimulationPosition(canvasPoint: IPosition): IPosition { console.log('canvasPoint:', canvasPoint); throw new Error('Method not implemented.'); diff --git a/src/views/orb-view.ts b/src/views/orb-view.ts index 283730b..1b41b3c 100644 --- a/src/views/orb-view.ts +++ b/src/views/orb-view.ts @@ -4,7 +4,7 @@ import { easeLinear } from 'd3-ease'; // @ts-ignore: Transition needs to be imported in order to be available to d3 import transition from 'd3-transition'; /* eslint-enable @typescript-eslint/no-unused-vars */ -import { D3ZoomEvent, zoom, ZoomBehavior } from 'd3-zoom'; +import { D3ZoomEvent, zoom, ZoomBehavior, ZoomTransform } from 'd3-zoom'; import { select } from 'd3-selection'; import { IPosition, isEqualPosition } from '../common'; import { ISimulator, SimulatorFactory } from '../simulator'; @@ -26,8 +26,18 @@ import { isBoolean } from '../utils/type.utils'; export interface IGraphInteractionSettings { isDragEnabled: boolean; isZoomEnabled: boolean; + keyboard: { + isKeyboardEnabled: boolean; + zoomInFactor: number; + zoomOutFactor: number; + draggingFactor: number; + }; } +const DEFAULT_ZOOM_IN_FACTOR = 1.2; +const DEFAULT_ZOOM_OUT_FACTOR = 0.8; +const DEFAULT_DRAGGING_FACTOR = 25; + export interface IOrbViewSettings { getPosition?(node: INode): IPosition | undefined; simulation: Partial; @@ -100,6 +110,13 @@ export class OrbView implements IOrbVi isDragEnabled: true, isZoomEnabled: true, ...settings?.interaction, + keyboard: { + isKeyboardEnabled: false, + zoomInFactor: DEFAULT_ZOOM_IN_FACTOR, + zoomOutFactor: DEFAULT_ZOOM_OUT_FACTOR, + draggingFactor: DEFAULT_DRAGGING_FACTOR, + ...settings?.interaction?.keyboard, + }, }, }; @@ -149,6 +166,8 @@ export class OrbView implements IOrbVi .on('mousemove', this.mouseMoved) .on('contextmenu', this.mouseRightClicked) .on('dblclick.zoom', this.mouseDoubleClicked); + + document.addEventListener('keydown', this._handleKeyDown); this._simulator = SimulatorFactory.getSimulator(); this._simulator.on(SimulatorEventType.SIMULATION_START, () => { @@ -237,6 +256,13 @@ export class OrbView implements IOrbVi // Update the internal isZoomEnabled setting based on the provided value this._settings.interaction.isZoomEnabled = settings.interaction.isZoomEnabled; } + + if (settings.interaction.keyboard) { + this._settings.interaction.keyboard = { + ...this._settings.interaction.keyboard, + ...settings.interaction.keyboard, + }; + } } } @@ -364,6 +390,80 @@ export class OrbView implements IOrbVi }, 1); }; + _handleKeyDown = (event: KeyboardEvent) => { + if (!this._settings.interaction.keyboard?.isKeyboardEnabled) { + return; + } + + switch (event.key) { + case "-": { + const zoomOutFactor = this._settings.interaction.keyboard.zoomOutFactor; + this._zoomOut(zoomOutFactor); + break; + } + case "+": { + const zoomInFactor = this._settings.interaction.keyboard.zoomInFactor; + this._zoomIn(zoomInFactor); + break; + } + case "ArrowLeft": { + const draggingFactor = + this._settings.interaction.keyboard.draggingFactor; + const dragLeftTransform = + this._renderer.getDragLeftTransform(draggingFactor); + this._dragByArrowKey(dragLeftTransform); + break; + } + case "ArrowRight": { + const draggingFactor = + this._settings.interaction.keyboard.draggingFactor; + const dragRightTransform = + this._renderer.getDragRightTransform(draggingFactor); + this._dragByArrowKey(dragRightTransform); + break; + } + case "ArrowUp": { + const draggingFactor = + this._settings.interaction.keyboard.draggingFactor; + const dragUpTransform = + this._renderer.getDragUpTransform(draggingFactor); + this._dragByArrowKey(dragUpTransform); + break; + } + case "ArrowDown": { + const draggingFactor = + this._settings.interaction.keyboard.draggingFactor; + const dragDownTransform = + this._renderer.getDragDownTransform(draggingFactor); + this._dragByArrowKey(dragDownTransform); + break; + } + default: + break; + } + }; + + _zoomOut = (zoomOutFactor: number) => { + const transform = this._renderer.getZoomTransform(zoomOutFactor); + this._d3Zoom.scaleTo(select(this._canvas), transform.k); + }; + + _zoomIn = (zoomInFactor: number) => { + const transform = this._renderer.getZoomTransform(zoomInFactor); + this._d3Zoom.scaleTo(select(this._canvas), transform.k); + }; + + _dragByArrowKey = (transform: ZoomTransform) => { + select(this._canvas) + .transition() + .duration(this._settings.zoomFitTransitionMs) + .ease(easeLinear) + .call(this._d3Zoom.transform, transform) + .call(() => { + this._renderer.render(this._graph); + }); + }; + getCanvasMousePosition(event: MouseEvent): IPosition { const rect = this._canvas.getBoundingClientRect(); let x = event.clientX ?? event.pageX ?? event.x; From 291573bde7f195e9803e3161078603d1ca9fe79f Mon Sep 17 00:00:00 2001 From: Abhinav Singh Parmar Date: Sat, 19 Aug 2023 12:36:15 +0530 Subject: [PATCH 2/6] Chore: Apply linting to enhance code quality --- src/renderer/canvas/canvas-renderer.ts | 21 ++++---------- src/renderer/webgl/webgl-renderer.ts | 20 +++++++------- src/views/orb-view.ts | 38 ++++++++++---------------- 3 files changed, 30 insertions(+), 49 deletions(-) diff --git a/src/renderer/canvas/canvas-renderer.ts b/src/renderer/canvas/canvas-renderer.ts index b893ce9..590b293 100644 --- a/src/renderer/canvas/canvas-renderer.ts +++ b/src/renderer/canvas/canvas-renderer.ts @@ -247,43 +247,32 @@ export class CanvasRenderer extends Em getZoomTransform(zoomFactor: number): ZoomTransform { const previousZoom = this.transform.k; - const newZoom = Math.max( - Math.min(zoomFactor * previousZoom, this._settings.maxZoom), - this._settings.minZoom - ); + const newZoom = Math.max(Math.min(zoomFactor * previousZoom, this._settings.maxZoom), this._settings.minZoom); return zoomIdentity.scale(newZoom); } getDragLeftTransform(draggingFactor: number): ZoomTransform { const currentTransform = this.transform; const newX = currentTransform.x + draggingFactor; - return zoomIdentity - .translate(newX, currentTransform.y) - .scale(currentTransform.k); + return zoomIdentity.translate(newX, currentTransform.y).scale(currentTransform.k); } getDragRightTransform(draggingFactor: number): ZoomTransform { const currentTransform = this.transform; const newX = currentTransform.x - draggingFactor; - return zoomIdentity - .translate(newX, currentTransform.y) - .scale(currentTransform.k); + return zoomIdentity.translate(newX, currentTransform.y).scale(currentTransform.k); } getDragUpTransform(draggingFactor: number): ZoomTransform { const currentTransform = this.transform; const newY = currentTransform.y + draggingFactor; - return zoomIdentity - .translate(currentTransform.x, newY) - .scale(currentTransform.k); + return zoomIdentity.translate(currentTransform.x, newY).scale(currentTransform.k); } getDragDownTransform(draggingFactor: number): ZoomTransform { const currentTransform = this.transform; const newY = currentTransform.y - draggingFactor; - return zoomIdentity - .translate(currentTransform.x, newY) - .scale(currentTransform.k); + return zoomIdentity.translate(currentTransform.x, newY).scale(currentTransform.k); } getSimulationPosition(canvasPoint: IPosition): IPosition { diff --git a/src/renderer/webgl/webgl-renderer.ts b/src/renderer/webgl/webgl-renderer.ts index f711dbb..0767b78 100644 --- a/src/renderer/webgl/webgl-renderer.ts +++ b/src/renderer/webgl/webgl-renderer.ts @@ -67,27 +67,27 @@ export class WebGLRenderer extends Emi } getZoomTransform(zoomFactor: number): ZoomTransform { - console.log("zoomFactor:", zoomFactor); - throw new Error("Method not implemented."); + console.log('zoomFactor:', zoomFactor); + throw new Error('Method not implemented.'); } getDragLeftTransform(draggingFactor: number): ZoomTransform { - console.log("draggingFactor:", draggingFactor); - throw new Error("Method not implemented."); + console.log('draggingFactor:', draggingFactor); + throw new Error('Method not implemented.'); } getDragRightTransform(draggingFactor: number): ZoomTransform { - console.log("draggingFactor:", draggingFactor); - throw new Error("Method not implemented."); + console.log('draggingFactor:', draggingFactor); + throw new Error('Method not implemented.'); } getDragUpTransform(draggingFactor: number): ZoomTransform { - console.log("draggingFactor:", draggingFactor); - throw new Error("Method not implemented."); + console.log('draggingFactor:', draggingFactor); + throw new Error('Method not implemented.'); } getDragDownTransform(draggingFactor: number): ZoomTransform { - console.log("draggingFactor:", draggingFactor); - throw new Error("Method not implemented."); + console.log('draggingFactor:', draggingFactor); + throw new Error('Method not implemented.'); } getSimulationPosition(canvasPoint: IPosition): IPosition { diff --git a/src/views/orb-view.ts b/src/views/orb-view.ts index 1b41b3c..b724519 100644 --- a/src/views/orb-view.ts +++ b/src/views/orb-view.ts @@ -166,7 +166,7 @@ export class OrbView implements IOrbVi .on('mousemove', this.mouseMoved) .on('contextmenu', this.mouseRightClicked) .on('dblclick.zoom', this.mouseDoubleClicked); - + document.addEventListener('keydown', this._handleKeyDown); this._simulator = SimulatorFactory.getSimulator(); @@ -396,45 +396,37 @@ export class OrbView implements IOrbVi } switch (event.key) { - case "-": { + case '-': { const zoomOutFactor = this._settings.interaction.keyboard.zoomOutFactor; this._zoomOut(zoomOutFactor); break; } - case "+": { + case '+': { const zoomInFactor = this._settings.interaction.keyboard.zoomInFactor; this._zoomIn(zoomInFactor); break; } - case "ArrowLeft": { - const draggingFactor = - this._settings.interaction.keyboard.draggingFactor; - const dragLeftTransform = - this._renderer.getDragLeftTransform(draggingFactor); + case 'ArrowLeft': { + const draggingFactor = this._settings.interaction.keyboard.draggingFactor; + const dragLeftTransform = this._renderer.getDragLeftTransform(draggingFactor); this._dragByArrowKey(dragLeftTransform); break; } - case "ArrowRight": { - const draggingFactor = - this._settings.interaction.keyboard.draggingFactor; - const dragRightTransform = - this._renderer.getDragRightTransform(draggingFactor); + case 'ArrowRight': { + const draggingFactor = this._settings.interaction.keyboard.draggingFactor; + const dragRightTransform = this._renderer.getDragRightTransform(draggingFactor); this._dragByArrowKey(dragRightTransform); break; } - case "ArrowUp": { - const draggingFactor = - this._settings.interaction.keyboard.draggingFactor; - const dragUpTransform = - this._renderer.getDragUpTransform(draggingFactor); + case 'ArrowUp': { + const draggingFactor = this._settings.interaction.keyboard.draggingFactor; + const dragUpTransform = this._renderer.getDragUpTransform(draggingFactor); this._dragByArrowKey(dragUpTransform); break; } - case "ArrowDown": { - const draggingFactor = - this._settings.interaction.keyboard.draggingFactor; - const dragDownTransform = - this._renderer.getDragDownTransform(draggingFactor); + case 'ArrowDown': { + const draggingFactor = this._settings.interaction.keyboard.draggingFactor; + const dragDownTransform = this._renderer.getDragDownTransform(draggingFactor); this._dragByArrowKey(dragDownTransform); break; } From 8d388e3e3b0c942656e6d0a34e87e112d4accd7e Mon Sep 17 00:00:00 2001 From: Abhinav Singh Parmar Date: Sat, 19 Aug 2023 13:14:03 +0530 Subject: [PATCH 3/6] Refactor: Streamline canvas transformations and introduce enum for pan directions --- docs/view-default.md | 12 +++--- src/renderer/canvas/canvas-renderer.ts | 48 +++++++++++++---------- src/renderer/shared.ts | 9 +---- src/renderer/webgl/webgl-renderer.ts | 20 ++-------- src/views/orb-view.ts | 53 ++++++++++++-------------- 5 files changed, 64 insertions(+), 78 deletions(-) diff --git a/docs/view-default.md b/docs/view-default.md index 86cf991..d9f98cf 100644 --- a/docs/view-default.md +++ b/docs/view-default.md @@ -85,10 +85,10 @@ interface IOrbViewSettings { isDragEnabled: boolean; isZoomEnabled: boolean; keyboard: { - isKeyboardEnabled: boolean; + isEnabled: boolean; zoomInFactor: number; zoomOutFactor: number; - draggingFactor: number; + panFactor: number; }; }; // Other default view parameters @@ -164,10 +164,10 @@ const defaultSettings = { isDragEnabled: true; isZoomEnabled: true; keyboard: { - isKeyboardEnabled: true, + isEnabled: false, zoomInFactor: 1.2, zoomOutFactor: 0.8, - draggingFactor: 25 + panFactor: 25 } }, zoomFitTransitionMs: 200, @@ -342,10 +342,10 @@ The `interaction` property controls the interactivity options available to users * `isZoomEnabled` - This property controls the zooming behavior within the application. Setting it to `true` enables zooming, allowing users to interactively zoom in and out of the graph. Setting it to `false` disables zooming, restricting the user's ability to change the zoom level. * `keyboard` - This property allows you to define keyboard interaction settings: - * `isKeyboardEnabled` (boolean): Set this property to `true` to enable keyboard interaction within the graph visualization. When enabled, users can use keyboard shortcuts for certain interactions. + * `isEnabled` (boolean): Set this property to `true` to enable keyboard interaction within the graph visualization. When enabled, users can use keyboard shortcuts for certain interactions. * `zoomInFactor` (number): The zoom-in factor determines the extent to which the graph will zoom in when the user presses the zoom-in keyboard shortcut. * `zoomOutFactor` (number): The zoom-out factor determines the extent to which the graph will zoom out when the user presses the zoom-out keyboard shortcut. - * `draggingFactor` (number): The dragging factor specifies the speed at which nodes and edges will move when the user interacts with them using keyboard shortcuts for dragging. + * `panFactor` (number): The pan factor specifies the speed at which nodes and edges will move when the user interacts with them using keyboard shortcuts for dragging. By customizing the interaction property, you can enable or disable dragging and zooming features based on the needs and requirements of your graph visualization application. Additionally, you can also choose to enable keyboard interactions for further user control and convenience. e.g: diff --git a/src/renderer/canvas/canvas-renderer.ts b/src/renderer/canvas/canvas-renderer.ts index 590b293..fe9275d 100644 --- a/src/renderer/canvas/canvas-renderer.ts +++ b/src/renderer/canvas/canvas-renderer.ts @@ -25,6 +25,13 @@ const DEBUG_GREEN = '#3CFF33'; const DEBUG_BLUE = '#3383FF'; const DEBUG_PINK = '#F333FF'; +export enum PanDirectionType { + UP, + DOWN, + LEFT, + RIGHT, +} + export class CanvasRenderer extends Emitter implements IRenderer { // Contains the HTML5 Canvas element which is used for drawing nodes and edges. private readonly _context: CanvasRenderingContext2D; @@ -251,28 +258,29 @@ export class CanvasRenderer extends Em return zoomIdentity.scale(newZoom); } - getDragLeftTransform(draggingFactor: number): ZoomTransform { - const currentTransform = this.transform; - const newX = currentTransform.x + draggingFactor; - return zoomIdentity.translate(newX, currentTransform.y).scale(currentTransform.k); - } - - getDragRightTransform(draggingFactor: number): ZoomTransform { + getPanTransform(panDirectionType: PanDirectionType, factor: number): ZoomTransform { const currentTransform = this.transform; - const newX = currentTransform.x - draggingFactor; - return zoomIdentity.translate(newX, currentTransform.y).scale(currentTransform.k); - } - - getDragUpTransform(draggingFactor: number): ZoomTransform { - const currentTransform = this.transform; - const newY = currentTransform.y + draggingFactor; - return zoomIdentity.translate(currentTransform.x, newY).scale(currentTransform.k); - } + let newX = currentTransform.x; + let newY = currentTransform.y; + + switch (panDirectionType) { + case PanDirectionType.UP: + newY += factor; + break; + case PanDirectionType.DOWN: + newY -= factor; + break; + case PanDirectionType.LEFT: + newX += factor; + break; + case PanDirectionType.RIGHT: + newX -= factor; + break; + default: + break; + } - getDragDownTransform(draggingFactor: number): ZoomTransform { - const currentTransform = this.transform; - const newY = currentTransform.y - draggingFactor; - return zoomIdentity.translate(currentTransform.x, newY).scale(currentTransform.k); + return zoomIdentity.translate(newX, newY).scale(currentTransform.k); } getSimulationPosition(canvasPoint: IPosition): IPosition { diff --git a/src/renderer/shared.ts b/src/renderer/shared.ts index 371675c..2054cc3 100644 --- a/src/renderer/shared.ts +++ b/src/renderer/shared.ts @@ -4,6 +4,7 @@ import { INodeBase } from '../models/node'; import { IEdgeBase } from '../models/edge'; import { IGraph } from '../models/graph'; import { IEmitter } from '../utils/emitter.utils'; +import { PanDirectionType } from './canvas/canvas-renderer'; export enum RendererType { CANVAS = 'canvas', @@ -61,13 +62,7 @@ export interface IRenderer extends IEm getZoomTransform(zoomFactor: number): ZoomTransform; - getDragLeftTransform(draggingFactor: number): ZoomTransform; - - getDragRightTransform(draggingFactor: number): ZoomTransform; - - getDragUpTransform(draggingFactor: number): ZoomTransform; - - getDragDownTransform(draggingFactor: number): ZoomTransform; + getPanTransform(panDirectionType: PanDirectionType, factor: number): ZoomTransform; getSimulationPosition(canvasPoint: IPosition): IPosition; diff --git a/src/renderer/webgl/webgl-renderer.ts b/src/renderer/webgl/webgl-renderer.ts index 0767b78..4dad4d9 100644 --- a/src/renderer/webgl/webgl-renderer.ts +++ b/src/renderer/webgl/webgl-renderer.ts @@ -13,6 +13,7 @@ import { IRendererSettings, } from '../shared'; import { copyObject } from '../../utils/object.utils'; +import { PanDirectionType } from '../canvas/canvas-renderer'; export class WebGLRenderer extends Emitter implements IRenderer { // Contains the HTML5 Canvas element which is used for drawing nodes and edges. @@ -70,23 +71,10 @@ export class WebGLRenderer extends Emi console.log('zoomFactor:', zoomFactor); throw new Error('Method not implemented.'); } - getDragLeftTransform(draggingFactor: number): ZoomTransform { - console.log('draggingFactor:', draggingFactor); - throw new Error('Method not implemented.'); - } - - getDragRightTransform(draggingFactor: number): ZoomTransform { - console.log('draggingFactor:', draggingFactor); - throw new Error('Method not implemented.'); - } - - getDragUpTransform(draggingFactor: number): ZoomTransform { - console.log('draggingFactor:', draggingFactor); - throw new Error('Method not implemented.'); - } - getDragDownTransform(draggingFactor: number): ZoomTransform { - console.log('draggingFactor:', draggingFactor); + getPanTransform(panDirectionType: PanDirectionType, factor: number): ZoomTransform { + console.log('panDirectionType:', panDirectionType); + console.log('factor:', factor); throw new Error('Method not implemented.'); } diff --git a/src/views/orb-view.ts b/src/views/orb-view.ts index b724519..921d1af 100644 --- a/src/views/orb-view.ts +++ b/src/views/orb-view.ts @@ -22,21 +22,22 @@ import { setupContainer } from '../utils/html.utils'; import { SimulatorEventType } from '../simulator/shared'; import { getDefaultGraphStyle } from '../models/style'; import { isBoolean } from '../utils/type.utils'; +import { PanDirectionType } from '../renderer/canvas/canvas-renderer'; export interface IGraphInteractionSettings { isDragEnabled: boolean; isZoomEnabled: boolean; keyboard: { - isKeyboardEnabled: boolean; + isEnabled: boolean; zoomInFactor: number; zoomOutFactor: number; - draggingFactor: number; + panFactor: number; }; } const DEFAULT_ZOOM_IN_FACTOR = 1.2; const DEFAULT_ZOOM_OUT_FACTOR = 0.8; -const DEFAULT_DRAGGING_FACTOR = 25; +const DEFAULT_PAN_FACTOR = 25; export interface IOrbViewSettings { getPosition?(node: INode): IPosition | undefined; @@ -111,10 +112,10 @@ export class OrbView implements IOrbVi isZoomEnabled: true, ...settings?.interaction, keyboard: { - isKeyboardEnabled: false, + isEnabled: false, zoomInFactor: DEFAULT_ZOOM_IN_FACTOR, zoomOutFactor: DEFAULT_ZOOM_OUT_FACTOR, - draggingFactor: DEFAULT_DRAGGING_FACTOR, + panFactor: DEFAULT_PAN_FACTOR, ...settings?.interaction?.keyboard, }, }, @@ -290,16 +291,9 @@ export class OrbView implements IOrbVi recenter(onRendered?: () => void) { const fitZoomTransform = this._renderer.getFitZoomTransform(this._graph); - - select(this._canvas) - .transition() - .duration(this._settings.zoomFitTransitionMs) - .ease(easeLinear) - .call(this._d3Zoom.transform, fitZoomTransform) - .call(() => { - this._renderer.render(this._graph); - onRendered?.(); - }); + this._applyTransformation(fitZoomTransform, () => { + onRendered?.(); + }); } destroy() { @@ -391,7 +385,7 @@ export class OrbView implements IOrbVi }; _handleKeyDown = (event: KeyboardEvent) => { - if (!this._settings.interaction.keyboard?.isKeyboardEnabled) { + if (!this._settings.interaction.keyboard?.isEnabled) { return; } @@ -407,27 +401,27 @@ export class OrbView implements IOrbVi break; } case 'ArrowLeft': { - const draggingFactor = this._settings.interaction.keyboard.draggingFactor; - const dragLeftTransform = this._renderer.getDragLeftTransform(draggingFactor); - this._dragByArrowKey(dragLeftTransform); + const panFactor = this._settings.interaction.keyboard.panFactor; + const dragLeftTransform = this._renderer.getPanTransform(PanDirectionType.LEFT, panFactor); + this._applyTransformation(dragLeftTransform); break; } case 'ArrowRight': { - const draggingFactor = this._settings.interaction.keyboard.draggingFactor; - const dragRightTransform = this._renderer.getDragRightTransform(draggingFactor); - this._dragByArrowKey(dragRightTransform); + const panFactor = this._settings.interaction.keyboard.panFactor; + const dragRightTransform = this._renderer.getPanTransform(PanDirectionType.RIGHT, panFactor); + this._applyTransformation(dragRightTransform); break; } case 'ArrowUp': { - const draggingFactor = this._settings.interaction.keyboard.draggingFactor; - const dragUpTransform = this._renderer.getDragUpTransform(draggingFactor); - this._dragByArrowKey(dragUpTransform); + const panFactor = this._settings.interaction.keyboard.panFactor; + const dragUpTransform = this._renderer.getPanTransform(PanDirectionType.UP, panFactor); + this._applyTransformation(dragUpTransform); break; } case 'ArrowDown': { - const draggingFactor = this._settings.interaction.keyboard.draggingFactor; - const dragDownTransform = this._renderer.getDragDownTransform(draggingFactor); - this._dragByArrowKey(dragDownTransform); + const panFactor = this._settings.interaction.keyboard.panFactor; + const dragDownTransform = this._renderer.getPanTransform(PanDirectionType.DOWN, panFactor); + this._applyTransformation(dragDownTransform); break; } default: @@ -445,7 +439,7 @@ export class OrbView implements IOrbVi this._d3Zoom.scaleTo(select(this._canvas), transform.k); }; - _dragByArrowKey = (transform: ZoomTransform) => { + _applyTransformation = (transform: ZoomTransform, callback?: () => void) => { select(this._canvas) .transition() .duration(this._settings.zoomFitTransitionMs) @@ -453,6 +447,7 @@ export class OrbView implements IOrbVi .call(this._d3Zoom.transform, transform) .call(() => { this._renderer.render(this._graph); + callback?.(); }); }; From 5948934ea893e0c8fff8e38d78c8dec32626e312 Mon Sep 17 00:00:00 2001 From: Abhinav Singh Parmar Date: Sun, 27 Aug 2023 18:06:35 +0530 Subject: [PATCH 4/6] Refactor: Optimize shared renderer logic and improve variable usage --- src/renderer/canvas/canvas-renderer.ts | 8 +------- src/renderer/shared.ts | 8 +++++++- src/renderer/webgl/webgl-renderer.ts | 2 +- src/views/orb-view.ts | 17 +++++++++-------- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/renderer/canvas/canvas-renderer.ts b/src/renderer/canvas/canvas-renderer.ts index fe9275d..9df95ae 100644 --- a/src/renderer/canvas/canvas-renderer.ts +++ b/src/renderer/canvas/canvas-renderer.ts @@ -12,6 +12,7 @@ import { DEFAULT_RENDERER_WIDTH, IRenderer, IRendererSettings, + PanDirectionType, RendererEvents as RE, RenderEventType, } from '../shared'; @@ -25,13 +26,6 @@ const DEBUG_GREEN = '#3CFF33'; const DEBUG_BLUE = '#3383FF'; const DEBUG_PINK = '#F333FF'; -export enum PanDirectionType { - UP, - DOWN, - LEFT, - RIGHT, -} - export class CanvasRenderer extends Emitter implements IRenderer { // Contains the HTML5 Canvas element which is used for drawing nodes and edges. private readonly _context: CanvasRenderingContext2D; diff --git a/src/renderer/shared.ts b/src/renderer/shared.ts index 2054cc3..c47c60d 100644 --- a/src/renderer/shared.ts +++ b/src/renderer/shared.ts @@ -4,7 +4,6 @@ import { INodeBase } from '../models/node'; import { IEdgeBase } from '../models/edge'; import { IGraph } from '../models/graph'; import { IEmitter } from '../utils/emitter.utils'; -import { PanDirectionType } from './canvas/canvas-renderer'; export enum RendererType { CANVAS = 'canvas', @@ -16,6 +15,13 @@ export enum RenderEventType { RENDER_END = 'render-end', } +export enum PanDirectionType { + UP, + DOWN, + LEFT, + RIGHT, +} + export interface IRendererSettings { fps: number; minZoom: number; diff --git a/src/renderer/webgl/webgl-renderer.ts b/src/renderer/webgl/webgl-renderer.ts index 4dad4d9..7d1b6c6 100644 --- a/src/renderer/webgl/webgl-renderer.ts +++ b/src/renderer/webgl/webgl-renderer.ts @@ -11,9 +11,9 @@ import { IRenderer, RendererEvents as RE, IRendererSettings, + PanDirectionType, } from '../shared'; import { copyObject } from '../../utils/object.utils'; -import { PanDirectionType } from '../canvas/canvas-renderer'; export class WebGLRenderer extends Emitter implements IRenderer { // Contains the HTML5 Canvas element which is used for drawing nodes and edges. diff --git a/src/views/orb-view.ts b/src/views/orb-view.ts index 921d1af..bf65273 100644 --- a/src/views/orb-view.ts +++ b/src/views/orb-view.ts @@ -16,13 +16,18 @@ import { DefaultEventStrategy, IEventStrategy, IEventStrategySettings } from '.. import { ID3SimulatorEngineSettings } from '../simulator/engine/d3-simulator-engine'; import { copyObject } from '../utils/object.utils'; import { OrbEmitter, OrbEventType } from '../events'; -import { IRenderer, RenderEventType, IRendererSettingsInit, IRendererSettings } from '../renderer/shared'; +import { + IRenderer, + RenderEventType, + IRendererSettingsInit, + IRendererSettings, + PanDirectionType, +} from '../renderer/shared'; import { RendererFactory } from '../renderer/factory'; import { setupContainer } from '../utils/html.utils'; import { SimulatorEventType } from '../simulator/shared'; import { getDefaultGraphStyle } from '../models/style'; import { isBoolean } from '../utils/type.utils'; -import { PanDirectionType } from '../renderer/canvas/canvas-renderer'; export interface IGraphInteractionSettings { isDragEnabled: boolean; @@ -389,37 +394,33 @@ export class OrbView implements IOrbVi return; } + const { zoomOutFactor, zoomInFactor, panFactor } = this._settings.interaction.keyboard; + switch (event.key) { case '-': { - const zoomOutFactor = this._settings.interaction.keyboard.zoomOutFactor; this._zoomOut(zoomOutFactor); break; } case '+': { - const zoomInFactor = this._settings.interaction.keyboard.zoomInFactor; this._zoomIn(zoomInFactor); break; } case 'ArrowLeft': { - const panFactor = this._settings.interaction.keyboard.panFactor; const dragLeftTransform = this._renderer.getPanTransform(PanDirectionType.LEFT, panFactor); this._applyTransformation(dragLeftTransform); break; } case 'ArrowRight': { - const panFactor = this._settings.interaction.keyboard.panFactor; const dragRightTransform = this._renderer.getPanTransform(PanDirectionType.RIGHT, panFactor); this._applyTransformation(dragRightTransform); break; } case 'ArrowUp': { - const panFactor = this._settings.interaction.keyboard.panFactor; const dragUpTransform = this._renderer.getPanTransform(PanDirectionType.UP, panFactor); this._applyTransformation(dragUpTransform); break; } case 'ArrowDown': { - const panFactor = this._settings.interaction.keyboard.panFactor; const dragDownTransform = this._renderer.getPanTransform(PanDirectionType.DOWN, panFactor); this._applyTransformation(dragDownTransform); break; From 837d4771ce8d220bbf74b1925ec6f2ffb644bf5a Mon Sep 17 00:00:00 2001 From: Abhinav Singh Parmar Date: Sun, 27 Aug 2023 18:11:44 +0530 Subject: [PATCH 5/6] Enhancement: Introduce transition options for generic transformation --- docs/view-default.md | 6 +++++- src/views/orb-view.ts | 32 +++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/docs/view-default.md b/docs/view-default.md index d9f98cf..58b2f4c 100644 --- a/docs/view-default.md +++ b/docs/view-default.md @@ -89,6 +89,7 @@ interface IOrbViewSettings { zoomInFactor: number; zoomOutFactor: number; panFactor: number; + transitionMs: number; }; }; // Other default view parameters @@ -167,7 +168,8 @@ const defaultSettings = { isEnabled: false, zoomInFactor: 1.2, zoomOutFactor: 0.8, - panFactor: 25 + panFactor: 25, + transitionMs: 100 } }, zoomFitTransitionMs: 200, @@ -346,6 +348,8 @@ The `interaction` property controls the interactivity options available to users * `zoomInFactor` (number): The zoom-in factor determines the extent to which the graph will zoom in when the user presses the zoom-in keyboard shortcut. * `zoomOutFactor` (number): The zoom-out factor determines the extent to which the graph will zoom out when the user presses the zoom-out keyboard shortcut. * `panFactor` (number): The pan factor specifies the speed at which nodes and edges will move when the user interacts with them using keyboard shortcuts for dragging. + * `transitionMs` (number): The transitionMs parameter defines the duration of the transition animation in milliseconds (ms). It controls how long the animation takes to smoothly transform the visual state from one point to another. + By customizing the interaction property, you can enable or disable dragging and zooming features based on the needs and requirements of your graph visualization application. Additionally, you can also choose to enable keyboard interactions for further user control and convenience. e.g: diff --git a/src/views/orb-view.ts b/src/views/orb-view.ts index bf65273..079316a 100644 --- a/src/views/orb-view.ts +++ b/src/views/orb-view.ts @@ -37,12 +37,14 @@ export interface IGraphInteractionSettings { zoomInFactor: number; zoomOutFactor: number; panFactor: number; + transitionMs: number; }; } const DEFAULT_ZOOM_IN_FACTOR = 1.2; const DEFAULT_ZOOM_OUT_FACTOR = 0.8; const DEFAULT_PAN_FACTOR = 25; +const DEFAULT_TRANSITION_MS = 200; export interface IOrbViewSettings { getPosition?(node: INode): IPosition | undefined; @@ -57,6 +59,11 @@ export interface IOrbViewSettings { areCollapsedContainerDimensionsAllowed: boolean; } +export interface IApplyTransitionOptions { + transitionMs: number; + callback: () => void; +} + export type IOrbViewSettingsInit = Omit< Partial>, 'render' @@ -121,6 +128,7 @@ export class OrbView implements IOrbVi zoomInFactor: DEFAULT_ZOOM_IN_FACTOR, zoomOutFactor: DEFAULT_ZOOM_OUT_FACTOR, panFactor: DEFAULT_PAN_FACTOR, + transitionMs: 100, ...settings?.interaction?.keyboard, }, }, @@ -296,9 +304,9 @@ export class OrbView implements IOrbVi recenter(onRendered?: () => void) { const fitZoomTransform = this._renderer.getFitZoomTransform(this._graph); - this._applyTransformation(fitZoomTransform, () => { - onRendered?.(); - }); + const transitionMs = this._settings.zoomFitTransitionMs; + + this._applyTransformation(fitZoomTransform, { transitionMs, callback: onRendered }); } destroy() { @@ -394,7 +402,7 @@ export class OrbView implements IOrbVi return; } - const { zoomOutFactor, zoomInFactor, panFactor } = this._settings.interaction.keyboard; + const { zoomOutFactor, zoomInFactor, panFactor, transitionMs } = this._settings.interaction.keyboard; switch (event.key) { case '-': { @@ -407,22 +415,22 @@ export class OrbView implements IOrbVi } case 'ArrowLeft': { const dragLeftTransform = this._renderer.getPanTransform(PanDirectionType.LEFT, panFactor); - this._applyTransformation(dragLeftTransform); + this._applyTransformation(dragLeftTransform, { transitionMs: transitionMs }); break; } case 'ArrowRight': { const dragRightTransform = this._renderer.getPanTransform(PanDirectionType.RIGHT, panFactor); - this._applyTransformation(dragRightTransform); + this._applyTransformation(dragRightTransform, { transitionMs: transitionMs }); break; } case 'ArrowUp': { const dragUpTransform = this._renderer.getPanTransform(PanDirectionType.UP, panFactor); - this._applyTransformation(dragUpTransform); + this._applyTransformation(dragUpTransform, { transitionMs: transitionMs }); break; } case 'ArrowDown': { const dragDownTransform = this._renderer.getPanTransform(PanDirectionType.DOWN, panFactor); - this._applyTransformation(dragDownTransform); + this._applyTransformation(dragDownTransform, { transitionMs: transitionMs }); break; } default: @@ -440,15 +448,17 @@ export class OrbView implements IOrbVi this._d3Zoom.scaleTo(select(this._canvas), transform.k); }; - _applyTransformation = (transform: ZoomTransform, callback?: () => void) => { + _applyTransformation = (transform: ZoomTransform, options?: Partial) => { + const transitionMs = options?.transitionMs ?? DEFAULT_TRANSITION_MS; + select(this._canvas) .transition() - .duration(this._settings.zoomFitTransitionMs) + .duration(transitionMs) .ease(easeLinear) .call(this._d3Zoom.transform, transform) .call(() => { this._renderer.render(this._graph); - callback?.(); + options?.callback?.(); }); }; From 8bde61a14d2eae8540968a6812728812ffe156f0 Mon Sep 17 00:00:00 2001 From: Abhinav Singh Parmar Date: Fri, 1 Sep 2023 08:46:33 +0530 Subject: [PATCH 6/6] Update: Modified default value of transition --- src/views/orb-view.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/orb-view.ts b/src/views/orb-view.ts index 079316a..651e8ac 100644 --- a/src/views/orb-view.ts +++ b/src/views/orb-view.ts @@ -44,7 +44,7 @@ export interface IGraphInteractionSettings { const DEFAULT_ZOOM_IN_FACTOR = 1.2; const DEFAULT_ZOOM_OUT_FACTOR = 0.8; const DEFAULT_PAN_FACTOR = 25; -const DEFAULT_TRANSITION_MS = 200; +const DEFAULT_TRANSITION_MS = 0; export interface IOrbViewSettings { getPosition?(node: INode): IPosition | undefined;