diff --git a/packages/tiffviewer/package.json b/packages/tiffviewer/package.json index 42f8f7a2..a60739cf 100644 --- a/packages/tiffviewer/package.json +++ b/packages/tiffviewer/package.json @@ -1,6 +1,6 @@ { "name": "@galaxyproject/tiffviewer", - "version": "0.0.3", + "version": "0.0.4", "description": "A visualization for Tiff files based on GeoTIFF and PanZoom libraries.", "license": "MIT", "files": [ diff --git a/packages/tiffviewer/src/ui-manager.ts b/packages/tiffviewer/src/ui-manager.ts index 83ac3eca..dae90904 100644 --- a/packages/tiffviewer/src/ui-manager.ts +++ b/packages/tiffviewer/src/ui-manager.ts @@ -21,7 +21,7 @@ export class UIManager { constructor( tiffService: TIFFService, paletteManager: PaletteManager, - appElement: HTMLElement + appElement: HTMLElement, ) { this.tiffService = tiffService; this.paletteManager = paletteManager; @@ -75,7 +75,7 @@ export class UIManager { const rgba = new Uint8ClampedArray(width * height * 4); if (samples === 1) { const palette = this.paletteManager.getPalette( - this.paletteManager.getCurrentPalette() + this.paletteManager.getCurrentPalette(), ); for (let i = 0; i < width * height; i++) { const v = data[i]; @@ -149,13 +149,13 @@ export class UIManager { zoomInBtn.title = "Zoom In"; zoomInBtn.type = "button"; zoomInBtn.setAttribute("aria-label", "Zoom In"); - zoomInBtn.onclick = () => this.panzoom.zoomIn(); + zoomInBtn.onclick = () => this.zoomFromCenterByStep(1); const zoomOutBtn = document.createElement("button"); zoomOutBtn.appendChild(this.createIcon("zoom-out", "Zoom Out")); zoomOutBtn.title = "Zoom Out"; zoomOutBtn.type = "button"; zoomOutBtn.setAttribute("aria-label", "Zoom Out"); - zoomOutBtn.onclick = () => this.panzoom.zoomOut(); + zoomOutBtn.onclick = () => this.zoomFromCenterByStep(-1); const resetZoomBtn = document.createElement("button"); resetZoomBtn.appendChild(this.createIcon("reset", "Reset Zoom")); resetZoomBtn.title = "Reset Zoom"; @@ -172,7 +172,7 @@ export class UIManager { // Palette panel (SVG) const palettePanelBtn = document.createElement("button"); palettePanelBtn.appendChild( - this.createIcon("palette", "Show Palette Selector") + this.createIcon("palette", "Show Palette Selector"), ); palettePanelBtn.title = "Show Palette Selector"; palettePanelBtn.type = "button"; @@ -253,6 +253,36 @@ export class UIManager { this.toolbar.appendChild(palettePanelBtn); } + private getCanvasCenterFocal(): { x: number; y: number } { + const rect = this.canvas.getBoundingClientRect(); + return { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2, + }; + } + + /** + * Use zoomToPoint to zoom from the container center by one wheel step. + * direction: +1 for zoom in, -1 for zoom out + */ + private zoomFromCenterByStep(direction: 1 | -1) { + const options = this.panzoom.getOptions(); + const step = Number(options.step ?? 0.3); + const minScale = Number(options.minScale ?? 0.1); + const maxScale = Number(options.maxScale ?? this.maxScale); + const currentScale = this.panzoom.getScale(); + const wheelLikeStep = Math.exp(direction * step); + const targetScale = Math.min( + maxScale, + Math.max(minScale, currentScale * wheelLikeStep), + ); + const focal = this.getCanvasCenterFocal(); + this.panzoom.zoomToPoint(targetScale, { + clientX: focal.x, + clientY: focal.y, + }); + } + private createPageSelector() { let currentIndex = this.currentPageIndex; const select = document.createElement("select"); @@ -312,7 +342,7 @@ export class UIManager { const rows = Object.entries(tags) .map( ([key, value]) => - `