javascript 如何使用 D3 布局非树层次结构

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11053511/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 11:56:35  来源:igfitidea点击:

How to layout a non-tree hierarchy with D3

javascriptd3.js

提问by John Walthour

D3 has a variety of layouts for directed graphs that are strict trees, such as the following:

D3 对于严格树的有向图有多种布局,例如:

A
|\
B C
 / \
D   E

I need to draw a hierarchy of nodes that is not a tree, but is a directed acyclic graph. This is a problem for a tree layout, because several of the branches converge:

我需要绘制一个不是树而是有向无环图的节点层次结构。这是树布局的问题,因为几个分支会聚:

A
|\
B C
 \|
  D

Does anyone know of a D3 layout for general hierarchies? Or alternatively, some clever hack to the existing treelayout? I've noticed GraphVis handles this situation well, but D3 produces a graph that better suits the requirements here.

有谁知道一般层次结构的 D3 布局?或者,对现有的树状布局进行一些巧妙的破解?我注意到 GraphVis 可以很好地处理这种情况,但 D3 生成的图形更适合此处的要求。

回答by Phil Laliberte

You could create your own code without having to rely on a D3 layout in order to get it done.

您可以创建自己的代码,而不必依赖 D3 布局来完成。

I've provided an example in a jsFiddle. The example is pretty simplistic and would need to be worked a little bit to accommodate more complex examples.

在 jsFiddle 中提供了一个示例。该示例非常简单,需要进行一些工作以适应更复杂的示例。

The example could be re-worked to process hierarchical data as well with relatively little effort.

该示例也可以通过相对较少的工作重新处理以处理分层数据。

Here is the code I have used in the jsFiddle:

这是我在 jsFiddle 中使用的代码:

 // Sample data set
var json = {
    nodes: [{
        name: 'A'},
    {
        name: 'B'},
    {
        name: 'C'},
    {
        name: 'D'}],
    links: [{
        source: 'A',
        target: 'B'},
    {
        source: 'A',
        target: 'C'},
    {
        source: 'B',
        target: 'D'},
    {
        source: 'C',
        target: 'D'}
                                                                                   ]

};

var vis = d3.select('#vis').attr('transform', 'translate(20, 20)');

// Build initial link elements - Build first so they are under the nodes
var links = vis.selectAll('line.link').data(json.links);
links.enter().append('line').attr('class', 'link').attr('stroke', '#000');

// Build initial node elements
var nodes = vis.selectAll('g.node').data(json.nodes);
nodes.enter().append('g').attr('class', 'node').append('circle').attr('r', 10).append('title').text(function(d) {
    return d.name;
});

// Store nodes in a hash by name
var nodesByName = {};
nodes.each(function(d) {
    nodesByName[d.name] = d;
});

// Convert link references to objects
links.each(function(link) {
    link.source = nodesByName[link.source];
    link.target = nodesByName[link.target];
    if (!link.source.links) {
        link.source.links = [];
    }
    link.source.links.push(link.target);
    if (!link.target.links) {
        link.target.links = [];
    }
    link.target.links.push(link.source);
});

// Compute positions based on distance from root
var setPosition = function(node, i, depth) {
    if (!depth) {
        depth = 0;
    }
    if (!node.x) {
        node.x = (i + 1) * 40;
        node.y = (depth + 1) * 40;
        if (depth <= 1) {
            node.links.each(function(d, i2) {
                setPosition(d, i2, depth + 1);
            });
        }

    }

};
nodes.each(setPosition);

// Update inserted elements with computed positions
nodes.attr('transform', function(d) {
    return 'translate(' + d.x + ', ' + d.y + ')';
});

links.attr('x1', function(d) {
    return d.source.x;
}).attr('y1', function(d) {
    return d.source.y;
}).attr('x2', function(d) {
    return d.target.x;
}).attr('y2', function(d) {
    return d.target.y;
});

回答by Ben Hyde

As this example: "Force Directed Trees" illustrates there is a trick that often works. In the example the force direction's behavior is adjusted on each tick so that nodes drift slightly up or down depending on the direction of the links. As shown this will do a fine job for trees, but I've found it also works tolerable well for acyclic graphs. No promises, but it may help.

正如这个例子:“强制定向树”说明了一个经常奏效的技巧。在示例中,力方向的行为在每个刻度上进行调整,以便节点根据链接的方向略微向上或向下漂移。如图所示,这对于树来说会做得很好,但我发现它对于非循环图也可以很好地工作。没有承诺,但它可能会有所帮助。

回答by Glenn

Speaking generally of trees and the data hierarchy, you just need to have "D" in the children list for both B and C.

一般说到树和数据层次结构,您只需要在 B 和 C 的子列表中都有“D”。

Creating your node list, make sure you have a unique id returned so that "D" doesn't appear twice.

创建你的节点列表,确保你有一个唯一的 id 返回,这样“D”就不会出现两次。

vis.selectAll("g.node").data(nodes, function(d) { return d.id; });

Then when you call

然后当你打电话

var links = tree.links(nodes)

you should get D appearing as the "target" twice (with B and C as the "source" respectively) which results in two lines to the single node "D".

您应该让 D 作为“目标”出现两次(分别将 B 和 C 作为“源”),这会导致单个节点“D”的两行。

回答by KateJean

I was able to do this using a combination of Dagre(https://github.com/dagrejs/dagre) and cytoscape

我能够使用 Dagre( https://github.com/dagrejs/dagre) 和 cytoscape的组合来做到这一点

There's a library called Dagre-D3 that you can use as a renderer for this library so it looks like the D3 solution you want.

有一个名为 Dagre-D3 的库,您可以将其用作该库的渲染器,因此它看起来像您想要的 D3 解决方案。

Check out this fiddle to see the basic implementation with Dagre and Cytoscape: https://jsfiddle.net/KateJean/xweudjvm/

查看此小提琴以查看 Dagre 和 Cytoscape 的基本实现:https://jsfiddle.net/KateJean/xweudjvm/

var cy = cytoscape({
  container: document.getElementById('cy'),
  elements: {
          nodes: [
            { data: { id: '1' } },
            { data: { id: '2' } },
            { data: { id: '3' } },
            { data: { id: '4' } },
            { data: { id: '5' } },
            { data: { id: '6' } },
            { data: { id: '7' } },
            { data: { id: '8' } },
            { data: { id: '9' } },
            { data: { id: '10' } },
            { data: { id: '11' } },
            { data: { id: '12' } },
            { data: { id: '13' } },
            { data: { id: '14' } },
            { data: { id: '15' } },
            { data: { id: '16' } },
            { data: { id: '17' } },
            { data: { id: '18' } }
          ],
          edges: [
            { data: { source: '1', target: '2' } },
            { data: { source: '1', target: '3' } },
            { data: { source: '2', target: '4' } },
            { data: { source: '4', target: '5' } },
            { data: { source: '4', target: '6' } },
            { data: { source: '5', target: '6' } },
            { data: { source: '5', target: '7' } },
            { data: { source: '7', target: '8' } },
            { data: { source: '3', target: '9' } },
            { data: { source: '3', target: '10' } },
            { data: { source: '10', target: '11' } },
            { data: { source: '11', target: '12' } },
            { data: { source: '12', target: '13' } },
            { data: { source: '12', target: '14' } },
            { data: { source: '14', target: '15' } },
            { data: { source: '15', target: '16' } },
            { data: { source: '16', target: '17' } },
            { data: { source: '16', target: '18' } }

          ]
        },
  layout: {
    name: "dagre",
    rankDir: 'TB' //love this. you can quickly change the orientation here from LR(left to right) TB (top to bottom), RL, BT. Great dropdown option for users here. 
  },
  style: [{
    selector: 'node',
    style: {
      'label': 'data(id)',
      'width': '30%',
      'font-size': '20px',
      'text-valign': 'center',
      'shape': 'circle',
      'background-color': 'rgba(113,158,252,1)', 
      'border': '2px grey #ccc'
    }
  }, {
    selector: 'edge',
    style: {
      'width': 2,
      'line-color': '#ccc',
      'target-arrow-color': '#ccc',
      'target-arrow-shape': 'triangle'
    }
  }]
});