I was trying to pack circles of different sizes into a rectangular container, not packing in circular container that d3.js
bundled with, under
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;
});
}
}