I have a function that moves circles out of the way of an expanding circle. It moves them along a ray from the center of the expanding circle through the center of the circle to move. This allows all surrounding circles to move without overlapping.

Here's a diagram to better explain this:


I expand the size of the red circle from 100px to 200px. I want to move all the blue circles out of the way by half the difference ( (200-100)/2 = 50px, in this example ) along the gray line (with the gray line being different for each blue circle).


$this.siblings( ".circle" ).each( function() {

    var $this = $( this ),
        circle = $this.data(),
        circleX = circle.left + circle.radius,
        circleY = circle.top + circle.radius,
        a = Math.abs( hoveredY - circleY ),
        b = Math.abs( hoveredX - circleX ),
        c = Math.sqrt( ( a*a ) + ( b*b ) ),
        A = Math.acos( b / c ), 
        C = 90 * ( Math.PI / 180 ),
        B = C - A,
        sinA = Math.sin( A ),
        sinB = Math.sin( B ),
        sinC = Math.sin( C ),
        newc = c + ( expand / 2 ),
        newa = ( newc * sinA ) / sinC,
        newb = ( newc * sinB ) / sinC,
        newX = hoveredX + ( hoveredX > circleX ? -newb : newb ),
        newY = hoveredY + ( hoveredY > circleY ? -newa : newa ),
        left = newX - circle.radius,
        top = newY - circle.radius;

    $this.animate( { 

        "left": left,
        "top": top

    }, 75 );


hoveredX and hoveredY are the left and top coordinates of the red circle and expand is how much the red circle is expanding in pixels.

Mathematically, I'm sure there is a better way to do this, but I got this way working, and it's fine (although I'm not opposed hearing better solutions). But the one part I don't like about this function are the two lines:

newX = hoveredX + ( hoveredX > circleX ? -newb : newb ),
newY = hoveredY + ( hoveredY > circleY ? -newa : newa ),

What bugs me is that I'm having to do the check at all. Those two lines are basically saying, "If the circle is on the left/top do it one way, and if it's on the right/bottom do it another way". I feel like I'm missing something earlier in the function that could be solved by switching the sides of operators, adding instead of subtracting, or getting the absolute value.

  • \$\begingroup\$ a = Math.abs( is why you have to check. Are you sure you need absolute values? \$\endgroup\$ – James Khoury Dec 14 '11 at 0:56

I had two thoughts. One was ... You really only need an angle and a distance. The distance is the amount you expand and the angle can be derived from the x/y coordinates.


first you calculate the angle:

var angle = Math.atan2(hoveredY - circleY, hoveredX - circleX);

Then you calculate the distance they move:

var topMove = ((expand /2 ) * Math.sin(angle)); // sin for Y
var leftMove = ((expand /2 ) * Math.cos(angle)); // cos for X

then use jQuery's built in animate +=:

$this.animate( { 
    "left": "-=" + leftMove + "px",
    "top":  "-=" + topMove + "px"
}, 75 );


Edit:- A couple of improvements in the code not supplied in the question:

In function inCircle() change the return statement to return (mouseDistance <= radius); Using the ternary operator to return a boolean? ... I'm sure it used to return some other values right?

In $( ".circle" ).click( :

if(!$( this ).data( "clicked" ) && inCircle( $( this ), event.pageX, event.pageY ) ) {

    $( this ).data( "clicked", true );
    setLocations( this, 200, event );

} else {

    $( this ).data( "clicked", false );


Makes it cleaner but not a real improvement otherwise, more of a preference.

In function setLocations() your circle parameter conflicts with the local circle variable. I would change the parameter to circleElement.

|improve this answer|||||
  • \$\begingroup\$ WOW. I'm not worthy. 20 lines to 3. Up-vote this, people. My demo page gets a lot of hits. I put a link to your SO profile in there with credit. \$\endgroup\$ – ThinkingStiff Dec 14 '11 at 4:12
  • \$\begingroup\$ @ThinkingStiff thanks. Don't worry we all do this. It took me a couple of goes till i really sat back and thought about it. It happens that I was recently working on some angle/circle/distance stuff myself. \$\endgroup\$ – James Khoury Dec 14 '11 at 4:37
  • \$\begingroup\$ @ThinkingStiff I just added some other suggestions for the rest of the code which don't really change a great deal. \$\endgroup\$ – James Khoury Dec 14 '11 at 5:07
  • \$\begingroup\$ Both good changes, updated. Thanks. Who knows how I ended up at a ternary boolean. :) The circle in setLocations() was a last minute change I made to get ready for this post. It was called hovered or something nonintuitive before. Good catch. \$\endgroup\$ – ThinkingStiff Dec 14 '11 at 21:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.