d3强制布局与节点组 [英] d3 force layout with node groups

查看:214
本文介绍了d3强制布局与节点组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要达到这样的效果:

I want to achieve something like this:

具有以下属性:


  • 组是圆角矩形,不会重叠

  • 每个节点组在顶部都有一个水平居中标题,至少有一个节点和至少一个链接

  • 每个节点都有一个标签a type:source或target和

  • 每个节点可以有多个链接

  • 链接的源始终是源节点,目标始终是目标节点

  • groups are rounded rects, which never overlap
  • every node group has a horizontal centered title at the top, at least one node, and at least one link
  • every node has a label a type: source or target and a group
  • every node can have multiple links
  • the source of a link is always a source node, and the target is always a target node

我很难知道如何实现这一点,因为我昨天开始使用d3.js ...

I have hard time to figure out how to implement this, since I started with d3.js yesterday...

目前我有这样的:

js:

var GraphView = Class.extend({
    init: function (data) {
        this.data = data;
    },
    render: function () {
        var width = 960;
        var height = 500;
        var svg = d3.select("body").append("svg")
                .attr("width", width)
                .attr("height", height);

        var force = d3.layout.force()
                .gravity(.05)
                .distance(100)
                .charge(-500)
                .size([width, height])
                .nodes(this.data.nodes)
                .links(this.data.links)
                .start();

        var link = svg.selectAll(".link")
                .data(this.data.links)
                .enter().append("line")
                .attr("class", function (d) {
                    return d.group.join(" ");
                });

        var node = svg.selectAll(".node")
                .data(this.data.nodes)
                .enter().append("g")
                .attr("class", function (d) {
                    return d.group.join(" ");
                })
                .call(force.drag);

        var component = node.filter(function (d) {
            return d.group[1] == "component";
        });

        var port = node.filter(function (d) {
            return d.group[1] == "port";
        });

        var input = port.filter(function (d) {
            return d.group[2] == "input";
        });

        var output = port.filter(function (d) {
            return d.group[2] == "output";
        });

        component.append("rect")
                .attr("x", -8)
                .attr("y", -8)
                .attr("width", 103)
                .attr("height", 64)
                .attr("rx", 15)
                .attr("ry", 15);

        port.append("circle")
                .attr("r", 6);

        component.append("text")
                .attr("dx", 24)
                .attr("dy", "1em")
                .text(function (d) {
                    return d.label
                });
        port.append("text")
                .attr("dx", 12)
                .attr("dy", ".35em")
                .text(function (d) {
                    return d.label
                });

        force.on("tick", function () {
            link
                    .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;
                    });

            node.attr("transform", function (d) {
                return "translate(" + d.x + "," + d.y + ")";
            });
        });
    }
});

css:

.link.internal {
    stroke: #ccc;
    stroke-width: 1px;
}

.link.external {
    stroke: #000;
    stroke-width: 2px;
}

.link.external.error {
    stroke: #f00;
}

.node text {
    pointer-events: none;
    font: 10px sans-serif;
}

.node.component rect {
    fill: #ff0;
    stroke: #000;
    stroke-width: 2px;
}

.node.component text {
    font-weight: bold;
}

.node.port circle {
    stroke: #ccc;
    stroke-width: 2px;
}

.node.port.input circle {
    fill: #000;
}

.node.port.output circle {
    fill: #fff;
}

json:

{
    "nodes": [
        {"label": "Traverser", "group": ["node", "component"]},
        {"label": "Standard Output", "group": ["node", "port", "output"]},
        {"label": "Subscriber", "group": ["node", "component"]},
        {"label": "Standard Input", "group": ["node", "port", "input"]}
    ],
    "links": [
        {"source": 0, "target": 1, "group": ["link", "internal"]},
        {"source": 3, "target": 2, "group": ["link", "internal"]},
        {"source": 1, "target": 3, "group": ["link", "external"]}
    ]
}

结果:

sadly不够接近:S

sadly not close enough :S

不是一个线索如何将节点放入矩形,以及如何将力布局添加到圆角矩形,其大小取决于节点数,它不具有相等的宽度和高度,所以我不能使用简单的中心点来计算力...:S任何想法?

Not a clue how to put the nodes into the rectangles, and how to add force layout to a rounded rect, which size depends on the node count and which does not have equal width and height, so I cannot use simply a center point to count the forces... :S Any ideas?

推荐答案

我想我有一个解决方案:

I think I have a solution:

这不是完美的,我必须拖放节点一段时间,但它只是最小努力解决方案。我通过名称组件调用节点组,节点通过名称ports调用。我只使用强制布局节点的组件,并将链接移动到端口的位置。就这样。

This is not perfect, I have to drag-drop nodes for a short while, but it is just the minimum effort solution. I call the node groups by the name "components", and the nodes by the name "ports". I only used force layout nodes by the components and moved the links to the position of the ports. That's all. It is not perfect, but much better than meditate for days on how to solve this with d3.

每个组件都有一个SVG,如下所示:

Each component has an SVG something like this:

    <g class="component worker" transform="translate(0,0)">
        <use class="container" xlink:href="#container"/>
        <use class="icon" xlink:href="#worker"/>
        <text class="label" text-anchor="middle" alignment-baseline="middle" dy="40">Worker</text>
        <g class="input" transform="translate(-31,-16)">
            <use class="port" xlink:href="#port">
                <title>stdin</title>
            </use>
            <use y="8" class="port" xlink:href="#port"/>
            <use y="16" class="port" xlink:href="#port"/>
            <use y="24" class="port" xlink:href="#port"/>
            <use y="32" class="port" xlink:href="#port"/>
        </g>
        <g class="output" transform="translate(31,16)">
            <use class="port" xlink:href="#port">
                <title>stdout</title>
            </use>
            <use y="-8" class="port" xlink:href="#port"/>
            <use y="-16" class="port" xlink:href="#port"/>
            <use y="-24" class="port" xlink:href="#port"/>
            <use y="-32" class="port" xlink:href="#port"/>
        </g>
    </g>

Ofc。没有5个输入和5个输出的节点,但这只是一个模板...所以我得到了端口名称,通过在鼠标悬停后阅读工具提示。这将是不必要的噪音,同时显示每个端口名...

Ofc. there are no nodes with 5 inputs and 5 outputs, but that's just a template... So I got the port name by reading the tooltip after mouseover. It would be unnecessary noise to display every port name at once...

JSON也改变了:

{
    "nodes": [
        {"label": "Traverser", "groups": ["node", "component", "publisher", "traverser"], "inputs": [], "outputs": ["stdout"]},
        {"label": "Subscriber", "groups": ["node", "component", "subscriber"], "inputs": ["stdin"], "outputs": []}
    ],
    "links": [
        {"source": 0, "sourceIndex": 0, "target": 1, "targetIndex": 0, "groups": ["link"]}
    ]
}

和js是这样的(没有defs部分):

and the js is something like this (without the defs part):

    var force = d3.layout.force()
            .gravity(.05)
            .distance(150)
            .charge(-1000)
            .size([width, height])
            .nodes(this.data.nodes)
            .links(this.data.links)
            .start();

    var node = svg.selectAll(".node").data(this.data.nodes).enter().append("g").attr({
        "class": function (d) {
            return d.groups.join(" ");
        }
    }).call(force.drag);
    node.append("use").attr({
        "class": "container",
        "xlink:href": "#container"
    });
    node.append("use").attr({
        "class": "icon",
        "xlink:href": function (d) {
            return d.icon;
        }
    });
    node.append("text").attr({
        "class": "label",
        "text-anchor": "middle",
        "alignment-baseline": "middle",
        dy: 40
    }).text(function (d) {
        return d.label
    });

    node.append("g").attr({
        "class": "input",
        transform: "translate(-31,-16)"
    }).selectAll("use").data(function (d) {
        return d.inputs;
    }).enter().append("use").attr({
        y: function (d, index) {
            return 8 * index;
        },
        "class": "port",
        "xlink:href": "#port"
    }).append("title").text(String);

    node.append("g").attr({
        "class": "output",
        transform: "translate(31,-16)"
    }).selectAll("use").data(function (d) {
        return d.outputs;
    }).enter().append("use").attr({
        y: function (d, index) {
            return 8 * index;
        },
        "class": "port",
        "xlink:href": "#port"
    }).append("title").text(String);

    var link = svg.selectAll(".link").data(this.data.links).enter().append("path").attr({
        "class": function (d) {
            return d.groups.join(" ");
        },
        "marker-end": "url(#arrow)"
    });

    force.on("tick", function () {
        link.attr("d", function (d) {
            var sx = d.source.x + 31 + 6;
            var sy = d.source.y - 16 + d.sourceIndex * 8;
            var tx = d.target.x - 31 - 6;
            var ty = d.target.y - 16 + d.targetIndex * 8;
            return "M" + sx + "," + sy + " " + tx + "," + ty;
        });
        node.attr("transform", function (d) {
            return "translate(" + d.x + "," + d.y + ")";
        });
    });

这篇关于d3强制布局与节点组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆