1/* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import { 18 Component, 19 ElementRef, 20 EventEmitter, 21 HostListener, 22 Inject, 23 Input, 24 OnDestroy, 25 OnInit, 26 Output, 27 SimpleChange, 28 SimpleChanges, 29} from '@angular/core'; 30import {CanColor} from '@angular/material/core'; 31import {MatIconRegistry} from '@angular/material/icon'; 32import {MatSelectChange} from '@angular/material/select'; 33import {DomSanitizer} from '@angular/platform-browser'; 34import {assertDefined} from 'common/assert_utils'; 35import {PersistentStore} from 'common/persistent_store'; 36import {UrlUtils} from 'common/url_utils'; 37import {Analytics} from 'logging/analytics'; 38import {TRACE_INFO} from 'trace/trace_info'; 39import {TraceType} from 'trace/trace_type'; 40import {DisplayIdentifier} from 'viewers/common/display_identifier'; 41import {UserOptions} from 'viewers/common/user_options'; 42import {RectDblClickDetail, ViewerEvents} from 'viewers/common/viewer_events'; 43import {UiRect} from 'viewers/components/rects/types2d'; 44import {iconDividerStyle} from 'viewers/components/styles/icon_divider.styles'; 45import {multlineTooltip} from 'viewers/components/styles/tooltip.styles'; 46import {viewerCardInnerStyle} from 'viewers/components/styles/viewer_card.styles'; 47import {Canvas} from './canvas'; 48import {Mapper3D} from './mapper3d'; 49import {Distance2D, ShadingMode} from './types3d'; 50 51@Component({ 52 selector: 'rects-view', 53 template: ` 54 <div class="view-header"> 55 <div class="title-section"> 56 <collapsible-section-title 57 [title]="title" 58 (collapseButtonClicked)="collapseButtonClicked.emit()"></collapsible-section-title> 59 <div class="right-btn-container"> 60 <button 61 color="accent" 62 class="shading-mode" 63 (mouseenter)="onInteractionStart([shadingModeButton])" 64 (mouseleave)="onInteractionEnd([shadingModeButton])" 65 mat-icon-button 66 [matTooltip]="getShadingMode()" 67 [disabled]="shadingModes.length < 2" 68 (click)="onShadingModeButtonClicked()" #shadingModeButton> 69 <mat-icon *ngIf="mapper3d.isWireFrame()" class="material-symbols-outlined" aria-hidden="true"> deployed_code </mat-icon> 70 <mat-icon *ngIf="mapper3d.isShadedByGradient()" svgIcon="cube_partial_shade"></mat-icon> 71 <mat-icon *ngIf="mapper3d.isShadedByOpacity()" svgIcon="cube_full_shade"></mat-icon> 72 </button> 73 74 <div class="icon-divider"></div> 75 76 <div class="slider-container"> 77 <mat-icon 78 color="accent" 79 matTooltip="Rotation" 80 class="slider-icon" 81 (mouseenter)="onInteractionStart([rotationSlider, rotationSliderIcon])" 82 (mouseleave)="onInteractionEnd([rotationSlider, rotationSliderIcon])" #rotationSliderIcon> rotate_90_degrees_ccw </mat-icon> 83 <mat-slider 84 class="slider-rotation" 85 step="0.02" 86 min="0" 87 max="1" 88 aria-label="units" 89 [value]="mapper3d.getCameraRotationFactor()" 90 (input)="onRotationSliderChange($event.value)" 91 (focus)="$event.target.blur()" 92 color="accent" 93 (mousedown)="onInteractionStart([rotationSlider, rotationSliderIcon])" 94 (mouseup)="onInteractionEnd([rotationSlider, rotationSliderIcon])" #rotationSlider></mat-slider> 95 <mat-icon 96 color="accent" 97 matTooltip="Spacing" 98 class="slider-icon material-symbols-outlined" 99 (mouseenter)="onInteractionStart([spacingSlider, spacingSliderIcon])" 100 (mouseleave)="onInteractionEnd([spacingSlider, spacingSliderIcon])" #spacingSliderIcon> format_letter_spacing </mat-icon> 101 <mat-slider 102 class="slider-spacing" 103 step="0.02" 104 min="0.02" 105 max="1" 106 aria-label="units" 107 [value]="getZSpacingFactor()" 108 (input)="onSeparationSliderChange($event.value)" 109 (focus)="$event.target.blur()" 110 color="accent" 111 (mousedown)="onInteractionStart([spacingSlider, spacingSliderIcon])" 112 (mouseup)="onInteractionEnd([spacingSlider, spacingSliderIcon])" #spacingSlider></mat-slider> 113 </div> 114 115 <div class="icon-divider"></div> 116 117 <button 118 color="accent" 119 (mouseenter)="onInteractionStart([zoomInButton])" 120 (mouseleave)="onInteractionEnd([zoomInButton])" 121 mat-icon-button 122 (click)="onZoomInClick()" #zoomInButton> 123 <mat-icon aria-hidden="true"> zoom_in </mat-icon> 124 </button> 125 <button 126 color="accent" 127 (mouseenter)="onInteractionStart([zoomOutButton])" 128 (mouseleave)="onInteractionEnd([zoomOutButton])" 129 mat-icon-button 130 (click)="onZoomOutClick()" #zoomOutButton> 131 <mat-icon aria-hidden="true"> zoom_out </mat-icon> 132 </button> 133 134 <div class="icon-divider"></div> 135 136 <button 137 color="accent" 138 (mouseenter)="onInteractionStart([resetZoomButton])" 139 (mouseleave)="onInteractionEnd([resetZoomButton])" 140 mat-icon-button 141 matTooltip="Restore camera settings" 142 (click)="resetCamera()" #resetZoomButton> 143 <mat-icon aria-hidden="true"> restore </mat-icon> 144 </button> 145 </div> 146 </div> 147 <div class="filter-controls view-controls"> 148 <user-options 149 class="block-filter-controls" 150 [userOptions]="userOptions" 151 [eventType]="ViewerEvents.RectsUserOptionsChange" 152 [traceType]="dependencies[0]" 153 [logCallback]="Analytics.Navigation.logRectSettingsChanged"> 154 </user-options> 155 156 <div class="displays-section"> 157 <span class="mat-body-1"> {{groupLabel}}: </span> 158 <mat-form-field appearance="none" class="displays-select"> 159 <mat-select 160 (selectionChange)="onDisplayChange($event)" 161 [value]="currentDisplay?.name"> 162 <mat-option 163 *ngFor="let name of displayNames" 164 [value]="name"> 165 {{ name }} 166 </mat-option> 167 </mat-select> 168 </mat-form-field> 169 </div> 170 </div> 171 </div> 172 <mat-divider></mat-divider> 173 <div class="rects-content"> 174 <div class="canvas-container"> 175 <canvas 176 class="large-rects-canvas" 177 (click)="onRectClick($event)" 178 (dblclick)="onRectDblClick($event)" 179 oncontextmenu="return false"></canvas> 180 <div class="large-rects-labels"></div> 181 <canvas 182 class="mini-rects-canvas" 183 (dblclick)="onMiniRectDblClick($event)" 184 oncontextmenu="return false"></canvas> 185 </div> 186 </div> 187 `, 188 styles: [ 189 ` 190 .view-header { 191 display: flex; 192 flex-direction: column; 193 } 194 .mat-title { 195 padding-top: 8px; 196 } 197 .right-btn-container { 198 display: flex; 199 align-items: center; 200 } 201 .right-btn-container .mat-slider-horizontal { 202 min-width: 64px !important; 203 } 204 .icon-divider { 205 height: 50%; 206 } 207 .slider-container { 208 padding: 0 5px; 209 display: flex; 210 align-items: center; 211 } 212 .slider-icon { 213 min-width: 18px; 214 width: 18px; 215 height: 18px; 216 line-height: 18px; 217 font-size: 18px; 218 } 219 .filter-controls { 220 justify-content: space-between; 221 } 222 .block-filter-controls { 223 display: flex; 224 flex-direction: row; 225 align-items: baseline; 226 } 227 .displays-section { 228 display: flex; 229 flex-direction: row; 230 align-items: center; 231 width: fit-content; 232 flex-wrap: nowrap; 233 } 234 .displays-select { 235 font-size: 14px; 236 background-color: var(--disabled-color); 237 border-radius: 4px; 238 height: 24px; 239 margin-left: 5px; 240 } 241 .rects-content { 242 height: 100%; 243 display: flex; 244 flex-direction: column; 245 padding: 0px 12px; 246 } 247 .canvas-container { 248 height: 100%; 249 width: 100%; 250 position: relative; 251 } 252 .large-rects-canvas { 253 position: absolute; 254 top: 0; 255 left: 0; 256 width: 100%; 257 height: 100%; 258 cursor: pointer; 259 } 260 .large-rects-labels { 261 position: absolute; 262 top: 0; 263 left: 0; 264 width: 100%; 265 height: 100%; 266 pointer-events: none; 267 } 268 .mini-rects-canvas { 269 cursor: pointer; 270 width: 30%; 271 height: 30%; 272 top: 16px; 273 display: block; 274 position: absolute; 275 z-index: 1000; 276 } 277 `, 278 multlineTooltip, 279 iconDividerStyle, 280 viewerCardInnerStyle, 281 ], 282}) 283export class RectsComponent implements OnInit, OnDestroy { 284 Analytics = Analytics; 285 ViewerEvents = ViewerEvents; 286 287 @Input() title = 'title'; 288 @Input() zoomFactor = 1; 289 @Input() store?: PersistentStore; 290 @Input() rects: UiRect[] = []; 291 @Input() miniRects: UiRect[] | undefined; 292 @Input() displays: DisplayIdentifier[] = []; 293 @Input() highlightedItem = ''; 294 @Input() groupLabel = 'Displays'; 295 @Input() isStackBased = false; 296 @Input() shadingModes: ShadingMode[] = [ShadingMode.GRADIENT]; 297 @Input() userOptions: UserOptions = {}; 298 @Input() dependencies: TraceType[] = []; 299 300 @Output() collapseButtonClicked = new EventEmitter(); 301 302 private internalRects: UiRect[] = []; 303 private internalMiniRects?: UiRect[]; 304 private storeKeyZSpacingFactor = ''; 305 private storeKeyShadingMode = ''; 306 private displayNames: string[] = []; 307 private internalDisplays: DisplayIdentifier[] = []; 308 private internalHighlightedItem = ''; 309 private currentDisplay: DisplayIdentifier | undefined; 310 private mapper3d: Mapper3D; 311 private largeRectsCanvas?: Canvas; 312 private miniRectsCanvas?: Canvas; 313 private resizeObserver: ResizeObserver; 314 private largeRectsCanvasElement?: HTMLCanvasElement; 315 private miniRectsCanvasElement?: HTMLCanvasElement; 316 private largeRectsLabelsElement?: HTMLElement; 317 private mouseMoveListener = (event: MouseEvent) => this.onMouseMove(event); 318 private mouseUpListener = (event: MouseEvent) => this.onMouseUp(event); 319 320 private static readonly ZOOM_SCROLL_RATIO = 0.3; 321 322 constructor( 323 @Inject(ElementRef) private elementRef: ElementRef, 324 @Inject(MatIconRegistry) private matIconRegistry: MatIconRegistry, 325 @Inject(DomSanitizer) private domSanitizer: DomSanitizer, 326 ) { 327 this.mapper3d = new Mapper3D(); 328 this.resizeObserver = new ResizeObserver((entries) => { 329 this.drawLargeRectsAndLabels(); 330 }); 331 this.matIconRegistry.addSvgIcon( 332 'cube_full_shade', 333 this.domSanitizer.bypassSecurityTrustResourceUrl( 334 UrlUtils.getRootUrl() + 'cube_full_shade.svg', 335 ), 336 ); 337 this.matIconRegistry.addSvgIcon( 338 'cube_partial_shade', 339 this.domSanitizer.bypassSecurityTrustResourceUrl( 340 UrlUtils.getRootUrl() + 'cube_partial_shade.svg', 341 ), 342 ); 343 } 344 345 ngOnInit() { 346 this.mapper3d.setAllowedShadingModes(this.shadingModes); 347 348 const canvasContainer: HTMLElement = 349 this.elementRef.nativeElement.querySelector('.canvas-container'); 350 this.resizeObserver.observe(canvasContainer); 351 352 const isDarkMode = () => this.store?.get('dark-mode') === 'true'; 353 354 this.largeRectsCanvasElement = canvasContainer.querySelector( 355 '.large-rects-canvas', 356 )! as HTMLCanvasElement; 357 this.largeRectsLabelsElement = assertDefined( 358 canvasContainer.querySelector('.large-rects-labels'), 359 ) as HTMLElement; 360 this.largeRectsCanvas = new Canvas( 361 this.largeRectsCanvasElement, 362 this.largeRectsLabelsElement, 363 isDarkMode, 364 ); 365 this.largeRectsCanvasElement.addEventListener('mousedown', (event) => 366 this.onCanvasMouseDown(event), 367 ); 368 369 if (this.store) { 370 this.updateControlsFromStore(); 371 } 372 373 this.currentDisplay = 374 this.internalDisplays.length > 0 375 ? this.getFirstDisplayWithRectsOrFirstDisplay(this.internalDisplays) 376 : undefined; 377 this.mapper3d.increaseZoomFactor(this.zoomFactor - 1); 378 this.drawLargeRectsAndLabels(); 379 380 this.miniRectsCanvasElement = canvasContainer.querySelector( 381 '.mini-rects-canvas', 382 )! as HTMLCanvasElement; 383 this.miniRectsCanvas = new Canvas( 384 this.miniRectsCanvasElement, 385 undefined, 386 isDarkMode, 387 ); 388 if (this.miniRects && this.miniRects.length > 0) { 389 this.drawMiniRects(); 390 } 391 } 392 393 blurTab() { 394 (document.activeElement as HTMLElement).blur(); 395 } 396 397 ngOnChanges(simpleChanges: SimpleChanges) { 398 if (simpleChanges['highlightedItem']) { 399 this.internalHighlightedItem = 400 simpleChanges['highlightedItem'].currentValue; 401 this.mapper3d.setHighlightedRectId(this.internalHighlightedItem); 402 if (!(simpleChanges['rects'] || simpleChanges['displays'])) { 403 this.drawLargeRectsAndLabels(); 404 } 405 } 406 let displayChange = false; 407 if (simpleChanges['displays']) { 408 const curr: DisplayIdentifier[] = simpleChanges['displays'].currentValue; 409 const prev: DisplayIdentifier[] | null = 410 simpleChanges['displays'].previousValue; 411 displayChange = 412 curr.length > 0 && 413 !curr.every((d, index) => d.displayId === prev?.at(index)?.displayId); 414 } 415 if (simpleChanges['rects']) { 416 this.internalRects = simpleChanges['rects'].currentValue; 417 if (!displayChange) { 418 this.drawLargeRectsAndLabels(); 419 } 420 } 421 if (displayChange) { 422 this.onDisplaysChange(simpleChanges['displays']); 423 } 424 if (simpleChanges['miniRects']) { 425 this.internalMiniRects = simpleChanges['miniRects'].currentValue; 426 this.drawMiniRects(); 427 } 428 } 429 430 ngOnDestroy() { 431 this.resizeObserver?.disconnect(); 432 } 433 434 onDisplaysChange(change: SimpleChange) { 435 const displays = change.currentValue; 436 this.internalDisplays = displays; 437 this.displayNames = this.internalDisplays.map((d) => d.name); 438 439 if (displays.length === 0) { 440 return; 441 } 442 443 if (change.firstChange) { 444 this.updateCurrentDisplay( 445 this.getFirstDisplayWithRectsOrFirstDisplay(this.internalDisplays), 446 ); 447 return; 448 } 449 450 const curr = this.internalDisplays.find( 451 (display) => display.displayId === this.currentDisplay?.displayId, 452 ); 453 if (curr) { 454 this.updateCurrentDisplay(curr); 455 return; 456 } 457 458 const displaysWithCurrentGroupId = this.internalDisplays.filter( 459 (display) => display.groupId === this.mapper3d.getCurrentGroupId(), 460 ); 461 if (displaysWithCurrentGroupId.length === 0) { 462 this.updateCurrentDisplay( 463 this.getFirstDisplayWithRectsOrFirstDisplay(this.internalDisplays), 464 ); 465 return; 466 } 467 468 this.updateCurrentDisplay( 469 this.getFirstDisplayWithRectsOrFirstDisplay(displaysWithCurrentGroupId), 470 ); 471 return; 472 } 473 474 updateControlsFromStore() { 475 this.storeKeyZSpacingFactor = `rectsView.${this.title}.zSpacingFactor`; 476 this.storeKeyShadingMode = `rectsView.${this.title}.shadingMode`; 477 478 const storedZSpacingFactor = assertDefined(this.store).get( 479 this.storeKeyZSpacingFactor, 480 ); 481 if (storedZSpacingFactor !== undefined) { 482 this.mapper3d.setZSpacingFactor(Number(storedZSpacingFactor)); 483 } 484 485 const storedShadingMode = assertDefined(this.store).get( 486 this.storeKeyShadingMode, 487 ); 488 if ( 489 storedShadingMode !== undefined && 490 this.shadingModes.includes(storedShadingMode as ShadingMode) 491 ) { 492 this.mapper3d.setShadingMode(storedShadingMode as ShadingMode); 493 } 494 } 495 496 onSeparationSliderChange(factor: number) { 497 Analytics.Navigation.logRectSettingsChanged( 498 'z spacing', 499 factor, 500 TRACE_INFO[this.dependencies[0]].name, 501 ); 502 this.store?.add(this.storeKeyZSpacingFactor, `${factor}`); 503 this.mapper3d.setZSpacingFactor(factor); 504 this.drawLargeRectsAndLabels(); 505 } 506 507 onRotationSliderChange(factor: number) { 508 this.mapper3d.setCameraRotationFactor(factor); 509 this.drawLargeRectsAndLabels(); 510 } 511 512 resetCamera() { 513 Analytics.Navigation.logZoom('reset', 'rects'); 514 this.mapper3d.resetCamera(); 515 this.drawLargeRectsAndLabels(); 516 } 517 518 @HostListener('wheel', ['$event']) 519 onScroll(event: WheelEvent) { 520 if ((event.target as HTMLElement).className === 'large-rects-canvas') { 521 if (event.deltaY > 0) { 522 Analytics.Navigation.logZoom('scroll', 'rects', 'out'); 523 this.doZoomOut(RectsComponent.ZOOM_SCROLL_RATIO); 524 } else { 525 Analytics.Navigation.logZoom('scroll', 'rects', 'in'); 526 this.doZoomIn(RectsComponent.ZOOM_SCROLL_RATIO); 527 } 528 } 529 } 530 531 onCanvasMouseDown(event: MouseEvent) { 532 document.addEventListener('mousemove', this.mouseMoveListener); 533 document.addEventListener('mouseup', this.mouseUpListener); 534 } 535 536 onMouseMove(event: MouseEvent) { 537 const distance = new Distance2D(event.movementX, event.movementY); 538 this.mapper3d.addPanScreenDistance(distance); 539 this.drawLargeRectsAndLabels(); 540 } 541 542 onMouseUp(event: MouseEvent) { 543 document.removeEventListener('mousemove', this.mouseMoveListener); 544 document.removeEventListener('mouseup', this.mouseUpListener); 545 } 546 547 onZoomInClick() { 548 Analytics.Navigation.logZoom('button', 'rects', 'in'); 549 this.doZoomIn(); 550 } 551 552 onZoomOutClick() { 553 Analytics.Navigation.logZoom('button', 'rects', 'out'); 554 this.doZoomOut(); 555 } 556 557 onDisplayChange(event: MatSelectChange) { 558 const displayName = event.value; 559 const display = assertDefined( 560 this.internalDisplays.find((d) => d.name === displayName), 561 ); 562 this.updateCurrentDisplay(display); 563 const viewerEvent = new CustomEvent(ViewerEvents.RectGroupIdChange, { 564 bubbles: true, 565 detail: {groupId: display.groupId}, 566 }); 567 this.elementRef.nativeElement.dispatchEvent(viewerEvent); 568 } 569 570 onRectClick(event: MouseEvent) { 571 event.preventDefault(); 572 573 const id = this.findClickedRectId(event); 574 if (id !== undefined) { 575 this.notifyHighlightedItem(id); 576 } 577 } 578 579 onRectDblClick(event: MouseEvent) { 580 event.preventDefault(); 581 582 const clickedRectId = this.findClickedRectId(event); 583 if (clickedRectId === undefined) { 584 return; 585 } 586 587 this.elementRef.nativeElement.dispatchEvent( 588 new CustomEvent(ViewerEvents.RectsDblClick, { 589 bubbles: true, 590 detail: new RectDblClickDetail(clickedRectId), 591 }), 592 ); 593 } 594 595 onMiniRectDblClick(event: MouseEvent) { 596 event.preventDefault(); 597 598 this.elementRef.nativeElement.dispatchEvent( 599 new CustomEvent(ViewerEvents.MiniRectsDblClick, {bubbles: true}), 600 ); 601 } 602 603 getZSpacingFactor(): number { 604 return this.mapper3d.getZSpacingFactor(); 605 } 606 607 getShadingMode(): ShadingMode { 608 return this.mapper3d.getShadingMode(); 609 } 610 611 onShadingModeButtonClicked() { 612 this.mapper3d.updateShadingMode(); 613 const newMode = this.mapper3d.getShadingMode(); 614 Analytics.Navigation.logRectSettingsChanged( 615 'shading mode', 616 newMode, 617 TRACE_INFO[this.dependencies[0]].name, 618 ); 619 this.store?.add(this.storeKeyShadingMode, newMode); 620 this.drawLargeRectsAndLabels(); 621 } 622 623 onInteractionStart(components: CanColor[]) { 624 components.forEach((c) => (c.color = 'primary')); 625 } 626 627 onInteractionEnd(components: CanColor[]) { 628 components.forEach((c) => (c.color = 'accent')); 629 } 630 631 private getFirstDisplayWithRectsOrFirstDisplay( 632 displays: DisplayIdentifier[], 633 ): DisplayIdentifier { 634 return ( 635 displays.find((display) => 636 this.internalRects.some( 637 (rect) => !rect.isDisplay && rect.groupId === display.groupId, 638 ), 639 ) ?? assertDefined(displays.at(0)) 640 ); 641 } 642 643 private updateCurrentDisplay(display: DisplayIdentifier) { 644 this.currentDisplay = display; 645 this.mapper3d.setCurrentGroupId(display.groupId); 646 this.drawLargeRectsAndLabels(); 647 } 648 649 private findClickedRectId(event: MouseEvent): string | undefined { 650 const canvas = event.target as Element; 651 const canvasOffset = canvas.getBoundingClientRect(); 652 653 const x = 654 ((event.clientX - canvasOffset.left) / canvas.clientWidth) * 2 - 1; 655 const y = 656 -((event.clientY - canvasOffset.top) / canvas.clientHeight) * 2 + 1; 657 const z = 0; 658 659 return this.largeRectsCanvas?.getClickedRectId(x, y, z); 660 } 661 662 private doZoomIn(ratio = 1) { 663 this.mapper3d.increaseZoomFactor(ratio); 664 this.drawLargeRectsAndLabels(); 665 } 666 667 private doZoomOut(ratio = 1) { 668 this.mapper3d.decreaseZoomFactor(ratio); 669 this.drawLargeRectsAndLabels(); 670 } 671 672 private drawLargeRectsAndLabels() { 673 // TODO(b/258593034): Re-create scene only when input rects change. With the other input events 674 // (rotation, spacing, ...) we can just update the camera and/or update the mesh positions. 675 // We'd probably need to get rid of the intermediate layer (Scene3D, Rect3D, ... types) and 676 // work directly with three.js's meshes. 677 this.mapper3d.setRects(this.internalRects); 678 this.largeRectsCanvas?.draw(this.mapper3d.computeScene()); 679 } 680 681 private drawMiniRects() { 682 // TODO(b/258593034): Re-create scene only when input rects change. With the other input events 683 // (rotation, spacing, ...) we can just update the camera and/or update the mesh positions. 684 // We'd probably need to get rid of the intermediate layer (Scene3D, Rect3D, ... types) and 685 // work directly with three.js's meshes. 686 if (this.internalMiniRects) { 687 const largeRectShadingMode = this.mapper3d.getShadingMode(); 688 const largeRectGroupId = this.mapper3d.getCurrentGroupId(); 689 const largeRectZSpacing = this.mapper3d.getZSpacingFactor(); 690 const largeRectCameraRotation = this.mapper3d.getCameraRotationFactor(); 691 692 this.mapper3d.setShadingMode(ShadingMode.GRADIENT); 693 this.mapper3d.setCurrentGroupId(this.internalMiniRects[0]?.groupId); 694 this.mapper3d.resetToOrthogonalState(); 695 696 this.mapper3d.setRects(this.internalMiniRects); 697 this.mapper3d.decreaseZoomFactor(this.zoomFactor - 1); 698 this.miniRectsCanvas?.draw(this.mapper3d.computeScene()); 699 this.mapper3d.increaseZoomFactor(this.zoomFactor - 1); 700 701 // Mapper internally sets these values to 100%. They need to be reset afterwards 702 if (this.miniRectsCanvasElement) { 703 this.miniRectsCanvasElement.style.width = '25%'; 704 this.miniRectsCanvasElement.style.height = '25%'; 705 } 706 707 this.mapper3d.setShadingMode(largeRectShadingMode); 708 this.mapper3d.setCurrentGroupId(largeRectGroupId); 709 this.mapper3d.setZSpacingFactor(largeRectZSpacing); 710 this.mapper3d.setCameraRotationFactor(largeRectCameraRotation); 711 } 712 } 713 714 private notifyHighlightedItem(id: string) { 715 const event: CustomEvent = new CustomEvent( 716 ViewerEvents.HighlightedIdChange, 717 { 718 bubbles: true, 719 detail: {id}, 720 }, 721 ); 722 this.elementRef.nativeElement.dispatchEvent(event); 723 } 724} 725