this.createOutline = function()

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();
  };