Packing different sized circles into rectangle - d3.js

后端 未结 5 452
北荒
北荒 2020-11-27 12:59

I was trying to pack circles of different sizes into a rectangular container, not packing in circular container that d3.js bundled with, under

5条回答
  •  一整个雨季
    2020-11-27 13:42

    Well, this is far from optimal packing, but it's something that others can try to beat.

    Updated, but still not great

    https://jsfiddle.net/LF9Yp/6/

    Key code, such as it is:

    var points = [[]]; //positioned circles, by row
    function assignNextPosition(d,index) {
        console.log("fitting circle ", index, d.size);
        var i, j, n;
        var radiusPlus = rScale(d.size) + padding;
        if (!points[0].length) { //this is first object
           d.x = d.y = radiusPlus; 
           points[0].push(d);
           points[0].width = points[0].height = 2*radiusPlus;
           points[0].base = 0;
           return;
        }
        i = 0; n = points.length - 1; 
        var tooTight, lastRow, left, rp2, hyp;
        while ((tooTight = (width - points[i].width < 2*radiusPlus)
                ||( points[i+1]? 
                    points[i+1].base - points[i].base < 2*radiusPlus 
                    : false) ) 
              &&(i < n) ) i++;
               //skim through rows to see if any can fit this circle
    
        if (!tooTight) { console.log("fit on row ", i);
            //one of the rows had room
            lastRow = points[i];
            j=lastRow.length;
    
            if (i == 0) {
              //top row, position tight to last circle and wall
                d.y = radiusPlus;
                rp2 = (rScale(lastRow[j-1].size) + padding);
                d.x = lastRow[j-1].x + Math.sqrt(
                    Math.pow( (radiusPlus + rp2), 2)
                    - Math.pow( (radiusPlus - rp2),2) );
            }
            else {
               //position tight to three closest circles/wall
               //(left, top left and top right)
                //or (left, top left and right wall)
               var left = lastRow[j-1];
               d.x = left.x + rScale(left.size) + padding + radiusPlus;
               var prevRow = points[i - 1];       
               j = prevRow.length;
               while ((j--) && (prevRow[j].x > d.x));
               j = Math.max(j,0);
               if (j + 1 < prevRow.length) {
                   console.log("fit between", prevRow[j], prevRow[j+1]);
                   d.y = prevRow[j].y 
                   + (Math.sqrt(Math.pow((radiusPlus + 
                               rScale(prevRow[j].size) +padding), 2) 
                               - Math.pow( (d.x - prevRow[j].x),2)
                           )||0);
                  j++;
                  d.y = Math.max(d.y, prevRow[j].y 
                   + (Math.sqrt(Math.pow((radiusPlus + 
                               rScale(prevRow[j].size) +padding), 2) 
                               - Math.pow( (d.x - prevRow[j].x),2)
                           )||0)  );
               }
               else { //tuck tight against wall
                   console.log("fit between", prevRow[j], "wall");
                d.x = width - radiusPlus;
                rp2 = (rScale(prevRow[j].size) + padding);
                d.y = prevRow[j].y + (Math.sqrt(
                    Math.pow( (radiusPlus + rp2), 2)
                    - Math.pow( (d.x - prevRow[j].x),2) )||0);
                if (i > 1)
                    d.y = Math.max(d.y, points[i-2].height + radiusPlus);
               }
            }
    
            lastRow.push(d); 
            lastRow.width = d.x + radiusPlus;
            lastRow.height = Math.max(lastRow.height, 
                                      d.y + radiusPlus);
            lastRow.base = Math.min(lastRow.base,
                                    d.y - radiusPlus);
    
        } else { console.log("new row ", points.length)
            prevRow = points[points.length -1];
            j=prevRow.length;
            while(j--) {
                var testY = prevRow[j].y + rScale(prevRow[j].size) + padding
                      + radiusPlus;
                if (testY + radiusPlus < prevRow.height) {
                    //tuck row in gap
                    d.x = prevRow[j].x;
                    d.y = testY;
                }
            }
            if (!d.x) {//start row at left
              d.x = radiusPlus;
              d.y = prevRow.height + radiusPlus;
            }
            var newRow = [d];
            newRow.width = d.x + radiusPlus;
            newRow.height = Math.max(d.y + radiusPlus, prevRow.height);
            newRow.base = d.y - radiusPlus;
            points.push(newRow); 
        } 
                if (!d.y) console.log("error",d);
        if (d.y + radiusPlus > height) {
          //change rScale by the ratio this exceeds the height
          var scaleFactor = height/(d.y + radiusPlus);
          rScale.range([0, rScale.range()[1]*scaleFactor]);
    
          //recalculate all positions
          points.forEach(function(row, j){
                row.forEach(function(d, i) {
                   d.x = (d.x - i*2*padding)*scaleFactor + i*2*padding;
                   d.y = (d.y - i*2*padding)*scaleFactor + i*2*padding;
                });
                row.width *= scaleFactor;
          });
    
        }
    
    }
    

提交回复
热议问题