1
0
Fork 0

GraphViewer: The CommunityNodes are now able to expand and collapse themselfes.

This commit is contained in:
Michael Hackstein 2013-07-19 09:16:58 +02:00
parent 3adfe55284
commit e1fd48c424
2 changed files with 268 additions and 86 deletions

View File

@ -66,6 +66,7 @@ function CommunityNode(parent, initial) {
self = this,
boundingBox,
nodes = {},
observer,
nodeArray = [],
internal = {},
inbound = {},
@ -75,6 +76,20 @@ function CommunityNode(parent, initial) {
// Private functions //
////////////////////////////////////
getObserver = function() {
if (!observer) {
observer = new WebKitMutationObserver(function(e){
if (_.any(e, function(obj) {
return obj.attributeName === "transform";
})) {
updateBoundingBox();
observer.disconnect();
}
});
}
return observer;
},
toArray = function(obj) {
var res = [];
_.each(obj, function(v) {
@ -205,14 +220,31 @@ function CommunityNode(parent, initial) {
},
expand = function() {
this._expanded = true;
// TODO: Just piped through for old Adapter Interface
dissolve();
//dissolve();
},
dissolve = function() {
parent.dissolveCommunity(self);
},
collapse = function() {
this._expanded = false;
// TODO
},
addDistortion = function() {
// Fake Layouting TODO
_.each(nodeArray, function(n) {
n.position = {
x: n.x,
y: n.y,
z: 1
};
});
},
addShape = function (g, shapeFunc, colourMapper) {
g.attr("stroke", colourMapper.getForegroundCommunityColour());
shapeFunc(g);
@ -244,6 +276,35 @@ function CommunityNode(parent, initial) {
textN.text(self._size);
}
},
addNodeShapes = function(g, shapeFunc, colourMapper) {
var interior = g.selectAll(".node")
.data(nodeArray, function(d) {
return d._id;
});
interior.enter()
.append("g")
.attr("class", "node")
.attr("id", function(d) {
return d._id;
});
// Remove all old
interior.exit().remove();
interior.selectAll("* > *").remove();
addShape(interior, shapeFunc, colourMapper);
},
addBoundingBox = function(g) {
boundingBox = g.append("rect")
.attr("rx", "8")
.attr("ry", "8")
.attr("fill", "none")
.attr("stroke", "black");
getObserver().observe(document.getElementById(self._id), {
subtree:true,
attributes:true
});
},
updateBoundingBox = function() {
var bbox = document.getElementById(self._id).getBBox();
@ -254,53 +315,14 @@ function CommunityNode(parent, initial) {
},
shapeAll = function(g, shapeFunc, colourMapper) {
/*
boundingBox = g.append("rect")
.attr("rx", "8")
.attr("ry", "8")
.attr("fill", "none")
.attr("stroke", "black");
*/
if (self._expanded) {
addBoundingBox(g);
addDistortion();
addNodeShapes(g, shapeFunc, colourMapper);
return;
}
addCollapsedShape(g, shapeFunc, colourMapper);
addCollapsedLabel(g, colourMapper);
/*
_.each(nodeArray, function(n) {
n.position = {
x: n.x,
y: n.y,
z: 1
};
});
var interior = g.selectAll(".node")
.data(nodeArray, function(d) {
return d._id;
});
interior.enter()
.append("g")
.attr("class", function(d) {
return "node";
}) // node is CSS class that might be edited
.attr("id", function(d) {
return d._id;
});
// Remove all old
interior.exit().remove();
interior.selectAll("* > *").remove();
var observer = new WebKitMutationObserver(function(e){
if (_.any(e, function(obj) {
return obj.attributeName === "transform";
})) {
updateBoundingBox();
observer.disconnect();
}
});
observer.observe(document.getElementById(self._id), {
subtree:true,
attributes:true
});
addShape(interior, shapeFunc, colourMapper);
*/
};
////////////////////////////////////
@ -321,7 +343,7 @@ function CommunityNode(parent, initial) {
this._size = 0;
this._inboundCounter = 0;
this._outboundCounter = 0;
this._expanded = false;
// Easy check for the other classes,
// no need for a regex on the _id any more.
this._isCommunity = true;
@ -345,13 +367,12 @@ function CommunityNode(parent, initial) {
this.removeNode = removeNode;
this.removeInboundEdge = removeInboundEdge;
this.removeOutboundEdge = removeOutboundEdge;
this.removeOutboundEdgesFromNode = removeOutboundEdgesFromNode;
this.dissolve = dissolve;
this.getDissolveInfo = getDissolveInfo;
this.collapse = collapse;
this.expand = expand;
this.shape = shapeAll;

View File

@ -1,8 +1,8 @@
/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */
/*global beforeEach, afterEach */
/*global beforeEach, afterEach, jasmine*/
/*global describe, it, expect, spyOn */
/*global helper*/
/*global document*/
/*global document, window*/
/*global CommunityNode*/
////////////////////////////////////////////////////////////////////////////////
@ -136,11 +136,7 @@
it('should offer a function to remove and return all outgoing edges of a node', function() {
expect(testee).toHaveFunction("removeOutboundEdgesFromNode", 1);
});
it('should offer a function to dissolve the community', function() {
expect(testee).toHaveFunction("dissolve", 0);
});
it('should offer a function to get the dissolve info', function() {
expect(testee).toHaveFunction("getDissolveInfo", 0);
});
@ -157,7 +153,9 @@
expect(testee).toHaveFunction("collapse", 0);
});
it('should offer a function to dissolve the community', function() {
expect(testee).toHaveFunction("dissolve", 0);
});
});
describe('node functionality', function() {
@ -295,7 +293,8 @@
describe('shaping functionality', function() {
var tSpan1, tSpan2, text, g, shaper, colourMapper, box, boxRect,
parent;
parent, c;
beforeEach(function() {
parent = {
dissolveCommunity: function() {}
@ -373,6 +372,8 @@
}
};
});
c = new CommunityNode(parent, nodes.slice(0, 5));
});
it('should initially contain the required attributes for shaping', function() {
@ -391,24 +392,23 @@
z: 1
}
},
initNodes = [n].concat(nodes.slice(3, 13)),
c = new CommunityNode(parent, initNodes);
expect(c.x).toBeDefined();
expect(c.x).toEqual(x);
expect(c.y).toBeDefined();
expect(c.y).toEqual(y);
expect(c._size).toEqual(11);
expect(c._isCommunity).toBeTruthy();
otherC = new CommunityNode(parent, [n].concat(nodes.slice(5,21)));
expect(otherC.x).toBeDefined();
expect(otherC.x).toEqual(x);
expect(otherC.y).toBeDefined();
expect(otherC.y).toEqual(y);
expect(otherC._size).toEqual(17);
expect(otherC._isCommunity).toBeTruthy();
});
it('should shape the collapsed community with given functions', function() {
var c = new CommunityNode(parent, nodes.slice(0, 2));
spyOn(g, "attr").andCallThrough();
spyOn(g, "append").andCallThrough();
spyOn(shaper, "shapeFunc").andCallThrough();
spyOn(colourMapper, "getForegroundCommunityColour").andCallThrough();
c.shape(g, shaper.shapeFunc, colourMapper);
expect(colourMapper.getForegroundCommunityColour).wasCalled();
expect(g.attr).wasCalledWith("stroke", "black");
expect(shaper.shapeFunc).wasCalledWith(g, 9);
@ -418,7 +418,6 @@
});
it('should add a label containing the size of a community', function() {
var c = new CommunityNode(parent, nodes.slice(0, 2));
spyOn(g, "append").andCallThrough();
spyOn(text, "attr").andCallThrough();
spyOn(text, "text").andCallThrough();
@ -430,12 +429,11 @@
expect(text.attr).wasCalledWith("text-anchor", "middle");
expect(text.attr).wasCalledWith("fill", "black");
expect(text.attr).wasCalledWith("stroke", "none");
expect(text.text).wasCalledWith(2);
expect(text.text).wasCalledWith(5);
expect(text.append).wasNotCalled();
});
it('should add a label if a reason is given', function() {
var c = new CommunityNode(parent, nodes.slice(0, 2));
c._reason = {
key: "key",
value: "label"
@ -467,25 +465,170 @@
expect(tSpan2.attr).wasCalledWith("dy", "16");
expect(tSpan2.text).wasCalledWith("label");
});
/*
it('should print the bounding box correctly', function() {
var c = new CommunityNode(nodes.slice(0, 2));
spyOn(g, "append").andCallThrough();
spyOn(boxRect, "attr").andCallThrough();
describe('if the community is expanded', function() {
c.shape(g, shaper.shapeFunc, colourMapper);
var nodeSelector, interior, iEnter, iExit, iAll, iG,
observer, observerCB;
beforeEach(function() {
g.selectAll = function(selector) {
return nodeSelector;
};
nodeSelector = {
data: function() {
return interior;
}
};
interior = {
enter: function() {
return iEnter;
},
exit: function() {
return iExit;
},
selectAll: function() {
return iAll;
},
attr: function() {
return this;
}
};
iEnter = {
append: function() {
return iG;
}
};
iG = {
attr: function() {
return this;
}
};
iExit = {
remove: function() {}
};
iAll = {
remove: function() {}
};
observer = {
observe: function() {},
disconnect: function() {}
};
spyOn(window, "WebKitMutationObserver").andCallFake(function(cb) {
observerCB = cb;
return observer;
});
c.expand();
});
it('should print the bounding box correctly', function() {
spyOn(g, "append").andCallThrough();
spyOn(boxRect, "attr").andCallThrough();
c.shape(g, shaper.shapeFunc, colourMapper);
expect(g.append).wasCalledWith("rect");
expect(boxRect.attr).wasCalledWith("rx", "8");
expect(boxRect.attr).wasCalledWith("ry", "8");
expect(boxRect.attr).wasCalledWith("fill", "none");
expect(boxRect.attr).wasCalledWith("stroke", "black");
});
it('should update the box as soon as the dom is ready', function() {
spyOn(boxRect, "attr").andCallThrough();
spyOn(observer, "observe").andCallThrough();
spyOn(observer, "disconnect").andCallThrough();
c.shape(g, shaper.shapeFunc, colourMapper);
expect(document.getElementById).wasCalledWith(c._id);
expect(observer.observe).wasCalledWith(
jasmine.any(Object),
{
subtree:true,
attributes:true
}
);
observerCB([{attributeName: "transform"}]);
expect(boxRect.attr).wasCalledWith("width", box.width + 10);
expect(boxRect.attr).wasCalledWith("height", box.height + 10);
expect(boxRect.attr).wasCalledWith("x", box.x - 5);
expect(boxRect.attr).wasCalledWith("y", box.y - 5);
expect(observer.disconnect).wasCalled();
});
it('should shape the expanded community with given functions', function() {
spyOn(g, "selectAll").andCallThrough();
spyOn(nodeSelector, "data").andCallFake(function(a, func) {
expect(func(nodes[0])).toEqual(nodes[0]._id);
expect(func(nodes[6])).toEqual(nodes[6]._id);
return interior;
});
spyOn(interior, "enter").andCallThrough();
spyOn(interior, "exit").andCallThrough();
spyOn(interior, "selectAll").andCallThrough();
spyOn(iEnter, "append").andCallThrough();
spyOn(iG, "attr").andCallThrough();
spyOn(iExit, "remove").andCallThrough();
spyOn(iAll, "remove").andCallThrough();
c.shape(g, shaper.shapeFunc, colourMapper);
expect(g.selectAll).wasCalledWith(".node");
expect(nodeSelector.data).wasCalledWith(c.getNodes(), jasmine.any(Function));
expect(interior.enter).wasCalled();
expect(iEnter.append).wasCalledWith("g");
expect(iG.attr).wasCalledWith("class", "node");
expect(iG.attr).wasCalledWith("id", jasmine.any(Function));
expect(interior.exit).wasCalled();
expect(iExit.remove).wasCalled();
expect(interior.selectAll).wasCalledWith("* > *");
expect(iAll.remove).wasCalled();
/*
var observer = new WebKitMutationObserver(function(e){
if (_.any(e, function(obj) {
return obj.attributeName === "transform";
})) {
updateBoundingBox();
observer.disconnect();
}
});
observer.observe(document.getElementById(self._id), {
subtree:true,
attributes:true
});
addShape(interior, shapeFunc, colourMapper);
*/
});
it('should apply distortion on the interior nodes', function() {
// Fake Layouting to test correctness
/*
nodes[0].x = -20;
nodes[0].y = 20;
nodes[1].x = -10;
nodes[1].y = 10;
nodes[2].x = 0;
nodes[2].y = 0;
nodes[3].x = 10;
nodes[3].y = -10;
nodes[4].x = 20;
nodes[4].y = -20;
*/
});
expect(g.append).wasCalledWith("rect");
expect(boxRect.attr).wasCalledWith("width", box.width + 10);
expect(boxRect.attr).wasCalledWith("height", box.height + 10);
expect(boxRect.attr).wasCalledWith("x", box.x - 5);
expect(boxRect.attr).wasCalledWith("y", box.y - 5);
expect(boxRect.attr).wasCalledWith("rx", "8");
expect(boxRect.attr).wasCalledWith("ry", "8");
expect(boxRect.attr).wasCalledWith("fill", "none");
expect(boxRect.attr).wasCalledWith("stroke", "black");
});
*/
});
describe('edge functionality', function() {
@ -807,9 +950,27 @@
};
});
it('should be possible to dissolve the community', function() {
var c = new CommunityNode(parent);
spyOn(parent, "dissolveCommunity");
c.dissolve();
expect(parent.dissolveCommunity).wasCalledWith(c);
});
it('should be possible to expand the community', function() {
var c = new CommunityNode(parent);
expect(c._expanded).toBeFalsy();
c.expand();
expect(c._expanded).toBeTruthy();
});
it('should be possible to collapse the community', function() {
var c = new CommunityNode(parent);
expect(c._expanded).toBeFalsy();
c.expand();
expect(c._expanded).toBeTruthy();
c.collapse();
expect(c._expanded).toBeFalsy();
});
});