I recently ran across this awesome utility class, GraphicsUtil, created by Noel Billig. It allows the ability to draw lines with an arrow on one end using the drawing API in AS3. This proved useful for a project I was working on. In addition to its default behavior however, I needed the utility class to provide the ability to draw lines with arrows on both ends. Well thanks to Noel’s clean code, I was able to easily modify the base class to do just that by adding the following static method:
/** * Draws a double-headed arrow. Pass in ArrowStyle * objects for both arrows to override the default settings. * @param graphics * @param start * @param end * @param startStyle * @param endStyle */ public static function drawArrows( graphics:Graphics, start:Point,end:Point, startStyle:Object=null, endStyle:Object=null ):void { // variables used for arrow 1 var startArrowStyle:ArrowStyle; var startHalfWidth:Number; var vect1:Point; var startNorm:Point; var start1:Point; var start2:Point; var end1:Point; var end2:Point; var startHeadPnt:Point; var startHeadPntNorm:Point; var startEdge1:Point; var startEdge2:Point; var startShaftCenter:Point; var startInter1:Point; var startInter2:Point; var startEdgeCenter:Point; var startEdgeNorm:Point; var startEdgeCntrl1:Point; var startEdgeCntrl2:Point; // variables used for arrow 2 var endArrowStyle:ArrowStyle; var endHalfWidth:Number; var vect2:Point; var endNorm:Point; var end3:Point; var end4:Point; var start3:Point; var start4:Point; var endHeadPnt:Point; var endHeadPntNorm:Point; var endEdge1:Point; var endEdge2:Point; var endShaftCenter:Point; var endInter1:Point; var endInter2:Point; var endEdgeCenter:Point; var endEdgeNorm:Point; var endEdgeCntrl1:Point; var endEdgeCntrl2:Point; if (start.equals(end)) return; ///////////////////////////////// start arrow config \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ if (startStyle == null) { startArrowStyle = new ArrowStyle(); } else if (startStyle is ArrowStyle) { startArrowStyle = startStyle as ArrowStyle; } else { startArrowStyle = new ArrowStyle( startStyle ); } vect1 = end.subtract( start ); startHalfWidth = (startArrowStyle.headWidth != -1) ? startArrowStyle.headWidth / 2 : startArrowStyle.headLength / 2; //Figure out the line start/end points startNorm = new Point( vect1.y, -vect1.x ); startNorm.normalize( startArrowStyle.shaftThickness/ 2 ); start1 = start.add( startNorm ); start2 = start.subtract( startNorm ); end1 = end.add( startNorm ); end2 = end.subtract( startNorm ); //figure out where the arrow head starts startHeadPnt = vect1.clone(); startHeadPnt.normalize( startHeadPnt.length - startArrowStyle.headLength ); startHeadPnt = startHeadPnt.add( start ); //calculate the arrowhead corners startHeadPntNorm = startNorm.clone(); startHeadPntNorm.normalize( startHalfWidth ); startEdge1 = startHeadPnt.add( startHeadPntNorm ); startEdge2 = startHeadPnt.subtract( startHeadPntNorm ); //Figure out where the arrow connects the the shaft, then calc the intersections startShaftCenter = Point.interpolate( end, startHeadPnt, startArrowStyle.shaftPosition ); startInter1 = GeomUtil.getLineIntersection( start1, end1, startShaftCenter, startEdge1 ); startInter2 = GeomUtil.getLineIntersection( start2, end2, startShaftCenter, startEdge2 ); //Figure out the control points startEdgeCenter = Point.interpolate( end, startHeadPnt, startArrowStyle.edgeControlPosition ); startEdgeNorm = startNorm.clone(); startEdgeNorm.normalize( startHalfWidth * startArrowStyle.edgeControlSize ); startEdgeCntrl1 = startEdgeCenter.add( startEdgeNorm ); startEdgeCntrl2 = startEdgeCenter.subtract( startEdgeNorm ); ///////////////////////////////// end arrow config \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ if (endStyle == null) { endArrowStyle = new ArrowStyle(); } else if (endStyle is ArrowStyle) { endArrowStyle = endStyle as ArrowStyle; } else { endArrowStyle = new ArrowStyle( endStyle ); } vect2 = start.subtract( end ); endHalfWidth = (endArrowStyle.headWidth != -1) ? endArrowStyle.headWidth / 2 : endArrowStyle.headLength / 2; //Figure out the line start/end points endNorm = new Point( vect2.y, -vect2.x ); endNorm.normalize( endArrowStyle.shaftThickness / 2 ); start3 = start.add( endNorm ); start4 = start.subtract( endNorm ); end3 = end.add( endNorm ); end4 = end.subtract( endNorm ); //figure out where the arrow head starts endHeadPnt = vect2.clone(); endHeadPnt.normalize( endHeadPnt.length - endArrowStyle.headLength ); endHeadPnt = endHeadPnt.add( end ); //calculate the arrowhead corners endHeadPntNorm = endNorm.clone(); endHeadPntNorm.normalize( endHalfWidth ); endEdge1 = endHeadPnt.add( endHeadPntNorm ); endEdge2 = endHeadPnt.subtract( endHeadPntNorm ); //Figure out where the arrow connects the the shaft, then calc the intersections endShaftCenter = Point.interpolate( start, endHeadPnt, endArrowStyle.shaftPosition ); endInter1 = GeomUtil.getLineIntersection( end3, start3, endShaftCenter, endEdge1 ); endInter2 = GeomUtil.getLineIntersection( end4, start4, endShaftCenter, endEdge2 ); //Figure out the control points endEdgeCenter = Point.interpolate( start, endHeadPnt, endArrowStyle.edgeControlPosition ); endEdgeNorm = endNorm.clone(); endEdgeNorm.normalize( endHalfWidth * endArrowStyle.edgeControlSize ); endEdgeCntrl1 = endEdgeCenter.add( endEdgeNorm ); endEdgeCntrl2 = endEdgeCenter.subtract( endEdgeNorm ); ///////////////////////////////// draw the graphics \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // draw 1st arrow graphics.moveTo( start1.x, start2.y ); graphics.lineTo( startInter1.x, startInter1.y ); graphics.lineTo( startEdge1.x, startEdge1.y ); graphics.curveTo( startEdgeCntrl1.x, startEdgeCntrl1.y, end.x, end.y ); graphics.curveTo( startEdgeCntrl2.x, startEdgeCntrl2.y, startEdge2.x, startEdge2.y ); graphics.lineTo( startInter2.x, startInter2.y ); graphics.lineTo( start2.x, start2.y ); graphics.lineTo( start1.x, start1.y ); // draw 2nd arrow graphics.moveTo( end3.x, end4.y ); graphics.lineTo( endInter1.x, endInter1.y ); graphics.lineTo( endEdge1.x, endEdge1.y ); graphics.curveTo( endEdgeCntrl1.x, endEdgeCntrl1.y, start.x, start.y ); graphics.curveTo( endEdgeCntrl2.x, endEdgeCntrl2.y, endEdge2.x, endEdge2.y ); graphics.lineTo( endInter2.x, endInter2.y ); graphics.lineTo( end4.x, end4.y ); graphics.lineTo( end3.x, end3.y ); }
Those are the basics. It can be tweaked to taste, etc. Kudos to Noel for such a useful class…


