電信網絡拓撲圖自動佈局之總線

在前面《電信網絡拓撲圖自動佈局》一文中,咱們大致介紹了 HT for Web 電信網絡拓撲圖自動佈局的相關知識,可是都沒有深刻地描述各類自動佈局的用法,咱們今天在這邊就重點介紹總線的具體實現方案。html

HT for Web 的連線手冊中,有說明能夠自定義連線類型,經過 ht.Default.setEdgeType(type, func, mutual) 函數定義,咱們今天要描述的總線也是經過這樣的方法來實現的。node

咱們來簡單地描述下這個方法,雖然在文檔(http://www.hightopo.com/guide...)中已經描述得很詳細了,爲了下面的工做可以更好的開展,我這邊仍是再強調下。segmentfault

這個函數名是 setEdgeType,顧名思義,它是用來自定義一個 EdgeType 的,那麼第一個參數 type 就是用來定義這個 EdgeType 的名稱,在 Edge 的樣式上設置 edge.type 屬性爲 type 值,就可讓這條連線使用是咱們自定義的 EdgeType。網絡

那麼第二個參數呢,就是用來計算連線的走線信息的函數,這個回調函數將會傳入四個參數,分別是:edge、gap、graphView、sameSourceWithFirstEdge,其中 edge 就是樣式上設置 edge.type 屬性爲 type 值的連線對象,這個參數是最重要的,一般有這個參數就能夠完成格式各樣的連線了。其餘參數在手冊中都描述得很清楚,能夠轉到手冊中閱讀,http://www.hightopo.com/guide...ide

那這第三個參數呢,是決定連線是否影響起始或結束節點上的全部連線,這個參數解釋起來比較複雜,後續有機會的話,咱們再詳細說明。函數

先來看看一個簡單的例子,http://www.hightopo.com/guide...佈局

013412_5YDj_1423144.png

上圖中,能夠看到節點間的連線並非普通的直線,或者簡單的折線,而是漂亮的曲線,那麼這樣的曲線是怎麼生成的呢?既然將這個例子放到這邊做爲案例,那麼它必定使用了自定義 EdgeType 的功能,觀察圖片能夠發現曲線其實能夠用二次方曲線來表示,因此呢,咱們在 setEdgeType 函數的回調中返回的連線走向信息中,將其描述爲一條二次方曲線就能夠了。說得有些繞,咱們來看看代碼實現吧。ui

ht.Default.setEdgeType('custom', function(edge, gap, graphView, sameSourceWithFirstEdge){
    var sourcePoint = edge.getSourceAgent().getPosition(),
        targetPoint = edge.getTargetAgent().getPosition(),
        points = new ht.List();       
        points.add(sourcePoint);
        points.add({
            x: (sourcePoint.x + targetPoint.x)/2, 
            y: (sourcePoint.y + targetPoint.y)/2 + 300
        });                  
        points.add(targetPoint);                                                       

    return {
        points: points,
        segments: new ht.List([1, 3])
    };                 
});

從代碼中能夠看出,返回到頂點是連線的起點和終點,還有中間的二次方曲線的控制點,還有設置了頂點的連線方式,就是在 return 中的 segments,1 表明是路徑的起點,3 表明的是二次方曲線,這些相關知識點在 HT for Web 的形狀手冊中描述得很清楚,不懂的能夠轉到手冊詳細瞭解,http://www.hightopo.com/guide...spa

前面的廢話太多了,下面就是咱們今天的主要內容了,看看如何經過自定義 EdgeType 來實現總線的效果,http://www.hightopo.com/demo/...設計

015821_OpwZ_1423144.png

上圖就是一個總線的簡單例子,全部的節點都經過線條連接黑色的總線,連線的走向都是節點到總線的最短距離。

來說講總體的設計思路吧,其實總的來講,就是點的線的垂直點計算問題。那麼問題來了,碰到曲線怎麼辦?其實曲線也是能夠微分紅線條來處理的,至於這個線段的劃分精細度就須要用戶來自定義了。

021902_2s1j_1423144.png

EdgeType 結合 ShapeLayout 實現均勻自動佈局:http://www.hightopo.com/demo/...

可是,像上圖所示的橢圓形總線該如何處理呢?對於這種有固定表達式的形狀,咱們就不須要用曲線分割的方法來作總線佈局了,咱們徹底能夠獲取到圓或者橢圓上的一點,因此在處理圓和橢圓上,咱們獲取 Edge 連邊節點中線連成線,而後計算出夾角,經過圓或者橢圓的三角函數表示法計算出總線上的一點,這樣來構成連線。

在上圖中,咱們用到了 ShapeLayout 來自動佈局和總線相連的節點,讓其相對均勻地分佈在總線周圍,對於 ShapeLayout 的相關設計思路咱們在後面的章節中再具體介紹。

那麼咱們今天的內容就到這裏了,對於總線的設計是否是很簡單呢,下面附上總線的全部代碼,有須要的話,能夠直接複製出來,在頁面中引入 HT for Web 的核心包 ht.js 後面引入如下代碼就能夠直接使用總線功能了。代碼很少,也就將近 200 行,感興趣的朋友能夠通讀一遍,有什麼問題,還請不吝賜教。

;(function(window, ht) {
    var getPoint = function(node, outPoint) {
        var rect = node.getRect(),
            pos = node.getPosition(),
            p = ht.Default.intersectionLineRect(pos, outPoint, rect);
        if (p) return { x: p[0], y: p[1] };
        return pos;
    };
    var pointToInsideLine = function(p1, p2, p) {
       var x1 = p1.x,
           y1 = p1.y,
           x2 = p2.x,
           y2 = p2.y,
           x = p.x,
           y = p.y,
           result = {},
           dx = x2 - x1,
           dy = y2 - y1,
           d = Math.sqrt(dx * dx + dy * dy),
           ca = dx / d, // cosine
           sa = dy / d, // sine
           mX = (-x1 + x) * ca + (-y1 + y) * sa;

       result.x = x1 + mX * ca;
       result.y = y1 + mX * sa;
       
       if (!isPointInLine(result, p1, p2)) {
           result.x = Math.abs(result.x - p1.x) < Math.abs(result.x - p2.x) ? p1.x : p2.x;
           result.y = Math.abs(result.y - p1.y) < Math.abs(result.y - p2.y) ? p1.y : p2.y;
       }

       dx = x - result.x;
       dy = y - result.y;
       result.z = Math.sqrt(dx * dx + dy * dy);

       return result;
    };
    var isPointInLine = function(p, p1, p2) {
        return p.x >= Math.min(p1.x, p2.x) &&
            p.x <= Math.max(p1.x, p2.x) &&
            p.y >= Math.min(p1.y, p2.y) &&
            p.y <= Math.max(p1.y, p2.y);
    };
    var bezier2 = function(t, p0, p1, p2) {
        var t1 = 1 - t;
        return t1*t1*p0 + 2*t*t1*p1 + t*t*p2;
    };
    var bezier3 = function(t, p0, p1, p2, p3 ) {
        var t1 = 1 - t;
        return t1*t1*t1*p0 + 3*t1*t1*t*p1 + 3*t1*t*t*p2 + t*t*t*p3;
    };
    var distance = function(p1, p2) {
        var dx = p2.x - p1.x,
            dy = p2.y - p1.y;
        return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
    };
    var getPointWithLength = function(length, p1, p2) {
        var dis = distance(p1, p2),
            temp = length / dis,
            dx = p2.x - p1.x,
            dy = p2.y - p1.y;
        return { x: p1.x + dx * temp, y: p1.y + dy * temp };
    };
    var getPointInOval = function(l, r, p1, p2) {
        var a = Math.atan2(p2.y - p1.y, p2.x - p1.x);
        return { x: l * Math.cos(a) + p1.x, y: r * Math.sin(a) + p1.y };
    };
    ht.Default.setEdgeType('bus', function(edge, gap, graphView, sameSourceWithFirstEdge) {
        var source = edge.getSourceAgent(),
            target = edge.getTargetAgent(),
            shapeList = ['circle', 'oval'],
            shape, beginNode, endNode;
            
        if (shapeList.indexOf(source.s('shape')) >= 0) {
            shape = source.s('shape');
            beginNode = source;
            endNode = target;
        }
        else if (shapeList.indexOf(target.s('shape')) >= 0) {
            shape = target.s('shape');
            beginNode = target;
            endNode = source;
        }
        
        if (shapeList.indexOf(shape) >= 0) {
            var w = beginNode.getWidth(),
                h = beginNode.getHeight(),
                l = Math.max(w, h) / 2,
                r = Math.min(w, h) / 2;
            if (shape === 'circle') l = r = Math.min(l, r);
            var p = getPointInOval(l, r, beginNode.getPosition(), endNode.getPosition());
            return {
                points: new ht.List([ p, getPoint(endNode, p) ]),
                segments: new ht.List([ 1, 2 ])
            };
        }
            
        var segments, points, endPoint;
        if (source instanceof ht.Shape) {
            segments = source.getSegments();
            points = source.getPoints();
            beginNode = source;
            endPoint = target.getPosition();
            endNode = target;
        }
        else if (target instanceof ht.Shape) {
            segments = target.getSegments();
            points = target.getPoints();
            beginNode = target;
            endPoint = source.getPosition();
            endNode = source;
        }
        
        if (!points) {
            return {
                points: new ht.List([
                    getPoint(source, target.getPosition()),
                    getPoint(target, source.getPosition())
                ]),
                segments: new ht.List([ 1, 2 ])
            };
        }
        
        if (!segments && points) {
            segments = new ht.List();
            points.each(function() { segments.add(2); });
            segments.set(0, 1);
        }
        
        var segLen = segments.size(),
            segV, segNextV, beginPoint, j,
            p1, p2, p3, p4, p, tP1, tP2, tRes,
            curveResolution = beginNode.a('edge.curve.resolution') || 50,
            pointsIndex = 0;
        for (var i = 0; i < segLen - 1; i++) {
            segNextV = segments.get(i + 1);
            if (segNextV === 1) {
                pointsIndex++;
                continue;
            }
                
            p1 = points.get(pointsIndex++);
            
            if (segNextV === 2 || segNextV === 5) {
                p2 = points.get((segNextV === 5) ? 0 : pointsIndex);
                p = pointToInsideLine(p1, p2, endPoint);
                if (!beginPoint || beginPoint.z > p.z)
                    beginPoint = p;
            }
            else if (segNextV === 3) {
                p2 = points.get(pointsIndex++);
                p3 = points.get(pointsIndex);
                tP2 = { x: p1.x, y: p1.y };
                for (j = 1; j <= curveResolution; j++) {
                    tP1 = tP2;
                    tRes = j / curveResolution;
                    tP2 = {
                        x: bezier2(tRes, p1.x, p2.x, p3.x),
                        y: bezier2(tRes, p1.y, p2.y, p3.y),
                    };
                    p = pointToInsideLine(tP1, tP2, endPoint);
                    if (!beginPoint || beginPoint.z > p.z)
                        beginPoint = p;
                }
            }
            else if (segNextV === 4) {
                p2 = points.get(pointsIndex++);
                p3 = points.get(pointsIndex++);
                p4 = points.get(pointsIndex);
                tP2 = { x: p1.x, y: p1.y };
                for (j = 1; j <= curveResolution; j++) {
                    tP1 = tP2;
                    tRes = j / curveResolution;
                    tP2 = {
                        x: bezier3(tRes, p1.x, p2.x, p3.x, p4.x),
                        y: bezier3(tRes, p1.y, p2.y, p3.y, p4.y),
                    };
                    p = pointToInsideLine(tP1, tP2, endPoint);
                    if (!beginPoint || beginPoint.z > p.z)
                        beginPoint = p;
                }
            }
        }
        endPoint = getPoint(endNode, beginPoint);
        return {
            points: new ht.List([
                { x: beginPoint.x, y: beginPoint.y },
                endPoint
            ]),
            segments: new ht.List([ 1, 2 ])
        };
    });
}(window, ht));
相關文章
相關標籤/搜索