+ /**
+ * Die Farben, die im Projekt genutzt werden.
+ * Aus dem TUM Styleguide.
+ * @type Object
+ */
+var const_Colors = {
+ NodeFilling: "#98C6EA", // Pantone 283, 100%
+ NodeBorder: "#0065BD", // Pantone 300, 100%, "TUM-Blau"
+ NodeBorderHighlight: "#C4071B", // Helles Rot 100% aus TUM Styleguide
+ NodeFillingHighlight: "#73B78D", // Dunkelgrün 55 % aus TUM Styleguide
+ NodeFillingLight: "#00c532", // Dunkelgrün 55 % aus TUM Styleguide
+ NodeFillingQuestion: "#C4071B", // Helles Rot 100% aus TUM Styleguide
+ EdgeHighlight1: "#C4071B", // Helles Rot 100% aus TUM Styleguide
+ EdgeHighlight2: "#73B78D", // Dunkelgrün 55 % aus TUM Styleguide
+ EdgeHighlight3: "#73B78D", // Dunkelgrün 55 % aus TUM Styleguide
+ EdgeHighlight4: "#007C30", // Dunkelgrün 100 % aus TUM Styleguide
+ RedText: "#C4071B", // Helles Rot 100% aus TUM Styleguide
+ GreenText: "#007C30", // Dunkelgrün 100 % aus TUM Styleguide
+ PQColor : "#FFFF70", // Helles Gelb
+ StartNodeColor : "#33CC33", // Dunklgrün
+ CurrentNodeColor : "#C4071B", // Helles Rot
+ FinishedNodeColor : "#73B78D", // Wie EdgeHighlight2
+ ShortestPathColor : "#73B78D", // Wie EdgeHighlight2
+ UnusedEdgeColor : "#0065BD", // Wie NodeBorder
+ NormalEdgeColor : "#000000" // Schwarz
+};
+
+/**
+ * Standardgröße eines Knotens
+ * @type Number
+ */
+var global_KnotenRadius = 15;
+
+/**
+ * Standardaussehen einer Kante.
+ * @type Object
+ */
+var global_Edgelayout = {
+ 'arrowAngle' : Math.PI/8, // Winkel des Pfeilkopfs relativ zum Pfeilkörper
+ 'arrowHeadLength' : 15, // Länge des Pfeilkopfs
+ 'lineColor' : "black", // Farbe des Pfeils
+ 'lineWidth' : 2, // Dicke des Pfeils
+ 'font' : 'Arial', // Schrifart
+ 'fontSize' : 14, // Schriftgrösse in Pixeln
+ 'isHighlighted': false, // Ob die Kante eine besondere Markierung haben soll
+ 'progressArrow': false, // Zusätzlicher Animationspfeil
+ 'progressArrowPosition': 0.0, // Position des Animationspfeils
+ 'progressArrowSource': null, // Animationspfeil Source Knoten
+ 'progressArrowTarget': null // Animationspfeil Target Knoten
+};
+
+/**
+ * Standardaussehen eines Knotens.
+ * @type Object
+ */
+var global_NodeLayout = {
+ 'fillStyle' : const_Colors.NodeFilling, // Farbe der Füllung
+ 'nodeRadius' : 15, // Radius der Kreises
+ 'borderColor' : const_Colors.NodeBorder, // Farbe des Rands (ohne Markierung)
+ 'borderWidth' : 2, // Breite des Rands
+ 'fontColor' : 'black', // Farbe der Schrift
+ 'font' : 'bold', // Schriftart
+ 'fontSize' : 14 // Schriftgrösse in Pixeln
+};
+
+/**
+ * Helper function to return a string representaiton of SVG's translate tranform
+ */
+function translate(x,y){
+ return "translate("+x+","+y+")";
+}
+
+/**
+ * Global map where we save the GraphDrawer instances. Used when saving svg to disc.
+ */
+GraphAlgos = d3.map();
+
+/**
+ * @classdesc
+ * The base class of a Network visualization of a graph. Based on D3 and SVG.
+ * Graph Editors and Graph Algorithms should inherit from this class.
+ * @constructor
+ */
+GraphDrawer = function(svgOrigin,extraMargin,transTime){
+
+ /////////////////
+ //PRIVATE
+ var id = svgOrigin.attr("id");
+ GraphAlgos.set(id,this);
+
+ var transTime = (transTime!=null) ? transTime : 250;
+
+ var extraMargin = extraMargin || {};
+
+ var xRange = +svgOrigin.attr("width") || 400;
+ yRange = +svgOrigin.attr("height") || 300;
+ var wS = global_NodeLayout['borderWidth'];
+
+ var margin = {
+ top: global_KnotenRadius+wS+ (extraMargin.top || 10),
+ right: global_KnotenRadius+wS,
+ bottom: global_KnotenRadius+wS,
+ left: global_KnotenRadius+wS +(extraMargin.left || 0)
+ }
+
+ var width = xRange - margin.left - margin.right;
+ var height = yRange - margin.top - margin.bottom;
+
+ this.height = height;
+ this.width = width;
+
+ this.margin = margin;
+
+ var radius = global_KnotenRadius;//20;
+
+ svgOrigin
+ .attr({version: '1.1' , xmlns:"http://www.w3.org/2000/svg"})
+ .attr("width", width + margin.left + margin.right)
+ .attr("height", height + margin.top + margin.bottom)
+
+ var svg = svgOrigin.append("g")
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+ this.svg=svg;
+
+ var svg_links=svg.append("g").attr("id", "edges");
+ var svg_nodes=svg.append("g").attr("id", "nodes");
+
+ this.x = d3.scale.linear()
+ .range([margin.left, width-margin.right])
+ .domain([0,xRange]);
+
+ this.y = d3.scale.linear()
+ .range([height-margin.top, margin.bottom])
+ .domain([0,yRange]);
+
+ var transform = function(d){
+ return translate(this.x(this.nodeX(d)),this.y(this.nodeY(d)));
+ }; transform = transform.bind(this);
+
+ //fit the graph extend in a smaller window. needed for algorithm tab
+ //since there the network graph is only half as wide as in the graph editor tab.
+ NOSQUEEZE=false;
+ this.squeeze = function(){
+ if(NOSQUEEZE) return; //define flag for debug
+ var nodes;
+
+ if(Graph.instance && (nodes = Graph.instance.getNodes())){
+ this.x.domain(d3.extent(nodes, function(d) { return d.x; }));
+ this.y.domain(d3.extent(nodes, function(d) { return d.y; }));
+ }
+ }
+
+ var xfun = function(d){
+ return this.x(this.nodeX(Graph.instance.nodes.get(d.id) || d));
+ }; xfun = xfun.bind(this);
+
+
+ var yfun = function(d){
+ return this.y(this.nodeY(Graph.instance.nodes.get(d.id) || d));
+ }; yfun = yfun.bind(this);
+
+ function lineAttribs(d){
+ var attr = { x1:xfun(d.start), y1:yfun(d.start), x2:xfun(d.end), y2:yfun(d.end)};
+ if(transTime) d3.select(this).transition().duration(transTime).attr(attr)
+ else d3.select(this).attr(attr);
+ };
+
+ function textAttribs(d){
+ var attr = { x : (xfun(d.start)+xfun(d.end))*0.5 , y : ( yfun(d.start)+yfun(d.end))*.5};
+ if(transTime) d3.select(this).transition().duration(transTime).attr(attr)
+ else d3.select(this).attr(attr);
+ };
+
+
+
+ /////////////////
+ //PRIVILEDGED
+
+ this.clear = function(){
+ svg_nodes.selectAll("g").remove();
+ svg_links.selectAll("g").remove();
+ };
+
+ this.type="GraphDrawer";
+ this.graph = Graph.instance;
+ this.svgOrigin = svgOrigin;
+
+ var that = this;
+
+ this.screenPosToNodePos = function(pos){
+ return {x: that.x.invert(pos[0]-margin.left), y: that.y.invert(pos[1]-margin.top)};
+ };
+
+ this.screenPosToTransform = function(pos){
+ return "translate(" + (pos[0]-margin.left) + "," + (pos[1]-margin.top) + ")";
+ }
+
+ /**
+ * D3's Data Join of node data with their visualization (circles)
+ */
+ this.updateNodes = function(){
+
+ // DATA JOIN
+ // Join new data with old elements, if any.
+ var selection = svg_nodes.selectAll(".node")
+ .data(Graph.instance.getNodes(),function(d){return d.id});
+
+
+ // UPDATE
+ // Update old elements as needed.
+
+ // ENTER
+ // Create new elements as needed.
+ var enterSelection = selection
+ .enter().append("g")
+ .attr("class","node")
+ .call(this.onNodesEntered);//Foo.prototype.setText.bind(bar))
+
+ enterSelection.append("circle")
+ .attr("r", radius)
+ .style("fill",global_NodeLayout['fillStyle'])
+ .style("stroke-width",global_NodeLayout['borderWidth'])
+ .style("stroke",global_NodeLayout['borderColor'])
+
+ enterSelection.append("text")
+ .attr("class","label unselectable")
+ .attr("dy", ".35em") // set offset y position
+ .attr("text-anchor", "middle")
+
+ enterSelection.append("text")
+ .attr("class","resource unselectable")
+ .attr("dy",-global_KnotenRadius+"px") // set offset y position
+ .attr("text-anchor", "middle")
+
+
+ // ENTER + UPDATE
+ // Appending to the enter selection expands the update selection to include
+ // entering elements; so, operations on the update selection after appending to
+ // the enter selection will apply to both entering and updating nodes.
+ if(transTime){
+ selection
+ .transition().duration(transTime)
+ .attr("transform",transform)
+ .call(this.onNodesUpdated);
+ }else{
+ selection
+ .attr("transform",transform)
+ .call(this.onNodesUpdated);
+ }
+
+ selection.selectAll("text.label")
+ .text(this.nodeLabel);
+
+ var res = selection.selectAll("text.resource")
+
+ if(this.nodeHtml){
+ res.html(this.nodeHtml);
+ }else{
+ res.text(this.nodeText);
+ }
+
+
+
+ // EXIT
+ // Remove old elements as needed.
+ selection.exit().remove();
+
+ } //end updateNodes()
+
+
+ /**
+ * D3's Data Join of edge data with their visualization (lines)
+ */
+ this.updateEdges = function(){
+
+ var selection = svg_links.selectAll(".edge")
+ .data(Graph.instance.getEdges(),function(d){
+ return d.id;
+ });
+
+ //ENTER
+
+ var enterSelection = selection
+ .enter()
+ .append("g")
+ .attr("class","edge")
+ .call(this.onEdgesEntered);
+
+
+ enterSelection.append("line")
+ .attr("class","arrow")
+ .style("marker-end", "url(#arrowhead2)")
+ .style("stroke","black")
+ .style("stroke-width",global_Edgelayout['lineWidth'])
+
+ enterSelection.append("text")
+// .style("text-anchor", "middle")
+// .attr("dominant-baseline","middle")
+// .attr("dy", "-.5em") // set offset y position
+ .attr("class","resource unselectable edgeLabel")
+
+
+ var that = this;
+
+
+ //ENTER + UPDATE
+ var selt = selection;//.transition().duration(1000);
+ selt.selectAll("line")
+ .each(lineAttribs)
+// .style("opacity",1e-6)
+// .transition()
+// .duration(750)
+// .style("opacity",1);
+
+ var res = selt.selectAll("text.resource");
+
+ if(this.edgeHtml){
+ res.html(this.edgeHtml);
+ }else{
+ res.text(this.edgeText);
+ }
+
+ res
+ .style("text-anchor", function(d){
+ var arrowXProj = that.nodeX(d.start)-that.nodeX(d.end);
+ return (arrowXProj>0) ? "start" : "end";
+ })
+ .attr("dominant-baseline",function(d){
+ var arrowYProj = that.nodeY(d.start)-that.nodeY(d.end);
+ return (arrowYProj>0) ? "text-before-edge" : "text-after-edge";
+ })
+ .each(textAttribs)
+
+
+ selection.call(this.onEdgesUpdated)
+
+ //EXIT
+ var exitSelection = selection.exit()
+ exitSelection.remove();
+
+ }
+
+ //initialize //TODO: is called twice when we init both tabs at the same time
+ if(Graph.instance==null){
+ //calls registered event listeners when loaded;
+ var GRAPH_FILENAME = GRAPH_FILENAME || null;
+ var filename = GRAPH_FILENAME || "graphs-new/"+$("#tg_select_GraphSelector").val()+".txt"; //the selected option
+ Graph.loadInstance(filename,function(error,text,filename){
+ console.log("error loading graph instance "+error + " from " + filename +" text: "+text);
+ });
+ }
+} //end constructor GraphDrawer
+
+/**
+ * The main function which triggers updates to node and edge selections.
+ */
+GraphDrawer.prototype.update= function(){
+ this.updateNodes();
+ this.updateEdges();
+}
+
+/**
+ * Called when new nodes are entering
+ */
+GraphDrawer.prototype.onNodesEntered = function(selection) {
+// console.log(selection[0].length + " nodes entered")
+}
+/**
+ * Called when exisitng nodes are updated
+ */
+GraphDrawer.prototype.onNodesUpdated = function(selection) {
+// console.log(selection[0].length + " nodes updated")
+}
+/**
+ * Called when new edges are entering
+ */
+GraphDrawer.prototype.onEdgesEntered = function(selection) {
+// console.log(selection[0].length + " edges entered")
+}
+/**
+ * Called when exisitng edges are updated
+ */
+GraphDrawer.prototype.onEdgesUpdated = function(selection) {
+// console.log(selection[0].length + " edges entered")
+}
+
+/**
+ * Displays in the middle of the edge (typically cost/resource vectors or capacity/flow)
+ */
+GraphDrawer.prototype.edgeText = function(d){
+ return d.toString();
+}
+
+/**
+ * Displays on top of a node (typically constraints or state variables)
+ */
+GraphDrawer.prototype.nodeText = function(d){
+ return d.toString();
+}
+
+/**
+ * Displays inside of a node (typically its id)
+ */
+GraphDrawer.prototype.nodeLabel = function(d){
+ return d.id;
+}
+
+/**
+ * X Position of a node
+ */
+GraphDrawer.prototype.nodeX = function(d){
+ return d.x;
+};
+/**
+ * Y Position of a node
+ */
+GraphDrawer.prototype.nodeY = function(d){
+ return d.y;
+};
+GraphDrawer.prototype.nodePos = function(d){
+ var obj = {};
+ obj.x = this.x(this.nodeX(d));
+ obj.y = this.y(this.nodeY(d));
+ return obj;
+}
+
+