我正在做这方面的一些工作,因为我也需要类似的东西,但是我已经推迟了算法的开发。你帮助我得到了一些动力:D
我也需要源代码,所以在这里它是。我用Mathematica解决了它,但是由于我没有大量使用函数特性,我想将其翻译成任何过程式语言都很容易。
历史视角
首先,我决定为圆形开发算法,因为交点更容易计算。它只取决于中心和半径。
我能够使用Mathematica方程求解器,并且它表现得很好。
看看这个:
![alt text](https://istack.dev59.com/SiElU.webp)
很容易。我只需将解算器加载以下问题:
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完成了所有工作。
我说:“哈!太容易了,现在让我们去做矩形!” 但我错了...
矩形之苦
矩形的主要问题是查询交集的函数非常复杂。类似于:
![](https://istack.dev59.com/g8R2k.webp)
因此,当我试图用大量这些方程条件来提高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
您可能会注意到,“最小移动”条件并没有完全得到满足(只在一个方向上)。但我发现,将矩形向任何方向移动以满足它,有时会导致用户混乱的地图变化。
由于我正在设计用户界面,我选择将矩形移动得更远,但更可预测。您可以更改算法以检查其当前位置周围的所有角度和所有半径,直到找到一个空位,尽管这会更加耗费时间。
无论如何,这些是结果示例(之前/之后):
![alt text](https://istack.dev59.com/tFpAc.webp)
编辑 > 更多示例 这里
正如您所见,“最小移动”没有得到满足,但结果已经足够好了。
我会把代码发布在这里,因为我在使用 SVN 存储库时遇到了一些问题。等问题解决后,我会将其删除。
编辑:
您也可以使用 R-Trees 来查找矩形交集,但对于处理少量矩形来说,这似乎有点过度。而且我还没有实现算法。也许其他人可以指导您在所选平台上使用现有的实现。
警告!代码是第一次尝试..质量不是很好,肯定有一些错误。
这是 Mathematica。
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]];
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_] :=
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];
countIntersects[l_, i_] :=
Count[Table[intersectsQ[l, i, j], {j, 1, Length[l]}], True]-1;
countIntersectsR[l_, r_] := (
Return[Count[Table[intersectsQ[Append[l, r], Length[l] + 1, j],
{j, 1, Length[l] + 1}], True] - 2];)
findMaxIntesections[l_] := Max[Table[countIntersects[l, i],
{i, 1, Length[l]}]];
rectCenter[l_, i_] := {1/2 (maxX[l, i] + minX[l, i] ),
1/2 (maxY[l, i] + minY[l, i] )};
geometryCenter[l_] :=
Mean[Table[rectCenter[l, i], {i, Length[l]}]];
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_] :=
Module[{a, b},
a = MapIndexed[{countIntersectsR[l, #1], #2} &, l];
b = SortBy[a, -#[[1]] &];
Return[Table[l[[b[[i]][[2]][[1]]]], {i, Length[b]}]];
];
deb[x_] := (Print["--------"]; Print[x]; Print["---------"];)
tableForPlot[l_] :=
Table[{color[l, i], Rectangle[{minX[l, i], minY[l, i]},
{maxX[l, i], maxY[l, i]}]}, {i, Length[l]}];
genList[nonOverlap_, Overlap_] :=
Module[{alist, blist, a, b},
(alist =
Table[{{Mod[i, 3], Mod[i, 3] + .8},
{Mod[i, 4], Mod[i, 4] + .8},
rndCol[]}, {i, nonOverlap}];
blist =
Table[{{a = rnR[3], a + rnR[2]}, {b = rnR[3], b + rnR[2]},
rndCol[]}, {Overlap}];
Return[Join[alist, blist] ];)
];
"主要"
clist = genList[6, 4];
incr = 0.05;
clist = changeSize[clist,incr];
workList = {};
While[findMaxIntesections[clist] > 0,
clist = sortListByIntersections[clist];
PrependTo[workList, First[clist]];
clist = Delete[clist, 1];
];
While [workList != {},
PrependTo[clist, First[workList]];
workList = Delete[workList, 1];
toMoveIndex = 1;
g = geometryCenter[clist];
vectorToMove = rectCenter[clist, toMoveIndex] - g;
If [Norm[vectorToMove] < 0.01, vectorToMove = {1,1}];
vectorToMove = vectorToMove/Norm[vectorToMove];
i = 1;
While[countIntersects[clist, toMoveIndex] != 0,
clist[[toMoveIndex]][[1]] += (-1)^i i incr vectorToMove[[1]];
clist[[toMoveIndex]][[2]] += (-1)^i i incr vectorToMove[[2]];
i++;
];
];
clist = changeSize[clist, -incr];
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](https://istack.dev59.com/r3vpU.webp)
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)