在vis.js网络中,如何根据深度自动设置节点的级别?

In a vis.js network, how to set the level of a node automatically based on its depth?

我正在尝试使用 vis.js 创建层次图。我可以创建层次结构图,但布局不是我要找的:节点位于看似任意的级别上。我希望节点根据它们与根之间的边数处于不同的级别。

数据最初来自SQL。目前,我有一个 Python 脚本将数据处理成 DOT 语言(并且我可以使用 Graphviz 以我想要的布局显示图形),所以这就是我使用 .convertDot 方法的原因。我可以在导入 vis.js 后重新处理网络,并分别为每个节点添加正确的 "level" 属性,但必须有更好的方法。

这是我目前拥有的完整 HTML/JS 文档:

<!DOCTYPE HTML>
<html>
<head>
  <script src="./vis/dist/vis.js"></script>
  <link href="./vis/dist/vis.css" rel="stylesheet" type="text/css" />
</head>

<body>
<div id="graph", style="height: 1000px"></div> 

  <script> 
    // provide data in the DOT language
    var DOTstring = 'digraph {"13332500" -> "13483400" "13567500" -> "13483400" "10037901" -> "10037902" "10037902" -> "13483400" "15038400" -> "13455700" "13455700" -> "13455702" "13455702" -> "13483400" "13567300" -> "13483400" "11890500" -> "13483400" "13483400" -> "13554900"}';
    var parsedData = vis.network.convertDot(DOTstring);

    var data = {
      nodes: parsedData.nodes,
      edges: parsedData.edges
    }

    var container = document.getElementById('graph');
    var options = parsedData.options;

    // you can extend the options like a normal JSON variable:
    options.layout = {
      "hierarchical": true
    }

    // create a network
    var network = new vis.Network(container, data, options);
  </script>

</body>
</html>

代码将生成以下内容: 这是我正在寻找的布局,由 graphviz 生成:

如果你想知道为什么我在 vis.js 中尝试这样做,而我已经在 graphviz 中工作了:原因是我想让它具有交互性。

类似的内容可能对您有用。这是针对 React 的,但是有用的功能是分开的。

export default class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            graphData: {
                nodes: [
                    {"id": -1, "label": "Licenciatura en Sistemas", "approved": true},
                    {"id": 0, "label": "Introducción a la Programación"},
                    {"id": 1, "label": "Introducción a la Matemática"},
                    {"id": 2, "label": "Taller de Lectura y Escritura"},
                    {"id": 3, "label": "Programación I"},
                    {"id": 4, "label": "Lógica y Teoría de Números"},
                    {"id": 5, "label": "Organización del Computador I"},
                    {"id": 6, "label": "Programación II"},
                    {"id": 7, "label": "Álgebra Lineal"},
                    {"id": 8, "label": "Sistemas Operativos y Redes I"},
                    {"id": 9, "label": "Programación III"},
                    {"id": 10, "label": "Cálculo para Computación"},
                    {"id": 11, "label": "Problemas Socioeconómicos Contemporáneos"},
                    {"id": 12, "label": "Bases de Datos I"},
                    {"id": 13, "label": "Matemática Discreta"},
                    {"id": 14, "label": "Especificaciones y Verificación de Software"},
                    {"id": 15, "label": "Teoría de la Computación"},
                    {"id": 16, "label": "Ingeniería de Software I"},
                    {"id": 17, "label": "Probabilidad y Estadística"},
                    {"id": 18, "label": "Proyecto Profesional I"},
                    {"id": 19, "label": "Ingeniería de Software II"},
                    {"id": 20, "label": "Organización del Computador II"},
                    {"id": 21, "label": "Proyecto Profesional II"},
                    {"id": 22, "label": "Bases de Datos II"},
                    {"id": 23, "label": "Sistemas Operativos y Redes II"},
                    {"id": 24, "label": "Práctica Profesional Supervisada I"},
                    {"id": 25, "label": "Modelado y Optimización"},
                    {"id": 26, "label": "Informática y Sociedad"},
                    {"id": 27, "label": "Taller de Tesina de Licenciatura"},
                    {"id": 28, "label": "Gestión de Proyectos"},
                    {"id": 29, "label": "Laboratorio Interdisciplinario"},
                    {"id": 30, "label": "Taller de Utilitarios"},
                    {"id": 31, "label": "Inglés Lectocomprensión I"},
                    {"id": 32, "label": "Inglés Lectocomprensión II"},
                    {"id": 33, "label": "Inglés Lectocomprensión III"},
                    {"id": 34, "label": "TIC Lectura y Lectoescritura"},
                    {"id": 35, "label": "TIC Ciencias exactas"},
                    {"id": 36, "label": "TIC Matemáticas"},
                ],
                edges: [
                    {"from": -1, "to": 29},
                    {"from": -1, "to": 30},
                    {"from": -1, "to": 34},
                    {"from": -1, "to": 35},
                    {"from": -1, "to": 36},
                    {"from": 0, "to": 3},
                    {"from": 0, "to": 5},
                    {"from": 1, "to": 4},
                    {"from": 1, "to": 7},
                    {"from": 1, "to": 10},
                    {"from": 3, "to": 6},
                    {"from": 4, "to": 13},
                    {"from": 4, "to": 12},
                    {"from": 4, "to": 14},
                    {"from": 5, "to": 20},
                    {"from": 5, "to": 8},
                    {"from": 5, "to": 12},
                    {"from": 5, "to": 15},
                    {"from": 6, "to": 9},
                    {"from": 6, "to": 12},
                    {"from": 7, "to": 10},
                    {"from": 8, "to": 23},
                    {"from": 9, "to": 14},
                    {"from": 9, "to": 15},
                    {"from": 9, "to": 16},
                    {"from": 9, "to": 22},
                    {"from": 10, "to": 13},
                    {"from": 10, "to": 17},
                    {"from": 12, "to": 22},
                    {"from": 13, "to": 17},
                    {"from": 14, "to": 18},
                    {"from": 16, "to": 19},
                    {"from": 16, "to": 26},
                    {"from": 16, "to": 18},
                    {"from": 17, "to": 25},
                    {"from": 18, "to": 21},
                    {"from": 19, "to": 28},
                    {"from": 21, "to": 24},
                    {"from": 21, "to": 27},
                    {"from": 31, "to": 32},
                    {"from": 32, "to": 33},
                    {"from": 34, "to": 3},
                    {"from": 34, "to": 11},
                    {"from": 34, "to": 2},
                    {"from": 35, "to": 0},
                    {"from": 35, "to": 1},
                    {"from": 36, "to": 0},
                    {"from": 36, "to": 1}
                ]
            }
        };
        this.calculateLevels(this.state.graphData.nodes, this.state.graphData.edges);
    }

    calculateLevels(nodes, edges) {
        let reverseEdgesMap = new Map();
        let nodesMap = new Map();
        for (let edge of edges) {
            let from = edge.from;
            let to = edge.to;
            if (reverseEdgesMap.has(to)) {
                reverseEdgesMap.get(to).push(from);
            } else {
                reverseEdgesMap.set(to, [edge.from]);
            }
        }
        for (let node of nodes) {
            let id = node.id;
            nodesMap.set(id, node);
        }
        for (let node of nodes) {
            node.level = this.calculateMaxNodeLength(nodesMap, reverseEdgesMap, node.id);
        }
        console.log(nodes);
    }

    /**
     *
     * @param nodesMap
     * @param reverseEdgesMap
     * @param nodeId
     * @param parents
     * @returns {number}
     */
    calculateMaxNodeLength(nodesMap, reverseEdgesMap, nodeId) {
        if (!(nodesMap instanceof Map)) {
            throw new Error("nodesMap parameter should be an instance of Map")
        }
        if (!(reverseEdgesMap instanceof Map)) {
            throw new Error("reverseEdgesMap parameter should be an instance of Map")
        }
        let parents = [];
        let longestParentDepth = 0;
        if (reverseEdgesMap.has(nodeId)) {
            parents = reverseEdgesMap.get(nodeId);
            for (let parentId of parents) {
                let parentDepth = 1;
                parentDepth += this.calculateMaxNodeLength(nodesMap, reverseEdgesMap, parentId);
                if (parentDepth > longestParentDepth) {
                    longestParentDepth = parentDepth;
                }
            }
        }
        return longestParentDepth;
    }


    componentDidMount() {

    }


    render() {
        return (
            <div className="App">
                <VisNetwork nodes={this.state.graphData.nodes} edges={this.state.graphData.edges}/>
            </div>
        )
    }
}

从 vis.js 版本 6.2 开始,有一个新选项允许按需要显示图形。使用 sortMethod: 'directed'shakeTowards: 'roots'.

来源

例子

在我的原始脚本中修改 options.layout 得到了想要的结果:

<!DOCTYPE HTML>
<html>
<head>
  <script src="./vis/dist/vis.js"></script>
  <link href="./vis/dist/vis.css" rel="stylesheet" type="text/css" />
</head>

<body>
<div id="graph", style="height: 1000px"></div> 

  <script> 
    // provide data in the DOT language
    var DOTstring = 'digraph {"13332500" -> "13483400" "13567500" -> "13483400" "10037901" -> "10037902" "10037902" -> "13483400" "15038400" -> "13455700" "13455700" -> "13455702" "13455702" -> "13483400" "13567300" -> "13483400" "11890500" -> "13483400" "13483400" -> "13554900"}';
    var parsedData = vis.network.convertDot(DOTstring);

    var data = {
      nodes: parsedData.nodes,
      edges: parsedData.edges
    }

    var container = document.getElementById('graph');
    var options = parsedData.options;

    // you can extend the options like a normal JSON variable:
    options.layout = {
      hierarchical: {
        sortMethod: 'directed',  // hubsize, directed
        shakeTowards: 'roots',  // roots, leaves
        direction: 'DU'   // UD, DU, LR, RL
        }
    }
    
    // create a network
    var network = new vis.Network(container, data, options);
  </script>

</body>
</html>