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


18. September 2009 um 13:18
牛人啊
18. September 2009 um 19:21
Hi,
Thanks! a lot for posting such great information.
Keep it up with good post.
iamcoder
http://www.rsdhariwal.com
4. October 2009 um 13:21
This is interesting, but how fast would it be compared to Degrafa which has some pie menu which is simular. If I am looking for speed only, which is better?
http://www.degrafa.org/samples/
Thanks, Philip
4. October 2009 um 23:02
This is Flex4 / Spark compatible, where as Degrafa is for Flex 3 only at the moment, that’s the main feature of this one.
Please perform your own comparison, so you cover all relevant factors for your use case.
I have made a quick inspection of the filled arc drawing code as done in the Degrafa Pie Menu example, and due to the graphic command encoding that it needs to do, I would expect it to run marginally slower. I’d be interested to hear what you find out..
17. October 2009 um 22:31
I just updated Flex builder to the Beta 2 version now and sadly the computeMatrix function is gone.
What to do?
20. October 2009 um 19:59
Hi Philip,
The simplest way to update is to take the drawElement method from this class and drop it into a copy of the new Ellipse class from beta 2, of course updating the package / class / constructor names.
You will also need to remove the draw method from your copy of the Ellipse class (now called Wedge I assume) and rename the drawElement method to draw.
I haven’t tested this as I’m in the middle of a project using Beta 1, So this is pure theory. Please let me know if I was correct.
I may well have forgotten something,
29. October 2009 um 02:18
Jason, your recommended approach for Beta 2 (copying the Ellipse code and overriding a couple methods) will get the job done. Of course, you also need to copy the Wedge-specific property declarations to the new class as well, and it worked perfectly.
Alternatively, isn’t it cleaner to just extend spark.primitives.Ellipse and just override what you need and add the properties you need?
29. October 2009 um 09:01
It’s quicker to extend Ellipse, but not cleaner, because that wouldn’t be following the FlexSDK pattern.I suppose if the FlexSDK team had decided to create a hierarchy of shapes, Wedge extending Ellipse would follow that pattern. For example if the FlexSDK had Ellipse extending Rect instead of FilledElement, then you’d have that precedent. Being clean isn’t about being quick, it’s about consistency.
This was complete crap, of course when making new shape primitives, it’s better to extend the basic primitive that matches your requirements.
If there is none that fills this requirement, then yes, extend from the FilledElement superclass.
The fact that Ellipse doesn’t extend from Rect is an interesting point, since they share similar methods for measurement and bounding.
It’s a shame that even if we extended Ellipse in the first place, we’d still need to modify the class between Beta 2 and Beta 1.
However, only the drawElement(g:Graphics) method became draw(g:Graphics).
20. July 2010 um 17:13
Hi,
is there any way to use it with Flex 4 Final? E.g. the function computeMatrix() has gone and so the whole script isn’t working anymore.
20. July 2010 um 18:29
Sure, just need to update it. I’ll leave it for you to try … send the result to http://Gist.github.com .. I’ll have a closer look at it tomorrow.
Clue, look at the Ellipse filledshape …
24. July 2010 um 02:33
This would be a nice comp to add to the inventory of great flex comps if we can get a beta 2 flex 4 version going.
I cant seem to get mine going with the updates above - anyone got a demo of the beta 2 version up yet so I can see what I’m doing wrong?
27. July 2010 um 18:55
Just tried to update it, unsuccessfully. Converting it into an ellipse produced some type errors. Therefor i got no idea how to get a working example in flex 4 final.