You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻求Angular中使用JSON渲染mxGraph图形的实现方案

在Angular中结合mxGraph实现JSON图形序列化与渲染

我正尝试在Angular应用中结合mxGraph使用JSON来序列化图形并赋予其功能,这应该是在Angular中实现图形序列化与功能拓展的优质方案,但一直没找到从Angular渲染JSON格式图形的最简示例。

在原生JavaScript的mxGraph示例中,仅通过JSON结构即可渲染图形:

示例JSON结构

{
    "graph": [
        {
            "value": {
                "name": "Daffy Duck"
            },
            "geometry": {
                "x": 90,
                "y": 60,
                "width": 120,
                "height": 30,
                "relative": false,
                "TRANSLATE_CONTROL_POINTS": true,
                "alternateBounds": null,
                "sourcePoint": null,
                "targetPoint": null,
                "points": null,
                "offset": null
            },
            "id": "2",
            "vertex": true,
            "connectable": true,
            "parent": "1",
            "source": null,
            "target": null,
            "edges": [
                {
                    "value": "Edge",
                    "geometry": {
                        "x": 0,
                        "y": 0,
                        "width": 0,
                        "height": 0,
                        "relative": true
                    },
                    "id": "4",
                    "edge": true,
                    "parent": "1",
                    "source": "2",
                    "target": "3",
                    "mxObjectId": "mxCell#7"
                }
            ],
            "mxObjectId": "mxCell#6"
        },
        {
            "value": {
                "name": "Bugs Bunny"
            },
            "geometry": {
                "x": 1020,
                "y": 60,
                "width": 120,
                "height": 30,
                "relative": false,
                "TRANSLATE_CONTROL_POINTS": true,
                "alternateBounds": null,
                "sourcePoint": null,
                "targetPoint": null,
                "points": null,
                "offset": null
            },
            "id": "3",
            "vertex": true,
            "connectable": true,
            "parent": "1",
            "source": null,
            "target": null,
            "edges": [
                {
                    "value": "Edge",
                    "geometry": {
                        "x": 0,
                        "y": 0,
                        "width": 0,
                        "height": 0,
                        "relative": true
                    },
                    "id": "4",
                    "edge": true,
                    "parent": "1",
                    "source": "2",
                    "target": "3",
                    "mxObjectId": "mxCell#7"
                }
            ],
            "mxObjectId": "mxCell#8"
        },
        {
            "value": "Edge",
            "geometry": {
                "x": 0,
                "y": 0,
                "width": 0,
                "height": 0,
                "relative": true
            },
            "id": "4",
            "edge": true,
            "parent": "1",
            "source": "2",
            "target": "3",
            "mxObjectId": "mxCell#7"
        },
        {
            "value": {
                "name": "Elmer Fudd"
            },
            "geometry": {
                "x": 90,
                "y": 220,
                "width": 120,
                "height": 30,
                "relative": false,
                "TRANSLATE_CONTROL_POINTS": true,
                "alternateBounds": null,
                "sourcePoint": null,
                "targetPoint": null,
                "points": null,
                "offset": null
            },
            "id": "5",
            "vertex": true,
            "connectable": true,
            "parent": "1",
            "source": null,
            "target": null,
            "edges": [
                {
                    "value": "Edge",
                    "geometry": {
                        "x": 0,
                        "y": 0,
                        "width": 0,
                        "height": 0,
                        "relative": true
                    },
                    "id": "7",
                    "edge": true,
                    "parent": "1",
                    "source": "5",
                    "target": "6",
                    "mxObjectId": "mxCell#10"
                }
            ],
            "mxObjectId": "mxCell#9"
        },
        {
            "value": {
                "name": "Tasmanian Devil"
            },
            "geometry": {
                "x": 1020,
                "y": 220,
                "width": 120,
                "height": 30,
                "relative": false,
                "TRANSLATE_CONTROL_POINTS": true,
                "alternateBounds": null,
                "sourcePoint": null,
                "targetPoint": null,
                "points": null,
                "offset": null
            },
            "id": "6",
            "vertex": true,
            "connectable": true,
            "parent": "1",
            "source": null,
            "target": null,
            "edges": [
                {
                    "value": "Edge",
                    "geometry": {
                        "x": 0,
                        "y": 0,
                        "width": 0,
                        "height": 0,
                        "relative": true
                    },
                    "id": "7",
                    "edge": true,
                    "parent": "1",
                    "source": "5",
                    "target": "6",
                    "mxObjectId": "mxCell#10"
                }
            ],
            "mxObjectId": "mxCell#11"
        },
        {
            "value": "Edge",
            "geometry": {
                "x": 0,
                "y": 0,
                "width": 0,
                "height": 0,
                "relative": true
            },
            "id": "7",
            "edge": true,
            "parent": "1",
            "source": "5",
            "target": "6",
            "mxObjectId": "mxCell#10"
        }
    ]
}

原生JS渲染代码

/******************************************************************
Demo
******************************************************************/

const graphX = new GraphX(document.getElementById('graphContainer'));

document.getElementById('buttons').appendChild(mxUtils.button('From JSON', () => {
  const dataModel = JSON.parse(document.getElementById('from').innerHTML);
  graphX.render(dataModel);
}));

document.getElementById('buttons').appendChild(mxUtils.button('To JSON', () => {
  const jsonNodes = graphX.getJsonModel();
  document.getElementById('to').innerHTML = `<pre>${syntaxHighlight(stringifyWithoutCircular(jsonNodes))}</pre>`;
}));

完整原生JS核心代码

/******************************************************************
Core
******************************************************************/

class JsonCodec extends mxObjectCodec {
    constructor() {
      super((value)=>{});
    }
    encode(value) {
        const xmlDoc = mxUtils.createXmlDocument();
        const newObject = xmlDoc.createElement("Object");
        for(let prop in value) {
          newObject.setAttribute(prop, value[prop]);
        }
        return newObject;
    }
    decode(model) {
      return Object.keys(model.cells).map(
        (iCell)=>{
          const currentCell = model.getCell(iCell);
          return (currentCell.value !== undefined)? currentCell : null;
        }
      ).filter((item)=> (item !== null));
    }
}

class GraphX {
  constructor(container){
            if (!mxClient.isBrowserSupported()) {
                  return mxUtils.error('Browser is not supported!', 200, false);
            } 
      mxEvent.disableContextMenu(container);
      this._graph = new mxGraph(container);
      this._graph.setConnectable(true);
      this._graph.setAllowDanglingEdges(false);
      new mxRubberband(this._graph); // Enables rubberband selection

      this.labelDisplayOveride();
      this.styling();
  }
  
  labelDisplayOveride() { // Overrides method to provide a cell label in the display
    this._graph.convertValueToString = (cell)=> {
      if (mxUtils.isNode(cell.value)) {
        if (cell.value.nodeName.toLowerCase() === 'object') {
          const name = cell.getAttribute('name', '');
          return name;
        }
      }
      return '';
    };
  }
  
  styling() {
    // Creates the default style for vertices
    let style = [];
    style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
    style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
    style[mxConstants.STYLE_STROKECOLOR] = 'gray';
    style[mxConstants.STYLE_ROUNDED] = true;
    style[mxConstants.STYLE_FILLCOLOR] = '#EEEEEE';
    style[mxConstants.STYLE_GRADIENTCOLOR] = 'white';
    style[mxConstants.STYLE_FONTCOLOR] = 'black';
    style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
    style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
    style[mxConstants.STYLE_FONTSIZE] = '12';
    style[mxConstants.STYLE_FONTSTYLE] = 1;
    this._graph.getStylesheet().putDefaultVertexStyle(style);

    // Creates the default style for edges
    style = this._graph.getStylesheet().getDefaultEdgeStyle();
    style[mxConstants.STYLE_STROKECOLOR] = '#0C0C0C';
    style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR] = 'white';
    style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
    style[mxConstants.STYLE_ROUNDED] = true;
    style[mxConstants.STYLE_FONTCOLOR] = 'black';
    style[mxConstants.STYLE_FONTSIZE] = '10';
    this._graph.getStylesheet().putDefaultEdgeStyle(style);
  }
  
  getJsonModel(){
      const encoder = new JsonCodec();
      const jsonModel = encoder.decode(this._graph.getModel());
      return {
        "graph": jsonModel
      }
  }
  
  render(dataModel) {
        const jsonEncoder = new JsonCodec();
    
        this._vertices = {};
        this._dataModel = dataModel;
    
                const parent = this._graph.getDefaultParent();
                this._graph.getModel().beginUpdate(); // Adds cells to the model in a single step
                try {
          
          this._dataModel.graph.map(
            (node)=> {
                if(node.value) {
                  if(typeof node.value === "object") {
                       const xmlNode = jsonEncoder.encode(node.value);
                       this._vertices[node.id] = this._graph.insertVertex(parent, null, xmlNode, node.geometry.x, node.geometry.y, node.geometry.width, node.geometry.height);
                  } else if(node.value === "Edge") {
                       this._graph.insertEdge(parent, null, 'Edge', this._vertices[node.source],  this._vertices[node.target])
                  }
                }
            }
          );
          
                } finally {
                    this._graph.getModel().endUpdate(); // Updates the display
        }
  }  
}

/******************************************************************
Demo
******************************************************************/

const graphX = new GraphX(document.getElementById('graphContainer'));

document.getElementById('buttons').appendChild(mxUtils.button('From JSON', () => {
  const dataModel = JSON.parse(document.getElementById('from').innerHTML);
  graphX.render(dataModel);
}));

document.getElementById('buttons').appendChild(mxUtils.button('To JSON', () => {
  const jsonNodes = graphX.getJsonModel();
  document.getElementById('to').innerHTML = `<pre>${syntaxHighlight(stringifyWithoutCircular(jsonNodes))}</pre>`;
}));


/******************************************
Utils
********************************************/

function stringifyWithoutCircular(json){
  return JSON.stringify(
      json,
      ( key, value) => {
        if((key === 'parent' || key == 'source' || key == 'target') && value !== null) { 
          return value.id;
        } else if(key === 'value' && value !== null && value.localName) {
          let results = {};
          Object.keys(value.attributes).forEach(
            (attrKey)=>{
              const attribute = value.attributes[attrKey];
              results[attribute.nodeName] = attribute.nodeValue;
            }
          )
          return results;
        }
        return value;
      },
      4
  );
}

function syntaxHighlight(json) {
    json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*)?:?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
        var cls = 'number';
        if (/^"/.test(match)) {
            if (/:$/.test(match)) {
                cls = 'key';
            } else {
                cls = 'string';
            }
        } else if (/true|false/.test(match)) {
            cls = 'boolean';
        } else if (/null/.test(match)) {
            cls = 'null';
        }
        return '<span class="' + cls + '">' + match + '</span>';
    });
}

请问有没有人知道如何在Angular中实现该功能?我希望能整理相关文档分享给大家。

非常感谢。愿原力与你同在。

注:mxHierarchicalLayout.js类可用于创建图形的层级模型。

我会继续尝试并分享所学内容,若有相关思路,恳请不吝赐教。


内容的提问来源于stack exchange,提问作者Owl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 08:15:44