first commit

Change-Id: I8a65ee5527dd16d81e87c8ac5d4bdb471e5e759d
Signed-off-by: lombardof <flombardo@cnit.it>
diff --git a/static/topology3D/js/d3-context-menu.js b/static/topology3D/js/d3-context-menu.js
new file mode 100644
index 0000000..6f77042
--- /dev/null
+++ b/static/topology3D/js/d3-context-menu.js
@@ -0,0 +1,131 @@
+(function (root, factory) {
+    if (typeof module === 'object' && module.exports) {
+        module.exports = function (d3) {
+            d3.contextMenu = factory(d3);
+            return d3.contextMenu;
+        };
+    } else if (typeof define === 'function' && define.amd) {
+        try {
+            var d3 = require('d3');
+        } catch (e) {
+            d3 = root.d3;
+        }
+
+        d3.contextMenu = factory(d3);
+        define([], function () {
+            return d3.contextMenu;
+        });
+    } else if (root.d3) {
+        root.d3.contextMenu = factory(root.d3);
+    }
+}(this,
+    function (d3) {
+        return function (menu, opts) {
+            var openCallback,
+                closeCallback;
+
+            if (typeof opts === 'function') {
+                openCallback = opts;
+            } else {
+                opts = opts || {};
+                openCallback = opts.onOpen;
+                closeCallback = opts.onClose;
+            }
+
+            // create the div element that will hold the context menu
+            d3.selectAll('.d3-context-menu').data([1])
+                .enter()
+                .append('div')
+                .attr('class', 'd3-context-menu');
+
+            // close menu
+            d3.select('body').on('click.d3-context-menu', function () {
+                d3.select('.d3-context-menu').style('display', 'none');
+                if (closeCallback) {
+                    closeCallback();
+                }
+            });
+
+            // this gets executed when a contextmenu event occurs
+            return function (data, index) {
+                var elm = this;
+                d3.selectAll('.d3-context-menu').html('');
+                var list = d3.selectAll('.d3-context-menu')
+                    .on('contextmenu', function (d) {
+                        d3.select('.d3-context-menu').style('display', 'none');
+                        d3.event.preventDefault();
+                        d3.event.stopPropagation();
+                    })
+                    .append('ul');
+                list.selectAll('li').data(typeof menu === 'function' ? menu(data) : menu).enter()
+                    .filter(function (d) {
+                        if(opts.type_object == 'node'){
+                            if (opts.edit_mode  ||  opts.edit_mode == d.edit_mode ) {
+                                if ((d.nodes == undefined || d.nodes.length == 0) ||
+                                    (d.nodes != undefined && d.nodes.length > 0 && d.nodes.indexOf(data.info.type) > -1))
+                                    return true
+                            }
+                        }
+                        if(opts.type_object == 'link'){
+                            if (opts.edit_mode == d.edit_mode) {
+
+                                    return true
+                            }
+                        }
+                        return false;
+                    })
+                    .append('li')
+                    .attr('class', function (d) {
+                        var ret = '';
+                        if (d.divider) {
+                            ret += ' is-divider';
+                        }
+                        if (d.disabled) {
+                            ret += ' is-disabled';
+                        }
+                        if (!d.action) {
+                            ret += ' is-header';
+                        }
+                        //if()
+                        return ret;
+                    })
+                    .html(function (d) {
+                        if (d.divider) {
+                            return '<hr>';
+                        }
+                        if (!d.title) {
+                            console.error('No title attribute set. Check the spelling of your options.');
+                        }
+                        return (typeof d.title === 'string') ? d.title : d.title(data);
+                    })
+                    .on('click', function (d, i) {
+                        if (d.disabled) return; // do nothing if disabled
+                        if (!d.action) return; // headers have no "action"
+                        d.action(elm, data, index);
+                        d3.select('.d3-context-menu').style('display', 'none');
+
+                        if (closeCallback) {
+                            closeCallback();
+                        }
+                    });
+
+                // the openCallback allows an action to fire before the menu is displayed
+                // an example usage would be closing a tooltip
+                if (openCallback) {
+                    if (openCallback(data, index) === false) {
+                        return;
+                    }
+                }
+
+                // display context menu
+                d3.select('.d3-context-menu')
+                    .style('left', (d3.event.pageX - 2) + 'px')
+                    .style('top', (d3.event.pageY - 2) + 'px')
+                    .style('display', 'block');
+
+                d3.event.preventDefault();
+                d3.event.stopPropagation();
+            };
+        };
+    }
+));
\ No newline at end of file
diff --git a/static/topology3D/js/event.js b/static/topology3D/js/event.js
new file mode 100755
index 0000000..cfe815c
--- /dev/null
+++ b/static/topology3D/js/event.js
@@ -0,0 +1,73 @@
+/*
+   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 = {};
+}
+
+dreamer.Event = (function (global) {
+    'use strict';
+
+    function Event () {
+        this._listeners = {};     
+    }
+
+    Event.prototype.addL = function (type, listener) {
+        if (typeof this._listeners[type] == "undefined"){
+            this._listeners[type] = [];
+        }
+        this._listeners[type].push(listener);
+    };
+
+    Event.prototype.fire = function (event, args) {
+        if (typeof event == "string"){
+            event = { type: event };
+        }
+        if (!event.target){
+            event.target = this;
+        }
+
+        if (!event.type){  //falsy
+            throw new Error("Event object missing 'type' property.");
+        }
+
+        if (this._listeners[event.type] instanceof Array){
+            var listeners = this._listeners[event.type];
+            for (var i=0, len=listeners.length; i < len; i++){
+                listeners[i].call(this, event, args);
+            }
+        }
+    };
+
+    Event.prototype.addListener = function (type, listener) {
+        if (this._listeners[type] instanceof Array){
+            var listeners = this._listeners[type];
+            for (var i=0, len=listeners.length; i < len; i++){
+                if (listeners[i] === listener){
+                    listeners.splice(i, 1);
+                    break;
+                }
+            }
+        }
+
+    };
+
+    return Event;
+}());
+
+if (typeof module === 'object') {
+    module.exports = dreamer.Event;
+}
diff --git a/static/topology3D/js/graph_editor.js b/static/topology3D/js/graph_editor.js
new file mode 100755
index 0000000..36e7c8e
--- /dev/null
+++ b/static/topology3D/js/graph_editor.js
@@ -0,0 +1,1084 @@
+/*
+   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.GraphEditor = (function (global) {
+    'use strict';
+
+    var DEBUG = true;
+    var SHIFT_BUTTON = 16;
+    var CANC_BUTTON = 46;
+    var default_link_color = "#888";
+    var nominal_text_size = 15;
+    var nominal_stroke = 1.5;
+    var EventHandler = dreamer.Event;
+    //    var IMAGE_PATH = "/static/assets/img/";
+
+
+
+    /**
+     * Constructor
+     */
+    function GraphEditor(args) {
+        log("Constructor");
+        this.eventHandler = new EventHandler();
+        this.lastKeyDown = -1;
+        this._selected_node = undefined;
+        this._selected_link = undefined;
+        this._edit_mode = true;
+        this.filter_parameters = {
+            node: {
+                type: [],
+                group: [],
+            },
+            link: {
+                group: [],
+                view: [],
+            }
+        };
+        this.current_view_id = '';
+        // graph data initailization
+        this.d3_graph = {
+            nodes: [],
+            links: [],
+            graph_parameters: {}
+
+        };
+
+
+    }
+
+
+
+    GraphEditor.prototype.init = function (args) {
+        args = args || {}
+        var self = this;
+        this.width = 550//args.width || 500;
+        this.height = 550// args.height || 500;
+        this.forceSimulationActive = false;
+
+        //FixMe
+        this.width = this.width - this.width * 0.007;
+        this.height = this.height - this.height * 0.07;
+
+        //console.log("this.width", this.width, "this.height", this.height);
+        var min_zoom = 0.1;
+        var max_zoom = 7;
+        this._setupBehaviorsOnEvents();
+        this._setupFiltersBehaviors(args);
+
+        this.type_property = {
+            "unrecognized": {
+                "shape": d3.symbolCircle,
+                "color": "white",
+                "node_label_color": "black",
+                "size": 15
+            },
+        };
+
+        this.type_property_link = {
+            "unrecognized": {
+                "color": "#888",
+                //"color": "red",
+            },
+        };
+
+        this.force = d3.forceSimulation()
+            .force("collide", d3.forceCollide().radius(40))
+            .force("link", d3.forceLink().distance(80).iterations(1).id(function (d) {
+                return d.id;
+            }))
+            .force("center", d3.forceCenter(this.width / 2, this.height / 2));
+
+        var zoom = d3.zoom().scaleExtent([min_zoom, max_zoom])
+
+        var size = d3.scalePow().exponent(2)
+            .domain([1, 100])
+            .range([8, 24]);
+
+        this.svg = d3.select("#graph_ed_container").append("svg")
+            .attr("id", "graph_svg")
+            .attr("perserveAspectRatio", "xMinYMid")
+            .attr("width", this.width)
+            .attr("height", this.height);
+
+        //End Arrow style
+        this.defs = this.svg.append("svg:defs");
+
+        this.defs.selectAll("marker")
+            .data(["unrecognized"]) // Different link/path types can be defined here
+            .enter().append("svg:marker") // This section adds in the arrows
+            .attr("id", String)
+            .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', this.type_property_link['unrecognized']['color']);
+
+        d3.select(window)
+            .on('keydown', function () {
+                log('keydown ' + d3.event.keyCode);
+                //d3.event.preventDefault();
+                if (self.lastKeyDown !== -1) return;
+                self.lastKeyDown = d3.event.keyCode;
+                if (self.lastKeyDown === CANC_BUTTON && self._selected_node != undefined) {
+                    self.removeNode(self._selected_node, null, showAlert);
+                } else if (self.lastKeyDown === CANC_BUTTON && self._selected_link != undefined) {
+                    self.removeLink(self._selected_link, null, showAlert);
+                }
+
+            })
+            .on('keyup', function () {
+                log('keyup' + self.lastKeyDown);
+                self.lastKeyDown = -1;
+            });
+        var popup = this.svg.append("g")
+            .attr("id", "popup")
+            .attr("class", "popup")
+            .attr("opacity", "0")
+            .attr("transform", "translate(1 1)")
+            .call(d3.drag()
+        .on("start", dragstarted)
+        .on("drag", dragged)
+        .on("end", dragended));
+
+        function dragstarted(d) {
+          //d3.select(this).raise().classed("active", true);
+        }
+
+        function dragged(d) {
+            //console.log(JSON.stringify(d))
+          d3.select(this).attr("transform", function () {
+                return "translate("+d3.event.x+","+d3.event.y+")";
+
+            })
+        }
+
+        function dragended(d) {
+          //d3.select(this).classed("active", false);
+        }
+
+        var chart = $("#graph_svg");
+        this.aspect = chart.width() / chart.height();
+        this.container = chart.parent();
+        $(window).on("resize", function() {
+
+            var palette_width = $("#palette").width()
+            var working_width = self.container.width() - palette_width;
+            self.width = (working_width < 0) ? 0 : working_width;
+            self.height = self.container.height();
+            chart.attr("width", self.width);
+            chart.attr("height", self.height);
+        }).trigger("resize");
+
+    }
+
+
+    GraphEditor.prototype.get_d3_symbol =
+        function (myString) {
+            log(myString)
+            switch (myString) {
+            case "circle":
+                return d3.symbolCircle;
+                break;
+            case "square":
+                return d3.symbolSquare;
+                break;
+            case "diamond":
+                return d3.symbolDiamond;
+                break;
+            case "triangle":
+                return d3.symbolTriangle;
+                break;
+            case "star":
+                return d3.symbolStar;
+                break;
+            case "cross":
+                return d3.symbolCross;
+                break;
+            default:
+                // if the string is not recognized
+                return d3.symbolCross;
+                //return d3.symbolCircleUnknown;
+            }
+
+        }
+
+     GraphEditor.prototype.get_name_from_d3_symbol =
+        function (mySymbol) {
+            //log(myString)
+            switch (mySymbol) {
+            case d3.symbolCircle:
+                return "circle";
+                break;
+            case d3.symbolSquare:
+                return "square";
+                break;
+            case d3.symbolDiamond:
+                return "diamond";
+                break;
+            case d3.symbolTriangle:
+                return "triangle";
+                break;
+            case d3.symbolStar:
+                return "star";
+                break;
+            case d3.symbolCross:
+                return "cross";
+                break;
+            default:
+                // if the string is not recognized
+                return "unknown";
+                //return d3.symbolCircleUnknown;
+            }
+
+        }
+
+    /**
+     * Start or Stop force layout
+     * @param {boolean} Required. Value true: start, false: stop
+     * @returns {boolean}
+     */
+    GraphEditor.prototype.handleForce = function (start) {
+        if (start)
+            this.force.stop();
+        this.forceSimulationActive = start;
+        this.node.each(function (d) {
+            d.fx = (start) ? null : d.x;
+            d.fy = (start) ? null : d.y;
+        });
+
+        if (start)
+            this.force.restart();
+
+        this.eventHandler.fire("force_status_changed_on", start);
+    };
+
+    /**
+     * Handle the parameters of basic filters: node type, view, group
+     * @param {Object} Required.
+     *
+     */
+    GraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) {
+        console.log("handleFiltersParams", filtersParams)
+        this.filter_parameters = (filtersParams != undefined) ? filtersParams : this.filter_parameters;
+        this.current_view_id = (this.filter_parameters != undefined && this.filter_parameters.link.view[0] != undefined) ? this.filter_parameters.link.view[0] : this.current_view_id
+        this.cleanAll();
+        this.refresh();
+        this.startForce();
+        this.force.restart();
+        this._deselectAllNodes();
+        this.handleForce(this.forceSimulationActive);
+        if (!notFireEvent)
+            this.eventHandler.fire("filters_changed", filtersParams);
+
+    };
+
+    /**
+     * Add a new node to the graph.
+     * @param {Object} Required. An object that specifies tha data of the new node.
+     * @returns {boolean}
+     */
+    GraphEditor.prototype.addNode = function (args) {
+        if (args.id && args.info && args.info.type) {
+            args.fixed = true;
+            this.force.stop();
+            this.cleanAll();
+            this.d3_graph.nodes.push(args);
+            this.refresh();
+            this.startForce();
+            this.force.restart();
+            this.handleForce(this.forceSimulationActive);
+            return true;
+        }
+
+        return false;
+
+    };
+
+    /**
+     * Update the data properties of the node
+     * @param {Object} Required. An object that specifies tha data of the node.
+     * @returns {boolean}
+     */
+    GraphEditor.prototype.updateDataNode = function (args) {
+
+    };
+
+    /**
+     * Remove a node from graph and related links.
+     * @param {String} Required. Id of node to remove.
+     * @returns {boolean}
+     */
+    GraphEditor.prototype.removeNode = function (node) {
+        if (node != undefined) {
+            var node_id = node.id;
+            this.d3_graph['nodes'].forEach(function (n, index, object) {
+                if (n.id == node_id) {
+                    object.splice(index, 1);
+
+                }
+
+            });
+            //TODO trovare una metodo piu efficace
+            var self = this;
+            var links_to_remove = [];
+            this.d3_graph['links'].forEach(function (l, index, object) {
+                if (node_id === l.source.id || node_id === l.target.id) {
+                    links_to_remove.push(index);
+                }
+
+            });
+            var links_removed = 0;
+            links_to_remove.forEach(function (l_index) {
+                self.d3_graph['links'].splice(l_index - links_removed, 1);
+                links_removed++;
+            });
+            this.cleanAll();
+            this.refresh();
+            this.startForce();
+            this.force.restart();
+
+            return true;
+        }
+        return false;
+    };
+
+
+    /**
+     * Add a new link to graph.
+     * @param {Object} Required. An object that specifies tha data of the new Link.
+     * @returns {boolean}
+     */
+    GraphEditor.prototype.addLink = function (link) {
+        console.log(JSON.stringify(link))
+        if (link.source && link.target) {
+            this.force.stop();
+            this.cleanAll();
+            this.d3_graph.links.push(link);
+            this.refresh();
+            this.startForce();
+            this.force.restart();
+            return true;
+        }
+
+        return false;
+    };
+
+    /**
+     * Remove a link from graph.
+     * @param {String} Required. The identifier of link to remove.
+     * @returns {boolean}
+     */
+    GraphEditor.prototype.removeLink = function (link_id) {
+        var self = this;
+        if (link_id !== 'undefined') {
+            this.d3_graph['links'].forEach(function (l, index, object) {
+                if (link_id === l.index) {
+                    object.splice(index, 1);
+
+                    self.cleanAll();
+                    self.refresh();
+                    self.startForce();
+                    self.force.restart();
+                    return true;
+                }
+
+            });
+        }
+
+        return false;
+    };
+
+
+    /**
+     * Force a refresh of GraphView
+     * @returns {}
+     */
+    GraphEditor.prototype.refresh = function () {
+
+        //log(data)
+        var self = this;
+
+        this.link = this.svg
+            .selectAll()
+            .data(self.d3_graph.links
+                .filter(this.link_filter_cb)
+            )
+            .enter().append("g")
+            .attr("class", "link cleanable")
+            .append("path")
+            .attr("class", "link")
+            .attr("class", "cleanable")
+            .style("stroke-width", nominal_stroke)
+            .style("stroke", function (d) {
+                return self._link_property_by_type((d.type_link) ? d.type_link : "unrecognized", "color");
+            })
+            .attr("marker-end", function (d) {
+                if (!d.directed_edge)
+                    return '';
+
+                var marker_url = (d.type_link) ? d.type_link : "unrecognized"
+                return (d.directed_edge ? "url(#" + marker_url + ")" : '');
+            });
+
+        this.nodeContainer = this.svg
+            .selectAll()
+            .data(self.d3_graph.nodes
+                .filter(this.node_filter_cb))
+            .enter()
+            .append("g")
+            // .attr("class", "nodosdads")
+            .attr("class", "node cleanable");
+
+        this.svg.selectAll('.node')
+            .data(self.d3_graph.nodes
+                .filter(this.node_filter_cb))
+
+            .filter(function (d) {
+                return (d.info.type == undefined) || (self._node_property_by_type(d.info.type, 'image', d) == undefined)
+            })
+
+            .append("svg:path")
+            .attr("d", d3.symbol()
+                .size(function (d) {
+                    return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d), 2) / 4;
+                })
+                .type(function (d) {
+                    // console.log(d.info.type, 'shape', self.current_view_id)
+                    return (self._node_property_by_type(d.info.type, 'shape', d));
+                })
+            )
+            .style("fill", function (d) {
+                return self._node_property_by_type(d.info.type, 'color', d);
+            })
+            .attr("transform", function () {
+                return "rotate(-45)";
+
+            })
+            .attr("stroke-width", 2.4)
+
+            .attr("class", "node_path")
+            .attr("id", function (d) {
+                return "path_" + d.id;
+            })
+
+            .call(d3.drag()
+                .on("start", dragstarted)
+                .on("drag", dragged)
+                .on("end", dragended));
+
+        var figure_node = this.svg.selectAll('.node')
+            .data(self.d3_graph.nodes
+                .filter(this.node_filter_cb))
+
+            .filter(function (d) {
+                return self._node_property_by_type(d.info.type, 'image', d) != undefined
+            });
+
+        figure_node.append("svg:image")
+            .attr("xlink:href", function (d) {
+                return self._node_property_by_type(d.info.type, 'image', d)
+            })
+            .attr("x", function (d) {
+                return -self._node_property_by_type(d.info.type, 'size', d) / 2
+            })
+            .attr("y", function (d) {
+                return -self._node_property_by_type(d.info.type, 'size', d) / 2
+            })
+            .attr("width", function (d) {
+                return self._node_property_by_type(d.info.type, 'size', d)
+            })
+            .attr("height", function (d) {
+                return self._node_property_by_type(d.info.type, 'size', d)
+            })
+            .style("stroke", "black")
+            .style("stroke-width", "1px")
+
+            .attr("class", "node_path")
+            .attr("id", function (d) {
+                return "path_" + d.id;
+            })
+            .call(d3.drag()
+                .on("start", dragstarted)
+                .on("drag", dragged)
+                .on("end", dragended));
+
+        figure_node.append("svg:path")
+            .attr("d", d3.symbol()
+                .size(function (d) {
+                    return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d) + 7, 2) / 4;
+                })
+                .type(function (d) {
+                    return (self.get_d3_symbol('circle'));
+                })
+            )
+            .style("fill", 'transparent')
+            .attr("transform", function () {
+                return "rotate(-45)";
+
+            })
+            .attr("stroke-width", 2.4)
+
+            .attr("class", "hidden_circle")
+            .attr("id", function (d) {
+                return "path_" + d.id;
+            })
+
+            .call(d3.drag()
+                .on("start", dragstarted)
+                .on("drag", dragged)
+                .on("end", dragended));
+
+
+
+        this.node = this.svg.selectAll('.node')
+            .data(self.d3_graph.nodes
+                .filter(this.node_filter_cb)).selectAll("image, path, circle");
+
+
+
+        this.node.on("contextmenu", self.behavioursOnEvents.nodes["contextmenu"])
+            .on("mouseover", self.behavioursOnEvents.nodes["mouseover"])
+            .on("mouseout", self.behavioursOnEvents.nodes["mouseout"])
+            .on('click', self.behavioursOnEvents.nodes["click"])
+            .on('dblclick', self.behavioursOnEvents.nodes["dblclick"]);
+
+        this.link
+            .on("contextmenu", self.behavioursOnEvents.links["contextmenu"])
+            .on("mouseover", self.behavioursOnEvents.links["mouseover"])
+            .on('click', self.behavioursOnEvents.links["click"])
+            .on("mouseout", self.behavioursOnEvents.links["mouseout"]);
+
+
+
+        this.text = this.svg.selectAll(".node")
+            .data(self.d3_graph.nodes
+                .filter(this.node_filter_cb))
+            .append("svg:text")
+            .attr("class", "nodetext")
+            .attr("class", "cleanable")
+            .attr("dy", function(d) { 
+                if (self._node_property_by_type(d.info.type, 'image', d) == undefined) {
+                    //shape
+                    return "-5"
+                }
+                else {
+                    //image
+                    return (-self._node_property_by_type(d.info.type, 'size', d)/2).toString()
+                }
+            })
+            .attr("pointer-events", "none")
+            .style("font-size", nominal_text_size + "px")
+            .style("font-family", "Lucida Console")
+            .style("fill", function (d) {
+                return self._node_property_by_type(d.info.type, 'node_label_color', d);
+            })
+            .style("text-anchor", "middle")
+            .text(function (d) {
+                return d.id;
+            });
+
+
+
+        function dragstarted(d) {
+            d.draggednode = true;
+            if (!d3.event.active) self.force.alphaTarget(0.3).restart();
+            d.fx = d.x;
+            d.fy = d.y;
+
+        }
+
+        function dragged(d) {
+            d.fx = d3.event.x;
+            d.fy = d3.event.y;
+        }
+
+        function dragended(d) {
+            d.draggednode = false;
+            if (!d3.event.active) self.force.alphaTarget(0);
+            if (self.forceSimulationActive) {
+                d.fx = null;
+                d.fy = null;
+            } else {
+                d.fx = d.x;
+                d.fy = d.y;
+                self.force.stop();
+                self.forceSimulationActive = false;
+            }
+        }
+
+
+    };
+
+    /**
+     *  Start force layout on Graph.
+     *
+     */
+    GraphEditor.prototype.startForce = function () {
+        //this.force.stop();
+        var self = this
+        this.force
+            .nodes(this.d3_graph.nodes)
+            .on("tick", ticked);
+
+
+        this.force
+            .force("link")
+            .links(this.d3_graph.links);
+
+        function ticked() {
+            self.node.attr("cx", function (d) {
+                    return d.x = Math.max(self._node_property_by_type(d.info.type, 'size', d), Math.min(self.width - self._node_property_by_type(d.info.type, 'size', d), d.x));
+                })
+                .attr("cy", function (d) {
+                    return d.y = Math.max(self._node_property_by_type(d.info.type, 'size', d), Math.min(self.height - self._node_property_by_type(d.info.type, 'size', d), d.y));
+                });
+
+            self.link.attr("d", function (d) {
+                var dx = d.target.x - d.source.x,
+                    dy = d.target.y - d.source.y,
+                    dr = Math.sqrt(dx * dx + dy * dy);
+                return "M" + d.source.x + "," + d.source.y + "," + d.target.x + "," + d.target.y;
+            });
+
+            self.node.attr("transform", function (d) {
+                return "translate(" + d.x + "," + d.y + ")";
+            });
+            self.text.attr("transform", function (d) {
+                var label_pos_y = d.y + self._node_property_by_type(d.info.type, 'size', d) + 10;
+                return "translate(" + d.x + "," + label_pos_y + ")";
+            });
+        };
+
+
+
+    };
+
+    /**
+     * This method attaches an event handler.
+     * @param {String} Required. A String that specifies the name of the event.
+     * @param {Function} Required. Specifies the function to run when the event occurs.
+     * @returns {}
+     */
+    GraphEditor.prototype.addListener = function (event_name, cb) {
+        this.eventHandler.addL(event_name, cb);
+    }
+
+    /**
+     * This method removes an event handler that has been attached with the addListener() method.
+     * @param {String} Required. A String that specifies the name of the event to remove.
+     * @param {Function} Required. Specifies the function to remove.
+     * @returns {}
+     */
+    GraphEditor.prototype.removeListener = function (event_name, cb) {
+
+    }
+
+
+    GraphEditor.prototype.setNodeClass = function (class_name, filter_cb) {
+        log("setNodeClass");
+        var self = this;
+        this.svg.selectAll('.node').classed(class_name, false);
+        this.svg.selectAll('.node')
+            .classed(class_name, filter_cb);
+    }
+
+    GraphEditor.prototype.setLinkClass = function (class_name, filter_cb) {
+        log("setLinkClass");
+        var self = this;
+        this.svg.selectAll('.link').classed(class_name, false);
+        this.svg.selectAll('.link')
+            .classed(class_name, filter_cb);
+    }
+
+    GraphEditor.prototype.showNodeInfo = function(args){
+        this.addLinesToPopup(args['node_info'], "Info about node selected")
+        this.handlePopupVisibility(true, 'right')
+    }
+    GraphEditor.prototype.addLinesToPopup = function(data, title) {
+        var self = this;
+        var index = 1;
+        var translate_y = 0;
+        var width_popup = 400;
+        var height_popup = 0;
+
+        d3.selectAll(".popupcleanable").remove(); // clean
+
+        var popupbg = d3.select(".popup").append("rect")
+            .attr("id", "popupbg")
+            .attr("class", "popup bg popupcleanable cleanable")
+            .attr("width", "400")
+            .attr("height", "0")
+            .attr("rx", 10) // set the x corner curve radius
+            .attr("ry", 10); // set the y corner curve radius
+
+
+        d3.select(".popup").append("svg:path")
+            .attr("d", d3.symbol()
+                .size(function (d) {
+                    return 80
+                })
+                .type(function (d) {
+                    return (self.get_d3_symbol());
+                })
+            )
+            .style("fill", 'red')
+            .attr("transform", function () {
+                return "translate(380,15) rotate(-45)";
+
+            })
+            .attr("stroke-width", 2.4)
+            .attr("id", "close_popup")
+            .attr("class", "popupcleanable cleanable")
+            .on("click", function(d) {
+                self.handlePopupVisibility(false);
+            });
+
+        d3.select(".popup").append("text")
+            .attr("class", "popup title popupcleanable cleanable")
+            .attr("x", "10")
+            .attr("y", "20")
+            .text(title);
+
+        for (var i in data) {
+            //console.log(i, data, data[i])
+            //var typeofvalue = typeof data[i];
+            var record = data[i];
+            index = this._addRecordToPopup(i, record,index)
+
+        }
+
+    };
+
+    GraphEditor.prototype._addRecordToPopup = function (key, record, index, tab) {
+        //console.log("_addRecordToPopup", key, record, index)
+        var translate_y = 23 * index;
+            var summary = d3.select(".popup").append("g")
+                    .attr("class", "popup summary d popupcleanable cleanable")
+                    .attr("transform", "translate(10 " + translate_y + ")");
+        if(Object.prototype.toString.call( record ) !== '[object Array]'){ //is a record simple key:value
+            //console.log(key, record)
+            var summary_g = summary.append("g");
+                summary_g.append("rect")
+                    .attr("class", "popup summary bg popupcleanable cleanable")
+                    .attr("width", "380")
+                    .attr("height", "20");
+
+                summary_g.append("text")
+                    .attr("class", "popup summary  popupcleanable cleanable")
+                    .attr("x", (tab)? tab: 10)
+                    .attr("y", "17")
+                    .attr("width", "100")
+                    .text(function(d){
+                        return key.toUpperCase() + ":";
+                    });
+
+                summary_g.append("text")
+                    .attr("class", "popup summary  popupcleanable cleanable")
+                    .attr("x", "370")
+                    .attr("y", "17")
+                    .attr("text-anchor", "end")
+                    .text(function(d){return record});
+        }
+        else {//is a record simple complex: have a list of sub record key:value
+                //index ++;
+                this._addRecordToPopup(key, "", index)
+                for(var r in record){
+                    //console.log(i, r, record, record[r])
+                    for(var k in record[r]){
+                        //console.log(i, r, k, record[r][k])
+                        var curr_key = k;
+                        var recordValue = record[r][k]
+
+                        index ++;
+                        this._addRecordToPopup(curr_key, recordValue, index, 20)
+                    }
+                }
+
+        }
+
+        translate_y = 30 * index++;
+        d3.select('#popupbg').attr("height", translate_y);
+        return index;
+    };
+
+
+
+    /**
+     *  Remove all the graph objects from the view
+     */
+    GraphEditor.prototype.cleanAll = function () {
+        this.svg.selectAll('.cleanable').remove();
+    };
+
+    /**
+     *  Internal functions
+     */
+
+    GraphEditor.prototype._node_property_by_type = function (type, property, node) {
+        //console.log(type, property, layer, group)
+        var unrecognized = function (ui_prop, property) {
+            return ui_prop['unrecognized'][property]
+        };
+
+        //type recognized
+        if (this.type_property[type]) {
+
+            if (this.type_property[type]['property']) {
+                var filt_property = this.type_property[type]['property']
+                return this.type_property[type][node.info[filt_property]][property]
+            } else { // type without property spec
+
+                return this.type_property[type][property]
+
+            }
+
+        } else { //type unrecognized
+            return unrecognized(this.type_property, property)
+        }
+
+    };
+
+    GraphEditor.prototype._link_property_by_type = function (type, property) {
+        //log(type + "-" + property)
+        if (this.type_property_link[type] != undefined && this.type_property_link[type][property] != undefined) {
+            //if(property == "shape")
+            //    log("dentro" + this.type_property[type][property])
+            return this.type_property_link[type][property];
+        } else {
+            return this.type_property_link['unrecognized'][property];
+        }
+
+    }
+
+
+    /**
+     *
+     *
+     *
+     */
+    GraphEditor.prototype._setupFiltersBehaviors = function (args) {
+
+        var self = this;
+
+        this.node_filter_cb = args.node_filter_cb || function (d) {
+
+            var cond_view = true,
+                cond_group = true;
+            //log(d.info.type + " " + self.filter_parameters.node.type + " group: " + self.filter_parameters.node.group + "- " + d.info.group)
+            // check filter by node type
+            if (self.filter_parameters.node.type.length > 0) {
+
+                if (self.filter_parameters.node.type.indexOf(d.info.type) < 0)
+                    cond_view = false;
+            }
+
+            // check filter by group
+            if (self.filter_parameters.node.group.length > 0) {
+                self.filter_parameters.node.group.forEach(function (group) {
+                    if (d.info.group.indexOf(group) < 0)
+                        cond_group = false;
+                });
+
+
+            }
+
+
+            return cond_view && cond_group;
+        };
+
+        this.link_filter_cb = args.link_filter_cb || function (d) {
+            var cond_view = true,
+                cond_group = true;
+
+            // check filter by view
+            if (self.filter_parameters.link.view.length > 0) {
+                self.filter_parameters.link.view.forEach(function (view) {
+                    if (d.view.indexOf(view) < 0)
+                        cond_view = false;
+                });
+            }
+
+            // check filter by group
+            if (self.filter_parameters.link.group.length > 0) {
+                self.filter_parameters.link.group.forEach(function (group) {
+                    if (d.group.indexOf(group) < 0)
+                        cond_group = false;
+                });
+            }
+            return cond_view && cond_group;
+        };
+
+    };
+
+    /**
+     *
+     *
+     */
+    GraphEditor.prototype._setupBehaviorsOnEvents = function () {
+        log("_setupBehaviorsOnEvents");
+        var self = this;
+        this.behavioursOnEvents = {
+            'nodes': {
+                'click': function (d) {
+                    d3.event.preventDefault();
+                    log('click', d);
+                    if (self.lastKeyDown == SHIFT_BUTTON && self._selected_node != undefined) {
+                        var source_id = self._selected_node.id;
+                        var target_id = d.id;
+                        log(JSON.stringify(self.filter_parameters.link.view));
+                        var new_link = {
+                            source: source_id,
+                            target: target_id,
+                            view: self.filter_parameters.link.view[0],
+                            group: self.filter_parameters.link.group[0],
+                        };
+                        self.addLink(new_link);
+                        self._deselectAllNodes();
+                    } else {
+                        self._selectNodeExclusive(this, d);
+                    }
+
+                },
+                'mouseover': function (d) {
+
+                },
+                'mouseout': function (d) {},
+                'dblclick': function (d) {
+                    d3.event.preventDefault();
+                    log('dblclick');
+                },
+                'contextmenu': function (d, i) {
+                    d3.event.preventDefault();
+                    log("contextmenu node");
+                    self.eventHandler.fire("right_click_node", d);
+                }
+            },
+            'links': {
+                'click': function (event) {
+
+                },
+                'dblclick': function (event) {
+
+                }
+            }
+        };
+    };
+
+    /**
+     *  Deselect previously selected nodes
+     *
+     */
+    GraphEditor.prototype._deselectAllNodes = function () {
+        log("_deselectAllNodes");
+        this.node.classed("node_selected", false);
+        this._selected_node = undefined;
+    };
+
+    GraphEditor.prototype._deselectAllLinks = function () {
+        log("_deselectAllLinks");
+        this.link.classed("link_selected", false).style('stroke-width', 2);
+        this._selected_link = undefined;
+    };
+    /**
+     *  Select node in exclusive mode
+     *  @param {Object} Required. Element selected on click event
+     */
+    GraphEditor.prototype._selectNodeExclusive = function (node_instance, node_id) {
+        log("_selectNodeExclusive ");
+        var activeClass = "node_selected";
+        var alreadyIsActive = d3.select(node_instance).classed(activeClass);
+        this._deselectAllNodes();
+        this._deselectAllLinks();
+        d3.select(node_instance).classed(activeClass, !alreadyIsActive);
+        this._selected_node = (alreadyIsActive) ? undefined : node_instance.__data__;
+    };
+
+    /**
+     *  Select node in exclusive mode
+     *  @param {Object} Required. Element selected on click event
+     */
+    GraphEditor.prototype._selectLinkExclusive = function (link_instance, link_id) {
+        log("_selectLinkExclusive ");
+        var activeClass = "link_selected";
+        var alreadyIsActive = d3.select(link_instance).classed(activeClass);
+        this._deselectAllNodes();
+        this._deselectAllLinks();
+        d3.select(link_instance).classed(activeClass, !alreadyIsActive);
+        d3.select(link_instance).style('stroke-width', 4)
+        this._selected_link = link_instance.__data__;
+    };
+
+    /**
+     *  Callback to resize SVG element on window resize
+     */
+    GraphEditor.prototype.resizeSvg = function (width, height) {
+        log("resizeSvg");
+        log(event);
+        this.width = width || this.width;
+        this.height = height || this.height;
+        this.svg.attr('width', width);
+        this.svg.attr('height', height);
+
+    }
+
+    GraphEditor.prototype.handlePopupVisibility = function(visible, side) {
+        var opacity = (visible) ? 1 : 0;
+
+        var translate_op = (side == "left") ? "translate(50 50)" : "translate("+(this.width - 450).toString()+" 50)";
+
+        if (!visible) {
+            d3.selectAll(".popupcleanable").remove();
+            d3.select(".popup")
+                .attr("transform", "translate(-1 -1)");
+        } else {
+            d3.select(".popup")
+                .attr("transform", translate_op);
+        }
+        d3.select(".popup").attr("opacity", opacity);
+    };
+
+    GraphEditor.prototype.refreshGraphParameters = function (graphParameters) {
+        this.eventHandler.fire("refresh_graph_parameters", graphParameters);
+    }
+
+    /**
+     * Log utility
+     */
+    function log(text) {
+        if (DEBUG)
+            console.log("::GraphEditor::", text);
+    }
+
+
+
+    return GraphEditor;
+
+
+}(this));
+
+if (typeof module === 'object') {
+    module.exports = dreamer.GraphEditor;
+}
\ No newline at end of file
diff --git a/static/topology3D/js/graph_request.js b/static/topology3D/js/graph_request.js
new file mode 100644
index 0000000..055fc1c
--- /dev/null
+++ b/static/topology3D/js/graph_request.js
@@ -0,0 +1,318 @@
+/*
+   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.GraphRequests = (function(global) {
+    'use strict';
+
+    var DEBUG = true;
+
+    GraphRequests.prototype.constructor = GraphRequests;
+
+    /**
+     * Constructor
+     */
+    function GraphRequests(args) {
+
+
+    }
+
+    GraphRequests.prototype.addNode = function(args, choice, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+
+        data = args_to_formdata(args, data);
+
+        //FIXME questo metodo dovrebbere essere generico
+        if(args.existing_element)
+            data.append('existing_element', args.existing_element ? args.existing_element : false)
+        //if (choice)
+        //    data.append('choice', choice);
+        $.ajax({
+            url: "addelement",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success();
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    };
+
+    GraphRequests.prototype.removeNode = function(args, choice, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+
+        data = args_to_formdata(args, data);
+
+        $.ajax({
+            url: "removeelement",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success();
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+            }
+        });
+    };
+
+    GraphRequests.prototype.getNodeOverview = function(args, success, error) {
+
+        var params = jQuery.param(args)
+        console.log("params", params)
+        $.ajax({
+            url: "overviewelement?"+params,
+            type: 'GET',
+            success: function(result) {
+                if (success)
+                    success(result);
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+            }
+        });
+    };
+
+    GraphRequests.prototype.addLink = function(args, choice, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+        data = args_to_formdata(args, data);
+
+        //data.append('destination', JSON.stringify(destination));
+        if (choice)
+            data.append('choice', choice);
+        //if(link.desc_id)
+        //    data.append('element_desc_id', link.desc_id || '');
+        $.ajax({
+            url: "addlink",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success();
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    };
+
+    GraphRequests.prototype.removeLink = function(args, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+        data = args_to_formdata(args, data);
+
+        $.ajax({
+            url: "removelink",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success();
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    };
+
+    //
+    GraphRequests.prototype.getAvailableNodes = function(args, success, error){
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+        $.ajax({
+            url: "availablenodes?layer="+args.layer,
+            type: 'GET',
+            success: function(result) {
+                if (success)
+                    success(result);
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    }
+
+    GraphRequests.prototype.savePositions = function(positions, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+        data.append('positions', JSON.stringify(positions));
+        $.ajax({
+            url: "positions",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success();
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    };
+
+    /*  START ETSI methods  */
+    GraphRequests.prototype.addVnffg = function(args, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+       /* data.append('group_id', args.info.group[0]);
+        data.append('element_id', args.id);
+        data.append('element_type', args.info.type);*/
+        data = args_to_formdata(args, data);
+        $.ajax({
+            url: "addelement",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success(result);
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    };
+
+    GraphRequests.prototype.addNodeToVnffg = function(args, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+       /* data.append('group_id', args.info.group[0]);
+        data.append('element_id', args.id);
+        data.append('element_type', args.info.type);
+        data.append('vnffg_id', args.vnffgId);*/
+        data = args_to_formdata(args, data);
+
+        $.ajax({
+            url: "addnodetovnffg",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success(result);
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    };
+
+    GraphRequests.prototype.getUnusedVnf = function(nsd_id, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+        $.ajax({
+            url: "unusedvnf/" + nsd_id,
+            type: 'GET',
+            success: function(result) {
+                if (success)
+                    success(result);
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+
+    };
+    /*  END ETSI methods  */
+
+    GraphRequests.prototype.getCookie = function(name) {
+        var cookieValue = null;
+        if (document.cookie && document.cookie !== '') {
+            var cookies = document.cookie.split(';');
+            for (var i = 0; i < cookies.length; i++) {
+                var cookie = jQuery.trim(cookies[i]);
+                // Does this cookie string begin with the name we want?
+                if (cookie.substring(0, name.length + 1) === (name + '=')) {
+                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+                    break;
+                }
+            }
+        }
+        return cookieValue;
+    };
+
+    function args_to_formdata(args, form_data){
+        for ( var key in args ) {
+            form_data.append(key, args[key]);
+        }
+        return form_data;
+    };
+
+
+    /**
+     * Log utility
+     */
+    function log(text) {
+        if (DEBUG)
+            console.log("::GraphRequests::", text);
+    }
+
+    return GraphRequests;
+
+
+}(this));
+
+if (typeof module === 'object') {
+    module.exports = dreamer.GraphRequests;
+}
diff --git a/static/topology3D/js/model_graph_editor.js b/static/topology3D/js/model_graph_editor.js
new file mode 100644
index 0000000..3c275dd
--- /dev/null
+++ b/static/topology3D/js/model_graph_editor.js
@@ -0,0 +1,512 @@
+/*
+   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