blob: 8cbca5a09e13c756f86479e01c3efe2e3ab7054d [file] [log] [blame]
/*
* Copyright 2018-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import * as d3 from 'd3';
import { LogService } from '../log.service';
export interface ZoomOpts {
svg: any; // D3 selection of <svg> element
zoomLayer: any; // D3 selection of <g> element
zoomMin: number; // Min zoom level - usually 0.25
zoomMax: number; // Max zoom level - usually 10
zoomEnabled(ev: any): boolean; // Function that takes event and returns boolean
zoomCallback(translate: number[], scale: number): void; // Function that is called on zoom
}
export interface Zoomer {
panZoom(translate: number[], scale: number, transition?: number): void;
reset(): void;
translate(): number[];
scale(): number;
scaleExtent(): number[];
}
export const CZ: string = 'ZoomService.createZoomer(): ';
export const D3S: string = ' (D3 selection) property defined';
/**
* ONOS GUI -- Topology Zoom Service Module.
*/
@Injectable({
providedIn: 'root',
})
export class ZoomService {
defaultSettings: ZoomOpts;
zoom: any;
public zoomer: Zoomer;
settings: ZoomOpts;
constructor(
protected log: LogService,
) {
this.defaultSettings = <ZoomOpts>{
zoomMin: 0.05,
zoomMax: 50,
zoomEnabled: (ev) => true,
zoomCallback: (t, s) => { return; }
};
this.log.debug('ZoomService constructed');
}
createZoomer(opts: ZoomOpts): Zoomer {
this.settings = Object.assign(this.defaultSettings, opts);
if (!this.settings.svg) {
this.log.error(CZ + 'No "svg" (svg tag)' + D3S);
throw new Error(CZ + 'No "svg" (svg tag)' + D3S);
}
if (!this.settings.zoomLayer) {
this.log.error(CZ + 'No "zoomLayer" (g tag)' + D3S);
throw new Error(CZ + 'No "zoomLayer" (g tag)' + D3S);
}
this.zoom = d3.zoom()
.scaleExtent([this.settings.zoomMin, this.settings.zoomMax])
.extent([[0, 0], [1000, 1000]])
.on('zoom', () => this.zoomed);
this.zoomer = <Zoomer>{
panZoom: (translate: number[], scale: number, transition?: number) => {
this.settings.svg.call(this.zoom.translateBy, translate[0], translate[1]);
this.settings.svg.call(this.zoom.scaleTo, scale);
this.adjustZoomLayer(translate, scale, transition);
},
reset: () => {
this.settings.svg.call(this.zoom.translateTo, 500, 500);
this.settings.svg.call(this.zoom.scaleTo, 1);
this.adjustZoomLayer([0, 0], 1, 0);
},
translate: () => {
const trans = d3.zoomTransform(this.settings.svg.node());
return [trans.x, trans.y];
},
scale: () => {
const trans = d3.zoomTransform(this.settings.svg.node());
return trans.k;
},
scaleExtent: () => {
return this.zoom.scaleExtent();
},
};
// apply the zoom behavior to the SVG element
/*
if (this.settings.svg ) {
this.settings.svg.call(this.zoom);
}
*/
// Remove zoom on double click (prevents a
// false zoom navigating regions)
// this.settings.svg.on('dblclick.zoom', null);
return this.zoomer;
}
/**
* zoom events from mouse gestures...
*/
zoomed() {
const ev = d3.event.sourceEvent;
if (this.settings.zoomEnabled(ev)) {
this.adjustZoomLayer(d3.event.translate, d3.event.scale);
}
}
/**
* Adjust the zoom layer
*/
adjustZoomLayer(translate: number[], scale: number, transition?: any): void {
this.settings.zoomLayer.transition()
.duration(transition || 0)
.attr('transform',
'translate(' + translate + ') scale(' + scale + ')');
this.settings.zoomCallback(translate, scale);
}
}