J'ai travaillé un peu là-dessus, car j'avais aussi besoin de quelque chose de similaire, mais j'avais retardé le développement de l'algorithme. Vous m'avez aidé à prendre de l'élan :D
J'avais également besoin du code source, le voici donc. J'ai travaillé en Mathematica, mais comme je n'ai pas beaucoup utilisé les fonctionnalités, je pense qu'il sera facile de le traduire dans n'importe quel langage procédural.
Une perspective historique
J'ai d'abord décidé de développer l'algorithme pour les cercles, car l'intersection est plus facile à calculer. Cela dépend juste des centres et des rayons.
J'ai pu utiliser le solveur d'équations Mathematica, qui s'est bien comporté.
Regarde juste :
C'était facile. J'ai juste chargé le solveur avec le problème suivant :
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
]
C'est aussi simple que ça, et Mathematica a fait tout le travail.
J'ai dit "Ha ! c'est facile, maintenant allons-y pour les rectangles !". Mais j'avais tort...
Rectangulaire Blues
Le principal problème avec les rectangles est que l'interrogation de l'intersection est une fonction désagréable. Quelque chose comme :
Ainsi, lorsque j'ai essayé d'alimenter Mathematica avec un grand nombre de ces conditions pour l'équation, il s'est si mal comporté que j'ai décidé de faire quelque chose de procédural.
Mon algorithme a abouti à ce qui suit :
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
Vous pouvez noter que la condition du "plus petit mouvement" n'est pas complètement satisfaite (seulement dans une direction). Mais j'ai trouvé qu'en déplaçant les rectangles dans n'importe quelle direction pour la satisfaire, on aboutit parfois à un changement de carte déroutant pour l'utilisateur.
Comme je conçois une interface utilisateur, je choisis de déplacer le rectangle un peu plus loin, mais de manière plus prévisible. Vous pouvez modifier l'algorithme pour qu'il inspecte tous les angles et tous les rayons entourant sa position actuelle jusqu'à ce qu'il trouve une place vide, mais cela sera beaucoup plus exigeant.
Quoi qu'il en soit, voici des exemples de résultats (avant/après) :
Edit> Plus d'exemples aquí
Comme vous pouvez le constater, le "mouvement minimum" n'est pas satisfait, mais les résultats sont suffisamment bons.
Je vais poster le code ici car j'ai quelques problèmes avec mon dépôt SVN. Je le supprimerai lorsque les problèmes seront résolus.
Edit :
Vous pouvez également utiliser Arbres R pour trouver les intersections de rectangles, mais cela semble excessif pour traiter un petit nombre de rectangles. Et je n'ai pas les algorithmes déjà implémentés. Peut-être que quelqu'un d'autre peut vous indiquer une implémentation existante sur la plate-forme de votre choix.
Attention ! Le code est une première approche pas encore de grande qualité, et a sûrement quelques bugs.
C'est 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 *)];)
];
Principal
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 : Recherche multi-angle
J'ai implémenté un changement dans l'algorithme permettant de chercher dans toutes les directions, mais en donnant la préférence à l'axe imposé par la symétrie géométrique.
Au prix d'un plus grand nombre de cycles, cela a permis d'obtenir des configurations finales plus compactes, comme vous pouvez le voir ci-dessous :
Plus d'échantillons aquí .
Le pseudo-code de la boucle principale a été modifié comme suit :
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
Je n'inclus pas le code source pour des raisons de brièveté, mais demandez-le si vous pensez pouvoir l'utiliser. Je pense que, si vous suivez cette voie, il est préférable de passer aux arbres R (beaucoup de tests d'intervalle sont nécessaires ici).
2 votes
Pouvons-nous supposer que les rectangles sont toujours orientés horizontalement ou verticalement, et non pas inclinés sur leur axe en formant un angle ?
2 votes
Oui, l'hypothèse est valable.
0 votes
Peut-on supposer que l'écran est toujours assez grand pour supporter les rectangles sans chevauchement ? Les rectangles ont-ils toujours la même taille ? Pouvez-vous être plus précis sur ce que signifie "déplacement minimal" ? Par exemple, si deux rectangles sont exactement superposés, est-il préférable d'en déplacer un seul de toute la distance pour supprimer le chevauchement, ou de déplacer les deux de la moitié de la distance ?
0 votes
@NickLarsen, j'ai répondu à vos questions dans la réponse éditée ci-dessus. Merci !
1 votes
@joe : peut-être qu'il aimerait comprendre la solution, pour pouvoir la soutenir.
0 votes
@joe : Eek ! Je t'ai complètement mal lu... j'ai manqué le "juste". C'est ma faute. Je suis définitivement d'accord avec vous (voir mon commentaire sur la réponse de belisarius).