views:

1677

answers:

4

Hi!

I am building a diagramming tool using Adobe Flex 3. I am about to implement connector lines and I have a question.

Imagine I have 2 squares at random positions on the canvas. I need to draw an arrowed connector line between them. I need it to tend to the target square's center but end on its border. alt text

How do I find out the exact points between which to draw the line?

Thank you

A: 

the most simple thing is probably using flash.geom.Point. take both centers c1 and c2. take the vector d that is their difference. depending on its angle (315 to 45, 45 to 135, 135 to 225, 225 to 315) you will know which sides are involved (respectively: right and left, top and bottom, left and right, bottom and top).

then calculate intersections between each side and the line connecting the centers.

the line connecting the centers can be represented as p=t*v+c1 (speaking in vectors). represent the side as a line and then calculate t such that both equations yield the same point p, which is the intersection you are looking for.

greetz

back2dos

back2dos
+2  A: 

Here is an example doing what you want.

package
{
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.ui.Mouse;

    /**
     * Sample class to draw squares and arrows between them.
     */
    public class SquareArrows extends Sprite
    {
        /**
         * Initialize the scene as soon as we can.
         */
        public function SquareArrows()
        {
            if(stage) {
                init();
            }
            else {
                addEventListener(Event.ADDED_TO_STAGE, init);
            }
        }

        /**
         * Draw two squares and an arrow between them.
         */
        private function init(e : Event = null) : void
        {
            if(hasEventListener(Event.ADDED_TO_STAGE)) {
                removeEventListener(Event.ADDED_TO_STAGE, init);
            }

            // Drawing random-sized squares.
            var squareOne : Shape =
                    getSquareShape((Math.random() * 50) + 20, 0xBBBBBB);
            var squareTwo : Shape =
                    getSquareShape((Math.random() * 50) + 20, 0xDDDDDD);
            addChild(squareOne);
            addChild(squareTwo);

            // Draw the connector.
            var connector : Shape = getConnectorShape(squareOne, squareTwo);
            addChild(connector);
        }

        /**
         * Draw a connector arrow between two square shapes.
         */
        private function getConnectorShape(connectFrom : Shape, connectTo : Shape) : Shape
        {
            // Getting the center of the first square.
            var centerFrom : Point = new Point();
            centerFrom.x = connectFrom.x + (connectFrom.width / 2);
            centerFrom.y = connectFrom.y + (connectFrom.height / 2);

            // Getting the center of the second square.
            var centerTo : Point = new Point();
            centerTo.x = connectTo.x + (connectTo.width / 2);
            centerTo.y = connectTo.y + (connectTo.height / 2);

            // Getting the angle between those two.
            var angleTo : Number =
                Math.atan2(centerTo.x - centerFrom.x, centerTo.y - centerFrom.y);
            var angleFrom : Number =
                Math.atan2(centerFrom.x - centerTo.x, centerFrom.y - centerTo.y);

            // Getting the points on both borders.
            var pointFrom : Point = getSquareBorderPointAtAngle(connectFrom, angleTo);
            var pointTo : Point = getSquareBorderPointAtAngle(connectTo, angleFrom);

            // Calculating arrow edges.
            var arrowSlope : Number = 30;
            var arrowHeadLength : Number = 10;
            var vector : Point =
                new Point(-(pointTo.x - pointFrom.x), -(pointTo.y - pointFrom.y));

            // First edge of the head...
            var edgeOneMatrix : Matrix = new Matrix();
            edgeOneMatrix.rotate(arrowSlope * Math.PI / 180);
            var edgeOneVector : Point = edgeOneMatrix.transformPoint(vector);
            edgeOneVector.normalize(arrowHeadLength);
            var edgeOne : Point = new Point();
            edgeOne.x = pointTo.x + edgeOneVector.x;
            edgeOne.y = pointTo.y + edgeOneVector.y;

            // And second edge of the head.
            var edgeTwoMatrix : Matrix = new Matrix();
            edgeTwoMatrix.rotate((0 - arrowSlope) * Math.PI / 180);
            var edgeTwoVector : Point = edgeTwoMatrix.transformPoint(vector);
            edgeTwoVector.normalize(arrowHeadLength);
            var edgeTwo : Point = new Point();
            edgeTwo.x = pointTo.x + edgeTwoVector.x;
            edgeTwo.y = pointTo.y + edgeTwoVector.y;

            // Drawing the arrow.
            var arrow : Shape = new Shape();
            with(arrow.graphics) {
                lineStyle(2);
                // Drawing the line.
                moveTo(pointFrom.x, pointFrom.y);
                lineTo(pointTo.x, pointTo.y);

                // Drawing the arrow head.
                lineTo(edgeOne.x, edgeOne.y);
                moveTo(pointTo.x, pointTo.y);
                lineTo(edgeTwo.x, edgeTwo.y);
            }
            return arrow;
        }

        /**
         * Utility method to get a point on a square border at a certain angle.
         */
        private function getSquareBorderPointAtAngle(square : Shape, angle : Number) : Point
        {
            // Calculating rays of inner and outer circles.
            var minRay : Number = Math.SQRT2 * square.width / 2;
            var maxRay : Number = square.width / 2;

            // Calculating the weight of each rays depending on the angle.
            var rayAtAngle : Number = ((maxRay - minRay) * Math.abs(Math.cos(angle * 2))) + minRay;

            // We have our point.
            var point : Point = new Point();
            point.x = rayAtAngle * Math.sin(angle) + square.x + (square.width / 2);
            point.y = rayAtAngle * Math.cos(angle) + square.y + (square.height / 2);
            return point;
        }

        /**
         * Utility method to draw a square of a given size in a new shape.
         */
        private function getSquareShape(edgeSize : Number, fillColor : Number) : Shape
        {
            // Draw the square.
            var square : Shape = new Shape();
            with(square.graphics) {
                lineStyle(1);
                beginFill(fillColor);
                drawRect(0, 0, edgeSize, edgeSize);
                endFill();
            }

            // Set a random position.
            square.x = Math.random() * (stage.stageWidth - square.width);
            square.y = Math.random() * (stage.stageHeight - square.height);

            return square;
        }
    }
}

This code isn't totally optimized. The idea is more to explain how it works. Basically, we are defining two (random) squares, and tracing a line between them. To trace the line, we calculate an angle from the center of the first square to the center of the second one, and we use a special method (getSquareBorderPointAtAngle) to extract a point on the square border in the right direction.

This method is the first key point of this snippet. We calculate that using simple circle geometry, with a little complexification on how we make the point match the border instead of matching a circle around or inside the square.

Then, we draw an arrow head. For that, we're making use of the Flash Matrix class, because it's much easier this way than to calculate it from the scratch.

And here we're done.

Tyn
This is totally awesome, man, thank you! I am now a little bit distracted so I have not tried it yet, but I will. Thank you very much
artemb
A: 

Hi Tyn, In your example, you have two static squares for which you draw the connector. In my example, i have a bunch of images whom i have to connect. I first click on the first image(which ever i click first becomes the first image), and then as i drag or move the mouse along to click on the next image, a connector shud be drawn. Once i click on the next image, that is where my connector should end with a arrow head. I am not able use your idea to implement this example. Could you please suggest some help? Thanks in advance.

Ronnie
+1  A: 

Hi

I was reading the answers here a month ago as I need the same thing. Found this connector drawing example in the meantime, and thought i'd share the link.

The example draws connector lines between uicomponents, and updates the lines as the connectors are dragged. Nice one!

alt text

http://sammyjoeosborne.com/Examples/Connector/ConnectorExample.html

Brian Bishop