看看这变态的自动布局

自动布局就是对数据元素中的节点进行定位,使节点分散在到一个比较好看的显示效果,自动布局的实质就是一种布局的算法的实现。比如TWaver默认提供圆形布局、交错布局、树形布局和弹簧布局,此外标签云、bus总线等也属于布局的应用,但需求总是变化无穷,数据结构不同,要求的呈现效果也不同,自动布局的定制在所难免,本文将介绍一种比较少见的布局效果。

 

默认的自动布局

首先我们来看看默认的自动布局的使用

var autoLayouter:AutoLayouter = new AutoLayouter(network);
autoLayouter.doLayout(Consts.LAYOUT_SYMMETRY);

代码分两步,首先定义一个自动布局器关联了network组件,然后调用自动布局,进行交错布局。
可以看出,TWaver中自动布局都是通过AutoLayouter类来实现,而布局类型包括:Consts.LAYOUT_***
布局的效果如下,实际程序中可以看到布局过程的动画效果:

看看这变态的自动布局

自动布局的实质

自动布局是自动对数据容器中所有网元进行重新定位,使节点分散开到好的显示效果,其实质是一种布局算法的实现,所以要定制自动布局就是要实现一种布局算法,以确定所有网元节点的位置。
TWaver
并没有自动布局的接口,通常一个自动布局器会实现一个叫“doLayout()”的函数,以保持命名上的统一。

自动布局“接口函数”
CustomLayouter#public function getLayoutResult():Dictionary
CustomLayouter#public function doLayout():void

getLayoutResult
getLayoutResult()
函数是布局算法的核心,其返回的是节点位置的计算结果,是一个Dictionary类型,< , >类型是。

doLayout
doLayout()
函数包含两步,先调用getLayoutResult()得到布局位置信息,然后将这些信息,设置到网元节点上,类似下面的代码:

public function doLayout(box:ElementBox, root:IData = null):void{
  var result:Dictionary = getLayoutResult(box, root);
  for(var element:* in result){
    var location:Point = result[element];
    (element as Node).location = location;
  }
}

 

从上面的示例代码可以看出,实际使用中会传入一些参数,这些根据实际情况而定。此外这里也可以使用动画的平移效果,起一个Timer,动画的将节点从原始位置移动到新的位置。

开始定制

下面开始布局算法,这里我们将布出下面的效果,整体是一个树结构,从左到右四层,最后一层使用Bus布局:

看看这变态的自动布局

因为只是一个示例,布局实现不可能尽善尽美,所以只提供些实现的思路,算是抛砖引玉,作为参考:

整理数据结构

首先理清数据的结构,本例是一个树状布局,具有固定的四层结构,故本例将每一层的节点找出来,如第一层一个节点,第二层四个节点,第三层十二个节点,第四层可能右八十多个节点。
本例中使用了dataBox的广度遍历,每遍历完一层,就对这层上的节点作布局,这样一次遍历完,布局也做完,也就是说“整理数据结构”和“分别对每层布局”是同时进行的,但这并不是很好的示范,通常还是老老实实先整理数据的结构,甚至做一些模型的封装了表示这些模型,然后再做布局。

 

看看这变态的自动布局

使用dataBox的广度遍历,每遍历完一层,就对这层上的节点作布局:

 

public function getLayoutResult(box:ElementBox, root:IData = null):Dictionary{
	var result:Dictionary = new Dictionary();

	function _calculateLocations(queue:Array, layer:int):void{
		var sum:int = queue.length;
		if(sum == 0){
			return;
		}
		queue.forEach(function(node:IData, index:int, a:*):void{
			var subIndex:int = index;
			var parentPoint:Point = null;
			var subSum:int = sum;
			if(node.parent){
				subIndex = node.parent.children.getItemIndex(node);
				parentPoint = result[node.parent];
				subSum = node.parent.childrenCount;
			}
			var location:Point = calculateLocation(node, layer, index , sum, subIndex, subSum, parentPoint);
			result[node] = location;
		});
	};

	var parent:IData = null;
	var layer:int = 0;
	var queue:Array = new Array();
	box.forEachByBreadthFirst(function(node:IData):void{
		if(!(node is Node)){
			return;
		}
		if(node.parent != parent){
			parent = node.parent;
			var l:int = 0;
			var p:IData = node.parent;
			while(p){
				l++;
				p = p.parent;
			}
			if(l != layer){
				_calculateLocations(queue, layer);
				layer++;
				queue = new Array();
			}
		}
		queue.push(node);
	}, root);

	_calculateLocations(queue, layer);
	return result;
}

 

分别对每层布局

有了上面这些信息,就很方便对各层作不同的布局:第一层到第四层,从左到右排列,每层节点从上到下排列,最后一层水平排列,本例中实现布局的函数是calculateLocation(),传入的参数包含了该节点布局相关的信息(所在层,在同层的位置,该层总共有多少节点,在其父节点的位置,其父节点所有的孩子数,父节点的布局位置)

 

public function calculateLocation(node:IData, layer:int, index:int, sum:int, subIndex:int, subSum:int, parentPoint:Point = null):Point

看看这变态的自动布局

var maxHeight:int = 700;
var startX:int = 10;
var startY:int = 10;
public function calculateLocation(node:IData, layer:int, index:int, sum:int, subIndex:int, subSum:int, parentPoint:Point = null):Point{
	var x:int = startX;
	var y:int = startY;
	var hGap:int = getHorizontalGap(layer);
	if(layer < 3){
		x = parentPoint ? parentPoint.x + hGap : startX;
		y = startY + getVerticalGap(layer, sum) * (index + 0.5);
	}else if(layer == 3 && parentPoint){
		x = parentPoint.x + hGap + subIndex/2 * 50;
		y = parentPoint.y + ((subIndex % 2 == 0 ) ? 15 : -15);
	}
	return new Point(x, y);
}

public function getHorizontalGap(layer:int):int{
	return layer == 3 ? 90 : 150;
}

public function getVerticalGap(layer:Number, sum:Number):Number{
	return Math.max(maxHeight/(sum + 0.5), 75);
}

连线样式的定制

本例中连线样式的配置也是关键,对于第四层Bus布局的呈现主要通过正交连线样式来体现,所以在数据添加时,对第四层的连线设置如下:

var link:Link = new Link(parent, node);
link.setStyle(Styles.LINK_TYPE, Consts.LINK_TYPE_HORIZONTAL_VERTICAL);
link.setStyle(Styles.LINK_CORNER, Consts.LINK_CORNER_NONE);
box.add(link);

查看完整源代码,请看这里

你可能感兴趣的:(看看这变态的自动布局)