3
\$\begingroup\$

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:

circles

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).

jsFiddle

$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.

\$\endgroup\$
1
  • \$\begingroup\$ a = Math.abs( is why you have to check. Are you sure you need absolute values? \$\endgroup\$ Commented Dec 14, 2011 at 0:56

1 Answer 1

3
\$\begingroup\$

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.

http://jsfiddle.net/uLu7v/38/

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 );

Tada!

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 {

    resetLocations();
    $( 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.

\$\endgroup\$
4
  • \$\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\$ Commented Dec 14, 2011 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\$ Commented Dec 14, 2011 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\$ Commented Dec 14, 2011 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\$ Commented Dec 14, 2011 at 21:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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