學習 kityminder 筆記(十)

今天學習 layout 下的各個 js. 在此以前, 須要回顧一下 core/layout.
注意在 core 下面有 layout.js, module 下面也有 layout.js, 我是被混淆了, 不能換個名字麼? node

=== 佈局基類 Layout 位於 core/layout.js === 算法

佈局的基類, 各類不一樣的佈局今後類派生.
class Layout {
  // 子類須要實現此佈局算法. 算法輸入是父節點, 及其子節點(數組), 
  //   要求佈局這些子節點(即相對於父節點的變換)
  virtual doLayout(parent, children[]): throw 'sub-class impl'
  align(),stack(),move() 等輔助方法(子類可以使用), 稍後看.
}

 

在理解佈局前, 須要先了解一點變換的知識, 以及 kity 中對變換的包裝(和實現). c#

變換這裏指將一個點(x1,y1) 經函數 f 映射到另外一個點 (x2,y2). 數組

對於平移(translate)有:
     x2 = x1 + a, y2 = y1 + b
這裏 a,b 分別是 x,y 軸平移量. svg

在 svg 中至關於爲元素設置一個 transform="translate(a,b)" (或其它等價形式). 函數

對於縮放(scale)有:
    x2 = a*x1, y2 = b*y1
在 svg 中至關於爲元素設置 transform="scale(a,b)" (或等價形式) 工具

對於旋轉(rotate)一個角度 α:
   x2 = x1*cosα + y1*sinα
   y2 = y1*cosα - x1*sinα
對應 svg 中至關於 transform="rotate(α)" 佈局

斜切變換 skewX(α), skewY() 類似:
    x2 = x1 + y1*tanα, y2 = y1 學習

上述各類變換均可是線性變化, 即 x2,y2 是 a*x1+b*y1+c 的形式, 所以可表示爲線性代數的
矩陣乘法. 這樣變換就能夠寫做: 動畫

點 x,y 平移 tx,ty 距離:
     [1 0 tx] [x]   [x+tx]
     [0 1 ty]*[y] = [y+ty]
     [0 0 1 ] [1]   [ 1  ]
點 x,y 縮放 sx,sy 倍:
     [sx 0 0] [x]   [x*sx]
     [0 sy 0]*[y] = [y*sy]
     [0  0 1] [1]   [1]
點 x,y 繞原點旋轉 α 角的矩陣, 向量和乘的結果略:
     [cos -sin 0]
     [sin cos  0]
     [ 0   0   1]
斜切  skewX:         skewY:
     [1 tan(ax) 0]  [1       0 0]
     [0  1      0]  [tan(ay) 1 0]
     [0  0      1]  [0       0 1]

使用矩陣的最強大的地方在於, 多個線性變換能夠乘積, 而矩陣*矩陣仍是矩陣, 這便是 svg matrix 的含義了.

多個(線性)變換組合在一塊兒, 便是多個矩陣的乘積, 矩陣乘積仍然是矩陣, 
svg 中 transform="matrix(a,b,c,d,e,f)" 即表示矩陣:
     [a c e]
     [b d f]
     [0 0 1]

這樣, 你不管爲一個元素施加多少次線性變換, 其最終結果都是一個簡單的矩陣.

所以我合理地猜想這些變換,矩陣在 kity svg 庫中都有對應的封裝類/方法. 例如 kity.Matrix 類...

 

=== 下面研究 layout/mind.js, btree.js ===

以上咱們看到了 Layout (虛)基類, 如今咱們找一個實作的子類來查看 doLayout() 是具體怎麼實現的.
位於 src/layout 目錄下有 6 個 js 文件. 例如 tianpan.js(拼音天盤?), fish-bone*(魚骨圖) 等.
我簡單看了下, 其中 mind.js 對應缺省佈局, 其引用了 btree.js, 因此咱們先研究這兩個.

// 實際名字可稱爲 MindLayout 或 DefaultLayout
class <noname-layout> : public Layout {
  doLayout(node, children): {  // 這裏算法須要仔細看
    // 1. 將 children 切分紅兩部分 left~right. 也可認爲是 up~down.
    var left[] = 前一半 children,
        right[] = 後一半 children;

    // 2. 獲得 left, right (子)佈局器 (layout)
    var left_layout = Minder.left_layout_object;
    // right_layout 也同樣, 因此只需研究明白 left 一半便可.
    
    // 3. 使用子佈局器 佈局各自一半.
    left_layout.doLayout(node, left[])  // right 一半相同

    // 4. 合在一塊兒, 設置 node 的 vertex_out, vector_out
    box = node.content_box
    set out-vertex,out-vector // 估計和連線有關, 之後看.
  }
}

 

這裏 left, right 子佈局器在 btree.js 中. 該文件其實生成了 dir=left,right,top,bottom 四個佈局器,
爲了簡化問題, 我實際代入該生成函數以 dir=left, 模擬出一個類, 以方便理解.

// 將一組子節點佈局到本身的左側, 子節點右邊界是對齊的(right, align), 
//   垂直方向(即沿着 y 軸)順序排列(stack).
class LeftLayout : public Layout {
  doLayout(node, children[]): {
    // 1.
    axis = 'x', oppsite = 'right'  // left 是 x 軸的, 相對方向爲 right.
    parent.out_vertex = ..., .out_vector = ... (略)

    // 2.
    for-each (child in children[]) {
      child.layout_transform = new kity.Matrix() // 等於重置了變換矩陣?
      child.in_vertex = ..., .in_vector = ... (暫時略)
    }

    // 3. 使用基類輔助方法. 對齊子節點, 堆疊成一列.
    base::align(children[], 'right')  // right = oppsite of left
    base::stack(children[], 'y')      // y = oppsite of x-axis

    // 4. 計算偏移各子節點的一個 dx,dy 值. 
    p_box = parent.content_box  // 父節點位置.
    c_box = getBranchBox() // 工具方法:獲取給點的子節點所佔的佈局區域
    // 這裏仍是對 dx 的計算有點疑惑, 可能須要調試下...
    dx = ..., dy = ...  // c_box 放 p_box 左邊+margin, y 值中心對齊.
    // 平移子節點到該偏移, 實際是調用 base::move() 方法. 
    for-each (child)
      node.translate(dx, dy)
  }
}

這裏重點是將一組子節點靠右對齊(即全部 child.right 值相同), 沿着 y 軸堆疊(順序排列, 中間有間距),
而後佈置在父節點的左側. 實際使用三個 Layout 基類的方法: align(), stack(), move().

下面特化 align() 爲 align_right(), 在上面的例子中調用的 align() 給出參數爲 'right':
(其它 align left,top,bottom 相似, 只是多一個 switch(dir) 判斷.

class Layout {
  align_right(nodes[]) {
    for-each (node in nodes[]) {
      // 經此平移, 全部 node 節點的 right 值變爲 0, 因此就右對齊了.
      node.transform_matrix.translate(-node.right, 0)
    }
  }
}

下面特化 stack() 爲 stack_y():

class Layout {
  stack_y(nodes) {
    distance = node.margin-bottom  // 節點間距離
    position = 0  // 節點在 y 軸上的位置.
    for-each (node) {
      // 對 node 的當前變換矩陣 再疊加一個 dy 平移, 使得其 y 軸定位到 position 位置.
      matrix = node.transform_matrix
      matrix.translate(0, position - node.top)
       // 計算下一個位置. 考慮兩個節點之間的間距.
       position += node.height + distance_of_curr_node_and_next_node
    }
  }
}

一組 nodes[] 在 align_right() 以後, node.right = 0; 在 stack_y() 以後 nodes[0].top = 0;
這至關於整組節點的右上角是 (0,0) 原點了. 上面計算偏移 dx,dy 的疑惑也略微解開了一些, 由於那裏已經
知道整個 children_box .top=0, .right=0 了. 

如今咱們知道了 Layout 的做用是計算子節點的佈局位置, 該位置是相對於父節點的. 對於其它佈局形式,
如魚骨圖, 天盤?圖估計做用相似, 只是計算方法有所不一樣, 佈局的子節點的位置也就有些不一樣.

這裏 Layout 只是算出了位置, 但並未將節點實際移動到那裏, 因而問題就產生了:    誰,何時,怎麼移動(動畫?)節點到新的位置的呢?

相關文章
相關標籤/搜索