package flare.vis.data.render { import flare.util.Geometry; import flare.util.Shapes; import flare.vis.data.DataSprite; import flare.vis.data.EdgeSprite; import flare.vis.data.NodeSprite; import flash.display.Graphics; import flash.geom.Point; import flash.geom.Rectangle; /** * Renderer that draws edges as lines. The EdgeRenderer supports straight * lines, poly lines, and curves as Bezier or cardinal splines. The type * of edge drawn is determined from an EdgeSprite's shape * property and control points found in the points property. * The line rendering properties are set by the setLineStyle * method, which can be overridden by subclasses. By default, the line * rendering settings are determined using default property values set * on this class (for example, the scaleMode and * caps properties). */ public class EdgeRenderer implements IRenderer { private static const ROOT3:Number = Math.sqrt(3); private static var _instance:EdgeRenderer = new EdgeRenderer(); /** Static EdgeRenderer instance. */ public static function get instance():EdgeRenderer { return _instance; } /** Pixel hinting flag for line rendering. */ public var pixelHinting:Boolean = false; /** Scale mode for line rendering (normal by default). */ public var scaleMode:String = "normal"; /** The joint style for line rendering. */ public var joints:String = null; /** The caps style for line rendering. */ public var caps:String = null; /** The miter limit for line rendering. */ public var miterLimit:int = 3; // temporary variables private var _p:Point = new Point(), _q:Point = new Point(); private var _pts:Vector. = new Vector.(20); /** @inheritDoc */ public function render(d:DataSprite):void { var e:EdgeSprite = d as EdgeSprite; if (e == null) { return; } // TODO: throw exception? var s:NodeSprite = e.source; var t:NodeSprite = e.target; var g:Graphics = e.graphics; var ctrls:Vector. = e.points as Vector.; var x1:Number = e.x1, y1:Number = e.y1; var x2:Number = e.x2, y2:Number = e.y2; var xL:Number = (!ctrls || ctrls.length<2) ? x1 : (ctrls[ctrls.length-2] as Number); var yL:Number = (!ctrls || ctrls.length<2) ? y1 : (ctrls[ctrls.length-1] as Number); var dx:Number, dy:Number, dd:Number; // modify end points as needed to accomodate arrow if (e.arrowType != ArrowType.NONE) { // determine arrow head size var ah:Number = e.arrowHeight, aw:Number = e.arrowWidth/2; if (ah < 0 && aw < 0) aw = 1.5 * e.lineWidth; if (ah < 0) { ah = ROOT3 * aw; } else if (aw < 0) { aw = ah / ROOT3; } // get arrow tip point as intersection of edge with bounding box if (t==null) { _p.x = x2; _p.y = y2; } else { var r:Rectangle = t.getBounds(t.parent); if (Geometry.intersectLineRect(xL,yL,x2,y2, r, _p,_q) <= 0) { _p.x = x2; _p.y = y2; } } // get unit vector along arrow line dx = _p.x - xL; dy = _p.y - yL; dd = Math.sqrt(dx*dx + dy*dy); dx /= dd; dy /= dd; // set final point positions dd = e.lineWidth/2; // if drawing as lines, offset arrow tip by half the line width if (e.arrowType == ArrowType.LINES) { _p.x -= dd*dx; _p.y -= dd*dy; dd += e.lineWidth; } // offset the anchor point (the end point for the edge connector) // so that edge doesn't "overshoot" the arrow head dd = ah - dd; x2 = _p.x - dd*dx; y2 = _p.y - dd*dy; } // draw the edge g.clear(); // clear it out setLineStyle(e, g); // set the line style if (e.shape == Shapes.BEZIER && ctrls != null && ctrls.length > 1) { if (ctrls.length < 4) { g.moveTo(x1, y1); g.curveTo(ctrls[0] as Number, ctrls[1] as Number, x2, y2); } else { Shapes.drawCubic(g, x1, y1, ctrls[0] as Number, ctrls[1] as Number, ctrls[2] as Number, ctrls[3] as Number, x2, y2); } } else if (e.shape == Shapes.CARDINAL) { Shapes.consolidate(x1, y1, ctrls, x2, y2, _pts); Shapes.drawCardinal(g, _pts, 2+ctrls.length/2); } else if (e.shape == Shapes.BSPLINE) { Shapes.consolidate(x1, y1, ctrls, x2, y2, _pts); Shapes.drawBSpline(g, _pts, 2+ctrls.length/2); } else { g.moveTo(x1, y1); if (ctrls != null) { for (var i:uint=0; i