Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

An algorithm to space out overlapping rectangles?

This problem actually deals with roll-overs, I'll just generalized below as such:

I have a 2D view, and I have a number of rectangles within an area on the screen. How do I spread out those boxes such that they don't overlap each other, but only adjust them with minimal moving?

The rectangles' positions are dynamic and dependent on user's input, so their positions could be anywhere.

Attachedalt text images show the problem and desired solution

The real life problem deals with rollovers, actually.

Answers to the questions in the comments

  1. Size of rectangles is not fixed, and is dependent on the length of the text in the rollover

  2. About screen size, right now I think it's better to assume that the size of the screen is enough for the rectangles. If there is too many rectangles and the algo produces no solution, then I just have to tweak the content.

  3. The requirement to 'move minimally' is more for asethetics than an absolute engineering requirement. One could space out two rectangles by adding a vast distance between them, but it won't look good as part of the GUI. The idea is to get the rollover/rectangle as close as to its source (which I will then connect to the source with a black line). So either 'moving just one for x' or 'moving both for half x' is fine.

like image 475
Extrakun Avatar asked Jul 16 '10 14:07

Extrakun


People also ask

How do you know if two rectangles are overlapping?

Following is a simpler approach. Two rectangles do not overlap if one of the following conditions is true. 1) One rectangle is above top edge of other rectangle. 2) One rectangle is on left side of left edge of other rectangle.


Video Answer


1 Answers

I was working a bit in this, as I also needed something similar, but I had delayed the algorithm development. You helped me to get some impulse :D

I also needed the source code, so here it is. I worked it out in Mathematica, but as I haven't used heavily the functional features, I guess it'll be easy to translate to any procedural language.

A historic perspective

First I decided to develop the algorithm for circles, because the intersection is easier to calculate. It just depends on the centers and radii.

I was able to use the Mathematica equation solver, and it performed nicely.

Just look:

alt text

It was easy. I just loaded the solver with the following problem:

For each circle  Solve[   Find new coördinates for the circle   Minimizing the distance to the geometric center of the image   Taking in account that       Distance between centers > R1+R2 *for all other circles       Move the circle in a line between its center and the                                           geometric center of the drawing    ] 

As straightforward as that, and Mathematica did all the work.

I said "Ha! it's easy, now let's go for the rectangles!". But I was wrong ...

Rectangular Blues

The main problem with the rectangles is that querying the intersection is a nasty function. Something like:

So, when I tried to feed up Mathematica with a lot of these conditions for the equation, it performed so badly that I decided to do something procedural.

My algorithm ended up as follows:

Expand each rectangle size by a few points to get gaps in final configuration While There are intersections     sort list of rectangles by number of intersections     push most intersected rectangle on stack, and remove it from list // Now all remaining rectangles doesn't intersect each other While stack not empty     pop  rectangle from stack and re-insert it into list     find the geometric center G of the chart (each time!)     find the movement vector M (from G to rectangle center)     move the rectangle incrementally in the direction of M (both sides)                                                   until no intersections   Shrink the rectangles to its original size 

You may note that the "smallest movement" condition is not completely satisfied (only in one direction). But I found that moving the rectangles in any direction to satisfy it, sometimes ends up with a confusing map changing for the user.

As I am designing a user interface, I choose to move the rectangle a little further, but in a more predictable way. You can change the algorithm to inspect all angles and all radii surrounding its current position until an empty place is found, although it'll be much more demanding.

Anyway, these are examples of the results (before/ after):

alt text

Edit> More examples here

As you may see, the "minimum movement" is not satisfied, but the results are good enough.

I'll post the code here because I'm having some trouble with my SVN repository. I'll remove it when the problems are solved.

Edit:

You may also use R-Trees for finding rectangle intersections, but it seems an overkill for dealing with a small number of rectangles. And I haven't the algorithms already implemented. Perhaps someone else can point you to an existing implementation on your platform of choice.

Warning! Code is a first approach .. not great quality yet, and surely has some bugs.

It's Mathematica.

(*Define some functions first*)  Clear["Global`*"]; rn[x_] := RandomReal[{0, x}]; rnR[x_] := RandomReal[{1, x}]; rndCol[] := RGBColor[rn[1], rn[1], rn[1]];  minX[l_, i_] := l[[i]][[1]][[1]]; (*just for easy reading*) maxX[l_, i_] := l[[i]][[1]][[2]]; minY[l_, i_] := l[[i]][[2]][[1]]; maxY[l_, i_] := l[[i]][[2]][[2]]; color[l_, i_]:= l[[i]][[3]];  intersectsQ[l_, i_, j_] := (* l list, (i,j) indexes,                                list={{x1,x2},{y1,y2}} *)                             (*A rect does intesect with itself*)           If[Max[minX[l, i], minX[l, j]] < Min[maxX[l, i], maxX[l, j]] &&              Max[minY[l, i], minY[l, j]] < Min[maxY[l, i], maxY[l, j]],                                                             True,False];  (* Number of Intersects for a Rectangle *) (* With i as index*) countIntersects[l_, i_] :=            Count[Table[intersectsQ[l, i, j], {j, 1, Length[l]}], True]-1;  (*And With r as rectangle *) countIntersectsR[l_, r_] := (     Return[Count[Table[intersectsQ[Append[l, r], Length[l] + 1, j],                         {j, 1, Length[l] + 1}], True] - 2];)  (* Get the maximum intersections for all rectangles*) findMaxIntesections[l_] := Max[Table[countIntersects[l, i],                                         {i, 1, Length[l]}]];  (* Get the rectangle center *) rectCenter[l_, i_] := {1/2 (maxX[l, i] + minX[l, i] ),                         1/2 (maxY[l, i] + minY[l, i] )};  (* Get the Geom center of the whole figure (list), to move aesthetically*) geometryCenter[l_] :=  (* returs {x,y} *)                       Mean[Table[rectCenter[l, i], {i, Length[l]}]];   (* Increment or decr. size of all rects by a bit (put/remove borders)*) changeSize[l_, incr_] :=                  Table[{{minX[l, i] - incr, maxX[l, i] + incr},                         {minY[l, i] - incr, maxY[l, i] + incr},                         color[l, i]},                         {i, Length[l]}];  sortListByIntersections[l_] := (* Order list by most intersecting Rects*)         Module[{a, b},                 a = MapIndexed[{countIntersectsR[l, #1], #2} &, l];                b = SortBy[a, -#[[1]] &];                Return[Table[l[[b[[i]][[2]][[1]]]], {i, Length[b]}]];         ];  (* Utility Functions*) deb[x_] := (Print["--------"]; Print[x]; Print["---------"];)(* for debug *) tableForPlot[l_] := (*for plotting*)                 Table[{color[l, i], Rectangle[{minX[l, i], minY[l, i]},                 {maxX[l, i], maxY[l, i]}]}, {i, Length[l]}];  genList[nonOverlap_, Overlap_] :=    (* Generate initial lists of rects*)       Module[{alist, blist, a, b},            (alist = (* Generate non overlapping - Tabuloid *)                 Table[{{Mod[i, 3], Mod[i, 3] + .8},                         {Mod[i, 4], Mod[i, 4] + .8},                          rndCol[]}, {i, nonOverlap}];            blist = (* Random overlapping *)                 Table[{{a = rnR[3], a + rnR[2]}, {b = rnR[3], b + rnR[2]},                        rndCol[]}, {Overlap}];            Return[Join[alist, blist] (* Join both *)];)       ]; 

Main

clist = genList[6, 4]; (* Generate a mix fixed & random set *)  incr = 0.05; (* may be some heuristics needed to determine best increment*)  clist = changeSize[clist,incr]; (* expand rects so that borders does not                                                           touch each other*)  (* Now remove all intercepting rectangles until no more intersections *)  workList = {}; (* the stack*)  While[findMaxIntesections[clist] > 0,                                                 (*Iterate until no intersections *)     clist    = sortListByIntersections[clist];                                        (*Put the most intersected first*)     PrependTo[workList, First[clist]];                                                (* Push workList with intersected *)     clist    = Delete[clist, 1];      (* and Drop it from clist *) ];  (* There are no intersections now, lets pop the stack*)  While [workList != {},      PrependTo[clist, First[workList]];                                         (*Push first element in front of clist*)     workList = Delete[workList, 1];                                            (* and Drop it from worklist *)      toMoveIndex = 1;                                                          (*Will move the most intersected Rect*)     g = geometryCenter[clist];                                                 (*so the geom. perception is preserved*)     vectorToMove = rectCenter[clist, toMoveIndex] - g;     If [Norm[vectorToMove] < 0.01, vectorToMove = {1,1}]; (*just in case*)       vectorToMove = vectorToMove/Norm[vectorToMove];                                                   (*to manage step size wisely*)      (*Now iterate finding minimum move first one way, then the other*)      i = 1; (*movement quantity*)      While[countIntersects[clist, toMoveIndex] != 0,                                             (*If the Rect still intersects*)                                            (*move it alternating ways (-1)^n *)        clist[[toMoveIndex]][[1]] += (-1)^i i incr vectorToMove[[1]];(*X coords*)       clist[[toMoveIndex]][[2]] += (-1)^i i incr vectorToMove[[2]];(*Y coords*)              i++;     ]; ]; clist = changeSize[clist, -incr](* restore original sizes*); 

HTH!

Edit: Multi-angle searching

I implemented a change in the algorithm allowing to search in all directions, but giving preference to the axis imposed by the geometric symmetry.
At the expense of more cycles, this resulted in more compact final configurations, as you can see here below:

enter image description here

More samples here.

The pseudocode for the main loop changed to:

Expand each rectangle size by a few points to get gaps in final configuration While There are intersections     sort list of rectangles by number of intersections     push most intersected rectangle on stack, and remove it from list // Now all remaining rectangles doesn't intersect each other While stack not empty     find the geometric center G of the chart (each time!)     find the PREFERRED movement vector M (from G to rectangle center)     pop  rectangle from stack      With the rectangle          While there are intersections (list+rectangle)               For increasing movement modulus                  For increasing angle (0, Pi/4)                     rotate vector M expanding the angle alongside M                     (* angle, -angle, Pi + angle, Pi-angle*)                     re-position the rectangle accorging to M     Re-insert modified vector into list Shrink the rectangles to its original size 

I'm not including the source code for brevity, but just ask for it if you think you can use it. I think that, should you go this way, it's better to switch to R-trees (a lot of interval tests needed here)

like image 145
17 revs, 2 users 100% Avatar answered Sep 23 '22 11:09

17 revs, 2 users 100%