Better controls on rects view.
Rect labels are clickable. Pan added via right click and drag, zoom added via scroll.
Bug: b/245914589
Test: npm run test:all
Change-Id: I49ffd26b1c735390960e6b523acf3070951c903d
diff --git a/tools/winscope-ng/src/viewers/components/rects/canvas_graphics.ts b/tools/winscope-ng/src/viewers/components/rects/canvas_graphics.ts
index f61fdb9..ab4bd54 100644
--- a/tools/winscope-ng/src/viewers/components/rects/canvas_graphics.ts
+++ b/tools/winscope-ng/src/viewers/components/rects/canvas_graphics.ts
@@ -16,60 +16,65 @@
import { Rectangle } from "viewers/common/rectangle";
import * as THREE from "three";
import { CSS2DRenderer, CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
+import { ViewerEvents } from "viewers/common/viewer_events";
export class CanvasGraphics {
constructor() {
//set up camera
- const left = -this.cameraHalfWidth,
- right = this.cameraHalfWidth,
- top = this.cameraHalfHeight,
- bottom = -this.cameraHalfHeight,
- near = 0.001,
- far = 100;
+ const left = -this.CAMERA_HALF_WIDTH,
+ right = this.CAMERA_HALF_WIDTH,
+ top = this.CAMERA_HALF_HEIGHT,
+ bottom = -this.CAMERA_HALF_HEIGHT,
+ near = 0.001,
+ far = 100;
this.camera = new THREE.OrthographicCamera(
- left,right,top,bottom,near,far
+ left, right, top, bottom, near, far
);
+ this.resetCamera();
}
- public initialise(canvas: HTMLCanvasElement, canvasContainer: Element) {
+ public initialiseCanvas(canvas: HTMLCanvasElement, canvasContainer: Element) {
// initialise canvas
this.canvas = canvas;
this.canvasContainer = canvasContainer;
+ this.enableOrbitControls();
}
public refreshCanvas() {
- //set canvas size
- this.canvas!.style.width = "100%";
- this.canvas!.style.height = "40rem";
+ if (!this.canvas) {
+ return;
+ }
- this.camera.position.set(this.xCameraPos, Math.abs(this.xCameraPos), 6);
- this.camera.lookAt(0, 0, 0);
- this.camera.zoom = this.camZoom;
- this.camera.updateProjectionMatrix();
+ //set canvas size
+ this.canvas.style.width = "100%";
+ this.canvas.style.height = "40rem";
+
+ this.orbit?.reset();
// scene
- const scene = new THREE.Scene();
+ this.scene = new THREE.Scene();
// renderers
- const renderer = new THREE.WebGLRenderer({
+ this.renderer = new THREE.WebGLRenderer({
antialias: true,
canvas: this.canvas,
alpha: true
});
- let labelRenderer: CSS2DRenderer;
+
if (this.canvasContainer && this.canvasContainer.querySelector(".labels-canvas")) {
- labelRenderer = new CSS2DRenderer({
+ this.labelRenderer = new CSS2DRenderer({
element: this.canvasContainer.querySelector(".labels-canvas")! as HTMLElement
});
} else {
- labelRenderer = new CSS2DRenderer();
- labelRenderer.domElement.style.position = "absolute";
- labelRenderer.domElement.style.top = "0px";
- labelRenderer.domElement.style.width = "100%";
- labelRenderer.domElement.style.height = "40rem";
- labelRenderer.domElement.className = "labels-canvas";
- labelRenderer.domElement.style.pointerEvents = "none";
- this.canvasContainer?.appendChild(labelRenderer.domElement);
+ this.labelRenderer = new CSS2DRenderer();
+ this.labelRenderer.domElement.style.position = "absolute";
+ this.labelRenderer.domElement.style.top = "0px";
+ this.labelRenderer.domElement.style.width = "100%";
+ this.labelRenderer.domElement.style.height = "40rem";
+ this.labelRenderer.domElement.className = "labels-canvas";
+ this.labelRenderer.domElement.style.pointerEvents = "none";
+ this.canvasContainer?.appendChild(this.labelRenderer.domElement);
}
// set various factors for shading and shifting
@@ -78,8 +83,8 @@
const numberOfVisibleRects = this.rects.filter(rect => rect.isVisible).length;
const numberOfNonVisibleRects = this.rects.filter(rect => !rect.isVisible).length;
- const zShift = numberOfRects*this.layerSeparation;
- let xShift = 0, yShift = 3.25, labelYShift = 0;
+ const zShift = numberOfRects * this.layerSeparation;
+ let xShift = 0, yShift = 3.5, labelYShift = 0;
if (this.isLandscape) {
xShift = 1;
@@ -90,7 +95,7 @@
const lowestY = Math.min(...this.rects.map(rect => {
const y = rect.topLeft.y - rect.height + this.lowestYShift;
if (this.isLandscape) {
- if (y<0) {
+ if (y < 0) {
return 0;
} else if (y > 2) {
return 2;
@@ -108,24 +113,41 @@
numberOfNonVisibleRects,
nonVisibleDarkFactor,
numberOfRects,
- scene,
xShift,
yShift,
zShift,
lowestY
);
- // const axesHelper = new THREE.AxesHelper(1);
- // const gridHelper = new THREE.GridHelper(5);
- // scene.add(axesHelper, gridHelper)
+ this.renderer.setSize(this.canvas!.clientWidth, this.canvas!.clientHeight);
+ this.renderer.setPixelRatio(window.devicePixelRatio);
+ this.renderer.compile(this.scene, this.camera);
+ this.renderer.render(this.scene, this.camera);
- renderer.setSize(this.canvas!.clientWidth, this.canvas!.clientHeight);
- renderer.setPixelRatio(window.devicePixelRatio);
- renderer.compile(scene, this.camera);
- renderer.render(scene, this.camera);
+ this.labelRenderer.setSize(this.canvas!.clientWidth, this.canvas!.clientHeight);
+ this.labelRenderer.render(this.scene, this.camera);
+ }
- labelRenderer.setSize(this.canvas!.clientWidth, this.canvas!.clientHeight);
- labelRenderer.render(scene, this.camera);
+ public enableOrbitControls() {
+ this.orbit = new OrbitControls(this.camera, this.canvas);
+ this.orbit.enablePan = true;
+ this.orbit.enableDamping = true;
+ this.orbit.enableZoom = true;
+ this.orbit.maxZoom = this.MAX_ZOOM;
+ this.orbit.minZoom = this.MIN_ZOOM;
+ this.orbit.panSpeed = this.PAN_SPEED;
+ this.orbit.mouseButtons = { RIGHT: THREE.MOUSE.PAN };
+ this.orbit.addEventListener("change", () => {
+ this.fontSize = this.camera.zoom * this.INIT_FONT_SIZE;
+ this.updateLabelsFontSize();
+ if (this.scene && this.renderer && this.labelRenderer) {
+ this.clearLabelElements();
+ this.renderer.compile(this.scene, this.camera);
+ this.renderer.render(this.scene, this.camera);
+ this.labelRenderer.render(this.scene, this.camera);
+ this.orbit?.saveState();
+ }
+ });
}
public getCamera() {
@@ -145,7 +167,7 @@
}
public getXCameraPos() {
- return this.xCameraPos;
+ return this.camera.position.x;
}
public getShowVirtualDisplays() {
@@ -157,10 +179,11 @@
}
public updateRotation(userInput: number) {
- this.xCameraPos = userInput;
- this.camZoom = userInput/4 * 0.2 + 0.9;
- this.labelShift = userInput/4 * this.maxLabelShift;
- this.lowestYShift = Math.abs(userInput)/4 + 2;
+ this.camera.position.x = userInput;
+ this.camera.position.y = Math.abs(userInput);
+ this.labelShift = userInput / 4 * this.MAX_LABEL_SHIFT;
+ this.lowestYShift = Math.abs(userInput) / 4 + 2;
+ this.updateCameraAndControls();
}
public updateHighlightedItems(newItems: Array<string>) {
@@ -183,24 +206,54 @@
this.showVirtualDisplays = show;
}
- public updateZoom(isZoomIn: boolean) {
- if (isZoomIn && this.camZoom < 2) {
- this.labelXFactor -= 0.001;
- this.camZoom += this.camZoomFactor * 1.5;
- } else if (!isZoomIn && this.camZoom > 0.5) {
- this.labelXFactor += 0.001;
- this.camZoom -= this.camZoomFactor * 1.5;
+ public resetCamera() {
+ this.camera.lookAt(
+ this.INIT_TARGET.x,
+ this.INIT_TARGET.y,
+ this.INIT_TARGET.z
+ );
+ this.camera.position.set(
+ this.INIT_CAMERA_POS.x,
+ this.INIT_CAMERA_POS.y,
+ this.INIT_CAMERA_POS.z
+ );
+ this.camera.zoom = this.INIT_ZOOM;
+ this.fontSize = this.INIT_FONT_SIZE;
+ this.labelShift = this.MAX_LABEL_SHIFT;
+ this.lowestYShift = this.INIT_LOWEST_Y_SHIFT;
+ this.layerSeparation = this.INIT_LAYER_SEPARATION;
+ this.camera.updateProjectionMatrix();
+ if (this.canvas) {
+ this.enableOrbitControls();
}
+ this.refreshCanvas();
+ }
+
+ public updateZoom(isZoomIn: boolean) {
+ if (isZoomIn && this.camera.zoom < this.MAX_ZOOM) {
+ this.camera.zoom += this.CAM_ZOOM_FACTOR;
+ if (this.camera.zoom > this.MAX_ZOOM) this.camera.zoom = this.MAX_ZOOM;
+ } else if (!isZoomIn && this.camera.zoom > this.MIN_ZOOM) {
+ this.camera.zoom -= this.CAM_ZOOM_FACTOR;
+ if (this.camera.zoom < this.MIN_ZOOM) this.camera.zoom = this.MIN_ZOOM;
+ }
+ this.fontSize = this.camera.zoom * this.INIT_FONT_SIZE;
+ this.updateCameraAndControls();
+ }
+
+ private updateCameraAndControls() {
+ this.camera.updateProjectionMatrix();
+ this.orbit?.update();
+ this.orbit?.saveState();
}
private drawScene(
rectCounter: number,
numberOfVisibleRects: number,
- visibleDarkFactor:number,
+ visibleDarkFactor: number,
numberOfNonVisibleRects: number,
nonVisibleDarkFactor: number,
numberOfRects: number,
- scene: THREE.Scene,
xShift: number,
yShift: number,
zShift: number,
@@ -211,7 +264,7 @@
this.rects.forEach(rect => {
const mustNotDrawInVisibleView = this.visibleView && !rect.isVisible;
const mustNotDrawInXrayViewWithoutVirtualDisplays =
- !this.visibleView && !this.showVirtualDisplays && rect.isVirtual;
+ !this.visibleView && !this.showVirtualDisplays && rect.isVirtual;
if (mustNotDrawInVisibleView || mustNotDrawInXrayViewWithoutVirtualDisplays) {
rectCounter++;
return;
@@ -232,13 +285,13 @@
//set plane geometry and material
const geometry = new THREE.PlaneGeometry(rect.width, rect.height);
const planeRect = this.setPlaneMaterial(rect, geometry, planeColor, xShift, yShift, zShift);
- scene.add(planeRect);
+ this.scene?.add(planeRect);
zShift -= this.layerSeparation;
// bolder edges of each plane if in x-ray view
if (!this.visibleView) {
const edgeSegments = this.setEdgeMaterial(planeRect, geometry);
- scene.add(edgeSegments);
+ this.scene?.add(edgeSegments);
}
// only some rects are clickable
@@ -247,10 +300,10 @@
// labelling elements
if (rect.label.length > 0) {
const circle = this.setCircleMaterial(planeRect, rect);
- scene.add(circle);
+ this.scene?.add(circle);
const [line, rectLabel] = this.createLabel(rect, circle, lowestY, rectCounter);
- scene.add(line);
- scene.add(rectLabel);
+ this.scene?.add(line);
+ this.scene?.add(rectLabel);
}
rectCounter++;
@@ -272,9 +325,9 @@
opacity: this.visibleView ? 1 : 0.75,
transparent: true,
}));
- planeRect.position.y = rect.topLeft.y - rect.height/2 + yShift;
- planeRect.position.x = rect.topLeft.x + rect.width/2 - xShift;
- planeRect.position.z = zShift;
+ planeRect.position.y = rect.topLeft.y - rect.height / 2 + yShift;
+ planeRect.position.x = rect.topLeft.x + rect.width / 2 - xShift;
+ planeRect.position.z = zShift;
planeRect.name = `${rect.id}`;
return planeRect;
}
@@ -282,7 +335,7 @@
private setEdgeMaterial(planeRect: THREE.Mesh, geometry: THREE.PlaneGeometry) {
const edgeColor = 0x000000;
const edgeGeo = new THREE.EdgesGeometry(geometry);
- const edgeMaterial = new THREE.LineBasicMaterial({color: edgeColor, linewidth: 1});
+ const edgeMaterial = new THREE.LineBasicMaterial({ color: edgeColor, linewidth: 1 });
const edgeSegments = new THREE.LineSegments(
edgeGeo, edgeMaterial
);
@@ -295,7 +348,7 @@
const circleMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
const circle = new THREE.Mesh(labelCircle, circleMaterial);
circle.position.set(
- planeRect.position.x + rect.width/2 - 0.05,
+ planeRect.position.x + rect.width / 2 - 0.05,
planeRect.position.y,
planeRect.position.z + 0.05
);
@@ -307,34 +360,35 @@
[THREE.Line, CSS2DObject] {
const labelText = this.shortenText(rect.label);
const isGrey = !this.visibleView && !rect.isVisible;
- let cornerPos, endPos;
- const labelYSeparation = 0.3;
+ let endPos;
+ const labelYSeparation = 0.5;
if (this.isLandscape) {
- cornerPos = new THREE.Vector3(
- circle.position.x, lowestY - 0.5 - rectCounter*labelYSeparation, circle.position.z
+ endPos = new THREE.Vector3(
+ circle.position.x, lowestY - 0.5 - rectCounter * labelYSeparation, circle.position.z
);
} else {
- cornerPos = new THREE.Vector3(
- circle.position.x, lowestY + 0.5 - rectCounter*labelYSeparation, circle.position.z
+ endPos = new THREE.Vector3(
+ circle.position.x, lowestY + 0.5 - rectCounter * labelYSeparation, circle.position.z
);
}
- const linePoints = [circle.position, cornerPos];
- if (this.isLandscape && cornerPos.x > 0 || !this.isLandscape) {
- endPos = new THREE.Vector3(cornerPos.x - 0.75, cornerPos.y - 0.75*this.labelShift, cornerPos.z);
- } else {
- endPos = cornerPos;
- }
- linePoints.push(endPos);
+ const linePoints = [circle.position, endPos];
+
//add rectangle label
const rectLabelDiv: HTMLElement = document.createElement("div");
rectLabelDiv.className = "rect-label";
rectLabelDiv.textContent = labelText;
- rectLabelDiv.style.fontSize = "10px";
+ rectLabelDiv.style.fontSize = `${this.fontSize}` + "px";
+ rectLabelDiv.style.marginTop = "5px";
if (isGrey) {
rectLabelDiv.style.color = "grey";
}
+ rectLabelDiv.style.pointerEvents = "auto";
+ rectLabelDiv.style.cursor = "pointer";
+ rectLabelDiv.addEventListener(
+ "click", (event) => this.propagateUpdateHighlightedItems(event, rect.id)
+ );
const rectLabel = new CSS2DObject(rectLabelDiv);
rectLabel.name = rect.label;
@@ -350,28 +404,45 @@
if (this.isLandscape && endPos.x < 0) {
rectLabel.position.set(
- endPos.x + 0.6, endPos.y - 0.15, endPos.z - 0.6
+ endPos.x + 0.6, endPos.y, endPos.z - 0.6
);
} else {
rectLabel.position.set(
- endPos.x - labelWidth * this.labelXFactor,
- endPos.y - this.labelShift * labelWidth * this.labelXFactor,
+ endPos.x - labelWidth * this.LABEL_X_FACTOR,
+ endPos.y - this.labelShift * labelWidth * this.LABEL_X_FACTOR,
endPos.z
);
}
const lineGeo = new THREE.BufferGeometry().setFromPoints(linePoints);
- const lineMaterial = new THREE.LineBasicMaterial({color: isGrey ? 0x808080 : 0x000000});
+ const lineMaterial = new THREE.LineBasicMaterial({ color: isGrey ? 0x808080 : 0x000000 });
const line = new THREE.Line(lineGeo, lineMaterial);
return [line, rectLabel];
}
+ private propagateUpdateHighlightedItems(event: MouseEvent, newId: number) {
+ event.preventDefault();
+ const highlightedChangeEvent: CustomEvent = new CustomEvent(
+ ViewerEvents.HighlightedChange,
+ {
+ bubbles: true,
+ detail: { id: `${newId}` }
+ });
+ event.target?.dispatchEvent(highlightedChangeEvent);
+ }
+
+ private updateLabelsFontSize() {
+ document.querySelectorAll(".rect-label").forEach(
+ el => (el as HTMLElement).style.fontSize = `${this.fontSize}` + "px"
+ );
+ }
+
private clearLabelElements() {
document.querySelectorAll(".rect-label").forEach(el => el.remove());
}
- private colorMapping(scale: string, numberOfRects: number, darkFactor:number): THREE.Color {
+ private colorMapping(scale: string, numberOfRects: number, darkFactor: number): THREE.Color {
if (scale === "highlighted") {
return new THREE.Color(0xD2E3FC);
} else if (scale === "grey") {
@@ -379,14 +450,14 @@
//Separate RGB values between 0 and 1
const lower = 120;
const upper = 220;
- const darkness = ((upper-lower)*(numberOfRects-darkFactor)/numberOfRects + lower)/255;
+ const darkness = ((upper - lower) * (numberOfRects - darkFactor) / numberOfRects + lower) / 255;
return new THREE.Color(darkness, darkness, darkness);
} else if (scale === "green") {
// darkness of green rect depends on z order
//Separate RGB values between 0 and 1
- const red = ((200-45)*(numberOfRects-darkFactor)/numberOfRects + 45)/255;
- const green = ((232-182)*(numberOfRects-darkFactor)/numberOfRects + 182)/255;
- const blue = ((183-44)*(numberOfRects-darkFactor)/numberOfRects + 44)/255;
+ const red = ((200 - 45) * (numberOfRects - darkFactor) / numberOfRects + 45) / 255;
+ const green = ((232 - 182) * (numberOfRects - darkFactor) / numberOfRects + 182) / 255;
+ const blue = ((183 - 44) * (numberOfRects - darkFactor) / numberOfRects + 44) / 255;
return new THREE.Color(red, green, blue);
} else {
return new THREE.Color(0, 0, 0);
@@ -401,21 +472,35 @@
}
// dynamic scaling and canvas variables
- readonly cameraHalfWidth = 2.8;
- readonly cameraHalfHeight = 3.2;
- private readonly maxLabelShift = 0.305;
- private labelXFactor = 0.009;
- private lowestYShift = 3;
- private camZoom = 1.1;
- private camZoomFactor = 0.1;
- private labelShift = this.maxLabelShift;
+ readonly CAMERA_HALF_WIDTH = 2.8;
+ readonly CAMERA_HALF_HEIGHT = 3.2;
+ private readonly MAX_LABEL_SHIFT = 0.305;
+ private readonly MAX_ZOOM = 2.5;
+ private readonly MIN_ZOOM = 0.5;
+ private readonly INIT_ZOOM = 1;
+ private readonly INIT_FONT_SIZE = 10;
+ private readonly INIT_CAMERA_POS = new THREE.Vector3(4, 4, 6);
+ private readonly INIT_TARGET = new THREE.Vector3(0, 0, 0);
+ private readonly INIT_LAYER_SEPARATION = 0.4;
+ private readonly INIT_LOWEST_Y_SHIFT = 3;
+ private readonly PAN_SPEED = 1;
+ private readonly LABEL_X_FACTOR = 0.009;
+ private readonly CAM_ZOOM_FACTOR = 0.15;
+
+ private fontSize = this.INIT_FONT_SIZE;
+ private labelShift = this.MAX_LABEL_SHIFT;
+ private lowestYShift = this.INIT_LOWEST_Y_SHIFT;
+ private layerSeparation = this.INIT_LAYER_SEPARATION;
+
private visibleView = false;
private isLandscape = false;
private showVirtualDisplays = false;
- private layerSeparation = 0.4;
- private xCameraPos = 4;
private highlightedItems: Array<string> = [];
private camera: THREE.OrthographicCamera;
+ private scene?: THREE.Scene;
+ private renderer?: THREE.WebGLRenderer;
+ private labelRenderer?: CSS2DRenderer;
+ private orbit?: OrbitControls;
private rects: Rectangle[] = [];
private targetObjects: any[] = [];
private canvas?: HTMLCanvasElement;
diff --git a/tools/winscope-ng/src/viewers/components/rects/rects.component.spec.ts b/tools/winscope-ng/src/viewers/components/rects/rects.component.spec.ts
index 7704948..96f3e59 100644
--- a/tools/winscope-ng/src/viewers/components/rects/rects.component.spec.ts
+++ b/tools/winscope-ng/src/viewers/components/rects/rects.component.spec.ts
@@ -101,9 +101,9 @@
isClickable: false
}
]);
- spyOn(component.rectsComponent, "drawRects").and.callThrough();
+ spyOn(component.rectsComponent, "refreshCanvas").and.callThrough();
fixture.detectChanges();
- expect(component.rectsComponent.drawRects).toHaveBeenCalled();
+ expect(component.rectsComponent.refreshCanvas).toHaveBeenCalled();
});
@Component({
diff --git a/tools/winscope-ng/src/viewers/components/rects/rects.component.ts b/tools/winscope-ng/src/viewers/components/rects/rects.component.ts
index 2c2a5f8..0dcb760 100644
--- a/tools/winscope-ng/src/viewers/components/rects/rects.component.ts
+++ b/tools/winscope-ng/src/viewers/components/rects/rects.component.ts
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Component, Input, OnChanges, OnDestroy, Inject, ElementRef, SimpleChanges, OnInit } from "@angular/core";
+import { Component, Input, OnChanges, OnDestroy, Inject, ElementRef, SimpleChanges, OnInit} from "@angular/core";
import { RectsUtils } from "viewers/components/rects/rects_utils";
import { Point, Rectangle, RectMatrix, RectTransform } from "viewers/common/rectangle";
import { CanvasGraphics } from "viewers/components/rects/canvas_graphics";
@@ -40,17 +40,26 @@
[checked]="showVirtualDisplays()"
(change)="updateVirtualDisplays($event.checked!)"
>Show virtual</mat-checkbox>
- <div class="zoom-container control-item">
- <button class="zoom-btn" (click)="updateZoom(true)">
+ <div class="right-btn-container control-item">
+ <button class="right-btn" (click)="updateZoom(true)">
<mat-icon aria-hidden="true">
zoom_in
</mat-icon>
</button>
- <button class="zoom-btn" (click)="updateZoom(false)">
+ <button class="right-btn" (click)="updateZoom(false)">
<mat-icon aria-hidden="true">
zoom_out
</mat-icon>
</button>
+ <button
+ class="right-btn"
+ (click)="resetCamera()"
+ matTooltip="Restore camera settings"
+ >
+ <mat-icon aria-hidden="true">
+ restore
+ </mat-icon>
+ </button>
</div>
</div>
</div>
@@ -58,7 +67,7 @@
<div class="slider" [class.rotation]="true">
<span class="slider-label">Rotation</span>
<mat-slider
- step="0.01"
+ step="0.001"
min="0"
max="4"
aria-label="units"
@@ -82,7 +91,7 @@
</mat-card-header>
<mat-card-content class="rects-content">
<div class="canvas-container">
- <canvas class="rects-canvas" (click)="onRectClick($event)">
+ <canvas class="rects-canvas" (click)="onRectClick($event)" oncontextmenu="return false">
</canvas>
</div>
<div class="tabs" *ngIf="displayIds.length > 1">
@@ -153,12 +162,12 @@
padding-top: 0px;
font-weight: bold;
}
- .zoom-container {
+ .right-btn-container {
position: relative;
vertical-align: middle;
float: right;
}
- .zoom-btn {
+ .right-btn {
position: relative;
display: inline-flex;
background: none;
@@ -192,26 +201,35 @@
@Input() hasVirtualDisplays = false;
@Input() displayIds: Array<number> = [];
@Input() highlightedItems: Array<string> = [];
+ canvasInitialised = false;
+ rectsComponentInitialised = false;
constructor(
@Inject(ElementRef) private elementRef: ElementRef,
) {
this.canvasGraphics = new CanvasGraphics();
- this.currentDisplayId = this.displayIds[0] ?? 0; //default stack id is usually zero
+ this.currentDisplayId = this.displayIds[0] ?? 0; // default stack id is usually zero
}
ngOnInit() {
+ this.canvas = this.elementRef.nativeElement.querySelector("canvas")! as HTMLCanvasElement;
+ this.canvasContainer = this.elementRef.nativeElement.querySelector(".canvas-container")!;
+
window.addEventListener("resize", () => this.refreshCanvas());
- this.canvasContainer = this.elementRef.nativeElement.querySelector(".canvas-container");
- this.resizeObserver = new ResizeObserver((entries) => {
- if (entries[0].contentRect.height > 0) {
- this.refreshCanvas();
- }
- });
- this.resizeObserver.observe(this.canvasContainer!);
+ this.addContainerResizeListener();
+
+ this.currentDisplayId = this.displayIds[0];
+ this.canvasGraphics.updateHighlightedItems(this.highlightedItems);
+ if (this.rects.length > 0) {
+ this.formatAndDrawRects(true);
+ }
+ this.rectsComponentInitialised = true; // prevent ngOnChanges from being called before ngOnInit
}
ngOnChanges(changes: SimpleChanges) {
+ if (!this.rectsComponentInitialised) {
+ return; // ngOnInit not yet called
+ }
if (changes["displayIds"]) {
if (!this.displayIds.includes(this.currentDisplayId)) {
this.currentDisplayId = this.displayIds[0];
@@ -253,6 +271,11 @@
this.refreshCanvas();
}
+ public resetCamera() {
+ this.canvasGraphics.resetCamera();
+ this.refreshCanvas();
+ }
+
public updateZoom(zoom: boolean) {
this.canvasGraphics.updateZoom(zoom);
this.refreshCanvas();
@@ -276,22 +299,26 @@
this.refreshCanvas();
}
- public onRectClick(event:MouseEvent) {
+ public onRectClick(event: MouseEvent) {
this.setNormalisedMousePos(event);
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(this.mouse, this.canvasGraphics.getCamera());
// create an array containing all objects in the scene with which the ray intersects
const intersects = raycaster.intersectObjects(this.canvasGraphics.getTargetObjects());
// if there is one (or more) intersections
- if (intersects.length > 0){
+ if (intersects.length > 0) {
const id = intersects[0].object.name;
this.updateHighlightedItems(id);
}
}
public drawRects() {
- const canvas = this.elementRef.nativeElement.querySelector(".rects-canvas") as HTMLCanvasElement;
- this.canvasGraphics.initialise(canvas, this.canvasContainer);
+ if (!this.canvasContainer || !this.canvas) {
+ return;
+ } else if (!this.canvasInitialised) {
+ this.canvasGraphics.initialiseCanvas(this.canvas, this.canvasContainer);
+ this.canvasInitialised = true;
+ }
this.refreshCanvas();
}
@@ -310,7 +337,7 @@
this.drawRects();
}
- private setNormalisedMousePos(event:MouseEvent) {
+ private setNormalisedMousePos(event: MouseEvent) {
event.preventDefault();
const canvas = (event.target as Element);
const canvasOffset = canvas.getBoundingClientRect();
@@ -345,7 +372,7 @@
});
}
- private refreshCanvas() {
+ public refreshCanvas() {
this.updateVariablesBeforeRefresh();
this.canvasGraphics.refreshCanvas();
}
@@ -391,9 +418,9 @@
private s(sourceCoordinates: Point): Point {
let scale;
if (this.boundsWidth < this.boundsHeight) {
- scale = this.canvasGraphics.cameraHalfHeight*2 * 0.6 / this.boundsHeight;
+ scale = this.canvasGraphics.CAMERA_HALF_HEIGHT * 2 * 0.6 / this.boundsHeight;
} else {
- scale = this.canvasGraphics.cameraHalfWidth*2 * 0.6 / this.boundsWidth;
+ scale = this.canvasGraphics.CAMERA_HALF_WIDTH * 2 * 0.6 / this.boundsWidth;
}
return {
x: sourceCoordinates.x * scale,
@@ -413,6 +440,15 @@
}
}
+ private addContainerResizeListener() {
+ this.resizeObserver = new ResizeObserver((entries) => {
+ if (entries[0].contentRect.height > 0 && this.canvasInitialised) {
+ this.refreshCanvas();
+ }
+ });
+ this.resizeObserver.observe(this.canvasContainer!);
+ }
+
private canvasGraphics: CanvasGraphics;
private boundsWidth = 0;
private boundsHeight = 0;
@@ -421,4 +457,5 @@
private currentDisplayId: number;
private resizeObserver!: ResizeObserver;
private canvasContainer!: Element;
+ private canvas!: HTMLCanvasElement;
}