--- /dev/null
+/*
+ Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+
+ 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 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.
+*/
+if (typeof dreamer === 'undefined') {
+ var dreamer = {};
+}
+var level = {}
+
+dreamer.ModelGraphEditor = (function (global) {
+ 'use strict';
+
+ var DEBUG = true;
+ var SHIFT_BUTTON = 16;
+ var IMAGE_PATH = "/static/assets/img/";
+ var GUI_VERSION = "v1";
+
+
+ ModelGraphEditor.prototype = new dreamer.GraphEditor();
+ ModelGraphEditor.prototype.constructor = ModelGraphEditor;
+ ModelGraphEditor.prototype.parent = dreamer.GraphEditor.prototype;
+
+ /**
+ * Constructor
+ */
+ function ModelGraphEditor(args) {
+
+ log("Constructor");
+
+ }
+
+
+ ModelGraphEditor.prototype.init = function (args) {
+ this.parent.init.call(this, args);
+
+ if (args.gui_properties[GUI_VERSION] != undefined) {
+ args.gui_properties = args.gui_properties[GUI_VERSION];
+ }
+
+ this.desc_id = args.desc_id || undefined;
+ this.type_property = {};
+ this.type_property["unrecognized"] = args.gui_properties["default"];
+ this.type_property["unrecognized"]["default_node_label_color"] = args.gui_properties["default"]["label_color"];
+ //this.type_property["unrecognized"]["shape"] = d3.symbolCross;
+ this._edit_mode = (args.edit_mode != undefined) ? args.edit_mode : this._edit_mode;
+
+ Object.keys(args.gui_properties["nodes"]).forEach(function (key, index) {
+ this.type_property[key] = args.gui_properties["nodes"][key];
+ if ( this.type_property[key]['property'] != undefined){
+ for(var c_prop in this.type_property[key]){
+ if(c_prop != 'property'){
+
+ this.type_property[key][c_prop]['shape'] = this.parent.get_d3_symbol(this.type_property[key][c_prop]['shape']);
+ if(this.type_property[key][c_prop]["image"] != undefined){
+ this.type_property[key][c_prop]["image"] = IMAGE_PATH + this.type_property[key][c_prop]["image"]
+ }
+ }
+ }
+ }
+ else{
+ this.type_property[key]["shape"] = this.parent.get_d3_symbol(this.type_property[key]["shape"]);
+ if (this.type_property[key]["image"] != undefined) {
+ this.type_property[key]["image"] = IMAGE_PATH + this.type_property[key]["image"];
+ }
+ }
+
+
+
+ }, this);
+ if(args.gui_properties["edges"]){
+ this.type_property_link = args.gui_properties["edges"];
+ var self = this;
+ var link_types = ['unrecognized'].concat(Object.keys(self.type_property_link))
+ this.defs.selectAll("marker")
+ .data(link_types)
+ .enter()
+ .append("svg:marker") // This section adds in the arrows
+ .attr("id", function(d){
+ return d;
+ })
+ .attr("viewBox", "-5 -5 10 10")
+ .attr("refX", 13) /*must be smarter way to calculate shift*/
+ .attr("refY", 0)
+ .attr("markerUnits", "userSpaceOnUse")
+ .attr("markerWidth", 12)
+ .attr("markerHeight", 12)
+ .attr("orient", "auto")
+ .append("path")
+ .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z")
+ .attr('fill', function(d){
+ return self.type_property_link[d].color;
+ });
+ }
+
+ this.customBehavioursOnEvents = args.behaviorsOnEvents || undefined;
+
+ var self = this;
+ var data_url = (args.data_url) ? args.data_url : "graph_data/";
+ if (!args.graph_data) {
+ d3.json(data_url, function (error, data) {
+ //console.log(JSON.stringify(data))
+ self.d3_graph.nodes = data.vertices;
+ self.d3_graph.links = data.edges;
+ self.d3_graph.graph_parameters = data.graph_parameters;
+ self.model = data.model;
+ self.refreshGraphParameters(self.d3_graph.graph_parameters);
+ self.refresh();
+ self.startForce();
+ //if(args.filter_base != undefined)
+
+ setTimeout(function () {
+ //self.handleForce(self.forceSimulationActive);
+ //var f_t = {"node":{"type":[],"group":["vlan_r3u0"]},"link":{"group":["vlan_r3u0"],"view":[""]}}
+ //var f_t ={"node":{"type":["vnf_vl","vnf_ext_cp","vnf_vdu_cp","vnf_vdu","vnf_click_vdu"],"group":["vlan_r3u0"]},"link":{"group":["vlan_r3u0"],"view":["vnf"]}}
+ self.handleFiltersParams(args.filter_base);
+ //self.handleFiltersParams(f_t);
+ //console.log(JSON.stringify(args.filter_base))
+ //console.log(self.d3_graph.nodes.length)
+ //console.log(JSON.stringify(self.d3_graph.nodes))
+ //self.d3_graph.nodes.forEach(function(key, index){
+ //console.log(key, index);
+ //})
+ }, 500);
+
+ });
+ } else {
+ this.updateData(args)
+ }
+ }
+
+ /**
+ * Update data of the graph.
+ * @param {Object} Required. An object that specifies tha data of the new node.
+ * @returns {boolean}
+ */
+ ModelGraphEditor.prototype.updateData = function (args) {
+ console.log("updateData")
+ this.d3_graph.nodes = args.graph_data.vertices;
+ this.d3_graph.links = args.graph_data.edges;
+ this.d3_graph.graph_parameters = args.graph_parameters;
+ this.model = args.model;
+ this.refreshGraphParameters(this.d3_graph.graph_parameters);
+ this.refresh();
+ this.startForce();
+ //if(args.filter_base != undefined)
+
+ //if(args.filter_base){
+ var self = this;
+ setTimeout(function () {
+ self.handleForce(true);
+ self.handleFiltersParams(args.filter_base);
+ }, 500);
+ //}
+ }
+
+ /**
+ * Add a new node to the graph.
+ * @param {Object} Required. An object that specifies tha data of the new node.
+ * @returns {boolean}
+ */
+ ModelGraphEditor.prototype.addNode = function (node, success, error) {
+ var self = this;
+ var current_layer = self.getCurrentView();
+ var node_type = node.info.type;
+
+ if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].addable) {
+ if (self.model.layer[current_layer].nodes[node_type].addable.callback) {
+ var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].addable.callback].class;
+ var controller = new dreamer[c]();
+ controller[self.model.layer[current_layer].nodes[node_type].addable.callback](self, node, function () {
+ self.parent.addNode.call(self, node);
+ success && success();
+ }, error);
+
+ } else {
+
+ log('addNode: callback undefined in model spec.');
+ error && error("You can't add a " + node.info.type + ", callback undefined.");
+ }
+ } else {
+ //FIXME Error handling????
+ log("You can't add a " + node.info.type + " in a current layer " + current_layer);
+ error && error("You can't add a " + node.info.type + " in a current layer " + current_layer);
+ }
+ };
+
+
+
+ /**
+ * Update the data properties of the node
+ * @param {Object} Required. An object that specifies tha data of the node.
+ * @returns {boolean}
+ */
+ ModelGraphEditor.prototype.updateDataNode = function (args) {
+ //FIXME updating a node properties need commit to server side!
+ this.parent.updateDataNode.call(this, args);
+ };
+
+ /**
+ * Remove a node from graph and related links.
+ * @param {String} Required. Id of node to remove.
+ * @returns {boolean}
+ */
+ ModelGraphEditor.prototype.removeNode = function (node, success, error) {
+ console.log('removeNode', JSON.stringify(node))
+ var self = this;
+ var current_layer = self.getCurrentView();
+ var node_type = node.info.type;
+ if (node.info.desc_id == undefined){
+ node.info.desc_id = self.desc_id;
+ }
+ if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].removable) {
+ if (self.model.layer[current_layer].nodes[node_type].removable.callback) {
+ var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].removable.callback].class;
+ var controller = new dreamer[c]();
+ controller[self.model.layer[current_layer].nodes[node_type].removable.callback](self, node, function () {
+ self.parent.removeNode.call(self, node);
+ success && success();
+ }, error);
+ } else {
+
+ log('removeNode: callback undefined in model spec.');
+ error && error("You can't remove a " + node.info.type + ", callback undefined.");
+ }
+ } else {
+ //FIXME we need to manage alert in a different way: FAILBACK
+ log("You can't remove a " + node.info.type);
+ error && error("You can't remove a " + node.info.type);
+ }
+ };
+
+ /**
+ * Add a new link to graph.
+ * @param {Object} Required. An object that specifies tha data of the new Link.
+ * @returns {boolean}
+ */
+ ModelGraphEditor.prototype.addLink = function (s, d, success, error) {
+ var self = this;
+ var source_id = s.id;
+ var target_id = d.id;
+ var source_type = s.info.type;
+ var destination_type = d.info.type;
+ var link = {
+ source: s,
+ target: d,
+ view: this.filter_parameters.link.view[0],
+ group: this.filter_parameters.link.group,
+ desc_id: this.desc_id
+ };
+ log("addLink: " + JSON.stringify(link))
+ var current_layer = self.getCurrentView()
+ if (self.model.layer[current_layer].allowed_edges && self.model.layer[current_layer].allowed_edges[source_type] && self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type]) {
+
+ if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback) {
+ var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback;
+ console.log(callback, self.model.callback)
+ var direct_edge = 'direct_edge' in self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type] ? self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type]['direct_edge'] : false;
+ link.directed_edge = direct_edge;
+ var c = self.model.callback[callback].class;
+ var controller = new dreamer[c]();
+ controller[callback](self, link, function () {
+ self._deselectAllNodes();
+ self.parent.addLink.call(self, link);
+ if (success)
+ success();
+ }, error);
+ } else {
+ log('addLink: callback undefined in model spec.');
+ error && error("You can't add a link, callback undefined.");
+ }
+
+ } else {
+ //FIXME we need to manage alert in a different way: FAILBACK
+ log("You can't link a " + source_type + " with a " + destination_type);
+
+ error && error("You can't link a " + source_type + " with a " + destination_type);
+ }
+ };
+
+ /**
+ * Remove a link from graph.
+ * @param {String} Required. The identifier of link to remove.
+ * @returns {boolean}
+ */
+ ModelGraphEditor.prototype.removeLink = function (link, success, error) {
+ var self = this;
+ var s = link.source;
+ var d = link.target;
+ var source_type = s.info.type;
+ var destination_type = d.info.type;
+ var current_layer = self.getCurrentView()
+ if (self.model.layer[current_layer].allowed_edges && self.model.layer[current_layer].allowed_edges[source_type] && self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type] &&
+ self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable
+ ) {
+ if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback) {
+ var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback;
+ var c = self.model.callback[callback].class;
+ var controller = new dreamer[c]();
+ controller[callback](self, link, function () {
+ self._deselectAllNodes();
+ self._deselectAllLinks();
+ self.parent.removeLink.call(self, link.index);
+ success && success();
+ }, error);
+ } else {
+ log('removeLink: callback undefined in model spec.');
+ error && error("You can't remove a link, callback undefined.");
+ }
+
+ } else {
+ //FIXME we need to manage alert in a different way: FAILBACK
+ log("You can't delete the link");
+ error && error("You can't delete the link");
+ }
+
+
+ };
+
+
+ ModelGraphEditor.prototype.savePositions = function (data) {
+ var vertices = {}
+ this.node.each(function (d) {
+ vertices[d.id] = {}
+ vertices[d.id]['x'] = d.x;
+ vertices[d.id]['y'] = d.y;
+ });
+ new dreamer.GraphRequests().savePositions({
+ 'vertices': vertices
+ });
+
+ }
+
+ /**
+ * Internal functions
+ */
+
+ /**
+ *
+ *
+ */
+
+ ModelGraphEditor.prototype._setupBehaviorsOnEvents = function (layer) {
+
+ var self = this;
+ var contextMenuLinksAction = [{
+ title: 'Delete Link',
+ action: function (elm, link, i) {
+ self.removeLink(link, null, showAlert);
+ },
+ edit_mode: true
+ }];
+ var contextMenuNodesAction = [{
+ title: 'Edit',
+ action: function (elm, d, i) {
+ if (d.info.type != undefined) {
+ self.eventHandler.fire("edit_descriptor", self.project_id, d);
+ }
+ },
+ nodes: [],
+ edit_mode: true
+ },
+ {
+ title: 'Delete',
+ action: function (elm, d, i) {
+ self.removeNode(d, null, showAlert);
+ },
+ edit_mode: true
+ },
+
+ ];
+ if(this.customBehavioursOnEvents){
+ contextMenuNodesAction = contextMenuNodesAction.concat(this.customBehavioursOnEvents['behaviors'].nodes);
+ }
+
+
+ if ( self.model && self.model.layer && self.model.layer[layer] && self.model.layer[layer].action && self.model.layer[layer].action.node) {
+ for (var i in self.model.layer[layer].action.node) {
+ var action = self.model.layer[layer].action.node[i]
+ contextMenuNodesAction.push({
+ title: action.title,
+ action: function (elm, d, i) {
+ var callback = action.callback;
+ var c = self.model.callback[callback].class;
+ var controller = new dreamer[c]();
+ var args = {
+ elm: elm,
+ d: d,
+ i: i
+ }
+
+ controller[callback](self, args);
+ },
+ edit_mode: (action.edit_mode != undefined) ? action.edit_mode: undefined
+ });
+ }
+ }
+
+ this.behavioursOnEvents = {
+ 'nodes': {
+ 'click': function (d) {
+
+ d3.event.preventDefault();
+
+ if (self._edit_mode && self.lastKeyDown == SHIFT_BUTTON && self._selected_node != undefined) {
+ self.addLink(self._selected_node, d, null, showAlert);
+ } else {
+ self._selectNodeExclusive(this, d);
+ }
+
+ },
+ 'mouseover': function (d) {
+ self.link.style('stroke-width', function (l) {
+ if (d === l.source || d === l.target)
+ return 4;
+ else
+ return 2;
+ });
+ },
+ 'mouseout': function (d) {
+ self.link.style('stroke-width', 2);
+ },
+ 'contextmenu': d3.contextMenu(contextMenuNodesAction, {
+ 'edit_mode': self._edit_mode,
+ 'layer': layer,
+ 'type_object': 'node'
+ })
+ },
+ 'links': {
+ 'click': function (d) {
+ self._selectLinkExclusive(this, d);
+
+ },
+ 'dblclick': function (event) {
+
+ },
+ 'mouseover': function (d) {
+ d3.select(this).style('stroke-width', 4);
+ },
+ 'mouseout': function (d) {
+ if (d != self._selected_link)
+ d3.select(this).style('stroke-width', 2);
+ },
+ 'contextmenu': d3.contextMenu(contextMenuLinksAction, {
+ 'edit_mode': self._edit_mode,
+ 'layer': layer,
+ 'type_object': 'link'
+ })
+ }
+ }
+ };
+
+ ModelGraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) {
+
+ this.parent.handleFiltersParams.call(this, filtersParams, notFireEvent);
+ this._setupBehaviorsOnEvents(filtersParams.link.view[0]);
+ };
+
+ ModelGraphEditor.prototype.getAvailableNodes = function () {
+ log('getAvailableNodes');
+ log(this.model)
+ if (this.model && this.model.layer[this.getCurrentView()] != undefined)
+ return this.model.layer[this.getCurrentView()].nodes;
+ return [];
+ }
+
+
+ ModelGraphEditor.prototype.exploreLayer = function (args) {
+
+ };
+
+ ModelGraphEditor.prototype.getTypeProperty = function () {
+ return this.type_property;
+ };
+
+ ModelGraphEditor.prototype.getCurrentGroup = function () {
+ return this.filter_parameters.node.group[0];
+
+ }
+
+ ModelGraphEditor.prototype.getCurrentView = function () {
+ return this.filter_parameters.link.view[0];
+
+ }
+ /**
+ * Log utility
+ */
+ function log(text) {
+ if (DEBUG)
+ console.log("::ModelGraphEditor::", text);
+ }
+
+
+
+ return ModelGraphEditor;
+
+
+}(this));
+
+if (typeof module === 'object') {
+ module.exports = dreamer.ModelGraphEditor;
+}
\ No newline at end of file