1. 程式人生 > >【mxGraph】原始碼學習:(5)mxGraph

【mxGraph】原始碼學習:(5)mxGraph

由於mxGraph原始檔有一萬多行,且涉及很多其它原始檔,所以重點在於瞭解mxGraph的作用、結構以及定義了哪些方法

1. 概覽

1.1 作用

mxGraph繼承自mxEventSource以實現基於Web的圖形元件的功能性方面。要啟用平移和連線,使用setPanning和setConnectable,對於框線選擇,必須建立一個新的mxRubberband例項。預設情況下,以下監聽器新增到mouseListeners:

  1. tooltipHandler:顯示工具提示的mxTooltipHandler
  2. panningHandler:用於平移和彈出選單的mxPanningHandler
  3. connectionHandler:用於建立連線的mxConnectionHandler
  4. graphHandler:用於移動和克隆cell的mxGraphHandler

如果啟用了這些監聽器,則將按上述順序呼叫它們。

1.2 背景圖片

要顯示背景影象,使用setBackgroundImage設定影象URL和寬高。更改上述值之一後,應呼叫檢視的mxGraphView.validate。

1.3 cell影象

要在cell中使用影象,必須在預設vertex樣式(或任何命名樣式)中指定形狀。可能的形狀是mxConstants.SHAPE_IMAGE和mxConstants.SHAPE_LABEL。更改預設vertex樣式中使用的形狀的程式碼,如下所示:

var style = graph.
getStylesheet().getDefaultVertexStyle(); style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE;

對於預設vertex樣式,可以使用mxConstants.STYLE_IMAGE鍵和影象URL作為值在cell樣式中指定要顯示的影象,例如:

image=http://www.example.com/image.gif

對於命名樣式,stylename必須是cell樣式的第一個元素:

stylename;image=http://www.example.com/image.gif

cell樣式可以新增任意數量的鍵值對,用分號隔開,如下所示:

[stylename;|key=value;]

1.4 標籤

cell標籤由getLabel定義,如果labelsVisible為true ,則使用convertValueToString。如果必須將標籤呈現為HTML標記,則isHtmlLabel應對相應的cell格返回true。如果所有標籤都包含HTML標記,則htmlLabels可以設定為true。注意:啟用HTML標籤可能存在安全風險(請參閱手冊中的安全性部分)。

如果標籤需要包裝,那麼isHtmlLabel和isWrapping必須為其標籤應該被包裝的cell格返回true。請參閱isWrapping示例。

如果需要剪下以將HTML標籤的顯示保持在其vertex的邊界內,則isClipping應對相應的cell格返回true。

預設情況下,edge標籤是可移動的,vertex標籤是固定的。這可以通過設定edgeLabelsMovable和vertexLabelsMovable,或通過覆蓋isLabelMovable來更改。

1.5 就地編輯

通過雙擊或鍵入F2啟動就地編輯。以程式設計方式,edit用於檢查cell是否可編輯(isCellEditable)並呼叫startEditingAtCell,它呼叫mxCellEditor.startEditing。編輯器使用getEditingValue返回的值作為編輯值。

就地編輯後,labelChanged被呼叫,呼叫mxGraphModel.setValue,進而呼叫mxGraphModel.valueForCellChanged通過mxValueChange。

觸發就地編輯的事件將傳遞給cellEditor,後者可能會根據事件型別或滑鼠位置採取特殊操作,也會傳遞給getEditingValue。然後將事件傳遞迴事件處理函式,該函式可以基於觸發事件執行特定動作。

1.6 提示

工具提示由getTooltip實現,如果cell位於滑鼠指標下,則呼叫getTooltipForCell。預設實現檢查cell是否具有getTooltip函式,如果存在則呼叫它。因此,為了提供自定義工具提示,cell必須提供getTooltip函式,或者必須覆蓋上述兩個函式之一。

通常對於自定義cell工具提示,後一個函式被覆蓋如下:

graph.getTooltipForCell = function(cell) {
  var label = this.convertValueToString(cell);
  return 'Tooltip for '+label;
}

使用配置檔案時,使用以下項在mxGraph部分中覆蓋該函式:

<add as="getTooltipForCell"><![CDATA[
  function(cell) {
    var label = this.convertValueToString(cell);
    return 'Tooltip for '+label;
  }
]]></add>

this是指實現中的graph,例如為了檢查cell是否是edge,使用this.getModel().isEdge(cell)。

要替換getTooltipForCell的預設實現(而不是替換特定例項上的函式),在載入js檔案之後,但在使用mxGraph建立新的mxGraph例項之前,使用以下程式碼:

mxGraph.prototype.getTooltipForCell = function(cell) {
  var label = this.convertValueToString(cell);
  return 'Tooltip for '+label;
}

1.7 形狀和樣式

在示例中演示了新形狀的實現。假設已經實現了一個名為BoxShape的自定義形狀,想要用它來繪製vertex。要使用此形狀,必須首先在cell渲染器中註冊,如下所示:

mxCellRenderer.registerShape('box', BoxShape);

該程式碼在graph的cell渲染器中的名稱框下注冊BoxShape建構函式。現在可以使用樣式定義中的shape-key來引用形狀。(cell渲染器包含一組其他形狀,即每個常量一個,在mxConstants中具有SHAPE前​​綴)

樣式是鍵值對的集合,樣式表是命名樣式的集合。名稱由cellstyle引用,它以mxCell.style儲存,格式如[stylename;|key=value;]。該字串被解析為鍵值對的集合,其中鍵被字串中的值覆蓋。

引入新形狀時,必須在樣式表中使用註冊形狀的名稱。有三種方法可以做到這一點:

  1. 通過更改預設樣式,使所有vertex都使用新形狀
  2. 通過定義新樣式,只有具有相應cell樣式的vertex才會使用新形狀
  3. 通過在cellstyle的可選鍵值對列表中使用shape=box覆蓋

在第一種情況下,獲取和修改vertex的預設樣式的程式碼如下:

var style = graph.getStylesheet().getDefaultVertexStyle();
style[mxConstants.STYLE_SHAPE] = 'box';

該程式碼採用預設的vertex樣式,該樣式用於沒有特定cell樣式的所有vertex,並在原位修改shape-key的值以使用新的BoxShape繪製vertex。這是通過在第二行中指定框值來完成的,該值是指cell渲染器中BoxShape的名稱。

在第二種情況下,建立一組鍵值對,然後以新名稱新增到樣式表中。為了區分shapename和stylename,我們將使用boxstyle作為stylename:

var style = new Object();
style[mxConstants.STYLE_SHAPE] = 'box';
style[mxConstants.STYLE_STROKECOLOR] = '#000000';
style[mxConstants.STYLE_FONTCOLOR] = '#000000';
graph.getStylesheet().putCellStyle('boxstyle', style);

該程式碼將一個名為boxstyle的新樣式新增到樣式表中。要將此樣式與cell一起使用,必須從cellstyle引用它,如下所示:

var vertex = graph.insertVertex(parent, null, 'Hello, World!', 20, 20, 80, 20,
             'boxstyle');

總而言之,必須在mxCellRenderer中使用唯一名稱註冊每個新形狀。然後,該名稱將用作預設或自定義樣式中shape-key的值。如果有多個自定義形狀,則每個形狀應該有一個單獨的樣式。

1.8 繼承樣式

對於fill-,stroke-,gradient-和indicatorColors,可以使用特殊關鍵字。其中一種顏色的inherit關鍵字將繼承父cell中相同鍵的顏色。swimlane關鍵字執行相同操作,但繼承自祖先層次結構中最近的swimlane。最後,指示的關鍵字將使用指標的顏色作為給定鍵的顏色。

1.9 滾動條

containers overflow CSS屬性定義滾動條是否用於顯示graph。對於’auto’或’scroll’的值,將顯示滾動條。請注意,resizeContainer標誌通常不與滾動條一起使用,因為它會在每次更改後調整容器大小以匹配graph的大小。

1.10 多重性和驗證

要控制mxGraph中可能的連線,請使用getEdgeValidationError。函式的預設實現使用多重性,即mxMultiplicity陣列。使用此類可以建立簡單的多重性,由graph強制執行。

mxMultiplicity使用mxCell.is來確定它適用的終端。mxCell.is的預設實現與DOM節點(XML節點)一起使用,並檢查給定的型別引數是否與節點的nodeName匹配(不區分大小寫)。可選地,可以指定也檢查的屬性名和值。

只要edge的連線發生更改,就會呼叫getEdgeValidationError。如果edge無效,則返回空字串或錯誤訊息,如果edge有效,則返回null。如果返回的字串不為空,則顯示為錯誤訊息。

mxMultiplicity允許指定終端與其可能的鄰居之間的多重性。例如,如果任何矩形只能連線到最多兩個圓圈,則可以將以下規則新增到多重性:

graph.multiplicities.push(new mxMultiplicity(
  true, 'rectangle', null, null, 0, 2, ['circle'],
  'Only 2 targets allowed',
  'Only shape targets allowed'));

每當矩形連線到兩個以上的圓圈時,這將顯示第一條錯誤訊息,如果矩形連線到除圓形之外的任何東西,則顯示第二條錯誤訊息。

對於某些多重性,例如最少1個連線,在建立cell時不能強制執行(除非cell與連線一起建立),mxGraph提供validate,它檢查所有cell的所有多重性並顯示相應的錯誤訊息在cell上的疊加圖示中。

如果cell已摺疊且包含驗證錯誤,則會在摺疊的cell上附加相應的警告圖示。

1.11 自動佈局

對於自動佈局,在mxLayoutManager中提供了getLayout鉤子。可以重寫它以返回給定cell的子節點的佈局演算法。

1.12 未連線的edge

所有開關的預設值都旨在滿足一般圖表繪圖應用程式的要求。一組非常典型的設定可避免未連線的edge如下:

graph.setAllowDanglingEdges(false);
graph.setDisconnectOnMove(false);

將cloneInvalidEdges開關設定為true是可選的。此開關控制是否在複製,貼上或克隆拖動後插入edge(如果它們無效)。例如,如果在沒有選擇相應的終端且allowDanglingEdges為false的情況下複製或控制拖動edge,則edge無效,在這種情況下,如果開關為假,則不會克隆edge。

1.13 輸出

要為graph生成XML表示,可以使用以下程式碼:

var enc = new mxCodec(mxUtils.createXmlDocument());
var node = enc.encode(graph.getModel());

這將生成一個XML節點,而不是使用DOM API處理或使用以下程式碼轉換為字串表示:

var xml = mxUtils.getXml(node);

要獲取格式化字串,可以使用mxUtils.getPrettyXml。

此字串現在可以儲存在本地持久儲存中(例如使用Google Gears),也可以使用mxUtils.post將其傳遞到後端,如下所示。url變數是Java servlet、PHP頁面或HTTP處理程式的URL,具體取決於伺服器。

var xmlString = encodeURIComponent(mxUtils.getXml(node));
mxUtils.post(url, 'xml='+xmlString, function(req) {
  // Process server response using req of type mxXmlRequest
});

1.14 輸入

要將graph的XML表示載入到現有graph物件中,可以按如下方式使用mxUtils.load。url變數是生成XML字串的Java servlet、PHP頁面或HTTP處理程式的URL。

var xmlDoc = mxUtils.load(url).getXml();
var node = xmlDoc.documentElement;
var dec = new mxCodec(node.ownerDocument);
dec.decode(node, graph.getModel());

要建立使用單個請求載入客戶端和圖表的頁面,請參閱後端中的部署示例。

1.15 功能依賴

mxGraph的功能依賴關係如圖所示:
mxGraph的功能依賴

2. 構造

mxGraph的建構函式如下,可以瞭解到mxGraph的初始化過程:

function mxGraph(container, model, renderHint, stylesheet) {
    // 在原型被修改為儲存一些監聽器的情況下初始化變數
    // 這是可能的,因為無論傳遞給ctor的引數如何都要執行createHandlers呼叫。
    this.mouseListeners = null;

    // 將renderHint轉換為dialect
    // 設定渲染cell的語言
    this.renderHint = renderHint;
    if (mxClient.IS_SVG) {
        this.dialect = mxConstants.DIALECT_SVG;
    } else if (renderHint === mxConstants.RENDERING_HINT_EXACT && mxClient.IS_VML) {
        this.dialect = mxConstants.DIALECT_VML;
    } else if (renderHint === mxConstants.RENDERING_HINT_FASTEST) {
        this.dialect = mxConstants.DIALECT_STRICTHTML;
    } else if (renderHint === mxConstants.RENDERING_HINT_FASTER) {
        this.dialect = mxConstants.DIALECT_PREFERHTML;
    } else { // default for VML
        this.dialect = mxConstants.DIALECT_MIXEDHTML;
    }

    // 初始化不需要容器的主要成員
    this.model = (model != null) ? model : new mxGraphModel();
    this.multiplicities = [];
    this.imageBundles = [];
    this.cellRenderer = this.createCellRenderer();
    this.setSelectionModel(this.createSelectionModel());
    this.setStylesheet((stylesheet != null) ? stylesheet : this.createStylesheet());
    this.view = this.createGraphView();

    // 新增graph model監聽器以更新檢視
    this.graphModelChangeListener = mxUtils.bind(this, function (sender, evt) {
        this.graphModelChanged(evt.getProperty('edit').changes);
    });
    this.model.addListener(mxEvent.CHANGE, this.graphModelChangeListener);

    // 使用預設的禁用設定建立基本事件處理程式
    this.createHandlers();

    // 如果指定了container,則初始化顯示
    if (container != null) {
        this.init(container);
    }

    this.view.revalidate();
}

mxGraph採用原型鏈方式繼承自mxEventSource:

mxGraph.prototype = new mxEventSource();
mxGraph.prototype.constructor = mxGraph;

同時在mxGraph類載入時會載入所需的語言資源:

if (mxLoadResources) {
    mxResources.add(mxClient.basePath + '/resources/graph');
} else {
    mxClient.defaultBundles.push(mxClient.basePath + '/resources/graph');
}

在建立mxGraph例項時,如果傳入了container則會呼叫init方法進行初始化並建立相應的資料結構:

/**
 * container - DOM結點用於包含graph
 */
mxGraph.prototype.init = function (container) {
    this.container = container;

    // 初始化就地編輯器
    this.cellEditor = this.createCellEditor();

    // 使用檢視初始化容器
    this.view.init();

    // 更新當前圖形的容器大小
    this.sizeDidChange();

    // 如果滑鼠離開容器,則隱藏工具提示並重置工具提示計時器
    mxEvent.addListener(container, 'mouseleave', mxUtils.bind(this, function () {
        if (this.tooltipHandler != null) {
            this.tooltipHandler.hide();
        }
    }));

    // 自動釋放記憶體
    if (mxClient.IS_IE) {
        mxEvent.addListener(window, 'unload', mxUtils.bind(this, function () {
            this.destroy();
        }));

        // 禁用文字的shift-click
        mxEvent.addListener(container, 'selectstart',
            mxUtils.bind(this, function (evt) {
                return this.isEditing() || (!this.isMouseDown && !mxEvent.isShiftDown(evt));
            })
        );
    }

    // 如果沒有顯示初始圖形或沒有定義形狀標籤,則在IE8標準模式下缺少最後一個形狀和連線預覽的解決方法
    if (document.documentMode == 8) {
        container.insertAdjacentHTML('beforeend', '<' + mxClient.VML_PREFIX + ':group' +
            ' style="DISPLAY: none;"></' + mxClient.VML_PREFIX + ':group>');
    }
};

3. 功能性

由於mxGraph定義了很多原型屬性,這裡不能一一列舉,需要的時候可以檢視原始碼。主要關注mxGraph功能性方面有哪些,以及結構如何。

3.1 控制代碼

mxGraph會在init方法呼叫之前建立幾個控制代碼用於處理對應的事件,有如下幾個控制代碼:tooltip、panning、connection、graph,不過目前都是關閉的:

mxGraph.prototype.createHandlers = function () {
    this.tooltipHandler = this.createTooltipHandler();
    this.tooltipHandler.setEnabled(false);
    
    this.selectionCellsHandler = this.createSelectionCellsHandler();
    
    this.connectionHandler = this.createConnectionHandler();
    this.connectionHandler.setEnabled(false);
    
    this.graphHandler = this.createGraphHandler();
    
    this.panningHandler = this.createPanningHandler();
    this.panningHandler.panningEnabled = false;
    
    this.popupMenuHandler = this.createPopupMenuHandler();
};

3.2 cell重疊

當cell重疊時,mxGraph需要額外進行一些操作記錄重疊的cell並更新graph的顯示,這樣才能實現cell重疊層次的改變:

/**
 * cell - 被疊加的mxCell
 * overlay - 新增到cell上的mxCellOverlay
 */
mxGraph.prototype.addCellOverlay = function (cell, overlay) {
	// overlays是例項變數
    if (cell.overlays == null) {
        cell.overlays = [];
    }

    cell.overlays.push(overlay);

    var state = this.view.getState(cell);

    // 如果state存在,立即更新cell的重疊顯示
    // state儲存的是cell在graph中的所有狀態
    if (state != null) {
        this.cellRenderer.redraw(state);
    }

    this.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY,
        'cell', cell, 'overlay', overlay));

    return overlay;
};

還有其他相關方法:getCellOverlays、removeCellOverlay、removeCellOverlays、clearCellOverlays,這裡就不詳述具體實現了。

3.3 就地編輯

就地編輯通過在graph中雙擊觸發,可以在雙擊的地方建立一個文字輸入框,具體實現如下:

/**
 * cell - 開始就地編輯的cell
 * evt - 可選的觸發編輯的滑鼠事件
 */
mxGraph.prototype.startEditingAtCell = function (cell, evt) {
    if (evt == null || !mxEvent.isMultiTouchEvent(evt)) {
        if (cell == null) {
            cell = this.getSelectionCell();

            // 選中的cell是否可以編輯
            if (cell != null && !this.isCellEditable(cell)) {
                cell = null;
            }
        }

        if (cell != null) {
            // 觸發START_EDITING事件
            this.fireEvent(new mxEventObject(mxEvent.START_EDITING,
                'cell', cell, 'event', evt));
            // 開始編輯
            this.cellEditor.startEditing(cell, evt);
            // 觸發EDITING_STARTED事件
            this.fireEvent(new mxEventObject(mxEvent.EDITING_STARTED,
                'cell', cell, 'event', evt));
        }
    }
};

還有其他相關方法:getEditingValue、stopEditing、labelChanged、cellLabelChanged、escape,這裡就不詳述具體實現了。

3.4 事件處理

事件處理方法的具體實現各異,不能一一詳述,只需要知道如何觸發,以及有什麼效果即可,列舉幾個事件處理函式:

方法 描述
escape 處理ESC鍵事件
click 處理cell上的單擊事件
dblClick 處理cell上的雙擊事件
tapAndHold 處理按住cell的事件

3.5 Cell樣式

cell的樣式處理有許多方法,列舉幾個graph處理cell的方法,這裡不詳述了:

方法 描述
getCellStyle 返回表示給定cell樣式的鍵值對的陣列
setCellStyle 設定指定cell的樣式。 如果沒有給出cell,則改變選中的cell
toggleCellStyle 以給定cell的樣式切換給定鍵的布林值,並將新值返回為0或1。如果未指定cell,則使用選中的cell
setCellStyleFlags 在指定cell的樣式中設定或切換給定鍵的給定位

3.6 Cell的排列和方向

這裡涉及處理cell的排列和方向的許多方法,列舉一些就不詳述了:

方法 描述
alignCells 使用可選引數作為座標,根據給定的對齊方式垂直或水平對齊給定cell
flipEdge 在null(或空)和alternateEdgeStyle之間切換給定邊的樣式。事務正在進行時,此方法將觸發mxEvent.FLIP_EDGE。返回被翻轉的邊
addImageBundle 新增指定的mxImageBundle
orderCells 將給定的cell移動到前面或後面。使用cellsOrdered執行更改。事務正在進行時,此方法將觸發mxEvent.ORDER_CELLS

3.7 分組

graph的分組是一個非常強大的功能,列舉一些方法:

方法 描述
groupCells 將cell新增到給定組中。使用cellsAdded,cellsMoved和cellsResized執行更改。事務正在進行時,此方法將觸發mxEvent.GROUP_CELLS。返回新組。僅當給定cell陣列中至少有一個條目時,才會建立組
getBoundsForGroup 返回用於給定組和子項的邊界
createGroupCell 如果沒有為group函式提供組cell,則鉤子用於建立組cell以儲存給定的mxCells陣列

3.8 Cell克隆、插入和刪除

graph的cell可以克隆、插入,也可以刪除,列舉一些常用的方法:

方法 描述
cloneCells 返回給定cell的克隆。克隆是使用mxGraphModel.cloneCells遞迴建立的。如果Edge的終端不在給定陣列中,則為相應的端分配終端點並移除終端
insertVertex 使用value作為使用者物件並將給定座標作為新vertex的mxGeometry,將新vertex新增到給定父mxCell中。id和style用於返回的新mxCell的各個屬性
removeCells 如果includeEdges為true,則從graph中移除給定的cell,包括所有連線的edge。使用cellsRemoved執行更改。事務正在進行時,此方法將觸發mxEvent.REMOVE_CELLS。刪除的單元格作為陣列返回

3.9 Cell的可見性

cell能設定它的可見性,用如下兩個方法設定:

方法 描述
toggleCells 如果includeEdges為true,則設定指定cell和所有連線edge的可見狀態。使用cellsToggled執行更改。事務正在進行時,此方法將觸發mxEvent.TOGGLE_CELLS。返回可見狀態已更改的cell
cellsToggled 設定指定cell的可見狀態

3.10 摺疊

graph的分組以及有些cell是可以摺疊的,用如下方法實現:

方法 描述
foldCells 如果recurse為true,則設定指定cell和所有後代的摺疊狀態。使用cellsFolded執行更改。事務正在進行時,此方法將觸發mxEvent.FOLD_CELLS。返回摺疊狀態已更改的cell
swapBounds 在執行交換之前,交換呼叫updateAlternateBounds的給定cell的幾何中的替代邊界和實際邊界

3.11 Cell大小

列舉一些改變cell大小的方法:

方法 描述
updateCellSize 使用cellSizeUpdated更新模型中給定cell的大小。事務正在進行時,此方法將觸發mxEvent.UPDATE_CELL_SIZE。返回其大小已更新的單元格
getPreferredSizeForCell 返回給定mxCell的首選寬度和高度,作為mxRectangle。要實現最小寬度,要新增新樣式
resizeCell 使用resizeCells設定給定cell的邊界。返回傳遞給函式的cell
resizeChildCells 相對於cell的當前幾何圖形,針對給定的新幾何圖形調整給定cell的