/**
* 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;
}