mxgraph 系列【3】: 底層狀態樹 mxCell

1. 簡介

mxgraph 內部使用一種樹形數據結構記錄圖形文檔的內容,在內容樹上,每個節點表明一個圖形元素,元素信息會被存儲在節點對象上;節點的父子關係表示父節點圖形包含子節點圖形,能夠用於實現圖形的分層、分組功能。例如,對於下面的圖形:javascript

內容樹是圖形文檔的底層數據模型,有點像 vdom 之於 react;vnode 之於 vue。mxgraph 的許多功能都是圍繞內容樹展開的,例如渲染器 mxCellRenderer 根據內容樹渲染出圖形文檔;編解碼器 mxCellCodec 實現了外部存儲格式與內容樹之間的互相轉換;各類 佈局算法 的基本操做單位也都是內容樹節點。所以,深刻理解內容樹模型可以爲後面的源碼學習過程打下紮實基礎!html

內容樹模型由如下幾個類實現:vue

  1. mxGraphModel: 內容樹模型,主要實現一系列樹結構操做方法。
  2. mxCell: 內容樹節點,用於存儲圖形、鏈接線、分層等圖元的狀態信息。
  3. mxGeometry: 樹節點的幾何信息,記錄了圖形元素的寬高、座標位置;連線的開始節點、結束節點等幾何特性。

上面是哪一個類的關係以下:java

本文主要關注內容樹節點 mxCell 的用法,關於 mxGraphModel、mxGeometry 的內容後續會用專門章節做介紹。node

2. 初識 mxCell

mxCell 類定義在 javascript/src/js/model/mxCell.js 文件中,構造函數簽名: function mxCell(value, geometry, style),參數解釋:react

  • value: 用於定義圖元的內容,支持傳入 dom 對象、字符串。
  • geometry: 圖形的幾何數值,對於 vertex 類型記錄了圖形的 x、y、width、height 屬性;edge 類型還會記錄線段兩端鏈接的點。
  • style: 圖形樣式

以下示例展現瞭如何構造 mxCell 對象:git

// 構造mxCell實例
const cell = new mxCell('Hello world!', new mxGeometry(60, 80, 100, 100), 'fillColor=#ddd');
// 將cell設定爲幾何圖案類型
cell.vertex = true;

// 使用 cell 對象構造模型
const model = new mxGraphModel(cell);
// 渲染模型
new mxGraph(document.getElementById('container'), model);

渲染結果:github

提示:

mxgraph 的樣式功能是在 CSS 樣式體系基礎上擴展而成,除標準的樣式屬性外還定義了不少自有的樣式屬性,後續會另開章節作介紹。算法

3. 使用 mxCell 畫圖

3.1 矩形

vertex=true 時,mxCell 默認圖形樣式就是矩形,因此渲染矩形時只要指定起始座標、寬高屬性便可,例如:npm

// 構造mxCell實例
const cell = new mxCell(
    null,
    // 經過 mxGeometry 指定矩形的寬高、起始座標
    new mxGeometry(60, 80, 100, 100)
);
// 將cell設定爲幾何圖案類型
cell.vertex = true;

渲染效果:

3.2 線段

使用 mxCell 畫線段的邏輯會稍微複雜一些,代碼片斷:

const cell = new mxCell('Hello world!', new mxGeometry(), 'strokeWidth=3;');
// 將cell設定爲線段
cell.edge = true;
// 設置起始點
cell.geometry.setTerminalPoint(new mxPoint(60, 180), true);
// 設置終結點
cell.geometry.setTerminalPoint(new mxPoint(230, 20), false);

線段的位置是由線段兩端端點位置肯定,例如上例初始化 mxCell 後,還須要調用 setTerminalPoint 設定線段的起始結束位置才能正常渲染。上例渲染結果:

更進一步的,在一條線段內還可使用 points 屬性拆分出多個片斷,例如:

const cell = new mxCell('Hello world!', new mxGeometry(), 'strokeWidth=3;');
// 將cell設定爲線段
cell.edge = true;
// 設置起始點
cell.geometry.setTerminalPoint(new mxPoint(60, 180), true);
// 設置終結點
cell.geometry.setTerminalPoint(new mxPoint(230, 20), false);
// 使用 points 定義多箇中間節點
cell.geometry.points = [new mxPoint(70, 50), new mxPoint(120, 80)];

渲染效果:

3.3 更多內置圖形

除了矩形和線段外,mxGraph 還內置支持其它圖形,先來看一個示例:

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width,initial-scale=1.0" />
        <title>mxgraph Example</title>
        <style>
            #container {
                background: url('../../assets/grid.gif');
            }
        </style>
        <script type="text/javascript">
            mxBasePath = '//cdn.jsdelivr.net/npm/mxgraph@4.1.1/javascript/src';
        </script>
    </head>
    <body>
        <div id="container" style="width: 400px; height: 400px;"></div>

        <script src="//cdn.jsdelivr.net/npm/mxgraph@4.1.1/javascript/mxClient.min.js"></script>
        <script type="text/javascript">
            function run(container) {
                const shapes = [
                    // mxGraph 內置支持以下圖形:
                    'actor',
                    'cloud',
                    'cylinder',
                    'doubleEllipse',
                    'ellipse',
                    'hexagon',
                    'image;image=https://jgraph.github.io/mxgraph/docs/images/mxgraph_logo.gif',
                    'rectangle',
                    'rhombus',
                    'swimlane',
                    'triangle',
                ];
                const root = new mxCell(null, new mxGeometry(), null);
                for (let i = 0; i < shapes.length; i++) {
                    const shape = shapes[i];
                    const xOffset = i % 4,
                        yOffset = Math.floor(i / 4),
                        x = xOffset * 100 + 20,
                        y = yOffset * 100 + 20;
                    const geometry = new mxGeometry(x, y, 60, 60);
                    const cell = new mxCell(
                        shape.split(';')[0],
                        geometry,
                        // 經過shape指定cell的圖形類別
                        `shape=${shape};verticalLabelPosition=bottom;spacingBottom=40`
                    );
                    cell.vertex = true;
                    root.insert(cell);
                }
                const model = new mxGraphModel(root);
                new mxGraph(container, model);
            }

            window.addEventListener('load', () => {
                run(document.getElementById('container'));
            });
        </script>
    </body>
</html>

示例效果:

示例中,須要在構造 mxCell 實例時,經過 style 參數設定 cell 的圖形類別,核心代碼:

const cell = new mxCell(
    null,
    new mxGeometry(0, 0, 100, 100),
    //  經過shape屬性修改圖形類別
    `shape=triangle`
);

若是是圖像類型,還須要經過 image 傳入圖片地址:

const cell = new mxCell(
    null,
    new mxGeometry(0, 0, 100, 100),
    // shape 屬性指定位image
    // image 屬性設定圖片地址
    `shape=image;image=https://jgraph.github.io/mxgraph/docs/images/mxgraph_logo.gif`
);

3.4 自定義圖形類

在 mxGraph 中,全部圖形的基類都是 mxShape 類,用戶只須要繼承該類便可定義出新的圖形類別,核心步驟:

// 1. 繼承 mxShape 類
class CustomShape extends mxShape {
    constructor() {
        super();
    }

    // 圖形的渲染方法
    paintBackground(c, x, y, w, h) {}
}

// 2. 在渲染器 mxCellRenderer 中註冊圖形類
mxCellRenderer.registerShape('customShape', CustomShape);

const cell = new mxCell(
    null,
    new mxGeometry(100, 50, 50, 100),
    // 3. 建立 mxCell時,依然經過 style 參數的shape屬性定義圖形類別
    'shape=customShape'
);

完整示例:

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width,initial-scale=1.0" />
        <title>mxgraph Example</title>
        <style>
            #container {
                background: url('../../assets/grid.gif');
            }
        </style>
        <!-- Sets the basepath for the library if not in same directory -->
        <script type="text/javascript">
            mxBasePath = '//cdn.jsdelivr.net/npm/mxgraph@4.1.1/javascript/src';
        </script>
    </head>
    <body>
        <div id="container" style="width: 300px; height: 200px;"></div>

        <script src="//cdn.jsdelivr.net/npm/mxgraph@4.1.1/javascript/mxClient.min.js"></script>
        <script type="text/javascript">
            // 繼承 mxShape 基類,擴展自定義圖形類
            class CustomShape extends mxShape {
                constructor() {
                    super();
                }

                paintBackground(c, x, y, w, h) {
                    c.translate(x, y);

                    // Head
                    c.ellipse(w / 4, 0, w / 2, h / 4);
                    c.fillAndStroke();

                    c.begin();
                    c.moveTo(w / 2, h / 4);
                    c.lineTo(w / 2, (2 * h) / 3);

                    // Arms
                    c.moveTo(w / 2, h / 3);
                    c.lineTo(0, h / 3);
                    c.moveTo(w / 2, h / 3);
                    c.lineTo(w, h / 3);

                    // Legs
                    c.moveTo(w / 2, (2 * h) / 3);
                    c.lineTo(0, h);
                    c.moveTo(w / 2, (2 * h) / 3);
                    c.lineTo(w, h);
                    c.end();

                    c.stroke();
                }
            }

            // 須要在渲染器 mxCellRenderer 中註冊圖形
            mxCellRenderer.registerShape('customShape', CustomShape);

            function run(container) {
                const cell = new mxCell(
                    'Hello world!',
                    new mxGeometry(100, 50, 50, 100),
                    // 依然經過style參數的 shape 屬性指定圖形類別
                    'shape=customShape'
                );
                cell.vertex = true;

                const model = new mxGraphModel(cell);
                new mxGraph(container, model);
            }

            window.addEventListener('load', () => {
                run(document.getElementById('container'));
            });
        </script>
    </body>
</html>

示例效果:

3.5 使用 stencils 畫圖

除了經過擴展 mxShape 實現自定義圖形類以外,還可使用 stencils 接口定義新的圖形類別,主要步驟有:

// 1. xml 格式定義圖形內容
const shapes = `<shapes>...</shapes>`;
// 2. 將字符串轉化爲 DOM 對象
const node = new DOMParser().parseFromString(shapes, 'text/xml');
// 3. 註冊 stencils 對象
mxStencilRegistry.addStencil('or', new mxStencil(node.firstChild));

const cell = new mxCell(
    null,
    new mxGeometry(100, 50, 50, 100),
    // 4. 建立 mxCell時,依然經過 style 參數的shape屬性定義圖形類別
    'shape=or'
);

完整示例:

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width,initial-scale=1.0" />
        <title>mxgraph Example</title>
        <style>
            #container {
                background: url('../../assets/grid.gif');
            }
        </style>
        <!-- Sets the basepath for the library if not in same directory -->
        <script type="text/javascript">
            mxBasePath = '//cdn.jsdelivr.net/npm/mxgraph@4.1.1/javascript/src';
        </script>
    </head>
    <body>
        <div id="container" style="width: 300px; height: 200px;"></div>

        <script src="//cdn.jsdelivr.net/npm/mxgraph@4.1.1/javascript/mxClient.min.js"></script>
        <script type="text/javascript">
            // xml 形式定義圖形內容
            const shapes = `<shapes>
          <shape name="or" aspect="variable">
              <background>
                  <path>
                      <move x="0" y="0" />
                      <quad x1="100" y1="0" x2="100" y2="50" />
                      <quad x1="100" y1="100" x2="0" y2="100" />
                      <close/>
                  </path>
              </background>
              <foreground>
                  <fillstroke/>
              </foreground>
          </shape>
      </shapes>`;
            // 將字符串的xml值解析爲dom對象
            const parser = new DOMParser();
            const node = parser.parseFromString(shapes, 'text/xml');
            // 註冊畫筆
            mxStencilRegistry.addStencil('or', new mxStencil(node.firstChild));

            function run(container) {
                const cell = new mxCell(null, new mxGeometry(100, 50, 50, 100), 'shape=or');
                cell.vertex = true;

                const model = new mxGraphModel(cell);
                new mxGraph(container, model);
            }

            window.addEventListener('load', () => {
                run(document.getElementById('container'));
            });
        </script>
    </body>
</html>

示例效果:

提示:

自定義 mxShape 與 stencils 接口都能擴展出新的圖形類別,mxShape 經過類形式定義,會有必定的開發成本,可是可以定製更多圖形邏輯;而 stencils 能夠經過外部的 xml 文件定義,開發與管理上會更方便一些。具體使用何種方式,可根據實際需求選擇。

兩套方法論都比較複雜,不是一個小片斷就能說清楚的做者後續會另開專題,作詳細介紹。

<!-- TODO -->
<!-- 違反了單一原則 -->

<!-- TODO -->
<!-- 第四節 -->
<!-- 如何執行變換,例如:translate、rotate等 -->

4. 總結

至此爲止,本節內容就差很少結束了。文章主要介紹了底層數據結構 mxCell 以及如何使用 mxCell 畫出各類不一樣圖形。隨着學習的深刻,咱們發現有更多能夠挖掘的點,包括:

  1. mxGraphModel 的使用方法
  2. mxGraph 中的 style 體系是如何使用、運做的
  3. 如何執行圖形的幾何變換
  4. 自定義圖形類、自定義 stencils 的使用方法
  5. ...

將來會逐步補充系列文章,力求可以幫助有須要的人深刻理解 mxGraph 的用法和原理,感興趣的同窗歡迎關注。

相關文章
相關標籤/搜索