GUI2 Added selection of background maps
Change-Id: I88ee69fe2ff24bb1b4b3fe633b04f2f1778f3a82
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html
index d745ce8..bb9e638 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html
@@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<svg:g xmlns:svg="http://www.w3.org/2000/svg" onos-mapsvg />
+<svg:g xmlns:svg="http://www.w3.org/2000/svg" onos-mapsvg [map]="map"/>
<svg:g xmlns:svg="http://www.w3.org/2000/svg">
<svg:text>Layout: {{ layoutData.id }} {{ layoutData.bgDesc }}</svg:text>
<svg:text>Region: {{ layoutData.region }} {{ layoutData.regionName }}</svg:text>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.spec.ts
index f1e1e69..a3eed06 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.spec.ts
@@ -16,21 +16,52 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BackgroundSvgComponent } from './backgroundsvg.component';
+import {MapSvgComponent} from '../mapsvg/mapsvg.component';
+import {from} from 'rxjs';
+import {HttpClient} from '@angular/common/http';
+import {LogService} from 'gui2-fw-lib';
+import {MapObject} from '../maputils';
+
+class MockHttpClient {
+ get() {
+ return from(['{"id":"app","icon":"nav_apps","cat":"PLATFORM","label":"Applications"}']);
+ }
+
+ subscribe() {}
+}
describe('BackgroundSvgComponent', () => {
+ let logServiceSpy: jasmine.SpyObj<LogService>;
let component: BackgroundSvgComponent;
let fixture: ComponentFixture<BackgroundSvgComponent>;
+ const testmap: MapObject = <MapObject>{
+ scale: 1.0,
+ id: 'test',
+ description: 'test map'
+ };
beforeEach(async(() => {
+ const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+
TestBed.configureTestingModule({
- declarations: [ BackgroundSvgComponent ]
+ declarations: [
+ BackgroundSvgComponent,
+ MapSvgComponent
+ ],
+ providers: [
+ { provide: LogService, useValue: logSpy },
+ { provide: HttpClient, useClass: MockHttpClient },
+ ]
})
.compileComponents();
+
+ logServiceSpy = TestBed.get(LogService);
}));
beforeEach(() => {
fixture = TestBed.createComponent(BackgroundSvgComponent);
component = fixture.componentInstance;
+ component.map = testmap;
fixture.detectChanges();
});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts
index e07f304..5536784 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts
@@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Component, OnInit } from '@angular/core';
+import {Component, Input, OnInit} from '@angular/core';
+import {MapObject} from '../maputils';
/**
* model of the topo2CurrentLayout attrs from BgZoom below
@@ -76,6 +77,7 @@
styleUrls: ['./backgroundsvg.component.css']
})
export class BackgroundSvgComponent implements OnInit {
+ @Input() map: MapObject;
layoutData: Layout = <Layout>{};
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
index b511886..24dd029 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
@@ -52,6 +52,10 @@
height: number;
}
+/**
+ * The inspiration for this approach comes from
+ * https://medium.com/netscape/visualizing-data-with-angular-and-d3-209dde784aeb
+ */
export class ForceDirectedGraph {
public ticker: EventEmitter<d3.Simulation<Node, Link>> = new EventEmitter();
public simulation: d3.Simulation<any, any>;
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.css
index f206973..0dac42a 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.css
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.css
@@ -16,4 +16,11 @@
/**
* ONOS GUI -- Topology Map Layer -- CSS file
- */
\ No newline at end of file
+ */
+/* --- Topo Map --- */
+
+path.topo-map {
+ stroke-width: 2px;
+ stroke: #f4f4f4;
+ fill: #e5e5e6;
+}
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html
index 157a87b..adc0346 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html
@@ -13,5 +13,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<svg:text xmlns:svg="http://www.w3.org/2000/svg">Map of Croatia</svg:text>
-<svg:path xmlns:svg="http://www.w3.org/2000/svg" d="m 187.50819,219.30177 -3.14986,-1.57795 z M 45.764161,56.635237 48.914029,55.022914 Z m 62.997349,-32.313055 6.29973,4.85593 6.29974,11.31816 v 11.300949 h 9.4496 l 12.59947,8.061616 6.29973,12.880483 v 8.039036 l 6.29974,14.448536 -3.14987,9.616908 -6.29973,12.80342 v 14.37787 l 3.14986,7.97587 -9.4496,3.18799 3.14987,9.55592 6.29973,4.77344 v 7.94906 l -6.29973,1.58881 3.14987,15.86987 6.29973,6.33869 v 6.33341 l 6.29974,1.58253 v 20.54314 h 6.29973 18.89921 l 3.14986,4.73289 v 1.57699 6.30469 l -3.14986,4.72511 6.29973,6.29564 v 1.57311 l 3.14987,7.8607 -9.4496,17.26536 h -9.44961 l -6.29973,4.70203 v 14.08899 l -6.29974,3.12741 v 15.61816 l -3.14986,6.23849 -22.04907,3.11737 -3.14987,7.78797 h -6.29974 l 9.44961,17.10623 -3.14987,6.21118 -6.29974,-1.55233 v 10.85988 l -6.29973,7.74786 -18.89921,-6.19768 v -20.17635 l -6.299731,-3.10867 v -6.22107 l -6.299735,-10.8988 -9.449602,-3.11675 v -4.67746 l -9.449602,-1.55978 -6.299735,-6.24224 -9.449602,-3.123 -9.449602,-12.5046 -3.149867,-14.09183 12.599469,12.52734 3.149868,-12.52734 -9.449602,-9.40886 6.299734,1.56894 v -7.84788 l -6.299734,1.57021 -6.299735,-9.4261 3.149867,-4.71738 V 228.7627 l 3.149868,-9.46093 6.299734,-3.15624 v -31.63431 l 6.299735,-7.92914 -3.149867,-6.34927 H 52.063896 V 155.9275 l 9.449602,-9.5519 -9.449602,-7.96914 h 12.599469 v -27.15822 l -12.599469,-6.40444 -6.299735,1.60162 V 82.385126 l 3.149868,-3.213886 6.299734,1.607116 3.149868,-8.039036 -12.59947,-9.658277 6.299735,-9.6708 -3.149867,-1.613022 6.299734,-11.300949 6.299735,-4.848528 v -6.469632 l 6.299735,-3.236933 v -8.098523 l 9.449602,-4.863368 3.149867,3.2426 v 14.574161 l 6.299735,3.234816 12.59947,-3.234816 z"></svg:path>
\ No newline at end of file
+<svg:desc xmlns:svg="http://www.w3.org/2000/svg">Map of {{map.id}} in SVG format</svg:desc>
+<svg:path class="topo-map"
+ *ngFor="let f of mapPathGenerator?.geodata.features"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ [attr.d]="pathGenerator(f)">
+ <svg:title>{{ f.id }}</svg:title>
+</svg:path>
+
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.spec.ts
index e1000c8..ab73820 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.spec.ts
@@ -16,25 +16,45 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MapSvgComponent } from './mapsvg.component';
+import {HttpClient} from '@angular/common/http';
+import {from} from 'rxjs';
+import {LogService} from 'gui2-fw-lib';
+
+class MockHttpClient {
+ get() {
+ return from(['{"id":"app","icon":"nav_apps","cat":"PLATFORM","label":"Applications"}']);
+ }
+
+ subscribe() {}
+}
describe('MapSvgComponent', () => {
- let component: MapSvgComponent;
- let fixture: ComponentFixture<MapSvgComponent>;
+ let logServiceSpy: jasmine.SpyObj<LogService>;
+ let component: MapSvgComponent;
+ let fixture: ComponentFixture<MapSvgComponent>;
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [ MapSvgComponent ]
- })
- .compileComponents();
- }));
+ beforeEach(async(() => {
+ const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
- beforeEach(() => {
- fixture = TestBed.createComponent(MapSvgComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
+ TestBed.configureTestingModule({
+ declarations: [ MapSvgComponent ],
+ providers: [
+ { provide: LogService, useValue: logSpy },
+ { provide: HttpClient, useClass: MockHttpClient },
+ ]
+ })
+ .compileComponents();
- it('should create', () => {
- expect(component).toBeTruthy();
- });
+ logServiceSpy = TestBed.get(LogService);
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MapSvgComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.ts
index 2de6c12..a10cf4c 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.ts
@@ -13,18 +13,206 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Component, OnInit } from '@angular/core';
+import {
+ Component,
+ Input,
+ OnChanges,
+ SimpleChanges
+} from '@angular/core';
+import { MapObject } from '../maputils';
+import {LogService} from 'gui2-fw-lib';
+import {HttpClient} from '@angular/common/http';
+import * as d3 from 'd3';
+import * as topojson from 'topojson-client';
+
+const BUNDLED_URL_PREFIX = 'data/map/';
+
+/**
+ * Model of the transform attribute of a topojson file
+ */
+interface TopoDataTransform {
+ scale: number[];
+ translate: number[];
+}
+
+/**
+ * Model of the Generator setting for D3 GEO
+ */
+interface GeneratorSettings {
+ objectTag: string;
+ projection: Object;
+ logicalSize: number;
+ mapFillScale: number;
+}
+
+/**
+ * Model of the Path Generator
+ */
+interface PathGenerator {
+ geodata: FeatureCollection;
+ pathgen: (Feature) => string;
+ settings: GeneratorSettings;
+}
+
+/**
+ * Model of the Feature returned prom topojson library
+ */
+interface Feature {
+ geometry: Object;
+ id: string;
+ properties: Object;
+ type: string;
+}
+
+/**
+ * Model of the Features Collection returned by the topojson.features function
+ */
+interface FeatureCollection {
+ type: string;
+ features: Feature[];
+}
+
+/**
+ * Model of the topojson file
+ */
+interface TopoData {
+ type: string; // Usually "Topology"
+ objects: Object; // Can be a list of countries or individual countries
+ arcs: number[][][]; // Coordinates
+ bbox: number[]; // Bounding box
+ transform: TopoDataTransform; // scale and translate
+}
+
+/**
+ * Default settings for the path generator for TopoJson
+ */
+const DEFAULT_GEN_SETTINGS: GeneratorSettings = <GeneratorSettings>{
+ objectTag: 'states',
+ projection: d3.geoMercator(),
+ logicalSize: 1000,
+ mapFillScale: .95,
+};
@Component({
selector: '[onos-mapsvg]',
templateUrl: './mapsvg.component.html',
styleUrls: ['./mapsvg.component.css']
})
-export class MapSvgComponent implements OnInit {
+export class MapSvgComponent implements OnChanges {
+ @Input() map: MapObject = <MapObject>{id: 'none'};
- constructor() { }
+ cache = new Map<string, TopoData>();
+ topodata: TopoData;
+ mapPathGenerator: PathGenerator;
- ngOnInit() {
+ constructor(
+ private log: LogService,
+ private httpClient: HttpClient,
+ ) {
+ this.log.debug('MapSvgComponent constructed');
}
+ static getUrl(id: string): string {
+ if (id && id[0] === '*') {
+ return BUNDLED_URL_PREFIX + id.slice(1) + '.topojson';
+ }
+ return id + '.topojson';
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ this.log.debug('Change detected', changes);
+ if (changes['map']) {
+ const map: MapObject = <MapObject>(changes['map'].currentValue);
+ if (map.id) {
+ if (this.cache.get(map.id)) {
+ this.topodata = this.cache.get(map.id);
+ } else {
+ this.httpClient
+ .get(MapSvgComponent.getUrl(map.filePath))
+ .subscribe((topoData: TopoData) => {
+ this.mapPathGenerator = this.handleTopoJson(map, topoData);
+ this.log.debug('Path Generated for', map.id,
+ 'from', MapSvgComponent.getUrl(map.filePath));
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * Wrapper for the path generator function
+ * @param feature The county or state within the map
+ */
+ pathGenerator(feature: Feature): string {
+ return this.mapPathGenerator.pathgen(feature);
+ }
+
+ /**
+ * Handle the topojson file stream as it arrives back from the server
+ *
+ * The topojson library converts the topojson file in to a FeatureCollection
+ * d3.geo then further converts this in to a Path
+ *
+ * @param map The Map chosen in the GUI
+ * @param topoData The data in the TopoJson file
+ */
+ handleTopoJson(map: MapObject, topoData: TopoData): PathGenerator {
+ this.topodata = topoData;
+ this.cache.set(map.id, topoData);
+ this.log.debug('Map retrieved', topoData);
+
+ const topoObject = topoData.objects[map.id];
+ const geoData: FeatureCollection = <FeatureCollection>topojson.feature(topoData, topoObject);
+ this.log.debug('Map retrieved', topoData, geoData);
+
+ const settings: GeneratorSettings = Object.assign({}, DEFAULT_GEN_SETTINGS);
+ const path = d3.geoPath().projection(settings.projection);
+ this.rescaleProjection(
+ settings.projection,
+ settings.mapFillScale,
+ settings.logicalSize,
+ path,
+ geoData);
+ this.log.debug('Scale adjusted');
+
+ return <PathGenerator>{
+ geodata: geoData,
+ pathgen: path,
+ settings: settings
+ };
+ }
+
+ /**
+ * Adjust projection scale and translation to fill the view
+ * with the map
+ * @param proj
+ * @param mfs
+ * @param dim
+ * @param path
+ * @param geoData
+ * @param adjustScale
+ */
+ rescaleProjection(proj: any, mfs: number, dim: number, path: any,
+ geoData: FeatureCollection, adjustScale: number = 1.0) {
+ // start with unit scale, no translation..
+ proj.scale(1).translate([0, 0]);
+
+ // figure out dimensions of map data..
+ const b = path.bounds(geoData);
+ const x1 = b[0][0];
+ const y1 = b[0][1];
+ const x2 = b[1][0];
+ const y2 = b[1][1];
+ const dx = x2 - x1;
+ const dy = y2 - y1;
+ const x = (x1 + x2) / 2;
+ const y = (y1 + y2) / 2;
+
+ // size map to 95% of minimum dimension to fill space..
+ const s = (mfs / Math.min(dx / dim, dy / dim)) * adjustScale;
+ const t = [dim / 2 - s * x, dim / 2 - s * y];
+
+ // set new scale, translation on the projection..
+ proj.scale(s).translate(t);
+ }
}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/maputils.ts b/web/gui2/src/main/webapp/app/view/topology/layer/maputils.ts
new file mode 100644
index 0000000..45c3140
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/maputils.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2019-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.
+ */
+export interface MapObject {
+ id: string;
+ description: string;
+ filePath: string;
+ scale: number;
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts
index f298bf5..23d75c5 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts
@@ -15,26 +15,51 @@
*/
import { ZoomableDirective } from './zoomable.directive';
import {inject, TestBed} from '@angular/core/testing';
-import {LogService, ConsoleLoggerService} from 'gui2-fw-lib';
+import {LogService, ConsoleLoggerService, FnService} from 'gui2-fw-lib';
import {ElementRef} from '@angular/core';
+import {ActivatedRoute, Params} from '@angular/router';
+import {of} from 'rxjs';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
describe('ZoomableDirective', () => {
+ let fs: FnService;
+ let ar: MockActivatedRoute;
let log: LogService;
let mockWindow: Window;
beforeEach(() => {
log = new ConsoleLoggerService();
+ ar = new MockActivatedRoute({ 'debug': 'txrx' });
mockWindow = <any>{
navigator: {
userAgent: 'HeadlessChrome',
vendor: 'Google Inc.'
+ },
+ location: <any>{
+ hostname: 'foo',
+ host: 'foo',
+ port: '80',
+ protocol: 'http',
+ search: { debug: 'true' },
+ href: 'ws://foo:123/onos/ui/websock/path',
+ absUrl: 'ws://foo:123/onos/ui/websock/path'
}
};
+ fs = new FnService(ar, log, mockWindow);
+
TestBed.configureTestingModule({
providers: [ZoomableDirective,
- {provide: LogService, useValue: log},
+ { provide: FnService, useValue: fs },
+ { provide: LogService, useValue: log },
+ { provide: 'Window', useValue: mockWindow },
{ provide: ElementRef, useValue: mockWindow }
]
});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts
index bd69db1..0484384 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts
@@ -13,46 +13,107 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Directive, ElementRef, Input, OnChanges} from '@angular/core';
-import {LogService} from 'gui2-fw-lib';
+import {
+ Directive,
+ ElementRef,
+ Input,
+ OnChanges,
+ OnInit,
+ SimpleChanges
+} from '@angular/core';
+import {LogService, PrefsService} from 'gui2-fw-lib';
import * as d3 from 'd3';
+
+/**
+ * Model of the Zoom preferences
+ */
+export interface TopoZoomPrefs {
+ tx: number;
+ ty: number;
+ sc: number;
+}
+
+const TOPO_ZOOM_PREFS = 'topo_zoom';
+
+const ZOOM_PREFS_DEFAULT: TopoZoomPrefs = <TopoZoomPrefs>{
+ tx: 0, ty: 0, sc: 1.0
+};
+
+/**
+ * A directive that takes care of Zooming and Panning the Topology view
+ *
+ * It wraps the D3 Pan and Zoom functionality
+ * See https://github.com/d3/d3-zoom/blob/master/README.md
+ */
@Directive({
selector: '[onosZoomableOf]'
})
-export class ZoomableDirective implements OnChanges {
+export class ZoomableDirective implements OnChanges, OnInit {
@Input() zoomableOf: ElementRef;
+ zoom: any; // The d3 zoom behaviour
+
constructor(
private _element: ElementRef,
private log: LogService,
- ) {}
+ private ps: PrefsService
+ ) {
+ const container = d3.select(this._element.nativeElement);
+
+ const zoomed = () => {
+ const transform = d3.event.transform;
+ container.attr('transform', 'translate(' + transform.x + ',' + transform.y + ') scale(' + transform.k + ')');
+ this.updateZoomState(transform.x, transform.y, transform.k);
+ };
+
+ this.zoom = d3.zoom().on('zoom', zoomed);
+ }
+
+ ngOnInit() {
+ const zoomState: TopoZoomPrefs = this.ps.getPrefs(TOPO_ZOOM_PREFS, ZOOM_PREFS_DEFAULT);
+ const svg = d3.select(this.zoomableOf);
+
+ svg.call(this.zoom);
+
+ svg.transition().call(this.zoom.transform,
+ d3.zoomIdentity.translate(zoomState.tx, zoomState.ty).scale(zoomState.sc));
+ this.log.debug('Loaded topo_zoom_prefs',
+ zoomState.tx, zoomState.ty, zoomState.sc);
+
+ }
+
+ /**
+ * Updates the cache of zoom preferences locally and onwards to the PrefsService
+ */
+ updateZoomState(x: number, y: number, scale: number): void {
+ this.ps.setPrefs(TOPO_ZOOM_PREFS, <TopoZoomPrefs>{
+ tx: x,
+ ty: y,
+ sc: scale
+ });
+ }
/**
* If the input object is changed then re-establish the zoom
*/
- ngOnChanges() {
- let zoomed, zoom;
-
- const svg = d3.select(this.zoomableOf);
- const container = d3.select(this._element.nativeElement);
-
- zoomed = () => {
- const transform = d3.event.transform;
- container.attr('transform', 'translate(' + transform.x + ',' + transform.y + ') scale(' + transform.k + ')');
- };
-
- zoom = d3.zoom().on('zoom', zoomed);
- svg.call(zoom);
- this.log.debug('Applying zoomable behaviour on', this.zoomableOf, this._element.nativeElement);
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes['zoomableOf']) {
+ const svg = d3.select(changes['zoomableOf'].currentValue);
+ svg.call(this.zoom);
+ this.log.debug('Applying zoomable behaviour on', this.zoomableOf, this._element.nativeElement);
+ }
}
/**
* Reset the zoom when the R key is pressed when in Topology view
+ *
+ * Animated to run over 750ms
*/
resetZoom(): void {
- const container = d3.select(this._element.nativeElement);
- container.attr('transform', 'translate(0,0) scale(1.0)');
+ const svg = d3.select(this.zoomableOf);
+ svg.transition().duration(750).call(this.zoom.transform, d3.zoomIdentity);
+ this.updateZoomState(0, 0, 1.0);
this.log.debug('Pan to 0,0 and zoom to 1.0');
}