• 看看这变态的自动布局


    自动布局就是对数据元素中的节点进行定位,使节点分散在到一个比较好看的显示效果,自动布局的实质就是一种布局的算法的实现。比如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);
    

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

  • 相关阅读:
    Ant Design Charts更改tooltip样式的方法
    css更改滚动条样式
    css实现多行文本设置省略号
    css-背景图置于背景色的下方
    js使用reduce实现扁平化数组转换为树形数据
    js实现从0到指定数据的跳动
    原生js模拟vue的响应式
    柯里化函数
    vue中keepalived的使用
    常用网址
  • 原文地址:https://www.cnblogs.com/twaver/p/2123644.html
Copyright © 2020-2023  润新知