1
0
Fork 0
arangodb/html/admin/js/graphViewer/graph/nodeShaper.js

465 lines
12 KiB
JavaScript

/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */
/*global $, _, d3*/
/*global ColourMapper*/
////////////////////////////////////////////////////////////////////////////////
/// @brief Graph functionality
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
///
/// 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 "AS IS" 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.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Michael Hackstein
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
/*
* flags example format:
* {
* shape: {
* type: NodeShaper.shapes.CIRCLE,
* radius: value || function(node)
* },
* label: "key" || function(node),
* actions: {
* "click": function(node),
* "drag": function(node)
* },
* update: function(node)
* }
*
* {
* shape: {
* type: NodeShaper.shapes.RECT,
* width: value || function(node),
* height: value || function(node),
* },
* label: "key" || function(node),
* actions: {
* "click": function(node),
* "drag": function(node)
* },
* update: function(node)
* }
*/
function NodeShaper(parent, flags, idfunc) {
"use strict";
var self = this,
communityRegEx = /^\*community/,
nodes = [],
visibleLabels = true,
splitLabel = function(label) {
if (label === undefined) {
return [""];
}
if (typeof label !== "string") {
label = String(label);
}
var chunks = label.match(/[\w\W]{1,10}(\s|$)|\S+?(\s|$)/g);
chunks[0] = $.trim(chunks[0]);
chunks[1] = $.trim(chunks[1]);
if (chunks.length > 2) {
chunks.length = 2;
chunks[1] += "...";
}
return chunks;
},
noop = function (node) {
},
defaultDistortion = function(n) {
return {
x: n.x,
y: n.y,
z: 1
};
},
distortion = defaultDistortion,
addDistortion = function() {
_.each(nodes, function(n) {
n.position = distortion(n);
});
},
colourMapper = new ColourMapper(),
events,
addUpdate,
idFunction = function(d) {
return d._id;
},
addColor = noop,
addShape = noop,
addLabel = noop,
addCommunityShape = function(g) {
g.append("polygon")
.attr("points", "0,-25 -16,20 23,-10 -23,-10 16,20");
},
addCommunityLabel = function(g) {
g.append("text") // Append a label for the node
.attr("text-anchor", "middle") // Define text-anchor
.text(function(d) {
return d._size;
});
},
unbindEvents = function() {
// Hard unbind the dragging
self.parent
.selectAll(".node")
.on("mousedown.drag", null);
events = {
click: noop,
dblclick: noop,
drag: noop,
mousedown: noop,
mouseup: noop,
mousemove: noop,
mouseout: noop,
mouseover: noop
};
addUpdate = noop;
},
addEvents = function (nodes) {
_.each(events, function (func, type) {
if (type === "drag") {
nodes.call(func);
} else {
nodes.on(type, func);
}
});
},
addQue = function (g) {
var community = g.filter(function(n) {
return communityRegEx.test(n._id);
}),
normal = g.filter(function(n) {
return !communityRegEx.test(n._id);
});
addCommunityShape(community);
addShape(normal);
if (visibleLabels) {
addCommunityLabel(community);
addLabel(normal);
}
addColor(g);
addEvents(g);
addDistortion();
},
bindEvent = function (type, func) {
if (type === "update") {
addUpdate = func;
} else if (events[type] === undefined) {
throw "Sorry Unknown Event " + type + " cannot be bound.";
} else {
events[type] = func;
}
},
updateNodes = function () {
var nodes = self.parent.selectAll(".node");
addDistortion();
nodes.attr("transform", function(d) {
return "translate(" + d.position.x + "," + d.position.y + ")scale(" + d.position.z + ")";
});
addUpdate(nodes);
},
shapeNodes = function (newNodes) {
if (newNodes !== undefined) {
nodes = newNodes;
}
var g = self.parent
.selectAll(".node")
.data(nodes, idFunction);
// Append the group and class to all new
g.enter()
.append("g")
.attr("class", function(d) {
if (communityRegEx.test(d._id)) {
return "node communitynode";
}
return "node";
}) // node is CSS class that might be edited
.attr("id", idFunction);
// Remove all old
g.exit().remove();
g.selectAll("* > *").remove();
addQue(g);
updateNodes();
},
parseShapeFlag = function (shape) {
var radius, width, height, translateX, translateY;
switch (shape.type) {
case NodeShaper.shapes.NONE:
addShape = noop;
break;
case NodeShaper.shapes.CIRCLE:
radius = shape.radius || 25;
addShape = function (node) {
node
.append("circle") // Display nodes as circles
.attr("r", radius); // Set radius
};
break;
case NodeShaper.shapes.RECT:
width = shape.width || 120;
height = shape.height || 24;
if (_.isFunction(width)) {
translateX = function(d) {
return -(width(d) / 2);
};
} else {
translateX = -(width / 2);
}
if (_.isFunction(height)) {
translateY = function(d) {
return -(height(d) / 2);
};
} else {
translateY = -(height / 2);
}
addShape = function(node) {
node.append("rect") // Display nodes as rectangles
.attr("width", width) // Set width
.attr("height", height) // Set height
.attr("x", translateX)
.attr("y", translateY);
};
break;
case undefined:
break;
default:
throw "Sorry given Shape not known!";
}
},
parseLabelFlag = function (label) {
if (_.isFunction(label)) {
addLabel = function (node) {
var textN = node.append("text") // Append a label for the node
.attr("text-anchor", "middle") // Define text-anchor
.attr("fill", "black") // Force a black color
.attr("stroke", "none"); // Make it readable
textN.each(function(d) {
var chunks = splitLabel(label(d));
d3.select(this).append("tspan")
.attr("x", "0")
.attr("dy", "0")
.text(chunks[0]);
if (chunks.length === 2) {
d3.select(this).append("tspan")
.attr("x", "0")
.attr("dy", "20")
.text(chunks[1]);
}
});
};
} else {
addLabel = function (node) {
var textN = node.append("text") // Append a label for the node
.attr("text-anchor", "middle") // Define text-anchor
.attr("fill", "black") // Force a black color
.attr("stroke", "none"); // Make it readable
textN.each(function(d) {
var chunks = splitLabel(d._data[label]);
d3.select(this).append("tspan")
.attr("x", "0")
.attr("dy", "0")
.text(chunks[0]);
if (chunks.length === 2) {
d3.select(this).append("tspan")
.attr("x", "0")
.attr("dy", "20")
.text(chunks[1]);
}
});
};
}
},
parseActionFlag = function (actions) {
if (actions.reset !== undefined && actions.reset) {
unbindEvents();
}
_.each(actions, function(func, type) {
if (type !== "reset") {
bindEvent(type, func);
}
});
},
parseColorFlag = function (color) {
switch (color.type) {
case "single":
addColor = function (g) {
g.attr("stroke", color.stroke);
g.attr("fill", color.fill);
};
break;
case "expand":
addColor = function (g) {
g.attr("fill", function(n) {
if (n._expanded) {
return color.expanded;
}
return color.collapsed;
});
g.attr("stroke", function(n) {
if (n._expanded) {
return color.expanded;
}
return color.collapsed;
});
};
break;
case "attribute":
addColor = function (g) {
g.attr("fill", function(n) {
if (n._data === undefined) {
return colourMapper.getColour(undefined);
}
return colourMapper.getColour(n._data[color.key]);
});
g.attr("stroke", function(n) {
if (n._data === undefined) {
return colourMapper.getColour(undefined);
}
return colourMapper.getColour(n._data[color.key]);
});
};
break;
default:
throw "Sorry given colour-scheme not known";
}
},
parseDistortionFlag = function (dist) {
if (dist === "reset") {
distortion = defaultDistortion;
} else if (_.isFunction(dist)) {
distortion = dist;
} else {
throw "Sorry distortion cannot be parsed.";
}
},
parseConfig = function(config) {
if (config.shape !== undefined) {
parseShapeFlag(config.shape);
}
if (config.label !== undefined) {
parseLabelFlag(config.label);
}
if (config.actions !== undefined) {
parseActionFlag(config.actions);
}
if (config.color !== undefined) {
parseColorFlag(config.color);
}
if (config.distortion !== undefined) {
parseDistortionFlag(config.distortion);
}
};
self.parent = parent;
unbindEvents();
if (flags === undefined) {
flags = {
color: {
type: "single",
fill: "#FF8F35",
stroke: "#8AA051"
}
};
}
if (flags.shape === undefined) {
flags.shape = {
type: NodeShaper.shapes.RECT
};
}
if (flags.color === undefined) {
flags.color = {
type: "single",
fill: "#FF8F35",
stroke: "#8AA051"
};
}
if (flags.distortion === undefined) {
flags.distortion = "reset";
}
parseConfig(flags);
if (_.isFunction(idfunc)) {
idFunction = idfunc;
}
/////////////////////////////////////////////////////////
/// Public functions
/////////////////////////////////////////////////////////
self.changeTo = function(config) {
parseConfig(config);
shapeNodes();
};
self.drawNodes = function (nodes) {
shapeNodes(nodes);
};
self.updateNodes = function () {
updateNodes();
};
self.reshapeNodes = function() {
shapeNodes();
};
self.activateLabel = function(toogle) {
if (toogle) {
visibleLabels = true;
} else {
visibleLabels = false;
}
shapeNodes();
};
}
NodeShaper.shapes = Object.freeze({
"NONE": 0,
"CIRCLE": 1,
"RECT": 2
});