in public/bubblesets.js [651:764]
this.createOutline = function(members, nonmem, edges) {
if(!members.length) return [];
var memberItems = members.map(function(m) {
return new Rectangle(m);
});
var nonMembers = nonmem.map(function(m) {
return new Rectangle(m);
});
// calculate and store virtual edges
calculateVirtualEdges(memberItems, nonMembers);
edges && edges.forEach(function(e) {
virtualEdges.push(new Line(e.x1, e.y1, e.x2, e.y2));
});
activeRegion = null;
memberItems.forEach(function(m) {
if(!activeRegion) {
activeRegion = new Rectangle(m.rect());
} else {
activeRegion.add(m);
}
});
virtualEdges.forEach(function(l) {
activeRegion.add(l.rect());
});
activeRegion.rect({
x: activeRegion.minX() - Math.max(edgeR1, nodeR1) - morphBuffer,
y: activeRegion.minY() - Math.max(edgeR1, nodeR1) - morphBuffer,
width: activeRegion.width() + 2 * Math.max(edgeR1, nodeR1) + 2 * morphBuffer,
height: activeRegion.height() + 2 * Math.max(edgeR1, nodeR1) + 2 * morphBuffer,
});
potentialArea = new Area(Math.ceil(activeRegion.width() / pixelGroup), Math.ceil(activeRegion.height() / pixelGroup));
var estLength = (Math.floor(activeRegion.width()) + Math.floor(activeRegion.height())) * 2;
var surface = new PointList(estLength);
var tempThreshold = threshold;
var tempNegativeNodeInfluenceFactor = negativeNodeInfluenceFactor;
var tempNodeInfluenceFactor = nodeInfluenceFactor;
var tempEdgeInfluenceFactor = edgeInfluenceFactor;
var iterations = 0;
// add the aggregate and all it's members and virtual edges
fillPotentialArea(activeRegion, memberItems, nonMembers, potentialArea);
// try to march, check if surface contains all items
while((!calculateContour(surface, activeRegion, memberItems, nonMembers, potentialArea)) && (iterations < maxMarchingIterations)) {
surface.clear();
iterations += 1;
// reduce negative influences first; this will allow the surface to
// pass without making it fatter all around (which raising the threshold does)
if(iterations <= maxMarchingIterations * 0.5) {
if(negativeNodeInfluenceFactor != 0) {
threshold *= 0.95;
negativeNodeInfluenceFactor *= 0.8;
fillPotentialArea(activeRegion, memberItems, nonMembers, potentialArea);
}
}
// after half the iterations, start increasing positive energy and lowering the threshold
if(iterations > maxMarchingIterations * 0.5) {
threshold *= 0.95;
nodeInfluenceFactor *= 1.2;
edgeInfluenceFactor *= 1.2;
fillPotentialArea(activeRegion, memberItems, nonMembers, potentialArea);
}
}
lastThreshold = threshold;
threshold = tempThreshold;
negativeNodeInfluenceFactor = tempNegativeNodeInfluenceFactor;
nodeInfluenceFactor = tempNodeInfluenceFactor;
edgeInfluenceFactor = tempEdgeInfluenceFactor;
// start with global SKIP value, but decrease skip amount if there aren't enough points in the surface
var thisSkip = skip;
// prepare viz attribute array
var size = surface.size();
if(thisSkip > 1) {
size = Math.floor(surface.size() / thisSkip);
// if we reduced too much (fewer than three points in reduced surface) reduce skip and try again
while((size < 3) && (thisSkip > 1)) {
thisSkip -= 1;
size = Math.floor(surface.size() / thisSkip);
}
}
// add the offset of the active area to the coordinates
var xcorner = activeRegion.minX();
var ycorner = activeRegion.minY();
var fhull = new PointList(size);
// copy hull values
for(var i = 0, j = 0;j < size;j += 1,i += thisSkip) {
fhull.add(new Point(surface.get(i).x() + xcorner, surface.get(i).y() + ycorner));
}
if(!debug) {
// getting rid of unused memory preventing a memory leak
activeRegion = null;
potentialArea = null;
}
return fhull.list();
};