1
0
Fork 0

GraphViewer: Started with new interface for zoommanager with better integration of D3 and Fisheye

This commit is contained in:
Michael Hackstein 2013-05-06 10:30:18 +02:00
parent ee9473adbd
commit 169dcfeb66
3 changed files with 403 additions and 355 deletions

View File

@ -29,7 +29,7 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function ZoomManager(width, height, config) { function ZoomManager(width, height, g, nodeShaper, edgeShaper, config) {
"use strict"; "use strict";
if (width === undefined || width < 0) { if (width === undefined || width < 0) {
@ -38,125 +38,134 @@ function ZoomManager(width, height, config) {
if (height === undefined || height < 0) { if (height === undefined || height < 0) {
throw("A height has to be given."); throw("A height has to be given.");
} }
if (g === undefined || g.node === undefined || g.node().tagName !== "G") {
throw("A group has to be given.");
}
if (nodeShaper === undefined || nodeShaper.activate === undefined) {
throw("The Node shaper has to be given.");
}
if (edgeShaper === undefined || edgeShaper.activate === undefined) {
throw("The Edge shaper has to be given.");
}
var self = this, var self = this,
fontMax, fontSize,
fontMin, nodeRadius,
rMax, labelToggle,
rMin,
rMid,
radiusStep,
fontStep,
distortionStep,
currentZoom, currentZoom,
currentFont,
currentRadius,
currentLimit, currentLimit,
currentDistortion, currentDistortion,
currentDistortionRadius,
size = width * height, size = width * height,
zoom,
calcNodeLimit = function () { calcNodeLimit = function () {
var div; var div, reqSize;
if (currentFont !== null) { if (currentZoom >= labelToggle) {
div = 60 * currentFont * currentFont; reqSize = fontSize * currentZoom;
reqSize *= reqSize;
div = 60 * reqSize;
} else { } else {
div = 4 * currentRadius * currentRadius * Math.PI; reqSize = nodeRadius * currentZoom;
reqSize *= reqSize;
div = 4 * Math.PI * reqSize;
} }
return Math.floor(size / div); return Math.floor(size / div);
}, },
parseConfig = function (conf) { parseConfig = function (conf) {
fontMax = 16; if (conf === undefined) {
fontMin = 6; conf = {};
rMax = 25; }
rMin = 1; var
rMid = (rMax - rMin) / 2 + rMin; fontMax = conf.maxFont || 16,
fontMin = conf.minFont || 6,
rMax = conf.maxRadius || 25,
rMin = conf.minRadius || 1;
fontStep = (fontMax - fontMin) / 100; fontSize = fontMax;
radiusStep = (rMax - rMin) / 200; nodeRadius = rMax;
distortionStep = 0.001; // TODO!
currentFont = fontMax; labelToggle = 0;
currentRadius = rMax;
currentDistortion = 0; currentDistortion = 0;
currentDistortionRadius = 100;
currentLimit = calcNodeLimit(); currentLimit = calcNodeLimit();
currentZoom = 0; currentZoom = 1;
},
adjustValues = function (out) {
if (out) {
currentZoom++;
if (currentZoom > 100) {
currentFont = null;
if (currentZoom === 200) {
currentRadius = rMin;
} else {
currentRadius -= radiusStep;
}
} else {
if (currentZoom === 100) { zoom = d3.behavior.zoom()
currentFont = fontMin; .scaleExtent([rMin/rMax, 1])
currentRadius = rMid; .on("zoom", function() {
} else { // TODO: Still to be implemented
currentRadius -= radiusStep; currentZoom = d3.event.scale;
currentFont -= fontStep;
}
}
currentDistortion += distortionStep;
} else {
currentZoom--;
if (currentZoom < 100) {
if (currentZoom === 0) {
currentFont = fontMax;
currentRadius = rMax;
} else {
currentFont += fontStep;
currentRadius += radiusStep;
}
} else {
if (currentZoom === 100) {
currentFont = fontMin;
currentRadius = rMid;
} else {
currentRadius += radiusStep;
currentFont = null;
}
}
currentDistortion -= distortionStep;
}
currentLimit = calcNodeLimit(); currentLimit = calcNodeLimit();
//curTrans = $.extend({}, d3.event.translate);
/*
//curTrans[0] /= curZoom;
//curTrans[1] /= curZoom;
//console.log("here", d3.event.translate, d3.event.scale);
g.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
if (d3.event.scale < stopLabel) {
test.remove();
}
/*
fisheye
.distortion(1/d3.event.scale * fe_dist - 1);
*/
//.radius(1/d3.event.scale * fe_radius);
});
}; };
parseConfig(config); parseConfig(config);
self.getFontSize = function() { g.call(zoom);
return currentFont;
self.translation = function() {
return null;
}; };
self.getRadius = function() { self.scaleFactor = function() {
return currentRadius; return currentZoom;
};
self.scaledMouse = function() {
return null;
};
self.mouseMoveHandle = function() {
// TODO
var focus = d3.mouse(this);
focus[0] += curTrans[0];
focus[1] += curTrans[1];
fisheye.focus(focus);
node.each(function(d) { d.fisheye = fisheye(d); })
.attr("cx", function(d) { return d.fisheye.x; })
.attr("cy", function(d) { return d.fisheye.y; })
.attr("r", function(d) { return d.fisheye.z * 25; });
link.attr("x1", function(d) { return d.source.fisheye.x; })
.attr("y1", function(d) { return d.source.fisheye.y; })
.attr("x2", function(d) { return d.target.fisheye.x; })
.attr("y2", function(d) { return d.target.fisheye.y; });
}; };
self.getDistortion = function() { self.getDistortion = function() {
return currentDistortion; return currentDistortion;
}; };
self.getDistortionRadius = function() {
return currentDistortionRadius;
};
self.getNodeLimit = function() { self.getNodeLimit = function() {
return currentLimit; return currentLimit;
}; };
self.zoomIn = function() {
if (currentZoom === 0) {
return;
}
adjustValues(false);
};
self.zoomOut = function() {
if (currentZoom === 200) {
return;
}
adjustValues(true);
};
} }

View File

@ -53,4 +53,20 @@ var helper = helper || {};
testee.dispatchEvent(evt); testee.dispatchEvent(evt);
}; };
helper.simulateScrollUpMouseEvent = function (objectId) {
var evt = document.createEvent("MouseEvents"),
testee = document.getElementById(objectId);
evt.initMouseEvent("DOMMouseScroll", true, true, window,
-10, 0, 0, 0, 0, false, false, false, false, 0, null);
testee.dispatchEvent(evt);
};
helper.simulateScrollDownMouseEvent = function (objectId) {
var evt = document.createEvent("MouseEvents"),
testee = document.getElementById(objectId);
evt.initMouseEvent("DOMMouseScroll", true, true, window,
10, 0, 0, 0, 0, false, false, false, false, 0, null);
testee.dispatchEvent(evt);
};
}()); }());

View File

@ -38,11 +38,32 @@
"use strict"; "use strict";
describe('Zoom Manager', function () { describe('Zoom Manager', function () {
var svg; var svg,
g,
nodeShaperMock,
edgeShaperMock,
simulateZoomOut = function () {
helper.simulateScrollUpMouseEvent("svg");
},
simulateZoomIn = function () {
helper.simulateScrollDownMouseEvent("svg");
};
beforeEach(function () { beforeEach(function () {
svg = document.createElement("svg"); svg = document.createElement("svg");
document.body.appendChild(svg); document.body.appendChild(svg);
g = d3.select("svg").append("g");
g.attr("id", "svg");
nodeShaperMock = {
activateLabel: function() {}
};
edgeShaperMock = {
activateLabel: function() {}
};
spyOn(nodeShaperMock, "activateLabel");
spyOn(edgeShaperMock, "activateLabel");
}); });
afterEach(function () { afterEach(function () {
@ -63,14 +84,32 @@
}).toThrow("A height has to be given."); }).toThrow("A height has to be given.");
}); });
it('should not throw an error if mandatory information is given', function() { it('should throw an error if the group is not given', function() {
expect(function() { expect(function() {
var s = new ZoomManager(10, 10); var s = new ZoomManager(10, 10);
}).toThrow("A group has to be given.");
});
it('should throw an error if the node shaper is not given', function() {
expect(function() {
var s = new ZoomManager(10, 10, g);
}).toThrow("The Node shaper has to be given.");
});
it('should throw an error if the edge shaper is not given', function() {
expect(function() {
var s = new ZoomManager(10, 10, g, nodeShaperMock);
}).toThrow("The Edge shaper has to be given.");
});
it('should not throw an error if mandatory information is given', function() {
expect(function() {
var s = new ZoomManager(10, 10, g, nodeShaperMock, edgeShaperMock);
}).not.toThrow(); }).not.toThrow();
}); });
}); });
});
describe('setup with default values', function() { describe('setup with default values', function() {
@ -81,14 +120,14 @@
beforeEach(function() { beforeEach(function() {
w = 200; w = 200;
h = 200; h = 200;
manager = new ZoomManager(w, h); manager = new ZoomManager(w, h, g, nodeShaperMock, edgeShaperMock);
}); });
describe('the interface', function() { describe('the interface', function() {
it('should offer a function handle for zoom', function() { it('should offer a function handle for the mouse movement', function() {
expect(manager.zoomHandle).toBeDefined(); expect(manager.mouseMoveHandle).toBeDefined();
expect(manager.zoomHandle).toEqual(jasmine.any(Function)); expect(manager.mouseMoveHandle).toEqual(jasmine.any(Function));
}); });
it('should offer a function to get the current scale factor', function() { it('should offer a function to get the current scale factor', function() {
@ -121,27 +160,6 @@
expect(manager.getNodeLimit).toEqual(jasmine.any(Function)); expect(manager.getNodeLimit).toEqual(jasmine.any(Function));
}); });
// Old interface might be unused!
it('should offer a function to get the current font-size', function() {
expect(manager.getFontSize).toBeDefined();
expect(manager.getFontSize).toEqual(jasmine.any(Function));
});
it('should offer a function to get the current node-radius', function() {
expect(manager.getRadius).toBeDefined();
expect(manager.getRadius).toEqual(jasmine.any(Function));
});
it('should offer a function for zoom-in', function() {
expect(manager.zoomIn).toBeDefined();
expect(manager.zoomIn).toEqual(jasmine.any(Function));
});
it('should offer a function for zoom-out', function() {
expect(manager.zoomOut).toBeDefined();
expect(manager.zoomOut).toEqual(jasmine.any(Function));
});
}); });
describe('default values', function() { describe('default values', function() {
@ -153,7 +171,8 @@
nodeMax, nodeMax,
nodeMaxNoLabel, nodeMaxNoLabel,
nodeMinLabel, nodeMinLabel,
nodeMin; nodeMin,
distRBase;
beforeEach(function() { beforeEach(function() {
@ -167,6 +186,7 @@
fontMin = 6; fontMin = 6;
radMax = 25; radMax = 25;
radMin = 1; radMin = 1;
distRBase = 100;
nodeMax = Math.floor(w * h / labelSize(fontMax)); nodeMax = Math.floor(w * h / labelSize(fontMax));
nodeMinLabel = Math.floor(w * h / labelSize(fontMin)); nodeMinLabel = Math.floor(w * h / labelSize(fontMin));
nodeMaxNoLabel = Math.floor(w * h / circleSize((radMax - radMin) / 2 + radMin)); nodeMaxNoLabel = Math.floor(w * h / circleSize((radMax - radMin) / 2 + radMin));
@ -174,40 +194,39 @@
}); });
it('should offer maximized values if no zoom happens', function() { it('should offer maximized values if no zoom happens', function() {
expect(manager.getFontSize()).toEqual(fontMax);
expect(manager.getRadius()).toEqual(radMax);
expect(manager.getNodeLimit()).toEqual(nodeMax); expect(manager.getNodeLimit()).toEqual(nodeMax);
expect(manager.getDistortion()).toBeCloseTo(0, 6); expect(manager.getDistortion()).toBeCloseTo(0, 6);
expect(manager.getDistortionRadius()).toEqual(distRBase);
}); });
it('should not be possible to zoom in if max-zoom is reached', function() { it('should not be possible to zoom in if max-zoom is reached', function() {
var oldFS = manager.getFontSize(), var oldNL = manager.getNodeLimit(),
oldR = manager.getRadius(), oldD = manager.getDistortion(),
oldNL = manager.getNodeLimit(), oldDR = manager.getDistortionRadius(),
oldD = manager.getDistortion(); oldSF = manager.scaleFactor();
manager.zoomIn(); simulateZoomIn();
expect(manager.getFontSize()).toEqual(oldFS);
expect(manager.getRadius()).toEqual(oldR);
expect(manager.getNodeLimit()).toEqual(oldNL); expect(manager.getNodeLimit()).toEqual(oldNL);
expect(manager.getDistortion()).toEqual(oldD); expect(manager.getDistortion()).toEqual(oldD);
expect(manager.getDistortionRadius()).toEqual(oldDR);
expect(manager.scaleFactor()).not.toBeLessThan(oldSF);
}); });
it('should be possible to zoom-out until minimal font-size is reached', function() { it('should be possible to zoom-out until labels are removed', function() {
var oldFS, var oldNL,
oldR, oldSF,
oldNL,
oldD, oldD,
oldDR,
loopCounter = 0; loopCounter = 0;
while (manager.getFontSize() > fontMin && manager.getFontSize() !== null) { while (manager.getFontSize() > fontMin && manager.getFontSize() !== null) {
oldFS = manager.getFontSize();
oldR = manager.getRadius();
oldNL = manager.getNodeLimit(); oldNL = manager.getNodeLimit();
oldD = manager.getDistortion(); oldD = manager.getDistortion();
manager.zoomOut(); oldDR = manager.getDistortionRadius();
expect(manager.getFontSize()).toBeLessThan(oldFS); oldSF = manager.scaleFactor();
expect(manager.getRadius()).toBeLessThan(oldR); simulateZoomOut();
expect(manager.getNodeLimit()).not.toBeLessThan(oldNL); expect(manager.getNodeLimit()).not.toBeLessThan(oldNL);
expect(manager.getDistortion()).toBeGreaterThan(oldD); expect(manager.getDistortion()).toBeGreaterThan(oldD);
expect(manager.getDistortionRadius()).toBeGreaterThan(oldDR);
expect(manager.scaleFactor()).not.toBeLessThan(oldSF);
loopCounter++; loopCounter++;
if (loopCounter === 1000) { if (loopCounter === 1000) {
this.fail(new Error('The minimal font-size should have been reached')); this.fail(new Error('The minimal font-size should have been reached'));
@ -215,7 +234,7 @@
} }
} }
if (manager.getFontSize() === null) { if (manager.getFontSize() === null) {
manager.zoomIn(); simulateZoomIn();
} }
expect(manager.getFontSize()).toBeCloseTo(fontMin, 6); expect(manager.getFontSize()).toBeCloseTo(fontMin, 6);
expect(manager.getRadius()).toBeCloseTo((radMax-radMin) / 2 + radMin, 6); expect(manager.getRadius()).toBeCloseTo((radMax-radMin) / 2 + radMin, 6);
@ -228,7 +247,7 @@
beforeEach(function() { beforeEach(function() {
var loopCounter = 0; var loopCounter = 0;
while (manager.getFontSize() > fontMin && manager.getFontSize() !== null) { while (manager.getFontSize() > fontMin && manager.getFontSize() !== null) {
manager.zoomOut(); simulateZoomOut();
loopCounter++; loopCounter++;
if (loopCounter === 1000) { if (loopCounter === 1000) {
this.fail(new Error('The minimal font-size should have been reached')); this.fail(new Error('The minimal font-size should have been reached'));
@ -236,7 +255,7 @@
} }
} }
if (manager.getFontSize() === null) { if (manager.getFontSize() === null) {
manager.zoomIn(); simulateZoomIn();
} }
}); });
@ -251,7 +270,7 @@
oldR = manager.getRadius(); oldR = manager.getRadius();
oldNL = manager.getNodeLimit(); oldNL = manager.getNodeLimit();
oldD = manager.getDistortion(); oldD = manager.getDistortion();
manager.zoomIn(); simulateZoomIn();
expect(manager.getFontSize()).toBeGreaterThan(oldFS); expect(manager.getFontSize()).toBeGreaterThan(oldFS);
expect(manager.getRadius()).toBeGreaterThan(oldR); expect(manager.getRadius()).toBeGreaterThan(oldR);
expect(manager.getNodeLimit()).not.toBeGreaterThan(oldNL); expect(manager.getNodeLimit()).not.toBeGreaterThan(oldNL);
@ -269,12 +288,12 @@
}); });
it('should return null for font-size if further zoomed out', function() { it('should return null for font-size if further zoomed out', function() {
manager.zoomOut(); simulateZoomOut();
expect(manager.getFontSize()).toEqual(null); expect(manager.getFontSize()).toEqual(null);
}); });
it('should significantly increase the node limit if further zoomed out', function() { it('should significantly increase the node limit if further zoomed out', function() {
manager.zoomOut(); simulateZoomOut();
expect(manager.getNodeLimit()).toBeGreaterThan(nodeMaxNoLabel); expect(manager.getNodeLimit()).toBeGreaterThan(nodeMaxNoLabel);
}); });
@ -287,7 +306,7 @@
oldR = manager.getRadius(); oldR = manager.getRadius();
oldNL = manager.getNodeLimit(); oldNL = manager.getNodeLimit();
oldD = manager.getDistortion(); oldD = manager.getDistortion();
manager.zoomOut(); simulateZoomOut();
expect(manager.getFontSize()).toEqual(null); expect(manager.getFontSize()).toEqual(null);
expect(manager.getRadius()).toBeLessThan(oldR); expect(manager.getRadius()).toBeLessThan(oldR);
expect(manager.getNodeLimit()).not.toBeLessThan(oldNL); expect(manager.getNodeLimit()).not.toBeLessThan(oldNL);
@ -310,7 +329,7 @@
beforeEach(function() { beforeEach(function() {
var loopCounter = 0; var loopCounter = 0;
while (manager.getRadius() > radMin) { while (manager.getRadius() > radMin) {
manager.zoomOut(); simulateZoomOut();
loopCounter++; loopCounter++;
if (loopCounter === 2000) { if (loopCounter === 2000) {
this.fail(new Error('The minimal zoom level should have been reached')); this.fail(new Error('The minimal zoom level should have been reached'));
@ -323,7 +342,7 @@
var oldR = manager.getRadius(), var oldR = manager.getRadius(),
oldNL = manager.getNodeLimit(), oldNL = manager.getNodeLimit(),
oldD = manager.getDistortion(); oldD = manager.getDistortion();
manager.zoomOut(); simulateZoomOut();
expect(manager.getRadius()).toEqual(oldR); expect(manager.getRadius()).toEqual(oldR);
expect(manager.getNodeLimit()).toEqual(oldNL); expect(manager.getNodeLimit()).toEqual(oldNL);
expect(manager.getDistortion()).toEqual(oldD); expect(manager.getDistortion()).toEqual(oldD);
@ -338,7 +357,7 @@
oldR = manager.getRadius(); oldR = manager.getRadius();
oldNL = manager.getNodeLimit(); oldNL = manager.getNodeLimit();
oldD = manager.getDistortion(); oldD = manager.getDistortion();
manager.zoomIn(); simulateZoomIn();
expect(manager.getRadius()).toBeGreaterThan(oldR); expect(manager.getRadius()).toBeGreaterThan(oldR);
expect(manager.getNodeLimit()).not.toBeGreaterThan(oldNL); expect(manager.getNodeLimit()).not.toBeGreaterThan(oldNL);
expect(manager.getDistortion()).toBeLessThan(oldD); expect(manager.getDistortion()).toBeLessThan(oldD);
@ -362,4 +381,8 @@
}); });
});
}()); }());