Monthly archive September 2009

 
 

Flex 4 Wedge

I’ve created a wedge element for Flex4 which you may like to use for your projects, it’s a subclass of FilledElement from the new Spark framework, which already provides Ellipse and Rect primitives. The wedge will draw pie wedges of any radius, arc and start angle, you can also set thickness to create ring wedges, as you can see here.

Wedge Demo screen

You can download the code and the wedge demo application from the View Source panel on the demo page.

The Wedge class code is as follows.

package com.mentalaxis.view.draw
{
	
	import flash.display.Graphics;
	import flash.geom.Matrix;
	import flash.geom.Point;
	
	import mx.utils.MatrixUtil;
	
	import spark.primitives.supportClasses.FilledElement;
	
	/**
	 * <p>Draws a wedge shape using these parameters</p> 
	 * 
	 * <li>angle (degrees, 0 = 3O'Clock or Compass East)</li> 
	 * <li>arc (degrees)</li>
	 * <li>thickness (pixels)</li>
	 * <li>radius (pixels)</li>
	 * 
	 * <p>position, width and height determine the bounding area of the wedge 
	 * the wedge radius is measured from the center of this bounding area.</p> 
	 */
	public class Wedge extends FilledElement
	{
		
		//--------------------------------------------------------------------------
		//
		//  Constructor
		//
		//--------------------------------------------------------------------------
		
		/**
		 *  Wedge Constructor
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion Flex 4
		 */
		public function Wedge()
		{
			super();
		}
		
		//--------------------------------------------------------------------------
		//
		//  Properties
		//
		//--------------------------------------------------------------------------
		private var _angle:Number = 0;
		
		[Inspectable(category="General")]
		/**
		 * Start angle for the wedge, in degrees. Zero degrees is 3 O'clock, or East.
		 */
		public function get angle():Number
		{
			return _angle;
		}
		
		public function set angle(value:Number):void
		{
			if (value != _angle)
			{
				_angle = value;
				invalidateSize();
				invalidateDisplayList();
				invalidateParentSizeAndDisplayList();
			}
		}
		
		private var _arc:Number = 0;
		
		[Inspectable(category="General")]
		/**
		 * Arc angle for the wedge, in degrees.
		 */
		public function get arc():Number
		{
			return _arc;
		}
		
		public function set arc(value:Number):void
		{
			if (value != _arc)
			{
				_arc = value;
				invalidateSize();
				invalidateDisplayList();
				invalidateParentSizeAndDisplayList();
			}
		}
		private var _thickness:Number = 0;
		
		[Inspectable(category="General")]
		/**
		 * Thickness of the wedge, measured inward from radius. 
		 */
		public function get thickness():Number
		{
			return _thickness;
		}
		
		public function set thickness(value:Number):void
		{
			if (value != _thickness)
			{
				_thickness = value;
				invalidateSize();
				invalidateDisplayList();
				invalidateParentSizeAndDisplayList();
			}
		}
		
		private var _radius:Number = 0;
		
		[Inspectable(category="General")]
		/**
		 * Exterior radius of the wedge
		 */
		public function get radius():Number
		{
			return _radius;
		}
		
		public function set radius(value:Number):void
		{
			if (value != _radius)
			{
				_radius = value;
				invalidateSize();
				invalidateDisplayList();
				invalidateParentSizeAndDisplayList();
			}
		}
		
		//--------------------------------------------------------------------------
		//
		//  Overridden methods
		//
		//--------------------------------------------------------------------------
		
		/**
		 *  @private
		 */
		override protected function transformWidthForLayout(width:Number, height:Number, postLayoutTransform:Boolean = true):Number
		{
			if (postLayoutTransform)
			{
				var m:Matrix = computeMatrix();
				if (m)
					width = MatrixUtil.getEllipseBoundingBox(width / 2, height / 2, width / 2, height / 2, m).width;
			}
			
			// Take stroke into account
			return width + getStrokeExtents().x;
		}
		
		/**
		 *  @private
		 */
		override protected function transformHeightForLayout(width:Number, height:Number, postLayoutTransform:Boolean = true):Number
		{
			if (postLayoutTransform)
			{
				var m:Matrix = computeMatrix();
				if (m)
					height = MatrixUtil.getEllipseBoundingBox(width / 2, height / 2, width / 2, height / 2, m).height;
			}
			
			// Take stroke into account
			return height + getStrokeExtents().y;
		}
		
		/**
		 *  @inheritDoc
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 *  @playerversion AIR 1.1
		 *  @productversion Flex 3
		 */
		override public function getBoundsXAtSize(width:Number, height:Number, postLayoutTransform:Boolean = true):Number
		{
			var strokeExtents:Point = getStrokeExtents(postLayoutTransform);
			var m:Matrix = postLayoutTransform ? computeMatrix() : null;
			if (!m)
				return strokeExtents.x * -0.5 + this.x;
			
			if (!isNaN(width))
				width -= strokeExtents.x;
			if (!isNaN(height))
				height -= strokeExtents.y;
			
			// Calculate the width and height pre-transform:
			var newSize:Point = MatrixUtil.fitBounds(width, height, m, preferredWidthPreTransform(), preferredHeightPreTransform(), minWidth, minHeight, maxWidth, maxHeight);
			if (!newSize)
				newSize = new Point(minWidth, minHeight);
			
			return strokeExtents.x * -0.5 + MatrixUtil.getEllipseBoundingBox(newSize.x / 2, newSize.y / 2, newSize.x / 2, newSize.y / 2, m).x;
		}
		
		/**
		 *  @inheritDoc
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 *  @playerversion AIR 1.1
		 *  @productversion Flex 3
		 */
		override public function getBoundsYAtSize(width:Number, height:Number, postLayoutTransform:Boolean = true):Number
		{
			var strokeExtents:Point = getStrokeExtents(postLayoutTransform);
			var m:Matrix = postLayoutTransform ? computeMatrix() : null;
			if (!m)
				return strokeExtents.y * -0.5 + this.y;
			
			if (!isNaN(width))
				width -= strokeExtents.x;
			if (!isNaN(height))
				height -= strokeExtents.y;
			
			// Calculate the width and height pre-transform:
			var newSize:Point = MatrixUtil.fitBounds(width, height, m, preferredWidthPreTransform(), preferredHeightPreTransform(), minWidth, minHeight, maxWidth, maxHeight);
			if (!newSize)
				newSize = new Point(minWidth, minHeight);
			
			return strokeExtents.y * -0.5 + MatrixUtil.getEllipseBoundingBox(newSize.x / 2, newSize.y / 2, newSize.x / 2, newSize.y / 2, m).y;
		}
		
		/**
		 *  @private
		 */
		override public function getLayoutBoundsX(postLayoutTransform:Boolean = true):Number
		{
			var stroke:Number = -getStrokeExtents(postLayoutTransform).x * 0.5;
			
			if (postLayoutTransform)
			{
				var m:Matrix = computeMatrix();
				if (m)
					return stroke + MatrixUtil.getEllipseBoundingBox(width / 2, height / 2, width / 2, height / 2, m).x;
			}
			
			return stroke + this.x;
		}
		
		/**
		 *  @private
		 */
		override public function getLayoutBoundsY(postLayoutTransform:Boolean = true):Number
		{
			var stroke:Number = -getStrokeExtents(postLayoutTransform).y * 0.5;
			
			if (postLayoutTransform)
			{
				var m:Matrix = computeMatrix();
				if (m)
					return stroke + MatrixUtil.getEllipseBoundingBox(width / 2, height / 2, width / 2, height / 2, m).y;
			}
			
			return stroke + this.y;
		}
		
		/**
		 * @private 
		 */
		override protected function drawElement(g:Graphics):void
		{
			var xo:Number = drawX + (width/2);
			var yo:Number = drawY + (width/2);
			
			var workingArc:Number = _arc;
			// limit sweep to reasonable numbers
			if (Math.abs(workingArc) > 360)
				workingArc = 360;
			
			// Flash uses 8 segments per circle, to match that, we draw in a maximum
			// of 45 degree segments. First we calculate how many segments are needed
			// for our arc.
			var segs:Number = Math.ceil(Math.abs(workingArc) / 45);
			
			// Now calculate the sweep of each segment.
			var segAngle_a:Number = workingArc / segs
			var segAngle_b:Number = -workingArc / segs;
			
			// The math requires radians rather than degrees. To convert from degrees
			// use the formula (degrees/180)*Math.PI to get radians.
			var theta_a:Number = -(segAngle_a / 180) * Math.PI;
			var theta_b:Number = -(segAngle_b / 180) * Math.PI;
			
			// convert angle workingAngle to radians
			var workingAngle:Number = _angle;
			var angle:Number = -(workingAngle / 180) * Math.PI;
			
			// draw the curve in segments no larger than 45 degrees.
			if (segs > 0)
			{
				// draw a line from the end of the interior curve to the start of the exterior curve
				var workingRadius:Number = _radius;
				var ax:Number = xo + Math.cos(workingAngle / 180 * Math.PI) * workingRadius;
				var ay:Number = yo + Math.sin(-workingAngle / 180 * Math.PI) * workingRadius;
				g.moveTo(ax, ay);
				
				// Loop for drawing exterior  curve segments
				for (var i:int = 0; i < segs; i++)
				{
					angle += theta_a;
					var angleMid:Number;
					angleMid = angle - (theta_a / 2);
					var bx:Number = xo + Math.cos(angle) * workingRadius;
					var by:Number = yo + Math.sin(angle) * workingRadius;
					var cx:Number = xo + Math.cos(angleMid) * (workingRadius / Math.cos(theta_a / 2));
					var cy:Number = yo + Math.sin(angleMid) * (workingRadius / Math.cos(theta_a / 2));
					g.curveTo(cx, cy, bx, by);
				}
				
				// draw a line from the end of the exterior curve to the start of the interior curve
				workingAngle += workingArc;
				angle = -(workingAngle / 180) * Math.PI;
				
				// draw the interior (subtractive) wedge
				// draw a line from the center to the start of the interior curve
				var interiorRadius:Number = workingRadius - _thickness;
				var dx:Number = xo + Math.cos(workingAngle / 180 * Math.PI) * interiorRadius;
				var dy:Number = yo + Math.sin(-workingAngle / 180 * Math.PI) * interiorRadius;
				g.lineTo(dx, dy);
				
				// Loop for drawing interior curve segments
				for (i = 0; i < segs; i++)
				{
					angle += theta_b;
					angleMid = angle - (theta_b / 2);
					bx = xo + Math.cos(angle) * interiorRadius;
					by = yo + Math.sin(angle) * interiorRadius;
					cx = xo + Math.cos(angleMid) * (interiorRadius / Math.cos(theta_b / 2));
					cy = yo + Math.sin(angleMid) * (interiorRadius / Math.cos(theta_b / 2));
					g.curveTo(cx, cy, bx, by);
				}
				g.lineTo(ax, ay);
			}
		}
	}
}