675 lines
34 KiB
JavaScript
675 lines
34 KiB
JavaScript
"use strict";
|
|
(function () {
|
|
'use strict';
|
|
angular.module('graphModule')
|
|
.directive('directedGraph', ['$timeout', '$window', 'ciExplorerModel', '$interval', function ($timeout, $window, ciExplorerModel, $interval) {
|
|
return {
|
|
restrict: 'AE',
|
|
replace: true,
|
|
scope: {
|
|
nodes: '=',
|
|
edges: '=',
|
|
selections: '=',
|
|
servicesCount: '=',
|
|
isFixed: '=',
|
|
isFullScreen: '='
|
|
},
|
|
templateUrl: 'components/graph/directed-graph-template.html',
|
|
link: function (scope, element) {
|
|
var graphPan = element.find('.directed-graph__pan'), minZoom = 0.4, initialZoom = 1.5, zoomStep = 0.2, verticalLayoutDirection = false;
|
|
var userAgents = ['Chrome', 'Safari', 'Firefox'];
|
|
var userAgent = $window.navigator.userAgent;
|
|
var isIE = true;
|
|
_.each(userAgents, function (browser) {
|
|
var reg = new RegExp(browser, 'i');
|
|
if (reg.exec(userAgent)) {
|
|
isIE = false;
|
|
}
|
|
});
|
|
function generateGraphStyles() {
|
|
var styleChain = cytoscape.stylesheet()
|
|
.selector('node[?hover]')
|
|
.css({
|
|
'content': 'data(label)'
|
|
})
|
|
.selector('node[!hover]')
|
|
.css({
|
|
'content': 'data(shortLabel)'
|
|
})
|
|
.selector('node')
|
|
.css({
|
|
'background-fit': "cover",
|
|
'font-size': 8,
|
|
'shape': 'rectangle',
|
|
'background-opacity': 0,
|
|
'text-valign': 'bottom',
|
|
'text-wrap': 'wrap',
|
|
'text-max-width': 80
|
|
})
|
|
.selector('node.selected')
|
|
.css({
|
|
'background-fit': "cover",
|
|
'font-size': 8,
|
|
'shape': 'rectangle',
|
|
'background-opacity': 0,
|
|
'text-valign': 'bottom'
|
|
})
|
|
.selector('.big-node')
|
|
.css({
|
|
width: 35,
|
|
height: 35
|
|
})
|
|
.selector('.small-node[?hover]')
|
|
.css({
|
|
'content': 'data(label)'
|
|
})
|
|
.selector('.small-node[!hover]')
|
|
.css({
|
|
'content': 'data(shortLabel)'
|
|
})
|
|
.selector('.small-node')
|
|
.css({
|
|
'height': 20,
|
|
'width': 20,
|
|
'background-fit': 'cover',
|
|
'font-size': 5,
|
|
'background-opacity': 0,
|
|
'text-valign': 'bottom'
|
|
})
|
|
.selector('edge')
|
|
.css({
|
|
'width': 0.3,
|
|
'line-color': '#666666',
|
|
'target-arrow-shape': 'triangle'
|
|
})
|
|
.selector('.group-node')
|
|
.css({
|
|
'shape': 'roundrectangle'
|
|
})
|
|
.selector('.collapsed-node')
|
|
.css({
|
|
'content': 'data(label)',
|
|
'color': '#2bb5dc',
|
|
'background-color': '#2bb5dc',
|
|
'background-opacity': 0.5,
|
|
'background-image': 'none',
|
|
'border-width': 0.2,
|
|
'font-size': 12,
|
|
'text-valign': 'center',
|
|
'height': 75,
|
|
'width': 100
|
|
})
|
|
.selector('.expanded-node')
|
|
.css({
|
|
'content': '',
|
|
'background-color': '#2bb5dc',
|
|
'background-opacity': 0.2,
|
|
'background-image': 'none',
|
|
'border-width': 0.2
|
|
})
|
|
.selector('.hidden-node')
|
|
.css({
|
|
'visibility': 'hidden'
|
|
})
|
|
.selector('.expand-button')
|
|
.css({
|
|
'height': '30px',
|
|
'width': '75px'
|
|
});
|
|
if (isIE) {
|
|
styleChain
|
|
.selector('node')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/generic_ci.png'
|
|
})
|
|
.selector('node.selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/generic_ci_selected.png'
|
|
})
|
|
.selector('.collapsed-node')
|
|
.css({
|
|
'background-image': 'none'
|
|
})
|
|
.selector('.expanded-node')
|
|
.css({
|
|
'background-image': 'none'
|
|
})
|
|
.selector('.application')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/application.png'
|
|
})
|
|
.selector('.application-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/application_selected.png'
|
|
})
|
|
.selector('.cluster')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/cluster.png'
|
|
})
|
|
.selector('.cluster-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/cluster_selected.png'
|
|
})
|
|
.selector('.computersystem')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/computer_system.png'
|
|
})
|
|
.selector('.computersystem-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/computer_system_selected.png'
|
|
})
|
|
.selector('.database')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/database.png'
|
|
})
|
|
.selector('.database-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/database_selected.png'
|
|
})
|
|
.selector('.filesystem')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/file_system.png'
|
|
})
|
|
.selector('.filesystem-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/file_system_selected.png'
|
|
})
|
|
.selector('.group')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/group.png'
|
|
})
|
|
.selector('.group-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/group_selected.png'
|
|
})
|
|
.selector('.media')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/media.png'
|
|
})
|
|
.selector('.media-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/media_selected.png'
|
|
})
|
|
.selector('.network')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/network.png'
|
|
})
|
|
.selector('.network-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/network_selected.png'
|
|
})
|
|
.selector('.people')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/people.png'
|
|
})
|
|
.selector('.people-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/people_selected.png'
|
|
})
|
|
.selector('.resource')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/resource.png'
|
|
})
|
|
.selector('.resource-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/resource_selected.png'
|
|
})
|
|
.selector('.service')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/service.png'
|
|
})
|
|
.selector('.service-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/service_selected.png'
|
|
})
|
|
.selector('.software')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/software.png'
|
|
})
|
|
.selector('.software-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/software_selected.png'
|
|
})
|
|
.selector('.ups')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/ups.png'
|
|
})
|
|
.selector('.ups-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/ups_selected.png'
|
|
})
|
|
.selector('.collapsed-node')
|
|
.css({
|
|
'background-image': 'none'
|
|
});
|
|
}
|
|
else {
|
|
styleChain
|
|
.selector('node')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/generic_ci.svg',
|
|
})
|
|
.selector('node.selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/generic_ci_selected.svg'
|
|
})
|
|
.selector('.collapsed-node')
|
|
.css({
|
|
'background-image': 'none'
|
|
})
|
|
.selector('.expanded-node')
|
|
.css({
|
|
'background-image': 'none'
|
|
})
|
|
.selector('.application')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/application.svg'
|
|
})
|
|
.selector('.application-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/application_selected.svg'
|
|
})
|
|
.selector('.cluster')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/cluster.svg'
|
|
})
|
|
.selector('.cluster-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/cluster_selected.svg'
|
|
})
|
|
.selector('.computersystem')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/computer_system.svg'
|
|
})
|
|
.selector('.computersystem-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/computer_system_selected.svg'
|
|
})
|
|
.selector('.database')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/database.svg'
|
|
})
|
|
.selector('.database-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/database_selected.svg'
|
|
})
|
|
.selector('.filesystem')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/file_system.svg'
|
|
})
|
|
.selector('.filesystem-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/file_system_selected.svg'
|
|
})
|
|
.selector('.group')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/group.svg'
|
|
})
|
|
.selector('.group-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/group_selected.svg'
|
|
})
|
|
.selector('.media')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/media.svg'
|
|
})
|
|
.selector('.media-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/media_selected.svg'
|
|
})
|
|
.selector('.network')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/network.svg'
|
|
})
|
|
.selector('.network-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/network_selected.svg'
|
|
})
|
|
.selector('.people')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/people.svg'
|
|
})
|
|
.selector('.people-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/people_selected.svg'
|
|
})
|
|
.selector('.resource')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/resource.svg'
|
|
})
|
|
.selector('.resource-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/resource_selected.svg'
|
|
})
|
|
.selector('.service')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/service.svg'
|
|
})
|
|
.selector('.service-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/service_selected.svg'
|
|
})
|
|
.selector('.software')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/software.svg'
|
|
})
|
|
.selector('.software-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/software_selected.svg'
|
|
})
|
|
.selector('.ups')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/ups.svg'
|
|
})
|
|
.selector('.ups-selected')
|
|
.css({
|
|
'background-image': 'styles/img/ci-type-icons/ups_selected.svg'
|
|
});
|
|
}
|
|
return styleChain;
|
|
}
|
|
_.each(scope.nodes, function (node) {
|
|
node.data.label = ciExplorerModel.getGraphLabel(node.data.label);
|
|
node.data.shortLabel = ciExplorerModel.getShortGraphLabel(node.data.label);
|
|
});
|
|
var cy = scope.cy = cytoscape({
|
|
container: graphPan[0],
|
|
style: generateGraphStyles(),
|
|
elements: {
|
|
nodes: scope.nodes,
|
|
edges: scope.edges
|
|
},
|
|
layout: {
|
|
name: 'dagre',
|
|
fit: false
|
|
},
|
|
boxSelectionEnabled: false,
|
|
wheelSensitivity: 0.2,
|
|
selectionType: 'additive',
|
|
zoom: 1,
|
|
ready: function () {
|
|
$timeout(function () {
|
|
scope.init();
|
|
scope.$emit('impactGraphInit', { state: 0 });
|
|
scope.state.isLoading = false;
|
|
});
|
|
}
|
|
});
|
|
var tappedBefore = null;
|
|
cy.on('tap', function (event) {
|
|
var tappedNow = event.cyTarget;
|
|
$timeout(function () { tappedBefore = null; }, 300);
|
|
if (tappedBefore === tappedNow) {
|
|
tappedNow.trigger('doubleTap');
|
|
tappedBefore = null;
|
|
}
|
|
else {
|
|
tappedBefore = tappedNow;
|
|
}
|
|
});
|
|
cy.on('doubleTap', '.group-node', function (event) {
|
|
toggleGroupNode(event.cyTarget);
|
|
}); // on double click
|
|
cy.on('select', 'node', function (event) {
|
|
var nodes = this;
|
|
for (var n = 0; n < nodes.length; n++) {
|
|
var nd = nodes[n];
|
|
if (nd.hasClass('group-node')) {
|
|
continue;
|
|
}
|
|
if (nd.data('icon')) {
|
|
nd.removeClass(nd.data('icon'));
|
|
nd.addClass(nd.data('icon') + "-selected selected");
|
|
}
|
|
scope.selections.push(nd.data('id'));
|
|
}
|
|
cy.boxSelectionEnabled(false);
|
|
});
|
|
cy.on('unselect', 'node', function (event) {
|
|
var nodes = this;
|
|
for (var n = 0; n < nodes.length; n++) {
|
|
var nd = nodes[n];
|
|
if (nd.data('icon')) {
|
|
nd.addClass(nd.data('icon'));
|
|
nd.removeClass(nd.data('icon') + "-selected selected");
|
|
}
|
|
var idx = scope.selections.indexOf(nd.data('id'));
|
|
scope.selections.splice(idx, 1);
|
|
}
|
|
});
|
|
cy.on('mouseover', 'node', function (event) {
|
|
var node = event.cyTarget;
|
|
node.data('hover', true);
|
|
});
|
|
cy.on('mouseout', 'node', function (event) {
|
|
var node = event.cyTarget;
|
|
node.data('hover', false);
|
|
});
|
|
scope.init = function () {
|
|
var roots = cy.nodes().roots(), hasChildren = {}, rootsOutgoingEdges = roots.outgoers().filter('edge'), groupNode;
|
|
// first, iterate over all roots and find out if they have children
|
|
_.each(roots, function (root) {
|
|
var rootId = root.data('id'), successors = root.successors(), leaves = successors.leaves(), groupedNodes = successors.difference(leaves).filter('node');
|
|
hasChildren[rootId] = groupedNodes.length > 0;
|
|
});
|
|
_.each(roots, function (root) {
|
|
root.unselectify();
|
|
var rootId = root.data('id'), successors = root.successors(), leaves = successors.leaves(), groupedNodesandEdges = successors.difference(leaves), groupedNodes = groupedNodesandEdges.filter('node');
|
|
if (groupNode && hasChildren[rootId]) {
|
|
cy.add({ group: "edges", data: { source: rootId, target: groupNode[0].data.id } });
|
|
}
|
|
if (groupedNodes.length > 0) {
|
|
groupedNodes.addClass('small-node');
|
|
var nodesToAdd = [];
|
|
_.each(leaves, function (leaf) {
|
|
var leafId = leaf.data('id');
|
|
var edgesWithGroup = groupedNodes.edgesWith(leaf);
|
|
if (edgesWithGroup.length > 0) {
|
|
var edge = { group: "edges", data: { source: "groupNode__" + rootId, target: leafId } };
|
|
nodesToAdd.push(edge);
|
|
var edgesWithRoot = root.edgesWith(leaf).remove();
|
|
groupedNodesandEdges = groupedNodesandEdges.difference(edgesWithRoot);
|
|
}
|
|
leaf.addClass('big-node');
|
|
});
|
|
groupNode = [
|
|
{
|
|
group: "nodes",
|
|
data: {
|
|
id: "groupNode__" + rootId,
|
|
groupElements: groupedNodesandEdges.add(rootsOutgoingEdges),
|
|
parentId: rootId,
|
|
label: groupedNodes.length + " CIs"
|
|
},
|
|
classes: "group-node collapsed-node"
|
|
},
|
|
{ group: "edges", data: { source: rootId, target: "groupNode__" + rootId } }
|
|
];
|
|
root.addClass('big-node');
|
|
groupedNodes.remove();
|
|
cy.add(groupNode.concat(nodesToAdd));
|
|
}
|
|
});
|
|
layout();
|
|
};
|
|
scope.state = {
|
|
isLoading: true,
|
|
largeData: (scope.nodes.length > 1000)
|
|
};
|
|
scope.switchLayoutDirection = function () {
|
|
verticalLayoutDirection = !verticalLayoutDirection;
|
|
layout();
|
|
};
|
|
var animationDuration = 250, minimumDuration = 10;
|
|
scope.toggleFullScreen = function () {
|
|
toggleBrowserFullScreen();
|
|
if (scope.isFullScreen) {
|
|
unmakeCanvasFullScreen();
|
|
}
|
|
else {
|
|
makeCanvasFullScreen();
|
|
}
|
|
};
|
|
function makeCanvasFullScreen() {
|
|
scope.isFixed = !scope.isFixed;
|
|
$timeout(function () {
|
|
scope.isFullScreen = !scope.isFullScreen;
|
|
resizeCanvas(animationDuration);
|
|
}, minimumDuration);
|
|
}
|
|
function unmakeCanvasFullScreen() {
|
|
scope.isFullScreen = !scope.isFullScreen;
|
|
$timeout(function () {
|
|
scope.isFixed = !scope.isFixed;
|
|
resizeCanvas();
|
|
}, animationDuration);
|
|
}
|
|
var stop = $interval(function () {
|
|
if (!isBrowserFullScreen() && scope.isFullScreen) {
|
|
unmakeCanvasFullScreen();
|
|
}
|
|
}, 500);
|
|
scope.$on('$destroy', function () {
|
|
console.log('directedGraph: unbind events');
|
|
$interval.cancel(stop);
|
|
cy.removeListener('tap');
|
|
cy.removeListener('doubletap');
|
|
cy.removeListener('select');
|
|
cy.removeListener('unselect');
|
|
cy.removeListener('mouseover');
|
|
cy.removeListener('mouseout');
|
|
cy = null;
|
|
graphPan = null;
|
|
});
|
|
function toggleBrowserFullScreen() {
|
|
if (!isBrowserFullScreen()) { // current working methods
|
|
if (document.documentElement.requestFullscreen) {
|
|
document.documentElement.requestFullscreen();
|
|
}
|
|
else if (document.documentElement.msRequestFullscreen) {
|
|
document.documentElement.msRequestFullscreen();
|
|
}
|
|
else if (document.documentElement.mozRequestFullScreen) {
|
|
document.documentElement.mozRequestFullScreen();
|
|
}
|
|
else if (document.documentElement.webkitRequestFullscreen) {
|
|
document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
|
|
}
|
|
}
|
|
else {
|
|
if (document.exitFullscreen) {
|
|
document.exitFullscreen();
|
|
}
|
|
else if (document.msExitFullscreen) {
|
|
document.msExitFullscreen();
|
|
}
|
|
else if (document.mozCancelFullScreen) {
|
|
document.mozCancelFullScreen();
|
|
}
|
|
else if (document.webkitExitFullscreen) {
|
|
document.webkitExitFullscreen();
|
|
}
|
|
}
|
|
}
|
|
function isBrowserFullScreen() {
|
|
return document.fullscreenElement ||
|
|
document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement;
|
|
}
|
|
function resizeCanvas(duration) {
|
|
duration = duration || 0;
|
|
$timeout(function () {
|
|
cy.resize();
|
|
layout();
|
|
}, duration);
|
|
}
|
|
scope.resetCanvas = function () {
|
|
layout();
|
|
};
|
|
scope.changeZoomLevel = function (zoomType) {
|
|
var currentZoom = cy.zoom();
|
|
if (zoomType === 'in') {
|
|
currentZoom += zoomStep;
|
|
}
|
|
else if (zoomType === 'out') {
|
|
currentZoom -= zoomStep;
|
|
}
|
|
currentZoom = Math.max(minZoom, currentZoom);
|
|
cy.zoom(currentZoom);
|
|
};
|
|
scope.enableBoxSelection = function () {
|
|
cy.boxSelectionEnabled(true);
|
|
};
|
|
function collapseGroupNode(groupNode) {
|
|
var groupElemId = groupNode.data('id');
|
|
var parentEdges = groupNode.incomers().filter('edge'), endPointEdges = cy.elements('edge[source = "' + groupElemId + '"]');
|
|
parentEdges.show();
|
|
endPointEdges.show();
|
|
groupNode.descendants().remove();
|
|
groupNode.removeClass('expanded-node').addClass('collapsed-node');
|
|
}
|
|
function expandGroupNode(groupNode) {
|
|
var groupElements = groupNode.data('groupElements'), groupElemId = groupNode.data('id'), parentEdges = groupNode.incomers().filter('edge'), endPointEdges = cy.elements('edge[source = "' + groupElemId + '"]');
|
|
if (parentEdges) {
|
|
parentEdges.hide();
|
|
}
|
|
if (endPointEdges) {
|
|
endPointEdges.hide();
|
|
}
|
|
groupElements.move({ parent: groupElemId });
|
|
groupNode.removeClass('collapsed-node').addClass('expanded-node');
|
|
}
|
|
function toggleGroupNode(node) {
|
|
if (node && node.hasClass('collapsed-node')) {
|
|
expandGroupNode(node);
|
|
}
|
|
else {
|
|
collapseGroupNode(node);
|
|
}
|
|
layout();
|
|
}
|
|
scope.collapseAll = function () {
|
|
var expandedGroupNodes = cy.$('.group-node.expanded-node');
|
|
if (expandedGroupNodes && expandedGroupNodes.length > 0) {
|
|
_.each(expandedGroupNodes, function (groupNode) {
|
|
collapseGroupNode(groupNode);
|
|
});
|
|
layout();
|
|
}
|
|
};
|
|
scope.expandAll = function () {
|
|
var collapsedGroupNodes = cy.$('.group-node.collapsed-node');
|
|
if (collapsedGroupNodes && collapsedGroupNodes.length > 0) {
|
|
_.each(collapsedGroupNodes, function (groupNode) {
|
|
expandGroupNode(groupNode);
|
|
});
|
|
layout();
|
|
}
|
|
};
|
|
scope.clearSelection = function () {
|
|
var selected = cy.$(':selected');
|
|
selected.deselect();
|
|
};
|
|
function layout() {
|
|
return cy.one('layoutstop', function () {
|
|
_.each(cy.nodes(), function (node) {
|
|
if (!verticalLayoutDirection) {
|
|
var pos = node.position();
|
|
node.position({ x: pos.y, y: pos.x });
|
|
}
|
|
});
|
|
cy.fit();
|
|
if (cy.zoom() > initialZoom) {
|
|
cy.zoom(initialZoom);
|
|
}
|
|
else if (cy.zoom() < minZoom) {
|
|
cy.zoom(minZoom);
|
|
}
|
|
cy.center();
|
|
})
|
|
.layout();
|
|
}
|
|
}
|
|
};
|
|
}]);
|
|
}());
|