mxgraph的艱難入門

前言

公司今年有個需求是但願前端能展示出大數據計算過程的拓撲圖,最好仍是可以動態展示。部門老大給咱們推薦了mxgraph這個繪圖js庫,但願咱們可以熟練掌握。因而我就先着手開始研究起來了。不過我如今對於mxgraph的掌握還很是的渣,這篇文章是記錄到如今爲止個人學習成果。javascript

關於js繪圖

繪圖這塊是前端的一個細分領域,是屬於那種作的人很少,但深刻下去頗有前景的一個方向,固然也是一個比較難的方向。提到js圖庫,你們通常都會想到的是echarts、3D.js或者three.js之類的。echarts是使用他們給定的圖庫,大可能是圖表,改變本身的數據或者是定製一些細節。3D.js和three.js瞭解很少,可是貌似是從點線開始畫起的。若是用來開發,成本難度就稍微有點大。咱們公司是但願可以根據本身的需求定製化圖形,最好是有一些基礎的圖元,和封裝好的api操做,因此老大給咱們推薦了mxgraph。html

關於mxgraph

mxgraph庫是一個誕生比較久的項目,提供了基礎的圖元和繪製方法,封裝了繪製過程當中的基礎操做api。有一些比較著名的在線繪圖網站就是基於mxgraph二次開發的,好比draw.ioprocess onmxgraph庫在github上有三千多個star,提供了90多個demo給使用者閱讀,demo展示了絕大多數mxgraph所能作到的事。最多見的上手方式就是查看所給的示例demo,對照着示例的效果,查詢對應的api文檔查詢用法。下面我便經過介紹幾個我學習的demo來講明mxgraph的用法。前端

1.hello world

helloworld.html顧名思義,就像全部的程序學習最開始時,學習如何輸出「hello world」同樣,這個示例是全部的demo中最基礎的一個。介紹了是如何使用mxgraph,繪製兩個矩形,並將其鏈接在一塊兒。咱們來看完整的示例代碼:java

<html>
<head>
  <title>Hello, World! example for mxGraph</title>
<script type="text/javascript">
  mxBasePath = '../src';
</script>
<script type="text/javascript" src="../src/js/mxClient.js"></script>
<script type="text/javascript">
  function main(container)
  {
    if (!mxClient.isBrowserSupported()){
      mxUtils.error('Browser is not supported!', 200, false);
    } else {
      var graph = new mxGraph(container);
      new mxRubberband(graph);
      var parent = graph.getDefaultParent()       
      graph.getModel().beginUpdate();
      try
      {
        var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
        var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
        var e1 = graph.insertEdge(parent, null, '', v1, v2);
      } finally {
        graph.getModel().endUpdate();
      }
    }
  };
</script>
</head>
<body onload="main(document.getElementById('graphContainer'))">
  <div id="graphContainer" style="position:relative;overflow:hidden;width:321px;height:241px;background:url('editors/images/grid.gif');cursor:default;">
  </div>
</body>
</html>
複製代碼

這個示例中咱們看到,若是要使用mxgraph,須要定義路徑常量mxBasePath等於mxgraph核心資源的路徑'../../src',而後還要引用mxClient.js文件,在這以後咱們就可使用mxgraph了。 其中我把繪製的核心代碼單獨拿出來:node

// 新建一個mxgraph中的graph示例,graph能夠理解爲咱們繪製圖形的實例對象
var graph = new mxGraph(container);

//獲取當前圖層之上的父圖層
var parent = graph.getDefaultParent();
        
// 每次新增圖形,或者更新圖形的時候必需要調用這個方法
graph.getModel().beginUpdate();
try
{
  // 這條語句在圖層中繪製出一個內容爲'Hello'的矩形
  // insertVertex()函數中依次傳入的是父圖層,當前圖元的id,圖元中的內容,定位x,定位y,寬w,高h,後面還能夠添加參數爲當前圖源的樣式,是否爲相對位置
  var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
  var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
  // 這條語句使用insertEdge,在圖層中繪製出一個由v1指向v2的線。
  var e1 = graph.insertEdge(parent, null, '', v1, v2);
} finally {
  // 每次更新或者新增圖形以後必須調用這個方法,因此這個方法須要在finally中執行
  graph.getModel().endUpdate();
}
複製代碼
2.stylesheet.html

這個例子告訴咱們如何修改圖形的樣式。分爲兩種方式,修改全局樣式和修改單獨某個圖元的樣式。 先看全局修改樣式的代碼:git

// 修改矩形圖元的默認樣式
var style = [];
style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
style[mxConstants.STYLE_ROUNDED] = true;
style[mxConstants.STYLE_FILLCOLOR] = '#EEEEEE';
style[mxConstants.STYLE_FONTCOLOR] = '#774400';
style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
style[mxConstants.STYLE_FONTSIZE] = '12';
graph.getStylesheet().putDefaultVertexStyle(style);

// 修改連線的默認樣式
style = [];
style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR;
style[mxConstants.STYLE_STROKECOLOR] = '#6482B9';
style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
style[mxConstants.STYLE_FONTSIZE] = '10';
graph.getStylesheet().putDefaultEdgeStyle(style);
複製代碼

mxStylesheet類用於管理圖形樣式,經過 graph.getStylesheet() 能夠獲取當前圖形的 mxStylesheet對象。mxStylesheet 對象的 style 屬性也是一個對象,該對象默認狀況下包含兩個對象defaultVertexStyle、defaultEdgeStyle,修改這兩個對象裏的樣式屬性對全部線條/節點都生效。github

咱們看下如何單獨修改某一個圖元的樣式。算法

var myStyle = []; // 定義一個自定義樣式
style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
style[mxConstants.STYLE_STROKECOLOR] = '#6482B9';
style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
style[mxConstants.STYLE_FONTSIZE] = '10';
 
// 這裏咱們使用putCellStyle() 定義一個本身的樣式myCellStyle
graph.getStylesheet().putCellStyle('myCellStyle', myStyle);
// 注意下面咱們用了兩種方式添加給一個單獨的cell添加樣式
var v1 = graph.insertVertex(parent, null, 'myCell', 100, 100, 50, 30, 'myCellStyle;FONTCOLOR=#999999')
複製代碼

如上面所示,咱們可使用putCellStyle()本身定義個本身的樣式對象,在新建圖元的時候看成參數使用,也能夠同時直接添加你想要的樣式。格式以下:json

'stylename;key=value;'
複製代碼

分號前能夠跟命名樣式名稱或者一個樣式的 key、value 。對了,若是使用 [mxConstants.STYLE_IMAGE]: 'path/image.png'這個樣式屬性的話,能夠在節點上添加圖片。segmentfault

3.wrapping.html

這個例子告訴咱們如何在節點(vertex)和連線(edge)的標籤上使用HTML標籤和字符換行,直接來看其中最核心的代碼:

var graph = new mxGraph(container);

// 渲染的時候使用html標籤
graph.setHtmlLabels(true);

// 在渲染線edge的時候 禁止使用頁面內編輯
graph.isCellEditable = function(cell){
  return !this.model.isEdge(cell);
};

var parent = graph.getDefaultParent();

graph.getModel().beginUpdate();
try{
  var v1 = graph.insertVertex(parent, null, 'Cum Caesar vidisset, portum plenum esse, iuxta navigavit.',
      20, 20, 100, 70, 'whiteSpace=wrap;');
  var v2 = graph.insertVertex(parent, null, 'Cum Caesar vidisset, portum plenum esse, iuxta navigavit.',
      220, 150, 80, 70, 'whiteSpace=wrap;');
  var e1 = graph.insertEdge(parent, null, 'Cum Caesar vidisset, portum plenum esse, iuxta navigavit.',
      v1, v2, 'whiteSpace=wrap;');
  e1.geometry.width = 100;
} finally {
  graph.getModel().endUpdate();
}
複製代碼

因此中核心的一代碼爲graph.setHtmlLabels(true),同時配合節點的樣式'whiteSpace=wrap'就能夠實如今圖中換行。

4.htmlLabel.html

這個例子在節點(vertex)中添加了html代碼,並用html關聯了裏面的狀態:

var graph = new mxGraph(container);

graph.setHtmlLabels(true);

// 建立一個保存狀態的xml用戶對象
var doc = mxUtils.createXmlDocument();
var obj = doc.createElement('UserObject');
obj.setAttribute('label', 'Hello, World!');
obj.setAttribute('checked', 'false');

// Adds optional caching for the HTML label
var cached = true;

if (cached){
  // Ignores cached label in codec
  mxCodecRegistry.getCodec(mxCell).exclude.push('div');
  
  // Invalidates cached labels
  graph.model.setValue = function(cell, value){
    cell.div = null;
    mxGraphModel.prototype.setValue.apply(this, arguments);
  };
}

// 這裏是重寫了展現cell內部展示方法,改寫爲展現html標籤的內容
// 這裏的代碼能夠實現自定義cell內部的html結構
graph.convertValueToString = function(cell){
  if (cached && cell.div != null){
    // 使用緩存中的html
    return cell.div;
  } else if (mxUtils.isNode(cell.value) && cell.value.nodeName.toLowerCase() == 'userobject') {
    // 返回一個div標籤的 dom元素
    var div = document.createElement('div');
    div.innerHTML = cell.getAttribute('label');
    mxUtils.br(div); // 添加一個換行
    // 定義一個input checkbox元素
    var checkbox = document.createElement('input');
    checkbox.setAttribute('type', 'checkbox');
    // 設置input的初始屬性值
    if (cell.getAttribute('checked') == 'true') {
      checkbox.setAttribute('checked', 'checked');
      checkbox.defaultChecked = true;
    }

    // 添加checkbox被點擊以後的事件處理
    mxEvent.addListener(checkbox, (mxClient.IS_QUIRKS) ? 'click' : 'change', function(evt) {
      var elt = cell.value.cloneNode(true);
      elt.setAttribute('checked', (checkbox.checked) ? 'true' : 'false');

      graph.model.setValue(cell, elt);
    });
    // 在新建的div dom上添加input checkbox元素
    div.appendChild(checkbox);

    if (cached) { // 將新建的html添加到緩存中
      // Caches label
      cell.div = div;
    }
    return div;
  }

  return '';
};

// Overrides method to store a cell label in the model
var cellLabelChanged = graph.cellLabelChanged;
graph.cellLabelChanged = function(cell, newValue, autoSize) {
  if (mxUtils.isNode(cell.value) && cell.value.nodeName.toLowerCase() == 'userobject')
  {
    // Clones the value for correct undo/redo
    var elt = cell.value.cloneNode(true);
    elt.setAttribute('label', newValue);
    newValue = elt;
  }
  
  cellLabelChanged.apply(this, arguments);
};

// Overrides method to create the editing value
var getEditingValue = graph.getEditingValue;
graph.getEditingValue = function(cell) {
  if (mxUtils.isNode(cell.value) && cell.value.nodeName.toLowerCase() == 'userobject')
  {
    return cell.getAttribute('label');
  }
};

var parent = graph.getDefaultParent();
// 使用本身定義xml用戶對象 新建vertex
graph.insertVertex(parent, null, obj, 20, 20, 80, 60);
複製代碼

上面這個代碼包含了如何使用本身定義的html繪製節點(vertex)中的內容,且給其中的元素綁定事件。經過代碼能夠看出是須要同時新建一個xml對象和相應的dom元素。由此能夠知道在mxgraph中每一個圖形都有其對應的xml數據結構。

可是上述中的代碼還有幾處我也沒搞懂的地方,好比緩存處理、cellLabelChanged()和getEditingValue()的用法,以後還須要查閱資料,或者請教公司的小夥伴。

5.hierarchicallayout.html

這個示例是展現了繪圖中的兩種自動佈局方法,分別是分級和有機佈局算法。

var graph = new mxGraph(container);

// 在graph實例上新建這兩種佈局方法
var layout = new mxHierarchicalLayout(graph); // 分級佈局
var organic = new mxFastOrganicLayout(graph); // 有機佈局
organic.forceConstant = 120; // 這個值是有機佈局節點的平均間距。默認是50

var parent = graph.getDefaultParent();

// 添加一個觸發分級佈局的按鈕
var button = document.createElement('button');
mxUtils.write(button, 'Hierarchical');
mxEvent.addListener(button, 'click', function(evt){
  layout.execute(parent);
});
document.body.appendChild(button);

// 添加一個觸發有機算法佈局的按鈕
var button = document.createElement('button');
mxUtils.write(button, 'Organic');
mxEvent.addListener(button, 'click', function(evt){
  organic.execute(parent);
});

document.body.appendChild(button);

graph.getModel().beginUpdate();
try
{
  var v1 = graph.insertVertex(parent, null, '1', 0, 0, 80, 30);
  var v2 = graph.insertVertex(parent, null, '2', 0, 0, 80, 30);
  var v3 = graph.insertVertex(parent, null, '3', 0, 0, 80, 30);
  var v4 = graph.insertVertex(parent, null, '4', 0, 0, 80, 30);
  var v5 = graph.insertVertex(parent, null, '5', 0, 0, 80, 30);
  var v6 = graph.insertVertex(parent, null, '6', 0, 0, 80, 30);
  var v7 = graph.insertVertex(parent, null, '7', 0, 0, 80, 30);
  var v8 = graph.insertVertex(parent, null, '8', 0, 0, 80, 30);
  var v9 = graph.insertVertex(parent, null, '9', 0, 0, 80, 30);

  var e1 = graph.insertEdge(parent, null, '', v1, v2);
  var e2 = graph.insertEdge(parent, null, '', v1, v3);
  var e3 = graph.insertEdge(parent, null, '', v3, v4);
  var e4 = graph.insertEdge(parent, null, '', v2, v5);
  var e5 = graph.insertEdge(parent, null, '', v1, v6);
  var e6 = graph.insertEdge(parent, null, '', v2, v3);
  var e7 = graph.insertEdge(parent, null, '', v6, v4);
  var e8 = graph.insertEdge(parent, null, '', v6, v1);
  var e9 = graph.insertEdge(parent, null, '', v6, v7);
  var e10 = graph.insertEdge(parent, null, '', v7, v8);
  var e11 = graph.insertEdge(parent, null, '', v7, v9);
  var e12 = graph.insertEdge(parent, null, '', v7, v6);
  var e13 = graph.insertEdge(parent, null, '', v7, v5);
  
  // 添加完點線以後 默認使用分級佈局
  layout.execute(parent);
} finally {
  graph.getModel().endUpdate();
}

if (mxClient.IS_QUIRKS) { // 樣式的兼容處理
  document.body.style.overflow = 'hidden';
  new mxDivResizer(container);
}
複製代碼

這個例子比較簡單,看註釋就能夠理解了。同時mxgraph中還有其餘的佈局算法。

6.layers.html

這個例子是使用多圖層來放置細胞(cell),在動態繪圖的過程當中,或許可使用這個來實現動態添加新的圖層。

// 在所給的container中用model新建graph
// 須要個root和兩個layers
// 經過var layer = model.add(root, new mxCell())能夠新增layer
var root = new mxCell();
var layer0 = root.insert(new mxCell());
var layer1 = root.insert(new mxCell());
var model = new mxGraphModel(root);

var graph = new mxGraph(container, model);
graph.setEnabled(false);

var parent = graph.getDefaultParent();
model.beginUpdate();
try{
  // 分別在不一樣的圖層中添加vertex和edge
  var v1 = graph.insertVertex(layer1, null, 'Hello,', 20, 20, 80, 30, 'fillColor=#C0C0C0');
  var v2 = graph.insertVertex(layer1, null, 'Hello,', 200, 20, 80, 30, 'fillColor=#C0C0C0');
  var v3 = graph.insertVertex(layer0, null, 'World!', 110, 150, 80, 30);

  var e1 = graph.insertEdge(layer1, null, '', v1, v3, 'strokeColor=#0C0C0C');
  e1.geometry.points = [new mxPoint(60, 165)];

  var e2 = graph.insertEdge(layer0, null, '', v2, v3);
  e2.geometry.points = [new mxPoint(240, 165)];

  var e3 = graph.insertEdge(layer0, null, '', v1, v2,
      'edgeStyle=topToBottomEdgeStyle');
  e3.geometry.points = [new mxPoint(150, 30)];
  
  var e4 = graph.insertEdge(layer1, null, '', v2, v1,
      'strokeColor=#0C0C0C;edgeStyle=topToBottomEdgeStyle');
  e4.geometry.points = [new mxPoint(150, 40)];
} finally {
  model.endUpdate();
}

// 新增layer 0 按鈕,點擊控制layer0的顯示隱藏
document.body.appendChild(mxUtils.button('Layer 0', function(){
  model.setVisible(layer0, !model.isVisible(layer0));
}));
// 同上
document.body.appendChild(mxUtils.button('Layer 1', function(){
  model.setVisible(layer1, !model.isVisible(layer1));
}));
複製代碼

總結

上面知識簡單介紹了幾個跟公司需求有關的demo,其中還有有一些問題沒有解決。好比如何綁定節點中的鼠標事件,如何將json數據轉換爲一個圖形等等。還須要以後的努力。經過這幾個星期的學習,清楚的認識到本身的能力還有較大的提高,同時英文真的是工程師很重要的一個技能。同時感受到本身的對於問題的解決方案,仍是缺乏直覺。後面還須要繼續的全面提升本身。

參考文章

mxGraph

mxGraph api specification

mxGraph用戶手冊中文版

mxGraph入門實例教程

原文連接:tech.gtxlab.com/mxgraph.htm…


做者簡介: 宮晨光,人和將來大數據前端工程師。

相關文章
相關標籤/搜索