First part of migrating Topo2 to GUI2
Change-Id: I316dd34cba161688e01dfb7b340bff5f2c3c57d4
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyph.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyph.service.spec.ts
new file mode 100644
index 0000000..5b4669d
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyph.service.spec.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from '../log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { GlyphService } from './glyph.service';
+import { FnService } from '../util/fn.service';
+
+class MockFnService {}
+
+/**
+ * ONOS GUI -- SVG -- Glyph Service - Unit Tests
+ */
+describe('GlyphService', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [GlyphService,
+ { provide: FnService, useClass: MockFnService },
+ { provide: LogService, useValue: log },
+ ]
+ });
+ });
+
+ it('should be created', inject([GlyphService], (service: GlyphService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyphdata.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyphdata.service.spec.ts
new file mode 100644
index 0000000..ab770d5
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyphdata.service.spec.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from '..//log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { GlyphDataService } from './glyphdata.service';
+
+/**
+ * ONOS GUI -- SVG -- Glyph Data Service - Unit Tests
+ */
+describe('GlyphDataService', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [GlyphDataService,
+ { provide: LogService, useValue: log },
+ ]
+ });
+ });
+
+ it('should be created', inject([GlyphDataService], (service: GlyphDataService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.spec.ts
new file mode 100644
index 0000000..094baef
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.spec.ts
@@ -0,0 +1,50 @@
+/*
+ * 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 { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from '../log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { IconService } from './icon.service';
+import { GlyphService } from './glyph.service';
+import { SvgUtilService } from './svgutil.service';
+
+class MockGlyphService {}
+
+class MockSvgUtilService {}
+
+/**
+ * ONOS GUI -- SVG -- Icon Service - Unit Tests
+ */
+describe('IconService', () => {
+
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [IconService,
+ { provide: LogService, useValue: log },
+ { provide: GlyphService, useClass: MockGlyphService },
+ { provide: SvgUtilService, useClass: MockSvgUtilService },
+ ]
+ });
+ });
+
+ it('should be created', inject([IconService], (service: IconService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
index 6ced162..73480ed 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
@@ -43,6 +43,7 @@
['m_ports', 'm_ports'],
['topo', 'topo'],
+ ['bird', 'bird'],
['refresh', 'refresh'],
['query', 'query'],
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon/icon.component.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon/icon.component.spec.ts
new file mode 100644
index 0000000..8234551
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon/icon.component.spec.ts
@@ -0,0 +1,30 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LogService } from '../../log.service';
+import { ConsoleLoggerService } from '../../consolelogger.service';
+import { IconComponent } from './icon.component';
+import { IconService } from '../icon.service';
+
+class MockIconService {}
+
+describe('IconComponent', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ declarations: [ IconComponent ],
+ providers: [
+ { provide: LogService, useValue: log },
+ { provide: IconService, useClass: MockIconService },
+ ]
+ });
+ });
+
+ it('should create', () => {
+ const fixture = TestBed.createComponent(IconComponent);
+ const component = fixture.componentInstance;
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.spec.ts
new file mode 100644
index 0000000..7165b33
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.spec.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 { TestBed, inject } from '@angular/core/testing';
+
+import { LogService } from '../log.service';
+import { ConsoleLoggerService } from '../consolelogger.service';
+import { SvgUtilService } from './svgutil.service';
+import { FnService } from '../util/fn.service';
+
+class MockFnService {}
+
+/**
+ * ONOS GUI -- SVG -- Svg Util Service - Unit Tests
+ */
+describe('SvgUtilService', () => {
+ let log: LogService;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ TestBed.configureTestingModule({
+ providers: [SvgUtilService,
+ { provide: LogService, useValue: log },
+ { provide: FnService, useClass: MockFnService },
+ ]
+ });
+ });
+
+ it('should be created', inject([SvgUtilService], (service: SvgUtilService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.ts
index 13327fe..6107d16 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/svgutil.service.ts
@@ -16,6 +16,7 @@
import { Injectable } from '@angular/core';
import { FnService } from '../util/fn.service';
import { LogService } from '../log.service';
+import * as d3 from 'd3';
/**
* ONOS GUI -- SVG -- Util Service
@@ -26,11 +27,45 @@
providedIn: 'root',
})
export class SvgUtilService {
+ lightNorm: string[];
+ lightMute: string[];
+ darkNorm: string[];
+ darkMute: string[];
+ colors: any;
constructor(
private fs: FnService,
private log: LogService
) {
+
+ // --- Ordinal scales for 7 values.
+ // TODO: migrate these colors to the theme service.
+
+ // Colors per Mojo-Design's color palette.. (version one)
+ // blue red dk grey steel lt blue lt red lt grey
+ // var lightNorm = ['#5b99d2', '#d05a55', '#716b6b', '#7e9aa8', '#66cef6', '#db7773', '#aeada8' ],
+ // lightMute = ['#a8cceb', '#f1a7a7', '#b9b5b5', '#bdcdd5', '#a8e9fd', '#f8c9c9', '#d7d6d4' ],
+
+ // Colors per Mojo-Design's color palette.. (version two)
+ // blue lt blue red green brown teal lime
+ this.lightNorm = ['#5b99d2', '#66cef6', '#d05a55', '#0f9d58', '#ba7941', '#3dc0bf', '#56af00'];
+ this.lightMute = ['#9ebedf', '#abdef5', '#d79a96', '#7cbe99', '#cdab8d', '#96d5d5', '#a0c96d'];
+
+ this.darkNorm = ['#5b99d2', '#66cef6', '#d05a55', '#0f9d58', '#ba7941', '#3dc0bf', '#56af00'];
+ this.darkMute = ['#9ebedf', '#abdef5', '#d79a96', '#7cbe99', '#cdab8d', '#96d5d5', '#a0c96d'];
+
+
+ this.colors = {
+ light: {
+ norm: d3.scaleOrdinal().range(this.lightNorm),
+ mute: d3.scaleOrdinal().range(this.lightMute),
+ },
+ dark: {
+ norm: d3.scaleOrdinal().range(this.darkNorm),
+ mute: d3.scaleOrdinal().range(this.darkMute),
+ },
+ };
+
this.log.debug('SvgUtilService constructed');
}
@@ -40,4 +75,91 @@
}
return 'translate(' + x + ',' + y + ')';
}
+
+ scale(x, y) {
+ return 'scale(' + x + ',' + y + ')';
+ }
+
+ skewX(x) {
+ return 'skewX(' + x + ')';
+ }
+
+ rotate(deg) {
+ return 'rotate(' + deg + ')';
+ }
+
+ cat7() {
+ const tcid = 'd3utilTestCard';
+
+ function getColor(id, muted, theme) {
+ // NOTE: since we are lazily assigning domain ids, we need to
+ // get the color from all 4 scales, to keep the domains
+ // in sync.
+ const ln = this.colors.light.norm(id);
+ const lm = this.colors.light.mute(id);
+ const dn = this.colors.dark.norm(id);
+ const dm = this.colors.dark.mute(id);
+ if (theme === 'dark') {
+ return muted ? dm : dn;
+ } else {
+ return muted ? lm : ln;
+ }
+ }
+
+ function testCard(svg) {
+ let g = svg.select('g#' + tcid);
+ const dom = d3.range(7);
+ let k;
+ let muted;
+ let theme;
+ let what;
+
+ if (!g.empty()) {
+ g.remove();
+
+ } else {
+ g = svg.append('g')
+ .attr('id', tcid)
+ .attr('transform', 'scale(4)translate(20,20)');
+
+ for (k = 0; k < 4; k++) {
+ muted = k % 2;
+ what = muted ? ' muted' : ' normal';
+ theme = k < 2 ? 'light' : 'dark';
+ dom.forEach(function (id, i) {
+ const x = i * 20;
+ const y = k * 20;
+ const f = getColor(id, muted, theme);
+ g.append('circle').attr({
+ cx: x,
+ cy: y,
+ r: 5,
+ fill: f,
+ });
+ });
+ g.append('rect').attr({
+ x: 140,
+ y: k * 20 - 5,
+ width: 32,
+ height: 10,
+ rx: 2,
+ fill: '#888',
+ });
+ g.append('text').text(theme + what)
+ .attr({
+ x: 142,
+ y: k * 20 + 2,
+ fill: 'white',
+ })
+ .style('font-size', '4pt');
+ }
+ }
+ }
+
+ return {
+ testCard: testCard,
+ getColor: getColor,
+ };
+ }
+
}
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/zoom.service.spec.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/zoom.service.spec.ts
new file mode 100644
index 0000000..fe25860
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/zoom.service.spec.ts
@@ -0,0 +1,193 @@
+/*
+ * 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 { TestBed, inject } from '@angular/core/testing';
+import { ActivatedRoute, Params } from '@angular/router';
+import { of } from 'rxjs';
+import * as d3 from 'd3';
+
+import { LogService } from '../log.service';
+import { FnService } from '../util/fn.service';
+
+import { ZoomService, CZ, D3S, ZoomOpts, Zoomer } from './zoom.service';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
+
+
+/**
+ * ONOS GUI -- SVG -- Zoom Service - Unit Tests
+ */
+describe('ZoomService', () => {
+ let zs: ZoomService;
+ let ar: ActivatedRoute;
+ let fs: FnService;
+ let mockWindow: Window;
+ let logServiceSpy: jasmine.SpyObj<LogService>;
+
+ const svg = d3.select('body').append('svg').attr('id', 'mySvg');
+ const zoomLayer = svg.append('g').attr('id', 'myZoomlayer');
+
+ beforeEach(() => {
+ const logSpy = jasmine.createSpyObj('LogService', ['debug', 'warn', 'error']);
+ ar = new MockActivatedRoute({'debug': 'TestService'});
+ mockWindow = <any>{
+ innerWidth: 400,
+ innerHeight: 200,
+ navigator: {
+ userAgent: 'defaultUA'
+ }
+ };
+ fs = new FnService(ar, logSpy, mockWindow);
+
+ TestBed.configureTestingModule({
+ providers: [ ZoomService,
+ { provide: FnService, useValue: fs },
+ { provide: LogService, useValue: logSpy },
+ { provide: ActivatedRoute, useValue: ar },
+ { provide: 'Window', useFactory: (() => mockWindow ) }
+ ]
+ });
+
+ zs = TestBed.get(ZoomService);
+ logServiceSpy = TestBed.get(LogService);
+ });
+
+ it('should be created', () => {
+ expect(zs).toBeTruthy();
+ });
+
+ it('should define ZoomService', function () {
+ expect(zs).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(zs, [
+ 'createZoomer',
+ 'zoomed',
+ 'adjustZoomLayer'
+ ])).toBeTruthy();
+ });
+
+ function verifyZoomerApi() {
+ expect(fs.areFunctions(zs.zoomer, [
+ 'panZoom', 'reset', 'translate', 'scale', 'scaleExtent'
+ ])).toBeTruthy();
+ }
+
+ it('should fail gracefully with no option object', function () {
+ expect(() => zs.createZoomer(<ZoomOpts>{}))
+ .toThrow(new Error(CZ + 'No "svg" (svg tag)' + D3S));
+ expect(logServiceSpy.error)
+ .toHaveBeenCalledWith(CZ + 'No "svg" (svg tag)' + D3S);
+ });
+
+ it('should complain if we miss required options', function () {
+ expect(() => zs.createZoomer(<ZoomOpts>{svg: svg}))
+ .toThrow(new Error(CZ + 'No "zoomLayer" (g tag)' + D3S));
+ expect(logServiceSpy.error).toHaveBeenCalledWith(CZ + 'No "zoomLayer" (g tag)' + D3S);
+ });
+
+ it('should work with minimal parameters', function () {
+ const zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomLayer: zoomLayer
+ });
+ expect(logServiceSpy.error).not.toHaveBeenCalled();
+ verifyZoomerApi();
+ });
+
+ it('should start at scale 1 and translate 0,0', function () {
+ const zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomLayer: zoomLayer
+ });
+ verifyZoomerApi();
+ expect(zoomer.translate()).toEqual([0, 0]);
+ expect(zoomer.scale()).toEqual(1);
+ });
+
+ it('should allow programmatic pan/zoom', function () {
+ const zoomer: Zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomLayer: zoomLayer
+ });
+ verifyZoomerApi();
+
+ expect(zoomer.translate()).toEqual([0, 0]);
+ expect(zoomer.scale()).toEqual(1);
+
+ zoomer.panZoom([20, 30], 1);
+ expect(zoomer.translate()).toEqual([20, 30]);
+ expect(zoomer.scale()).toEqual(1);
+
+ zoomer.reset();
+ expect(zoomer.translate()).toEqual([0, 0]);
+ expect(zoomer.scale()).toEqual(1);
+
+
+ });
+
+ it('should provide default scale extent', function () {
+ const zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomLayer: zoomLayer
+ });
+ expect(zoomer.scaleExtent()).toEqual([0.05, 50]);
+ });
+
+ it('should allow us to override the minimum zoom', function () {
+ const zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomLayer: zoomLayer,
+ zoomMin: 1.23
+ });
+ expect(zoomer.scaleExtent()).toEqual([1.23, 50]);
+ });
+
+ it('should allow us to override the maximum zoom', function () {
+ const zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomLayer: zoomLayer,
+ zoomMax: 13
+ });
+ expect(zoomer.scaleExtent()).toEqual([0.05, 13]);
+ });
+
+ // TODO: test zoomed() where we fake out the d3.event.sourceEvent etc...
+ // need to check default enabled (true) and custom enabled predicate
+ // need to check that the callback is invoked also
+
+ it('should invoke the callback on programmatic pan/zoom', function () {
+ const foo = { cb() { return; } };
+ spyOn(foo, 'cb');
+
+ const zoomer = zs.createZoomer(<ZoomOpts>{
+ svg: svg,
+ zoomMin: 0.25,
+ zoomMax: 10,
+ zoomLayer: zoomLayer,
+ zoomEnabled: (ev) => true,
+ zoomCallback: foo.cb,
+ });
+
+ zoomer.panZoom([0, 0], 2);
+ expect(foo.cb).toHaveBeenCalled();
+ });
+});
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/zoom.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/zoom.service.ts
new file mode 100644
index 0000000..fdf08f9
--- /dev/null
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/zoom.service.ts
@@ -0,0 +1,148 @@
+/*
+ * 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);
+ }
+
+}