一尘不染

一种将重叠的矩形隔开的算法?

algorithm

这个问题实际上是与过渡有关的,下面我将其概括如下:

我有一个2D视图,并且在屏幕上的某个区域内有许多矩形。如何分散这些框,使它们彼此不重叠,而仅以最小的移动来调整它们?

矩形的位置是动态的,并且取决于用户的输入,因此它们的位置可以在任何地方。

所附替代文字图片显示了问题和所需的解决方案

实际上,现实生活中的问题涉及过渡。

对评论中问题的答案

  1. 矩形的大小不是固定的,并且取决于翻转中文本的长度

  2. 关于屏幕大小,现在我认为最好假定屏幕大小足以容纳矩形。如果矩形太多,并且算法没有产生任何解决方案,那么我只需要调整内容即可。

  3. 对于美学,“最低限度地移动”的要求比绝对的工程要求更多。可以通过增加两个矩形之间的距离来隔开两个矩形,但是作为GUI的一部分看起来效果不佳。这个想法是使翻转/矩形尽可能接近其来源(然后我将用黑线将其连接到来源)。因此,“只为x移动一个”或“两个都移动一半为x”都可以。


阅读 270

收藏
2020-07-28

共1个答案

一尘不染

我为此做了一些工作,因为我也需要类似的东西,但是我推迟了算法的开发。你帮我冲动了:D

我还需要源代码,就在这里。我在Mathematica中进行了研究,但是由于我并未大量使用功能性功能,因此我认为将其轻松转换为任何程序语言都非常容易。

历史的观点

首先,我决定开发圆形算法,因为相交点更容易计算。它仅取决于中心和半径。

我能够使用Mathematica方程求解器,并且表现出色。

只是看看:

替代文字

很容易。我刚刚向求解器加载了以下问题:

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
   ]

如此简单,Mathematica完成了所有工作。

我说:“哈!这很容易,现在让我们去看看矩形!”。但是我错了 …

矩形蓝调

矩形的主要问题是查询交点是一个讨厌的函数。就像是:

因此,当我试图为Mathematica提供许多这些方程式条件时,它的表现非常糟糕,以至于我决定做一些程序上的事情。

我的算法最终如下:

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

您可能会注意到,“最小移动”条件并未完全得到满足(仅在一个方向上)。但是我发现,向任何方向移动矩形都可以满足要求,有时最终会给用户带来混乱的地图更改。

在设计用户界面时,我选择将矩形移得更远,但方式更可预测。您可以更改算法以检查围绕其当前位置的所有角度和所有半径,直到找到一个空的地方,尽管这将要求更高。

无论如何,这些都是结果的示例(之前/之后):

替代文字

编辑>更多示例在这里

如您所见,“最小移动”并不令人满意,但是结果足够好。

我将代码发布在这里,因为我的SVN存储库遇到了一些麻烦。问题解决后,我将其删除。

编辑:

您也可以使用R-Trees查找矩形相交,但是处理少量矩形似乎是过大的选择。而且我还没有实现算法。也许其他人可以将您指向您选择的平台上的现有实现。

警告!代码是第一种方法..质量还不是很高,并且肯定有一些错误。

是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 *)];)
      ];

主要

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!

编辑:多角度搜索

我对算法进行了更改,允许在所有方向上搜索,但优先考虑由几何对称性施加的轴。
如要在下面看到的那样,以花费更多的周期为代价,这导致更紧凑的最终配置。

在此处输入图片说明

这里有更多样本。

主循环的伪代码更改为:

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

为了简洁起见,我没有提供源代码,只是问您是否认为可以使用它。我认为,如果您采用这种方式,最好切换到R树(此处需要进行很多间隔测试)

2020-07-28