src/org/un/cava/birdeye/ravis/graphLayout/visual/VisualGraph.as
/*
* The MIT License
*
* Copyright (c) 2007 The SixDegrees Project Team
* (Jason Bellone, Juan Rodriguez, Segolene de Basquiat, Daniel Lang).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.un.cava.birdeye.ravis.graphLayout.visual {
import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.utils.Dictionary;
import mx.containers.Canvas;
import mx.controls.Label;
import mx.core.ClassFactory;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.ScrollPolicy;
import mx.core.UIComponent;
import mx.effects.Effect;
import mx.events.EffectEvent;
import mx.managers.CursorManager;
import mx.utils.ObjectUtil;
import org.un.cava.birdeye.ravis.distortions.IDistortion;
import org.un.cava.birdeye.ravis.graphLayout.data.*;
import org.un.cava.birdeye.ravis.graphLayout.layout.ILayoutAlgorithm;
import org.un.cava.birdeye.ravis.graphLayout.visual.edgeRenderers.BaseEdgeRenderer;
import org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualEdgeEvent;
import org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualGraphEvent;
import org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualNodeEvent;
import org.un.cava.birdeye.ravis.utils.LogUtil;
import org.un.cava.birdeye.ravis.utils.events.VGraphEvent;
/**
* Dispatched when there is any change to the nodes and/or links of this graph.
*
* @eventType org.un.cava.birdeye.ravis.utils.events.VGraphEvent
*/
[Event(name=VGraphEvent.VGRAPH_CHANGED, type="org.un.cava.birdeye.ravis.utils.events.VGraphEvent")]
/**
* Dispatched when a drag event starts
*
* @eventType org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualNodeEvent
*/
[Event(name="nodeDragStart", type="org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualNodeEvent")]
/**
* Dispatched when a drag event ends
*
* @eventType org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualNodeEvent
*/
[Event(name="nodeDragEnd", type="org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualNodeEvent")]
/**
* Dispatched when a node is clicked it is totally independant of drags, this means you
* do not have to use double clicks to handle expanding or resetting the root
*
* @eventType org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualNodeEvent
*/
[Event(name="nodeClick", type="org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualNodeEvent")]
/**
* Dispatched when an edge has been rolled over
*
* @eventType org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualEdgeEvent
*/
[Event(name="edgeRollOver", type="org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualEdgeEvent")]
/**
* Dispatched when an edge has been rolled out
*
* @eventType org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualEdgeEvent
*/
[Event(name="edgeRollOut", type="org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualEdgeEvent")]
/**
* Dispatched when an edge has been clicked
*
* @eventType org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualEdgeEvent
*/
[Event(name="edgeClick", type="org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualEdgeEvent")]
/**
* Dispatched when a node is double clicked it is totally independant of drags.
*
* @eventType org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualNodeEvent
*/
[Event(name="nodeDoubleClick", type="org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualNodeEvent")]
/**
* Dispatched when the background is done dragging.
*
* @eventType org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualGraphEvent
*/
[Event(name="backgroundDragEnd", type="org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualGraphEvent")]
/**
* Dispatched when the background is done dragging.
*
* @eventType org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualGraphEvent
*/
[Event(name="graphScaled", type="org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualGraphEvent")]
/**
* Dispatched when the background has been clicked but no nodes selected, and no drag occured
*
* @eventType org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualGraphEvent
*/
[Event(name="backgroundClick", type="org.un.cava.birdeye.ravis.graphLayout.visual.events.VisualGraphEvent")]
/**
* This component can visualize and layout a graph data structure in
* a Flex application. It is derived from canvas and thus behaves much
* like that in general.
*
* Currently the graphs are required to be connected. And for most layouts
* a root node is required (as they are tree based).
*
* A graph object needs to be specified as well as a layouter object
* that implements the ILayoutAlgorithm interface.
*
* XXX provide example code here
* */
public class VisualGraph extends Canvas implements IVisualGraph {
private static const _LOG:String = "graphLayout.visual.VisualGraph";
[Embed('/org/un/cava/birdeye/ravis/assets/cursors/openhand.png')]
private static const HAND_CURSOR:Class;
/**
* Distortion to be applied on mouse over
**/
public var distortion:IDistortion;
private var _nodeMouseDownLocation:Point;
private var _mouseDownLocation:Point
/**
* Used to determine if a node has been moved or if it was just a click
*/
private var _nodeMovedInDrag:Boolean = false;
/**
* This flag for draw() specifies that the linklength
* shall be reset to 100 when calling draw();
* */
public static const DF_RESET_LL:uint = 1;
/**
* This property holds the Graph object with the graph
* data, that is supposed to be visualised. This is also
* the only data structure that keeps track of nodes and
* edges.
* */
protected var _graph:IGraph = null;
/**
* This property holds the layouter object. The layouter does the
* calculation of the layout and the placement of the nodes.
* It may be exchanged on the fly.
* */
protected var _layouter:ILayoutAlgorithm;
/**
* for cleanup we also need a reference source for
* vnodes and vedges
* */
protected var _vnodes:Dictionary;
protected var _vedges:Dictionary;
/**
* Every visual node is associated with an UIComponent that
* will be the actual visual representation of the node in the
* Flashplayer. This UIComponent (which is typically an ItemRenderer)
* is called a "view". Node's views are now mainly created on
* demand and destroyed if the node is currently not visible
* to save resources. This map keeps track of which VNode belongs
* to which view. This is required as in certain events, we get
* only access to the UIComponent and we need to get hold of
* the corresponding node.
* */
protected var _nodeViewToVNodeMap:Dictionary;
/**
* A similar map needs to exist for edges
* */
protected var _edgeLabelViewToVEdgeMap:Dictionary;
protected var _edgeViewToVEdgeMap:Dictionary;
/**
* The standard origin is the upper left corner, but if
* the graph is scrolled, this origin may change, so we keep
* track of that here.
* */
protected var _origin:Point;
/**
* The current zooming scale of the vgraph.
* This is used to facilitate the use of scaleX/scaleY
* and take it into account for drag and drop.
* Supported by getter/setting methods.
* (Contributed by Ivan Bulanov)
* */
protected var _scale:Number = 1;
/* drag and drop support */
/**
* This is the current UIComponent that is dragged by the mouse.
* */
protected var _dragComponent:UIComponent;
/**
* These two maps keep the drag cursor offset positions
* for each dragged component. This allows to (theoretically)
* drag more than one component at once and to correctly reposition the
* component during the drag and at the drop.
* */
protected var _drag_x_offsetMap:Dictionary;
protected var _drag_y_offsetMap:Dictionary;
/**
* There is generally support to restrict dragging and dropping
* to a certain area. These bounds are kept for each dragged
* component in this map.
* */
protected var _drag_boundsMap:Dictionary;
/**
* The drag cursors starting position is required
* if we do a "background drag", i.e. scroll the whole
* VisualGraph around. All this does is basically moving all
* components with the mouse, thus creating the effect of a
* background drag.
* */
protected var _dragCursorStartX:Number;
protected var _dragCursorStartY:Number;
/**
* To distinguish an active mouse move drag that drags
* a component from one that should drag the background,
* we need this property.
* */
protected var _backgroundDragInProgress:Boolean = false;
/**
* To enable/disable scrolling while background is being
* dragged
* */
protected var _scrollBackgroundInDrag:Boolean = true;
/**
* To enable/disable movement while node is being
* dragged
* */
protected var _moveNodeInDrag:Boolean = true;
/**
* To enable/disable movement while edge is being
* dragged
* */
protected var _moveEdgeInDrag:Boolean = true;
/**
* To enable/disable movement while background is being
* dragged
* */
protected var _moveGraphInDrag:Boolean = true;
/* Rendering */
/**
* We allow the specification of an EdgeRenderer (i.e. an IFactory)
* that allows us to specify the view's for each edge in MXML
* */
protected var _edgeRendererFactory:IFactory = null;
/**
* We allow the specification of an ItemRenderer (i.e. an IFactory)
* that allows us to specify the view's for each node in MXML
* */
protected var _itemRendererFactory:IFactory = null;
/**
* Also allow the specification of an IFactory for edge
* labels.
* */
protected var _edgeLabelRendererFactory:IFactory = null;
/**
* Flag to force a redraw of all edge even if the layout
* has not changed
* */
protected var _forceUpdateEdges:Boolean = false;
/**
* Flag to force a redraw of all nodes even if the layout
* has not changed
* */
protected var _forceUpdateNodes:Boolean = false;
/**
* Specify whether edge labels should be displayed or not
* */
protected var _displayEdgeLabels:Boolean = true;
/**
* We keep the default parameters
* to draw edges (line width, color, alpha channel)
* in this object. The params to be expected are all
* params which can be accepted by the lineStyle()
* method of the Graphics class.
* We keep a separate default set for regular edges and
* for distinguished edges.
* */
protected var _defaultEdgeStyle:Object = {
thickness:1,
alpha:1.0,
color:0xcccccc,
pixelHinting:false,
scaleMode:"normal",
caps:null,
joints:null,
miterLimit:3
}
/* The visibility of nodes can be controlled in a few ways.
* The principal limit is to restrict nodes to only be visible if they
* are within a certain distance (in degrees of separation) from the
* current root node. In addition previous root nodes can be
* made visible */
/**
* This property controls if any visibility limit is currently
* active at all. Strongly recommended for large graphs.
* The application will be brought to its knees if thousands of nodes
* should be displayed.
* */
protected var _visibilityLimitActive:Boolean = true;
/**
* Controls the maximum distance from the root that a node
* can have to still be visible.
* */
protected var _maxVisibleDistance:uint = int.MAX_VALUE;
/**
* This object hash contains all node ids
* of nodes which are currently within the visible
* distance limit. This hash is typically initialised from
* from the Graph object. These nodes are NOT all
* visible nodes (since the history nodes are also
* visible).
* */
protected var _nodeIDsWithinDistanceLimit:Dictionary;
/**
* This object contains the previuos hash of nodes
* within the distance. To keep this helps to avoid
* running through all nodes to render the olds
* invisible and the new ones visible.
* */
protected var _prevNodeIDsWithinDistanceLimit:Dictionary;
/**
* This is the number of nodes within the distance
* limit.
* */
protected var _noNodesWithinDistance:uint;
/**
* This Dictionary holds all visible nodes,
* i.e. those within the limit and the history
* nodes (if the history is enabled), or even all
* nodes, if the visibility limitation is disabled.
* This directory is indexed by VNode and the values
* are the same VNode.
* */
protected var _visibleVNodes:Dictionary;
protected var _visibleVNodesList:Array;
/**
* The number of currently visible VNodes.
* */
protected var _noVisibleVNodes:int;
/**
* This Dictionary keeps track of all currently
* visible edges. An edge is visible iff both
* attached nodes are visible. This hash is indexed
* with VEdges and the values are the same VEdge objects.
* */
protected var _visibleVEdges:Dictionary;
protected var _visibleVEdgesList:Array;
/* root nodes, distinguished nodes and history */
/**
* This is the current focused / root node. It will be
* used as the root for any tree computations and
* currently all layouters depend on this.
* Typically the root node is selected by double-click.
* */
protected var _currentRootVNode:IVisualNode = null;
/**
* This hash keeps track of all the past root VNodes
* thus being the history. If showHistory is enabled,
* these nodes are also visible even if they are outside
* the visible distance.
* */
protected var _currentVNodeHistory:Array = null;
/**
* This flag controls whether to show the history nodes or not.
* */
protected var _showCurrentNodeHistory:Boolean = false;
protected var edgeLayer:Canvas;
protected var edgeLabelLayer:Canvas;
protected var nodeLayer:Canvas;
/* public attributes */
/**
* enable bitmap caching in renderer components
* */
public var cacheRendererObjects:Boolean = false;
/**
* Default visibility setting for new nodes. If
* set all new nodes are created visible and with
* a view component. Beware of that if you have
* many nodes.
* */
public var newNodesDefaultVisible:Boolean = false;
/**
* This property controls whether the mouse cursor
* should be locked in the dragged node's center or not.
* */
public var dragLockCenter:Boolean = false;
/**
* If set, this effect will be applied if a view
* is created (e.g. while a node becomes visible
* or if a new node is created).
* */
public var addItemEffect:Effect;
/**
* If set, this effect will be applied if a view
* is removed (e.g. a node becomes invisible or
* is removed).
* */
public var removeItemEffect:Effect;
private var _clippingMask:UIComponent;
/**
* The constructor just initialises most data structures, but not all
* required. Currently it does neither set a Graph object, nor a
* Layouter object. Reasonable defaults may be added as an option.
* */
public function VisualGraph() {
/* call super class constructor */
super();
/* initialize maps for drag and drop */
_drag_x_offsetMap = new Dictionary;
_drag_y_offsetMap = new Dictionary;
_drag_boundsMap = new Dictionary;
/* initialise view/ItemRenderer and visibility mapping */
_vnodes = new Dictionary;
_vedges = new Dictionary;
_nodeViewToVNodeMap = new Dictionary;
_edgeLabelViewToVEdgeMap = new Dictionary;
_edgeViewToVEdgeMap = new Dictionary;
_visibleVNodes = new Dictionary;
_visibleVNodesList = new Array;
_visibleVEdges = new Dictionary;
_visibleVEdgesList = new Array;
_noVisibleVNodes = 0;
_visibilityLimitActive = true;
/* init the history array */
_currentVNodeHistory = new Array;
/* set an edge renderer, for now we use the Default,
* but at a later stage this could be set externally */
_edgeRendererFactory = new ClassFactory(BaseEdgeRenderer);
this.horizontalScrollPolicy = ScrollPolicy.OFF;
this.verticalScrollPolicy = ScrollPolicy.OFF;
this.clipContent = true;
/* add event handlers for background drag/drop i.e. scrolling */
this.addEventListener(MouseEvent.MOUSE_DOWN,backgroundDragBegin,false, int.MIN_VALUE);
this.addEventListener(MouseEvent.MOUSE_MOVE,mouseMoveHandler);
this.addEventListener(MouseEvent.ROLL_OVER,rollOverHandler);
this.addEventListener(MouseEvent.ROLL_OUT,rollOutHandler);
_origin = new Point(0,0);
}
protected override function createChildren():void
{
super.createChildren();
_clippingMask = new UIComponent();
addChild(_clippingMask);
this.mask = _clippingMask;
edgeLayer = new Canvas();
edgeLayer.clipContent = false;
edgeLayer.horizontalScrollPolicy = ScrollPolicy.OFF;
edgeLayer.verticalScrollPolicy = ScrollPolicy.OFF;
edgeLayer.percentWidth = 100;
edgeLayer.percentHeight = 100;
addChild(edgeLayer);
edgeLabelLayer = new Canvas();
edgeLabelLayer.clipContent = false;
edgeLabelLayer.horizontalScrollPolicy = ScrollPolicy.OFF;
edgeLabelLayer.verticalScrollPolicy = ScrollPolicy.OFF;
edgeLabelLayer.percentWidth = 100;
edgeLabelLayer.percentHeight = 100;
addChild(edgeLabelLayer);
nodeLayer = new Canvas();
nodeLayer.clipContent = false;
nodeLayer.horizontalScrollPolicy = ScrollPolicy.OFF;
nodeLayer.verticalScrollPolicy = ScrollPolicy.OFF;
nodeLayer.percentWidth = 100;
nodeLayer.percentHeight = 100;
addChild(nodeLayer);
}
private function mouseMoveHandler(e:MouseEvent):void
{
if(distortion && layouter.animInProgress == false)
{
var dp:Point = new Point(e.stageX,e.stageY);
dp = dp.subtract(localToGlobal(new Point(x,y)));
distortion.distort(dp);
}
}
private function rollOverHandler(e:MouseEvent):void
{
CursorManager.setCursor(HAND_CURSOR,3);
}
private function rollOutHandler(e:MouseEvent):void
{
if(distortion)
{
draw();
}
CursorManager.removeAllCursors();
}
public function releaseNodes():void
{
for each(var node:IVisualNode in visibleVNodes)
{
node.moveable = true;
}
}
/**
* This property allows access and setting of the underlying
* graph object. If set, it will automatically initialise the VGraph
* from the Graph object, i.e. create VNodes and VEdges for each
* Graph node and Graph edge.
* If there was already a Graph present, the VGraph is purged, but no other
* cleanup is done, which means that there could still be
* some references floating around thus leaking memory.
* For now, avoid setting it more than once in the same
* VGraph.
* @param g The Graph object to be assigned.
* */
public function set graph(g:IGraph):void {
if(_graph != null) {
LogUtil.warn(_LOG, "_graph in VisualGraph was not null when new graph was assigned."+
" Some cleanup done, but this may leak memory");
/* this cleanes the VGraph so we are pristine */
clearHistory();
purgeVGraph();
_graph.purgeGraph();
_nodeIDsWithinDistanceLimit = null;
_prevNodeIDsWithinDistanceLimit = null;
_noNodesWithinDistance = 0;
/* this may have been removed already before */
if(_layouter) {
_layouter.resetAll();
}
}
/* assign defaults */
_graph = g;
/* IMPORTANT: a layouter also has a graph reference
* separate, this must be updated in order for
* this to work properly
*/
if(_layouter) {
_layouter.graph = g;
}
/* better safe than sorry even if it is an empty one */
initFromGraph();
/* invalidate old root node */
_currentRootVNode = null;
/* now use the first node as the new default root node */
if(_graph.nodes.length > 0) {
_currentRootVNode = (_graph.nodes[0] as INode).vnode;
}
LogUtil.warn(_LOG, "Setting a new graph object invalidates the root node,"+
" a new default root node was set, but it may not be what you want");
}
/**
* @private
* */
public function get graph():IGraph {
return _graph;
}
/**
* @inheritDoc
* */
public function set itemRenderer(ifac:IFactory):void {
if(ifac != _itemRendererFactory) {
_itemRendererFactory = ifac;
/* if that has changed, we would need to recreate all
* currently visible nodes */
setAllInVisible();
updateVisibility();
}
}
/**
* @private
* */
public function get itemRenderer():IFactory {
return _itemRendererFactory;
}
/**
* @inheritDoc
* */
public function set edgeRendererFactory(er:IFactory):void {
if(er != _edgeRendererFactory) {
setAllEdgesInVisible();
_edgeRendererFactory = er;
updateEdgeVisibility();
}
}
/**
* @private
* */
public function get edgeRendererFactory():IFactory {
return _edgeRendererFactory;
}
/**
* @inheritDoc
* */
public function set edgeLabelRenderer(elr:IFactory):void {
/* if the factory was changed, then we have to remove all
* instances of vedgeViews to have them updated */
if(elr != _edgeLabelRendererFactory) {
/* set all edges invisible, this should delete all instances
* of view components */
setAllEdgesInVisible();
/* set the new renderer */
_edgeLabelRendererFactory = elr;
/* update i.e. recreate the instances */
updateEdgeVisibility();
}
}
/**
* @private
* */
public function get edgeLabelRenderer():IFactory {
return _edgeLabelRendererFactory;
}
/**
* @inheritDoc
* */
public function set displayEdgeLabels(del:Boolean):void {
var e:IEdge;
if(_displayEdgeLabels == del) {
// no change
} else {
_displayEdgeLabels = del;
setAllEdgesInVisible();
updateEdgeVisibility();
}
}
/**
* @private
* */
public function get displayEdgeLabels():Boolean {
return _displayEdgeLabels;
}
/**
* @inheritDoc
* */
public function get layouter():ILayoutAlgorithm {
return _layouter;
}
/**
* @private
* */
public function set layouter(l:ILayoutAlgorithm):void {
if(_layouter != null) {
_layouter.resetAll(); // to stop any pending animations
}
_layouter = l;
/* need to signal control components possibly */
this.dispatchEvent(new VGraphEvent(VGraphEvent.LAYOUTER_CHANGED));
}
/**
* @inheritDoc
* */
public function get origin():Point {
return _origin;
}
/**
* @inheritDoc
* */
public function get center():Point {
return new Point(this.width / 2.0, this.height / 2.0);
}
/**
* @inheritDoc
* */
public function get visibleVNodes():Array {
return _visibleVNodesList.sortOn("id");
}
/**
* @inheritDoc
* */
public function get noVisibleVNodes():uint {
return _noVisibleVNodes;
}
/**
* @inheritDoc
* */
public function get visibleVEdges():Array {
return _visibleVEdgesList.sortOn("id");
}
/**
* @inheritDoc
* */
[Bindable]
public function get visibilityLimitActive():Boolean {
return _visibilityLimitActive;
}
/**
* @private
* */
public function set visibilityLimitActive(ac:Boolean):void {
/* check for a change */
if(_visibilityLimitActive != ac) {
/* execute the change */
_visibilityLimitActive = ac;
/* activate? */
if(ac) {
if(_currentRootVNode == null) {
LogUtil.warn(_LOG, "No root selected, not creating limited graph, not doing anything.");
return;
}
//LogUtil.debug(_LOG, "getting limited node ids with limit:"+_maxVisibleDistance);
/* 1. Get the spanning tree, rooted in our current root node from
* the graph object.
* 2. Get the hash from this tree, that contains only the nodes
* within the set distance.
* 3. Use this to set our properties for the nodes within the distance
* limit.
*/
setDistanceLimitedNodeIds(_graph.getTree(_currentRootVNode.node).
getLimitedNodes(_maxVisibleDistance));
/* now update all other visibility data structure
* this also forces a redraw() (and layout) of the
* visualisation */
updateVisibility();
}
/* when we deactivate this limit, we render all nodes
* visible! */
else {
setAllVisible();
}
}
}
/**
* @inheritDoc
* */
[Bindable]
public function get maxVisibleDistance():int {
return _maxVisibleDistance;
}
/**
* @private
* */
public function set maxVisibleDistance(md:int):void {
/* check if there was a change */
if(_maxVisibleDistance != md) {
/* if yes, apply the change */
_maxVisibleDistance = md;
//LogUtil.debug(_LOG, "visible distance changed to: "+md);
/* if our current limits are active we create a new
* set of nodes within the distance and update the
* visibility */
if(_visibilityLimitActive) {
if(_currentRootVNode == null) {
LogUtil.warn(_LOG, "No root selected, not creating limited graph");
return;
} else {
setDistanceLimitedNodeIds(_graph.getTree(_currentRootVNode.node).
getLimitedNodes(_maxVisibleDistance));
updateVisibility();
}
}
}
}
/**
* This was added for testing. It may be removed
* again.
* */
public function get currentRootSID():String {
return _currentRootVNode.node.stringid;
}
/**
* @inheritDoc
* */
/* [Bindable] */
public function get currentRootVNode():IVisualNode {
return _currentRootVNode;
}
/**
* @private
* */
public function set currentRootVNode(vn:IVisualNode):void {
/* check for a change */
if(_currentRootVNode != vn) {
/* apply the change */
_currentRootVNode = vn;
/* now update the history with the new node */
_currentVNodeHistory.unshift(_currentRootVNode);
}
//LogUtil.debug(_LOG, "node:"+_currentRootVNode.id+" added to history");
//we always need to the following because:
//the _currentRootVNode can be set when you
//create a node. Then if you set a custom renderer for nodes
//every node is made to be invisible, and because this stuff
//following hasn't been called it stays that way and is not deployed
/* if we are currently limiting node visibility,
* update the set of visible nodes since we
* have changed the root, the spanning tree has changed
* and thus the set of visible nodes */
if(_visibilityLimitActive) {
setDistanceLimitedNodeIds(_graph.getTree(_currentRootVNode.node).
getLimitedNodes(_maxVisibleDistance));
updateVisibility();
} else {
//if the visibility limit is not active, get all the nodes
setDistanceLimitedNodeIds(getNodesAsDictionary());
updateVisibility();
}
}
private function getNodesAsDictionary():Dictionary {
var retVal:Dictionary = new Dictionary();
for each(var node:INode in _graph.nodes)
{
retVal[node] = node;
}
return retVal;
}
public function set scrollBackgroundInDrag(f:Boolean):void {
_scrollBackgroundInDrag = f;
}
public function set moveNodeInDrag(f:Boolean):void {
_moveNodeInDrag = f;
}
/**
* @inheritDoc
* */
public function get showHistory():Boolean {
return _showCurrentNodeHistory;
}
/**
* @private
* */
public function set showHistory(h:Boolean):void {
/* check for a change */
if(_showCurrentNodeHistory != h) {
_showCurrentNodeHistory = h;
/* makes no sense without root set */
if(_currentRootVNode != null) {
/* becomes only active if we have the limit active */
if(_visibilityLimitActive) {
/* now update the visibility. This also applies the
* history information to the node visibility */
updateVisibility();
}
}
}
}
/**
* @inheritDoc
* */
public function get scale():Number {
return _scale;
}
/**
* @private
* */
public function set scale(s:Number):void {
var w:Number = width - width/s;
var h:Number = height - height/s;
//the scroll takes care of the refresh
scroll(-w/2 - _origin.x ,-h/2 - _origin.y, false);
nodeLayer.scaleX = s;
nodeLayer.scaleY = s;
edgeLayer.scaleX = s;
edgeLayer.scaleY = s;
edgeLabelLayer.scaleX = s;
edgeLabelLayer.scaleY = s;
_scale = s;
dispatchEvent(new VisualGraphEvent(VisualGraphEvent.GRAPH_SCALED));
}
/**
* This initialises a VGraph from a Graph object.
* I.e. it crates a VNode for every Node found in
* the Graph and a VEdge for every Edge in the Graph.
* Careful, this currently does not check if the VGraph
* was already initialised and it does not purge anything.
* Things could break if used on an already initialized VGraph.
* */
public function initFromGraph():void {
var node:INode;
var edge:IEdge;
/* create the vnode from the node */
for each(node in _graph.nodes) {
this.createVNode(node);
//LogUtil.debug(_LOG, "created VNode for node:"+node.id);
}
/* we also create the edge objects, since they
* may carry additional label information or something
* like that, but they do not have a view */
for each(edge in _graph.edges) {
this.createVEdge(edge);
}
}
/**
* @inheritDoc
* */
public function clearHistory():void {
_currentVNodeHistory = new Array();
}
/**
* @inheritDoc
* */
public function createNode(sid:String = "", o:Object = null):IVisualNode {
var gnode:INode;
var vnode:IVisualNode;
/* first add a new node to the underlying graph */
gnode = _graph.createNode(sid,o);
/* Then create the VNode with associated with the graph node */
vnode = createVNode(gnode);
/* since it is a requirement from most layouters
* to always have a current root node
* we assign the current root node to the newly
* created node so we have one. Note that this does
* not affect the root node history. */
_currentRootVNode = vnode;
return vnode;
}
/**
* @inheritDoc
* */
public function removeNode(vn:IVisualNode):void {
var n:INode;
var e:IEdge;
var ve:IVisualEdge;
var i:int;
n = vn.node;
/* if the current root node is the
* node to be removed it must be
* changed.
*
* First, we set it to null, then we remove the
* node, then at the end we reset it
* to the first node still in the
* nodes array */
if(vn == _currentRootVNode) {
/* temporary set to null */
_currentRootVNode = null;
}
/* remove all incoming edges */
while(n.inEdges.length > 0) {
e = n.inEdges[0] as IEdge;
ve = e.vedge;
removeVEdge(ve);
_graph.removeEdge(e);
}
/* remove all outgoing edges */
while(n.outEdges.length > 0) {
e = n.outEdges[0] as IEdge;
ve = e.vedge;
removeVEdge(ve);
_graph.removeEdge(e);
}
/* remove the vnode */
removeVNode(vn);
/* remove the node from the graph */
_graph.removeNode(n);
/* now set a new root node, implies that there is
* still a node */
if(_currentRootVNode == null && _graph.noNodes > 0) {
_currentRootVNode = (_graph.nodes[0] as INode).vnode;
}
/* since we removed also edges, we need a refresh */
refresh();
}
/**
* @inheritDoc
* */
public function linkNodes(v1:IVisualNode, v2:IVisualNode):IVisualEdge {
var n1:INode;
var n2:INode;
var e:IEdge;
var ve:IVisualEdge;
/* make sure both nodes do exist */
if(v1 == null || v2 == null) {
throw Error("linkNodes: one of the nodes does not exist");
//return null;
}
n1 = v1.node;
n2 = v2.node;
/* now first link the graph nodes and create the corresponding edge */
e = _graph.link(n1,n2,null);
/* if the edge existed already, e is just the
* already existing edge. But if it existed
* previously it might already have a VEdge.
* So we only create a new VEdge, if it did not exist
* already. */
if(e == null) {
throw Error("Could not create or find Graph edge!!");
} else {
if(e.vedge == null) {
/* we have a new edge, so we create a new VEdge */
ve = createVEdge(e);
} else {
/* existing one, so we use the existing vedge */
LogUtil.info(_LOG, "Edge already existed, returning existing vedge");
ve = e.vedge;
}
}
//LogUtil.debug(_LOG, "linkNodes, created edge "+(e as Object).toString()+" from nodes: "+n1.id+", "+n2.id);
/* this changes the layout, so we have to do a full redraw */
// if we link nodes we may not necesarily want to draw();
/* just refresh the edges */
refresh();
return ve;
}
/**
* @inheritDoc
* */
public function unlinkNodes(v1:IVisualNode, v2:IVisualNode):void {
var n1:INode;
var n2:INode;
var e:IEdge;
var ve:IVisualEdge;
/* make sure both nodes exist */
if(v1 == null || v2 == null) {
throw Error("unlink nodes: one of the nodes does not exist");
return;
}
n1 = v1.node;
n2 = v2.node;
/* find the graph edge */
e = _graph.getEdge(n1,n2);
/* if we do not get an edge, it may simply not exist */
if(e == null) {
LogUtil.warn(_LOG, "No edge found between: "+n1.id+" and "+n2.id);
return;
}
/* now get and remove the VEdge first */
ve = e.vedge;
removeVEdge(ve);
/* now remove the edge itself, basically
* unlinking the nodes */
_graph.removeEdge(e);
refresh();
}
/**
* @inheritDoc
* */
public function scroll(deltaX:Number, deltaY:Number, reset:Boolean):void {
//set the x and y of each node with the diff
// do not commit the change because
// we want it to change the same time as the arrows do
for each(var node:INode in _graph.nodes)
{
node.vnode.x += deltaX;
node.vnode.y += deltaY;
}
//if we are resetting the origin do that
if(reset) {
_origin = new Point(0,0);
}
//update the origin with the new delta
_origin.offset(deltaX,deltaY);
//redraw everything on the next updateDisplayList
refresh();
}
/**
* @inheritDoc
* */
public function redrawNodes():void
{
if(_graph == null) {
LogUtil.debug(_LOG, "_graph object in VisualGraph is null");
return;
}
for each(var node:INode in _graph.nodes) {
if(node.vnode !=null && node.vnode.view != null) {
node.vnode.commit();
node.vnode.view.invalidateDisplayList();
}
}
}
/**
* @inheritDoc
* */
public function refresh():void {
/* this forces the next call of updateDisplayList()
* to redraw all edges and all nodes*/
_forceUpdateEdges = true;
_forceUpdateNodes = true;
if(_graph == null) {
return;
}
//we want this because we have our own
//specific display list things in updateDisplayList
invalidateDisplayList();
}
/**
* @inheritDoc
* */
public function draw(flags:uint = 0):void {
var completeFunction:Function = function():void
{
/* after the layout was done, the layout has
* probably changed again, the layouter will have
* itself set to that, but has maybe not
* invalidated the display list, so we make sure it
* happens here (may not always be necessary) */
invalidateDisplayList();
/* dispatch this change event, so some UI items
* in the application can poll for updated values
* for labels or something.
* XXX To do: specify a subtype for more specific changes
*/
dispatchEvent(new VGraphEvent(VGraphEvent.VGRAPH_CHANGED));
}
/* first refresh does layoutChanges to true and
* invalidate display list */
refresh();
if(flags == VisualGraph.DF_RESET_LL) {
if(_layouter != null && _layouter.linkLength == 0) {
_layouter.linkLength = 100;
}
}
/* we need to do some sanity checks, e.g. if the canvas window
* size was reduced to 0 or linklength 0 or similar things,
* the layouter might crash */
if(_layouter == null ||
_currentRootVNode == null ||
_graph.noNodes == 0 ||
width == 0 ||
height == 0 ||
_layouter.linkLength <= 0)
{
completeFunction();
return;
}
_layouter.layoutPass();
completeFunction();
}
/**
* Refresh the VGraph fully. I.e. recreate and
* reassign all data objects, etc.
* This is a heavy operation */
public function fullVGraphRefresh(xmlData:XML = null, directional:Boolean = false):void {
var graph:IGraph;
var oldroot:IVisualNode;
var oldsid:String;
var newroot:INode;
var theXMLData:XML = xmlData;
var layouter:ILayoutAlgorithm;
/* if we do not have been passed an XML object
* we try to get one from the old graph */
if(theXMLData == null && _graph != null) {
theXMLData = _graph.xmlData;
}
/* still null? then we have to bail out */
if(theXMLData == null) {
LogUtil.warn(_LOG, "No XML object passed or found in old graph");
return;
}
/* reset layouter and remember it */
if(_layouter != null) {
_layouter.resetAll();
layouter = _layouter;
_layouter = null;
}
/* init a graph object with the XML data */
graph = new Graph("myXMLbasedGraphID",directional,theXMLData);
/* remember the old root and id */
oldroot = _currentRootVNode;
oldsid = oldroot.node.stringid;
/* reapply the previous layouter
* IMPORTANT: this has to be done before the
* graph object is set, because otherwise the graph
* attribute in the layouter will not be updated!
*/
_layouter = layouter;
/* set the graph in the VGraph object, this automatically
* initializes the VGraph items */
this.graph = graph;
/* setting a new graph invalidated our old root, we need to reset it */
/* we try to find a node, that has the same string-id as the old root node */
newroot = _graph.nodeByStringId(oldsid);
if(newroot != null) {
this.currentRootVNode = newroot.vnode;
} else {
throw Error("Cannot set a default root, bailing out");
}
/* send an event for controls to reapply their currently
* set values to layouters */
this.dispatchEvent(new VGraphEvent(VGraphEvent.LAYOUTER_CHANGED));
/* trigger a redraw
* XXXX think if we should do that here */
this.draw(VisualGraph.DF_RESET_LL);
}
/**
* this function takes the node with the specified
* string id and selects it as a root
* node, automatically centering the layout around it
* */
public function centerNodeByStringId(nodeID:String):IVisualNode {
var newroot:INode;
if(_graph == null) {
LogUtil.warn(_LOG, "VGraph has no Graph object, probably not correctly initialised, yet");
return null;
}
newroot = _graph.nodeByStringId(nodeID);
/* if we have a node, set its vnode as the new root */
if(newroot) {
/* is it really a new node */
if(newroot.vnode != _currentRootVNode) {
/* set it */
this.currentRootVNode = newroot.vnode;
return newroot.vnode;
} else {
return _currentRootVNode;
}
}
LogUtil.warn(_LOG, "Node with id:"+nodeID+" not found!");
return null;
}
/**
* This calls the base updateDisplayList() method of the
* Canvas and in addition redraws all edges if the layouter
* indicates that the layout has changed.
*
* @inheritDoc
* */
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
/* call the original function */
super.updateDisplayList(unscaledWidth,unscaledHeight);
_clippingMask.graphics.clear();
_clippingMask.graphics.beginFill(0x000000,1);
_clippingMask.graphics.drawRect(0,0,unscaledWidth,unscaledHeight);
_clippingMask.graphics.endFill();
/* now add part to redraw edges */
if(_layouter) {
if(_layouter.layoutChanged) {
redrawEdges();
redrawNodes();
_forceUpdateNodes = false;
_forceUpdateEdges = false;
_layouter.layoutChanged = false;
}
if(_forceUpdateNodes) {
redrawNodes();
_forceUpdateNodes = false;
}
if(_forceUpdateEdges) {
redrawEdges();
_forceUpdateEdges = false;
}
}
}
/* private methods */
/**
* Creates VNode and requires a Graph node to associate
* it with. Originally also created the view, but we no
* longer do that directly but only on demand.
* @param n The graph node to be associated with.
* @return The created VisualNode.
* */
protected function createVNode(n:INode):IVisualNode {
var vnode:IVisualNode;
/* as an id we use the id of the graph node for simplicity
* for now, it is not really used separately anywhere
* we also use the graph data object as our data object.
* the view is set to null and remains so. */
vnode = new VisualNode(this, n, n.id, null, n.data);
/* if the node should be visible by default
* we need to make sure that the view is created */
if(newNodesDefaultVisible) {
setNodeVisibility(vnode, true);
}
/* now set the vnode in the node */
n.vnode = vnode;
/* add the node to the hash to keep track */
_vnodes[vnode] = vnode;
return vnode;
}
/**
* Removes a VNode, this also removes the node's view
* if it existed, but does not touch the Graph node.
* @param vn The VisualNode to be removed.
* */
protected function removeVNode(vn:IVisualNode):void {
var view:UIComponent;
/* get access to the node's view, but get the
* raw view to avoid unnecessary creation of a view
*/
view = vn.rawview;
/* delete reference to the view from the node */
vn.view = null;
/* remove the reference to this node from the graph node */
vn.node.vnode = null;
/* now remove the view component if it existed */
if(view != null) {
removeNodeView(view);
}
/* remove from the visible vnode map if present */
if(_visibleVNodes[vn] != undefined) {
deleteVisibleVNode(vn);
}
/* remove from tracking hash */
delete _vnodes[vn];
/* this should clean up all references to this VNode
* thus freeing it for garbage collection */
}
private function deleteVisibleVNode(vn:IVisualNode):void
{
vn.isVisible = false;
delete _visibleVNodes[vn];
var newVisibleVNodes:Array = new Array();
for each(var node:IVisualNode in _visibleVNodesList)
{
if(node != vn)
newVisibleVNodes.push(node);
}
_visibleVNodesList = newVisibleVNodes;
/* remove the view if there is one */
if(vn.view != null) {
removeNodeView(vn.view, false);
}
--_noVisibleVNodes;
}
/**
* Creates a VEdge from a graph Edge.
* @param e The Graph Edge.
* @return The created VEdge.
* */
protected function createVEdge(e:IEdge):IVisualEdge {
var vedge:IVisualEdge;
var n1:INode;
var n2:INode;
var lStyle:Object;
var edgeAttrs:XMLList;
var attr:XML;
var attname:String;
var attrs:Array;
/* create a copy of the default style */
lStyle = ObjectUtil.copy(_defaultEdgeStyle);
/* extract style data from associated XML data for each parameter */
attrs = ObjectUtil.getClassInfo(lStyle).properties;
for each(attname in attrs) {
if(e.data != null && (e.data as XML).attribute(attname).length() > 0) {
lStyle[attname] = e.data.@[attname];
}
}
vedge = new VisualEdge(this, e, e.id, e.data, null, lStyle);
/* set the VisualEdge reference in the graph edge */
e.vedge = vedge;
/* check if the edge is supposed to be visible */
n1 = e.node1;
n2 = e.node2;
/* if both nodes are visible, the edge should
* be made visible, which may also create a label
*/
if(n1.vnode.isVisible && n2.vnode.isVisible) {
setEdgeVisibility(vedge, true);
}
/* add to tracking hash */
_vedges[vedge] = vedge;
return vedge;
}
protected function edgeClicked(e:MouseEvent):void
{
e.stopImmediatePropagation();
_backgroundDragInProgress = false;
var t:UIComponent = e.target as UIComponent;
if(t == null)
return;
var edge:IVisualEdge = _edgeViewToVEdgeMap[t];
if(edge == null)
return;
dispatchEvent(new VisualEdgeEvent(VisualEdgeEvent.CLICK,edge,e.ctrlKey));
}
protected function edgeRollOver(e:MouseEvent):void
{
CursorManager.removeAllCursors();
var t:UIComponent = e.target as UIComponent;
if(t == null)
return;
var edge:IVisualEdge = _edgeViewToVEdgeMap[t];
if(edge == null)
return;
dispatchEvent(new VisualEdgeEvent(VisualEdgeEvent.ROLL_OVER,edge,e.ctrlKey));
}
protected function edgeRollOut(e:MouseEvent):void
{
CursorManager.setCursor(HAND_CURSOR,3);
var t:UIComponent = e.target as UIComponent;
if(t == null)
return;
var edge:IVisualEdge = _edgeViewToVEdgeMap[t];
if(edge == null)
return;
dispatchEvent(new VisualEdgeEvent(VisualEdgeEvent.ROLL_OUT,edge,e.ctrlKey));
}
/**
* Remove a VisualEdge, but leaves the Graph Edge alone.
* @param ve The VisualEdge to be removed.
* */
protected function removeVEdge(ve:IVisualEdge):void {
/* just in case */
if(ve == null) {
return;
}
/* first turn it invisible, which should
* remove the labelview */
setEdgeVisibility(ve, false);
delete _edgeViewToVEdgeMap[ve.edgeView];
/* remove the reference from the real edge */
ve.edge.vedge = null;
/* remove from tracking hash */
delete _vedges[ve];
}
/**
* Purges the VGraph by dropping all VNodes and VEdges.
* This is a bit tricky, since we do not really
* keep track of them in the VGraph, they are only referenced
* by the Graph nodes and egdes.
* */
protected function purgeVGraph():void {
var ves:Array = new Array;
var vns:Array = new Array;
var ve:IVisualEdge;
var vn:IVisualNode;
/* this appears rather inefficient, however
* ObjectUtil.copy does not work on dictionaries
* currently I have no other solution
*/
for each(ve in _vedges) {
ves.unshift(ve);
}
for each(vn in _vnodes) {
vns.unshift(vn);
}
LogUtil.debug(_LOG, "purgeVGraph called");
if(_graph != null) {
for each(ve in ves) {
removeVEdge(ve);
}
for each(vn in vns) {
removeVNode(vn);
}
} else {
LogUtil.warn(_LOG, "we had no graph to purge from, so nothing was done");
}
}
/**
* Redraw all edges, this is called from the updateDisplayList()
* method.
* @inheritDoc
* */
public function redrawEdges():void {
var vn1:IVisualNode;
var vn2:IVisualNode;
var vedge:IVisualEdge;
/* make sure we have a graph */
if(_graph == null) {
LogUtil.debug(_LOG, "_graph object in VisualGraph is null");
return;
}
for each(vedge in _edgeViewToVEdgeMap)
{
IEdgeRenderer(vedge.edgeView).render();
}
}
/**
* Lookup a node by its UIComponent. This is more a convenience
* method with some sanity check. Primarily used by event handlers.
* @param c The component to find the VisualNode for.
* @return The found Node.
* @throws An Error if the component was not registered in the map.
* */
protected function lookupNode(c:UIComponent):IVisualNode {
var vn:IVisualNode = _nodeViewToVNodeMap[c];
if(vn == null) {
throw Error("Component not in viewToVNodeMap");
}
return vn;
}
/**
* Create a "view" object (UIComponent) for the given node and
* return it. These methods are only exported to be used by
* the VisualNode. Alas, AS does not provide the "friend" directive.
* Not sure how to get around this problem right now.
* @param vn The node to replace/add a view object.
* @return The created view object.
* */
protected function createVNodeComponent(vn:IVisualNode):UIComponent {
var mycomponent:UIComponent = null;
if(_itemRendererFactory != null) {
mycomponent = _itemRendererFactory.newInstance();
} else {
mycomponent = new UIComponent();
}
/* assigns the item (VisualNode) to the IDataRenderer part of the view
* this is important to access the data object of the VNode
* which contains information for rendering. */
if(mycomponent is IDataRenderer) {
(mycomponent as IDataRenderer).data = vn;
}
/* set initial x/y values */
mycomponent.x = this.width / 2.0;
mycomponent.y = this.height / 2.0;
/* add event handlers for dragging and double click */
mycomponent.doubleClickEnabled = true;
mycomponent.addEventListener(MouseEvent.DOUBLE_CLICK, nodeDoubleClick,false,0,true);
mycomponent.addEventListener(MouseEvent.MOUSE_DOWN, nodeMouseDown,false,0,true);
mycomponent.addEventListener(MouseEvent.ROLL_OVER, nodeRollOver,false,0,true);
mycomponent.addEventListener(MouseEvent.ROLL_OUT, nodeRollOut,false,0,true);
mycomponent.addEventListener(MouseEvent.CLICK, nodeMouseClick,false,0,true);
/* enable bitmap cachine if required */
mycomponent.cacheAsBitmap = cacheRendererObjects;
/* add the component to its parent component */
nodeLayer.addChild(mycomponent);
/* do we have an effect set for addition of
* items? If yes, create and start it. */
if(addItemEffect != null) {
addItemEffect.createInstance(mycomponent).startEffect();
}
/* register it the view in the vnode and the mapping */
vn.view = mycomponent;
_nodeViewToVNodeMap[mycomponent] = vn;
/* we need to invalidate the display list since
* we created new children */
refresh();
return mycomponent;
}
private function nodeRollOver(e:MouseEvent):void {
CursorManager.removeAllCursors()
}
private function nodeRollOut(e:MouseEvent):void {
CursorManager.setCursor(HAND_CURSOR,3);
}
/**
* Remove a "view" object (UIComponent) for the given node and specify whether
* this should honor any specified add/remove effects.
* These methods are only exported to be used by
* the VisualNode. Alas, AS does not provide the "friend" directive.
* Not sure how to get around this problem right now.
* @param component The UIComponent to be removed.
* @param honorEffect To specify whether the effect should be applied or not.
* */
protected function removeNodeView(component:UIComponent, honorEffect:Boolean = true):void {
var vn:IVisualNode;
/* if there is an effect, start the effect and register a
* handler that actually calls this method again, but
* with honorEffect set to false */
if(honorEffect && (removeItemEffect != null)) {
removeItemEffect.addEventListener(EffectEvent.EFFECT_END,
removeEffectDone);
removeItemEffect.createInstance(component).startEffect();
} else {
/* remove the component from it's parent (which should be the canvas) */
if(component.parent != null) {
component.parent.removeChild(component);
}
/* remove event mouse listeners */
component.removeEventListener(MouseEvent.DOUBLE_CLICK,nodeDoubleClick);
component.removeEventListener(MouseEvent.MOUSE_DOWN,nodeMouseDown);
component.removeEventListener(MouseEvent.CLICK,nodeMouseClick);
component.removeEventListener(MouseEvent.MOUSE_UP, dragEnd);
/* get the associated VNode and remove the view from it
* and also remove the map entry */
vn = _nodeViewToVNodeMap[component];
vn.view = null;
delete _nodeViewToVNodeMap[component];
}
}
/**
* Create a "view" object (UIComponent) for the given edge and
* return it.
* @param ve The edge to replace/add a view object.
* @return The created view object.
* */
protected function createVEdgeLabelView(ve:IVisualEdge):UIComponent {
var mycomponent:UIComponent = null;
if(_edgeLabelRendererFactory != null) {
mycomponent = _edgeLabelRendererFactory.newInstance();
} else {
/* this is only for the basic default */
mycomponent = new Label; // this is our default label.
mycomponent.setStyle("textAlign","center");
if(ve.data != null) {
(mycomponent as Label).text = ve.data.@association;
}
}
/* assigns the edge to the IDataRenderer part of the view
* this is important to access the data object of the VEdge
* which contains information for rendering. */
if(mycomponent is IDataRenderer) {
(mycomponent as IDataRenderer).data = ve;
}
/* enable bitmap cachine if required */
mycomponent.cacheAsBitmap = cacheRendererObjects;
/* add the component to its parent component
* this can create problems, we have to see where we
* check for all children
* Add after the edges layer, but below all other elements such as nodes */
edgeLabelLayer.addChild(mycomponent);
ve.labelView = mycomponent;
_edgeLabelViewToVEdgeMap[mycomponent] = ve;
/* we need to invalidate the display list since
* we created new children */
refresh();
return mycomponent;
}
/**
* Remove a "view" object (UIComponent) for the given edge.
* @param component The UIComponent to be removed.
* */
protected function removeVEdgeLabelView(component:UIComponent):void {
var ve:IVisualEdge;
/* remove the component from it's parent (which should be the canvas) */
if(component.parent != null) {
component.parent.removeChild(component);
}
/* get the associated VEdge and remove the view from it
* and also remove the map entry */
ve = _edgeLabelViewToVEdgeMap[component];
ve.labelView = null;
delete _edgeLabelViewToVEdgeMap[component];
}
/**
* Create a "view" object (UIComponent) for the given edge and
* return it.
* @param ve The edge to replace/add a view object.
* @return The created view object.
* */
protected function createVEdgeView(ve:IVisualEdge):IEdgeRenderer {
var mycomponent:IEdgeRenderer = null;
if(_edgeRendererFactory != null) {
mycomponent = edgeRendererFactory.newInstance();
} else {
/* this is only for the basic default */
mycomponent = new BaseEdgeRenderer(); // this is our default label.
}
mycomponent.percentWidth = 100;
mycomponent.percentHeight = 100;
UIComponent(mycomponent).useHandCursor = true;
UIComponent(mycomponent).buttonMode = true;
mycomponent.addEventListener(MouseEvent.CLICK,edgeClicked,false,0,true);
mycomponent.addEventListener(MouseEvent.ROLL_OVER,edgeRollOver,false,0,true);
mycomponent.addEventListener(MouseEvent.ROLL_OUT,edgeRollOut,false,0,true);
/* assigns the edge to the IDataRenderer part of the view
* this is important to access the data object of the VEdge
* which contains information for rendering. */
if(mycomponent is IDataRenderer) {
(mycomponent as IDataRenderer).data = ve;
}
/* enable bitmap cachine if required */
mycomponent.cacheAsBitmap = cacheRendererObjects;
/* add the component to its parent component
* this can create problems, we have to see where we
* check for all children
* Add after the edges layer, but below all other elements such as nodes */
edgeLayer.addChild(DisplayObject(mycomponent));
ve.edgeView = mycomponent;
_edgeViewToVEdgeMap[mycomponent] = ve;
/* we need to invalidate the display list since
* we created new children */
refresh();
return mycomponent;
}
/**
* Remove a "view" object (UIComponent) for the given edge.
* @param component The UIComponent to be removed.
* */
protected function removeVEdgeView(component:IEdgeRenderer):void {
var ve:IVisualEdge;
/* remove the component from it's parent (which should be the canvas) */
if(component.parent != null) {
component.parent.removeChild(DisplayObject(component));
}
/* get the associated VEdge and remove the view from it
* and also remove the map entry */
ve = _edgeViewToVEdgeMap[component];
ve.edgeView = null;
delete _edgeViewToVEdgeMap[component];
}
/**
* Event handler for a removal node procedure. Calls
* removeComponent with a flag to avoid doing the effect again.
* */
protected function removeEffectDone(event:EffectEvent):void {
var mycomponent:UIComponent = event.effectInstance.target as UIComponent;
/* call remove component again, but specify to ignore the effect */
removeNodeView(mycomponent, false);
}
/**
* Event handler to work on double-click events.
* Any double click also counts as a drop event to
* the layouter. But primarily the double click
* sets a new root node.
* @param e The corresponding event.
* */
protected function nodeDoubleClick(e:MouseEvent):void {
var comp:UIComponent;
var vnode:IVisualNode;
/* get the view object that was klicked on (actually
* the one that has the event handler registered, which
* is the VNode's view */
comp = (e.currentTarget as UIComponent);
/* get the associated VNode */
vnode = lookupNode(comp);
var evt:VisualNodeEvent = new VisualNodeEvent(VisualNodeEvent.DOUBLE_CLICK, vnode.node,e.ctrlKey);
dispatchEvent(evt);
//LogUtil.debug(_LOG, "double click!");
/* Now we change the root node, we go through
* our public setter method to get all associated
* updates done. */
// this.currentRootVNode = vnode; (disabled)
//LogUtil.debug(_LOG, "currentVNode:"+this.currentRootVNode.id);
/* here we still want to implicitly redraw */
draw();
}
/**
* This is the event handler for a mouse down event on a node
* event. Currently does only one thing:
* - Starts a drag operation of this node.
* @param e The associated event.
* */
protected function nodeMouseDown(e:MouseEvent):void {
_nodeMovedInDrag = false;
dragBegin(e);
}
/**
* This is the event handler for a mouse click on a node
* The purpose of this event is to broadcast that a node has
* been clicked on by a user, it has no other internal function
*
* @param e The associated event
*/
protected function nodeMouseClick(e:MouseEvent):void {
if(e.currentTarget is UIComponent) {
var ecomponent:UIComponent = (e.currentTarget as UIComponent);
if(_nodeMovedInDrag)
{
return;
}
/* get the associated VNode of the view */
var evnode:IVisualNode = _nodeViewToVNodeMap[ecomponent];
/* stop propagation to prevent a concurrent backgroundDrag */
e.stopImmediatePropagation();
dispatchEvent(new VisualNodeEvent(VisualNodeEvent.CLICK,evnode.node,e.ctrlKey));
}
}
/**
* Start a drag operation. This sets the drag node and
* registeres a 'MouseMove' event handler with the
* VNode, so it can follow the mouse movement.
* @param event The MouseEvent that was triggered by clicking on the node.
* @see handleDrag()
* */
protected function dragBegin(event:MouseEvent):void {
var ecomponent:UIComponent;
var evnode:IVisualNode;
var pt:Point;
//LogUtil.debug(_LOG, "DragBegin was called...");
/* if there is an animation in progress, we ignore
* the drag attempt */
if(_layouter && _layouter.animInProgress) {
LogUtil.info(_LOG, "Animation in progress, drag attempt ignored");
return;
}
/* make sure we get the right component */
if(event.currentTarget is UIComponent) {
ecomponent = (event.currentTarget as UIComponent);
/* get the associated VNode of the view */
evnode = _nodeViewToVNodeMap[ecomponent];
/* stop propagation to prevent a concurrent backgroundDrag */
event.stopImmediatePropagation();
if(evnode != null) {
if(!dragLockCenter) {
// lockCenter is false, use the mouse coordinates at the point
pt = new Point(ecomponent.mouseX, ecomponent.mouseY);
} else {
// lockCenter is true, ignore the mouse coordinates
pt = new Point(ecomponent.width/2,ecomponent.height/2);
}
/* Save the offset values in the map
* so we can compute x and y correctly in case
* we use lockCenter */
_drag_x_offsetMap[ecomponent] = pt.x
_drag_y_offsetMap[ecomponent] = pt.y;
/* now we would need to set the bounds
* rectangle in _drag_boundsMap, but this is
* currently not implemented *
_drag_boundsMap[ecomponent] = rectangle;
*/
/* Register an eventListener with the component's stage that
* handles any mouse move. This wires the component
* to the mouse. On every mouse move, the event handler
* is called, which updates its coordinates.
* We need to save the drag component, since we have to
* register the event handler with the stage, not the component
* itself. But from the stage we have no way to get back to
* the component or the VNode in case of the mouse move or
* drop event.
*/
_dragComponent = ecomponent;
ecomponent.stage.addEventListener(MouseEvent.MOUSE_MOVE, handleDrag);
this.addEventListener(MouseEvent.MOUSE_UP,dragEnd);
/* also register a drop event listener */
// ecomponent.stage.addEventListener(MouseEvent.MOUSE_UP, dragEnd);
/* and inform the layouter about the dragEvent */
_nodeMouseDownLocation = globalMousePosition();
dispatchEvent(new VisualNodeEvent(VisualNodeEvent.DRAG_START,evnode.node,event.ctrlKey));
_layouter.dragEvent(event, evnode);
} else {
throw Error("Event Component was not in the viewToVNode Map");
}
} else {
throw Error("MouseEvent target was not UIComponent");
}
}
/**
* Called everytime the mouse moves after the dragBegin() method has
* been called. Updates the position of the Component based on
* the location of the mouse cursor.
* @param event The MouseMove event that has been triggered.
*/
protected function handleDrag(event:MouseEvent):void {
var myvnode:IVisualNode = _nodeViewToVNodeMap[_dragComponent];
/* Sometimes we get spurious events */
if(_dragComponent == null) {
LogUtil.info(_LOG, "received handleDrag event but _dragComponent is null, ignoring");
return;
}
if (_moveNodeInDrag) {
myvnode.viewX = mouseX/scale - _drag_x_offsetMap[_dragComponent];
myvnode.viewY = mouseY/scale - _drag_y_offsetMap[_dragComponent];
myvnode.refresh();
_nodeMovedInDrag = true;
}
_layouter.dragContinue(event, myvnode);
//refresh();
}
/**
* This handles a background drag (i.e. scroll). The
* event listener is usually registered with the canvas,
* i.e. this object.
* @param event The triggered event.
* */
protected function backgroundDragBegin(event:MouseEvent):void {
var mycomponent:UIComponent = event.target as UIComponent;
if(_edgeViewToVEdgeMap[mycomponent] != null ||
_edgeLabelViewToVEdgeMap[mycomponent] != null ||
_nodeViewToVNodeMap[mycomponent] != null)
return;
const mpoint:Point = globalMousePosition();
/* if there is an animation in progress, we ignore
* the drag attempt */
if(_layouter && _layouter.animInProgress) {
LogUtil.info(_LOG, "Animation in progress, drag attempt ignored");
dispatchEvent(new VisualGraphEvent(VisualGraphEvent.BACKGROUND_CLICK));
return;
}
/* set the progress flag and save the starting coordinates */
_backgroundDragInProgress = true;
_dragCursorStartX = mpoint.x;
_dragCursorStartY = mpoint.y;
_mouseDownLocation = mpoint;
/* register the backgroundDrag listener to react to
* the mouse movements */
this.addEventListener(MouseEvent.MOUSE_MOVE, backgroundDragContinue);
this.addEventListener(MouseEvent.MOUSE_UP,dragEnd);
this.addEventListener(MouseEvent.ROLL_OUT,dragEnd);
/* and inform the layouter about the dragEvent */
if(_layouter) {
_layouter.bgDragEvent(event);
}
}
/**
* This does the actual background drag by having
* all UIComponents move to follow the mouse
* @param event The triggered mouse move event.
* */
protected function backgroundDragContinue(event:MouseEvent):void {
const mpoint:Point = globalMousePosition();
var deltaX:Number;
var deltaY:Number;
if (_scrollBackgroundInDrag) {
/* compute the movement offset of this move by
* subtracting the current mouse position from
* the last mouse position */
deltaX = mpoint.x - _dragCursorStartX;
deltaY = mpoint.y - _dragCursorStartY;
deltaX /= scaleX;
deltaY /= scaleY;
/* scroll all objects by this offset */
scroll(deltaX, deltaY,false);
}
/* and inform the layouter about the dragEvent */
if(_layouter) {
_layouter.bgDragContinue(event);
}
/* reset the drag start point for the next step */
_dragCursorStartX = mpoint.x;
_dragCursorStartY = mpoint.y;
/* make sure edges are redrawn */
_forceUpdateEdges = true;
//invalidateDisplayList();
}
/**
* This method handles the drop event (usually MOUSE_UP).
* It stops any dragging in progress (including background drag)
* and unregisters the current dragged node.
* @param event The triggered event.
* */
protected function dragEnd(event:MouseEvent):void {
const mpoint:Point = globalMousePosition();
var mycomp:UIComponent;
var myback:DisplayObject;
var myvnode:IVisualNode;
this.removeEventListener(MouseEvent.ROLL_OUT,dragEnd);
this.removeEventListener(MouseEvent.MOUSE_UP,dragEnd);
if(_backgroundDragInProgress) {
/* if it was a background drag we stop it here */
_backgroundDragInProgress = false;
/* get the background drag object, which is usually
* the canvasm so we just set it to this */
myback = (this as DisplayObject);
/* unregister event handler */
myback.removeEventListener(MouseEvent.MOUSE_MOVE,backgroundDragContinue);
/* and inform the layouter about the dropEvent */
if(_layouter) {
_layouter.bgDropEvent(event);
}
if(event.type == MouseEvent.ROLL_OUT) {
CursorManager.removeAllCursors();
}
//dispatch the drag event only if we have moved somewhere
if(_mouseDownLocation &&
Math.abs(mpoint.x - _mouseDownLocation.x) > 2 ||
Math.abs(mpoint.y - _mouseDownLocation.y) > 2) {
dispatchEvent(new VisualGraphEvent(VisualGraphEvent.BACKGROUND_DRAG_END));
}else{
dispatchEvent(new VisualGraphEvent(VisualGraphEvent.BACKGROUND_CLICK));
}
} else {
/* if it was no background drag, the component
* is the saved dragComponent */
mycomp = _dragComponent;
/* But sometimes the dragComponent was already null,
* in this case we have to ignore the thing. */
if(mycomp == null) {
LogUtil.info(_LOG, "dragEnd: received dragEnd but _dragComponent was null, ignoring");
return;
}
/* remove the event listeners */
// HACK: I have to check the stage because there are eventual components not added to the display list
if (mycomp.stage != null)
{
mycomp.stage.removeEventListener(MouseEvent.MOUSE_MOVE, handleDrag);
}
/* get the associated VNode to notify the layouter */
myvnode = _nodeViewToVNodeMap[mycomp];
if(_layouter) {
_layouter.dropEvent(event, myvnode);
}
if(_nodeMouseDownLocation &&
Math.abs(mpoint.x - _nodeMouseDownLocation.x) > 2 ||
Math.abs(mpoint.y - _nodeMouseDownLocation.y) > 2) {
dispatchEvent(new VisualNodeEvent(VisualNodeEvent.DRAG_END,myvnode.node,event.ctrlKey));
}
/* reset the dragComponent */
_dragComponent = null;
}
}
/**
* return the current mouse position, used by
* certain drag&drop issues
* */
protected function globalMousePosition():Point {
return localToGlobal(new Point(mouseX, mouseY));
}
/**
* 1. saves the old nodeID hash object.
* 2. sets the new _nodeIDsWithinDistanceLimit Object from the object
* provided (typically provided from the GTree of a Graph).
* 3. updates the amount of nodes in that object, by linearly
* counting them. This may be optimized...
* @param vnids Object containing a hash with all node id's currently within the distance limit.
* */
protected function setDistanceLimitedNodeIds(vnids:Dictionary):void {
var val:Boolean;
var amount:uint;
var vn:IVisualNode;
var n:INode;
/* reset the amount */
amount = 0;
/* save the old hash */
_prevNodeIDsWithinDistanceLimit = _nodeIDsWithinDistanceLimit;
/* set the new hash object */
_nodeIDsWithinDistanceLimit = new Dictionary;
/* walk through the hash and build the distanceLimit hash */
for each(n in vnids) {
vn = n.vnode;
_nodeIDsWithinDistanceLimit[vn] = vn;
/* increase the amount */
++amount;
}
/* count all entries in this hash */
for each(val in vnids) {
if(val) {
++amount;
}
}
/* set the new amount */
_noNodesWithinDistance = amount;
//LogUtil.debug(_LOG, "current visible nodeids:"+_noNodesWithinDistance);
}
/**
* This needs to walk through all nodes in the graph, as some nodes
* have become invisible and other have become visible. There may be
* a better way to do this, when adjusting the visibility but it is
* not that clear.
*
* walk through the graph and the limitedGraph and
* turn off visibility for those that are not listed in
* both
* beware that the limited graph has no VItems, so
* we don't really need it, we would rather need
* an array of node ids....
* */
protected function updateVisibility():void {
var n:INode;
var e:IEdge;
var edges:Array;
var treeparents:Dictionary;
var vn:IVisualNode;
var vno:IVisualNode;
var newVisibleNodes:Dictionary;
var potentialInvisibleNodes:Dictionary;
/* since a layouter that uses timer based iterations
* might find itself on a changing node set, we need
* to stop/reset anything before altering the node
* visibility */
if(_layouter != null) {
_layouter.resetAll();
}
//LogUtil.debug(_LOG, "update node visibility");
/* create a copy of the currently visible
* node set, as the set for nodes to potentially
* turned invisible */
potentialInvisibleNodes = new Dictionary;
for each(vn in _visibleVNodes) {
potentialInvisibleNodes[vn] = vn;
}
/* now populate the set of nodes which should be
* turned visible, first by using the nodes within
* distance limit */
newVisibleNodes = new Dictionary;
for each(vn in _nodeIDsWithinDistanceLimit) {
newVisibleNodes[vn] = vn;
}
/* now add the history nodes to the set of new visible
* nodes if the history is enabled */
/* Step 3: render all (new?) history nodes and nodes on the path visible (if applicable) */
if(_showCurrentNodeHistory) {
/* this is mapping in the tree that provides a parent
* for each single node in the tree
* we need this to find the way to the root */
treeparents = _graph.getTree(_currentRootVNode.node).parents;
for each(vn in _currentVNodeHistory) {
n = vn.node;
/* we cannot use vn here, because it is n that is changed
* in this while loop. Basically we are walking the tree
* backward from the current vnode's node n to the root
* for every vn in the history */
while(n.vnode != _currentRootVNode) {
/* set it visible */
newVisibleNodes[n.vnode] = n.vnode;
//setNodeVisibility(n.vnode, true);
/* move to the parent node */
n = treeparents[n];
if(n == null) {
throw Error("parent node was null but node was not root node");
}
}
}
}
/* now from each set remove the common nodes, these
* are the nodes that should remain visible, so they
* must not be turned invisible and should also not
* be turned visible again. */
for each(vn in potentialInvisibleNodes) {
if(newVisibleNodes[vn] != null) {
/* this is a common node, remove it from
* both dictionaries
*/
delete potentialInvisibleNodes[vn];
delete newVisibleNodes[vn];
}
}
/* now finally turn all toInvisibleNodes invisible
* likewise any edge adjacent to an invisible node
* will become invisible */
for each(vn in potentialInvisibleNodes) {
setNodeVisibility(vn, false);
}
/* and all new visible nodes to visible */
for each(vn in newVisibleNodes) {
setNodeVisibility(vn, true);
}
/* and now walk again to update the edges */
for each(vn in potentialInvisibleNodes) {
updateConnectedEdgesVisibility(vn);
}
/* and all new visible nodes to visible */
for each(vn in newVisibleNodes) {
updateConnectedEdgesVisibility(vn);
}
/* send an event */
this.dispatchEvent(new VGraphEvent(VGraphEvent.VISIBILITY_CHANGED));
}
/**
* This methods walks through all nodes and updates
* the edge visibility (only the edge visibility)
* taking into account three factors:
* visibility of adjacent nodes and
* if we want edge labels or not at all
* */
protected function updateEdgeVisibility():void {
var vn:IVisualNode;
for each(vn in _visibleVNodes) {
updateConnectedEdgesVisibility(vn);
}
}
/**
* Reset visibility of all nodes, all nodes are back to visible.
* This can be a very very heavy operation if you have many nodes.
* */
protected function setAllVisible():void {
var n:INode;
var e:IEdge;
/* not sure if this is really, really needed, but
* since similar code was added, I optimise it a bit.
*/
if(_graph == null) {
LogUtil.warn(_LOG, "setAllVisible() called, but graph is null");
return;
}
/* since a layouter that uses timer based iterations
* might find itself on a changing node set, we need
* to stop/reset anything before altering the node
* visibility */
if(_layouter != null) {
_layouter.resetAll();
}
/* recreate those, this is cheaper probably */
_visibleVNodes = new Dictionary;
_visibleVNodesList = new Array;
_noVisibleVNodes = 0;
for each(n in _graph.nodes) {
setNodeVisibility(n.vnode, true);
}
/* same for edges */
_visibleVEdges = new Dictionary;
_visibleVNodesList = new Array;
for each(e in _graph.edges) {
setEdgeVisibility(e.vedge, true);
}
}
/**
* Reset visibility of all nodes, all nodes are INVISIBLE.
* */
protected function setAllInVisible():void {
var vn:IVisualNode;
var ve:IVisualEdge;
/* not sure if this is really, really needed, but
* since similar code was added, I optimise it a bit.
*/
if(_graph == null) {
LogUtil.warn(_LOG, "setAllInVisible() called, but graph is null");
return;
}
/* since a layouter that uses timer based iterations
* might find itself on a changing node set, we need
* to stop/reset anything before altering the node
* visibility */
if(_layouter != null) {
_layouter.resetAll();
}
for each(vn in _visibleVNodes) {
setNodeVisibility(vn, false);
}
for each(ve in _visibleVEdges) {
setEdgeVisibility(ve, false);
}
}
/**
* Reset visibility of all edges to INVISIBLE.
* */
protected function setAllEdgesInVisible():void {
var ve:IVisualEdge;
for each(ve in _visibleVEdges) {
setEdgeVisibility(ve, false);
}
}
/**
* This sets a VNode visible or invisible, updating all related
* data.
* @param vn The VisualNode to be turned invisible or not.
* @param visible The indicator if visible or not.
* */
protected function setNodeVisibility(vn:IVisualNode, visible:Boolean):void {
var comp:UIComponent;
/* was there actually a change, if not issue a warning */
if(vn.isVisible == visible) {
LogUtil.warn(_LOG, "Tried to set node:"+vn.id+" visibility to:"+visible.toString()+" but it was already.");
return;
}
if(visible == true) {
/* set the node to visible, this might create a view for it */
vn.isVisible = true;
/* add it to the hash of currently visible nodes */
_visibleVNodes[vn] = vn;
if(_visibleVNodesList.indexOf(vn) == -1)
_visibleVNodesList.push(vn);
/* increase the counter */
++_noVisibleVNodes;
/* create the node's view */
comp = createVNodeComponent(vn);
/* set it to visible, should be default anyway */
comp.visible = true;
} else { // i.e. set to invisible
deleteVisibleVNode(vn);
}
}
/**
* This sets a VEdge visible or invisible, updating all related
* data.
* @param ve The VisualEdge to be turned invisible or not.
* @param visible The indicator if visible or not.
* */
protected function setEdgeVisibility(ve:IVisualEdge, visible:Boolean):void {
var labelComp:UIComponent;
var edgeComp:IEdgeRenderer;
/* was there actually a change, if not issue a warning */
if(ve.isVisible == visible) {
//LogUtil.warn(_LOG, "Tried to set vedge:"+ve.id+" visibility to:"+visible.toString()+" but it was already.");
return;
}
if(visible == true) {
/* add it to the hash of currently visible nodes */
_visibleVEdges[ve] = ve;
if(_visibleVEdgesList.indexOf(ve) == -1)
_visibleVEdgesList.push(ve);
/* check if there is no view and we need one */
if(_displayEdgeLabels && ve.labelView == null) {
labelComp = createVEdgeLabelView(ve);
}
if(ve.edgeView == null)
edgeComp = createVEdgeView(ve);
/* set the edges view to visible */
ve.isVisible = true;
} else { // i.e. set to invisible
/* render node invisible, thus potentially destroying its view */
ve.isVisible = false;
deleteVisibleVEdge(ve);
}
}
private function deleteVisibleVEdge(ve:IVisualEdge):void
{
ve.isVisible = false;
var newEdgeList:Array = new Array();
for each(var e:IVisualEdge in _visibleVEdgesList)
{
if(e != ve)
newEdgeList.push(e);
}
_visibleVEdgesList = newEdgeList;
/* remove the view if there is one */
if(ve.labelView != null) {
removeVEdgeLabelView(ve.labelView);
}
if(ve.edgeView != null) {
removeVEdgeView(ve.edgeView);
}
/* remove it from the hash */
delete _visibleVEdges[ve];
}
/**
* This methods walks through all edges connected
* to a node and sets them either visible or invisible
* depending on the visibility of the given node and
* the node on the other end. An edge is only visible
* if both nodes are visible.
* @param vn The VisualNode of which connected edges should be updated.
* */
protected function updateConnectedEdgesVisibility(vn:IVisualNode):void {
var edges:Array;
var ovn:IVisualNode;
var e:IEdge;
/* now here we have to test each edges othernode
* if it is also visible */
edges = vn.node.inEdges;
/* concat might lead to duplication in the case of
* undirected graphs... :( not sure how to efficiently
* only add items which are not there, yet?
*/
edges = edges.concat(vn.node.outEdges);
for each(e in edges) {
/* get the other node at the end of the edge */
ovn = e.othernode(vn.node).vnode;
/* if this node either is still visible or in the
* list to become visible, then the edge is also
* visible */
if(vn.isVisible && ovn.isVisible) {
setEdgeVisibility(e.vedge,true);
} else {
setEdgeVisibility(e.vedge,false);
}
}
}
public function calcNodesBoundingBox():Rectangle {
var children:Array;
var result:Rectangle;
children = nodeLayer.getChildren();
result = new Rectangle(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY);
if(children.length == 0) {
LogUtil.warn(_LOG, "Canvas has no children, not even the drawing surface!");
}
for(var i:int = 0;i < children.length; ++i) {
var view:UIComponent = (children[i] as UIComponent);
if(view.visible) {
result.left = Math.min(result.left, view.x);
result.right = Math.max(result.right, view.x + view.width);
result.top = Math.min(result.top, view.y)
result.bottom = Math.max(result.bottom, view.y+view.height);
}
}
return result;
}
}
}