views:

218

answers:

2

I am trying to layout a bunch of overlapping rectangles that start out like this:

alt text

The 2-pass algorithm I thought up is roughly:

// Pass 1 - Move all rectangles to the right until they do not overlap any other rectangles
rects = getRectsSortedOnTopLeft(); // topmost first, all rects same size
foreach(rect in rects)
{
   while(rect.collidingRects().size() != 0)
   {
       rect.x += RECT_SIZE;
   }
}

This (probably) ends up with rectangles laid out like: alt text

This is not aesthetically pleasing so I thought of a second pass which would move them all left starting again from the topmost:

// Pass 2
foreach(rect in rects)
{
    while(rect.x >= LEFT_MARGIN)
    {
        assert(rect.collidingRects().size() == 0);
        rect.x -= RECT_WIDTH;
        if(rect.collidingRects().size() != 0)
        {
           rect.x += RECT_WIDTH;
           break;
        }
    }
}

I think this should end up looking like below (looks exactly correct in practice):

alt text

However, I am wary of this algorithm because I am not sure if it will lay out correctly in all cases and it may be really slow. Do you think this algorithm can work? Can you make some suggestions on a better algorithm?

+2  A: 

I think that this problem is of polynomial complexity. Assuming your example's limitation of only two rectangles overlapping at any given point is not a true limitation of the problem, you would need to try every possible order of bumping the rectangles to the right in order to produce the optimal (least wide) result. This is a form of space packing problem, and those are Hard unless your data set is small enough to brute force.

However, one small improvement to your pseudocode is possible, which would improve its performance in many cases.

Consider this desired final result:

A
A C
A C E
A C E
B C E
B D E
B D F
B D F
  D F
    F

(where all four of one character are a single rectangle)

Your first pass would move everything except A to the right, forming a staircase. Then in the second pass your code would decline to move B to the left margin, because the first attempt to move it would overlap with E. What you need to do is start at the left margin and check for the leftmost position you can move each rectangle to in pass 2.

Pseudocode:

// Pass 1 - Move all rectangles to the right until they do not overlap any other rectangles
rects = getRectsSortedOnTopLeft(); // topmost first, all rects same width
foreach(rect in rects)
    while(rect.collidingRects())
        rect.x += RECT_WIDTH;
// Pass 2 - Move all rectangles to the leftmost position in which they don't overlap any other rectangles
foreach(rect in rects)
    for(i=LEFT_MARGIN; i+=RECT_WIDTH; i<rect.x)
    {
        o = rect.x;
        rect.x = i;
        if(rect.collidingRects())
            rect.x = o;
    }
Sparr
Hmm, I think this actually fixes a bug I found while testing the original code... It still has some weird behaviour that I'd like to fix. I'm going to U/L a screencast of the runtime behaviour I'd like to fix.
cheez
Here is the screencast. I suppose that the algorithm is doing the right thing as far as the algorithm itself goes but what I'd like to do is be able to maintain the x position for all the rects as much as is possible:http://screencast.com/t/MzYxYmI1YjgAny ideas?
cheez
Ok, to avoid the selected rect from being repositioned on the x axis, I just ignore it for this algorithm. Seems to work. I'm still open to ideas of course.
cheez
Marked your answer as correct until I get a better description of exactly the behaviour I want :)
cheez
+1  A: 

You could use a physics-based approach, where the blocks are rigid bodies an fall to the left:

No, this wouldn't produce the best result all the time, but having watched your screencast I think it would be very intuitive to use in an interactive program, and it might be suitable :)

Autopulated
This is a really good idea. I'm going to prototype this and thanks for taking the time to view the screencast.
cheez