SmartIT_Extensions/BMC/smart-it-full-helix/components/graph/directed-graph-directive.js

696 lines
36 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',
'curve-style': 'straight'
})
.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.target;
$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.target);
}); // 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.target;
node.data('hover', true);
});
cy.on('mouseout', 'node', function (event) {
var node = event.target;
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();
}
expandGroupsWithNode(groupElements, { parent: groupElemId });
groupNode.removeClass('collapsed-node').addClass('expanded-node');
}
function expandGroupsWithNode(groupElements, struct) {
var parentId = struct.parent;
var parentExists = parentId === null || cy.getElementById(parentId).length > 0;
if (parentExists) {
var jsons = groupElements.jsons();
var descs = groupElements.descendants();
var descsEtc = descs.merge(descs.add(groupElements).connectedEdges());
groupElements.remove(); // NB: also removes descendants and their connected edges
for (var i = 0; i < groupElements.length; i++) {
var json = jsons[i];
if (json.group === 'nodes') {
json.data.parent = parentId === null ? undefined : parentId;
}
}
}
cy.add(jsons).merge(descsEtc && descsEtc.restore());
}
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.layout({
name: 'dagre',
fit: false,
ready: 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();
}
}).run();
}
}
};
}]);
}());