D3.js 在d3js中向有向图中的节点追加多行文本

D3.js 在d3js中向有向图中的节点追加多行文本,d3.js,directed-graph,D3.js,Directed Graph,我正在尝试创建多行文字作为定向图的节点,如下所示: <rect height="27" width="56" rx="100" ry="100" style="fill: #ffffff;"></rect> <text dx="6" dy="6"> <tspan x="0" dy="15">Donovan</tspan> <tspan x="0" dy="15">3</t

我正在尝试创建多行文字作为定向图的节点,如下所示:

    <rect height="27" width="56" rx="100" ry="100" style="fill: #ffffff;"></rect>
    <text dx="6" dy="6">
        <tspan x="0" dy="15">Donovan</tspan>
        <tspan x="0" dy="15">3</tspan>
        <tspan x="0" dy="15">what</tspan>
    </text>

多诺万
3.
什么
如图所示:

我目前有:

// setting up parameters to be use through rest of the code
var w = 2000;
var h = 800;
var r = 30;
var linkDistance = 100;
var boxHeight = 50;
var boxWidth = 50;
var colors = d3.scale.category10();

// This is what how we should be setting gravity, theta and charge ideally: http://stackoverflow.com/questions/9901565/charge-based-on-size-d3-force-layout
var charge = -5000;
var gravity = 0.3;
var theta = 0.01;

var dataset = {
    nodes: [
    { "name": "Adam", "id": "0" },
    { "name": "Bob", "id": "1" },
    { "name": "Carrie", "id": "2" },
    { "name": "Donovan", "id": "3" },
    { "name": "Edward", "id": "4" },
    { "name": "Felicity", "id": "5" },
    { "name": "George", "id": "6" },
    { "name": "Hannah", "id": "7" },
    { "name": "Iris", "id": "8" },
    { "name": "Jerry", "id": "9" }
    ],
    edges: [
    { "source": 0, "target": 4 },
    { "source": 1, "target": 5 },
    { "source": 2, "target": 5 },
    { "source": 2, "target": 5 },
    { "source": 5, "target": 8 },
    { "source": 5, "target": 9 },
    { "source": 6, "target": 7 },
    { "source": 7, "target": 8 },
    { "source": 8, "target": 9 }
    ]
};

var svg = d3.select("body").append("svg").attr({ "width": w, "height": h });

var force = d3.layout.force()
    .nodes(dataset.nodes)
    .links(dataset.edges)
    .size([w, h])
    .linkDistance([linkDistance])
    .charge(charge)
    .theta(theta)
    .gravity(gravity);

var edges = svg.selectAll("line")
  .data(dataset.edges)
  .enter()
  .append("line")
  .attr("id", function (d, i) { return 'edge' + i; })
  .attr('marker-end', 'url(#arrowhead)')
  .style("stroke", "#ccc")
  .style("pointer-events", "none");

var nodes = svg.selectAll("rect")
  .data(dataset.nodes)
  .enter()
  .append("rect")
  .attr({ "width": boxWidth })
  .attr({ "height": boxHeight })
  //.style("fill", function (d, i) { return colors(i); })
  .style("fill", 'white')
  .attr('stroke', 'black')
  .call(force.drag);

var nodelabels = svg.selectAll(".nodelabel")
   .data(dataset.nodes)
   .enter()
   .append("text")
   .attr({
       //"x": function (d) { return d.x; },
       //"y": function (d) { return d.y; },
       "class": "nodelabel",
       "stroke": "black"
   });

var edgepaths = svg.selectAll(".edgepath")
    .data(dataset.edges)
    .enter()
    .append('path')
    .attr({
        //'d': function (d) { return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y; },
        'class': 'edgepath',
        'fill-opacity': 0,
        'stroke-opacity': 0,
        'fill': 'blue',
        'stroke': 'red',
        'id': function (d, i) { return 'edgepath' + i; }
    })
    .style("pointer-events", "none");

var edgelabels = svg.selectAll(".edgelabel")
    .data(dataset.edges)
    .enter()
    .append('text')
    .style("pointer-events", "none")
    .attr({
        'class': 'edgelabel',
        'id': function (d, i) { return 'edgelabel' + i; },
        'dx': 80,
        'dy': 0,
        'font-size': 10,
        'fill': '#aaa'
    });

edgelabels.append('textPath')
    .attr('xlink:href', function (d, i) { return '#edgepath' + i; })
    .style("pointer-events", "none")
    .text(function (d, i) { return 'label ' + i; });


svg.append('defs').append('marker')
    .attr({
        'id': 'arrowhead',
        'viewBox': '-0 -5 10 10',
        'refX': 25,
        'refY': 0,
        //'markerUnits':'strokeWidth',
        'orient': 'auto',
        'markerWidth': 10,
        'markerHeight': 10,
        'xoverflow': 'visible'
    })
    .append('svg:path')
        .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
        .attr('fill', '#ccc')
        .attr('stroke', '#ccc');


force.on("tick", tick).start();

function ConstrainX(point) {
    return Math.max(r, Math.min(w - r, point));
}

function ConstrainY(point) {
    return Math.max(r, Math.min(h - r, point));
}

function tick(e) {
    // Push sources up and targets down to form a weak tree.
    var k = 60 * e.alpha;
    dataset.edges.forEach(function (d, i) {
        d.source.y -= k;
        d.target.y += k;
    });

    edges.attr({
        "x1": function (d) { return ConstrainX(d.source.x); },
        "y1": function (d) { return ConstrainY(d.source.y); },
        "x2": function (d) { return ConstrainX(d.target.x); },
        "y2": function (d) { return ConstrainY(d.target.y); }
    });

    nodes.attr({
        "x": function (d) { return ConstrainX(d.x) - boxWidth / 2; },
        "y": function (d) { return ConstrainY(d.y) - boxHeight / 2; }
    });

    // appending boxWidth/2 to make sure the labels are within the box
    nodelabels.attr("x", function (d) { return ConstrainX(d.x) - boxWidth / 2; })
              .attr("y", function (d) { return ConstrainY(d.y); });

    edgepaths.attr('d', function (d) {
        var path = 'M ' + ConstrainX(d.source.x) + ' ' + ConstrainY(d.source.y) + ' L ' + ConstrainX(d.target.x) + ' ' + ConstrainY(d.target.y);
        //console.log(d)
        return path;
    });

    edgelabels.attr('transform', function (d, i) {
        if (d.target.x < d.source.x) {
            bbox = this.getBBox();
            rx = bbox.x + bbox.width / 2;
            ry = bbox.y + bbox.height / 2;
            return 'rotate(180 ' + rx + ' ' + ry + ')';
        }
        else {
            return 'rotate(0)';
        }
    });

    var insertLinebreaks = function (d) {
        var el = d3.select(this);
        var name = d.name;
        var id = d.id;

        el.text('');

        //for (var i = 0; i < words.length; i++) {
        var tspan = el.append('tspan').text(name);
        tspan = el.append('tspan').text(id);
        //if (i > 0)
        tspan.attr('x', 0);
        tspan.attr('dy', '15');
        tspan = el.append('tspan').text('what');
        tspan.attr('x', '0');
        tspan.attr('dy', '15');
        //}
    };

    nodelabels.each(insertLinebreaks); <== Insert new lines
}
//设置要在其余代码中使用的参数
var w=2000;
var h=800;
var r=30;
var-linkDistance=100;
高度=50;
var-boxWidth=50;
var colors=d3.scale.category10();
//这就是我们理想情况下设置重力、θ和电荷的方法:http://stackoverflow.com/questions/9901565/charge-based-on-size-d3-force-layout
var费用=-5000;
var重力=0.3;
varθ=0.01;
变量数据集={
节点:[
{“name”:“Adam”,“id”:“0”},
{“name”:“Bob”,“id”:“1”},
{“姓名”:“嘉莉”,“身份证”:“2”},
{“姓名”:“多诺万”,“身份证”:“3”},
{“姓名”:“爱德华”,“身份证”:“4”},
{“姓名”:“费利西蒂”,“身份证”:“5”},
{“姓名”:“乔治”,“身份证”:“6”},
{“姓名”:“汉娜”,“身份证”:“7”},
{“name”:“Iris”,“id”:“8”},
{“姓名”:“杰瑞”,“身份证”:“9”}
],
边缘:[
{“源”:0,“目标”:4},
{“源”:1,“目标”:5},
{“源”:2,“目标”:5},
{“源”:2,“目标”:5},
{“源”:5,“目标”:8},
{“源”:5,“目标”:9},
{“源”:6,“目标”:7},
{“源”:7,“目标”:8},
{“源”:8,“目标”:9}
]
};
var svg=d3.select(“body”).append(“svg”).attr({“width”:w,“height”:h});
var-force=d3.layout.force()
.nodes(数据集.nodes)
.links(数据集.edges)
.尺寸([w,h])
.linkDistance([linkDistance])
.收费
.θ(θ)
.重力(重力);
var edges=svg.selectAll(“行”)
.data(数据集.edges)
.输入()
.附加(“行”)
.attr(“id”,函数(d,i){return'edge'+i;})
.attr('marker-end','url(#箭头)'
.style(“笔划”,“#ccc”)
.style(“指针事件”、“无”);
var nodes=svg.selectAll(“rect”)
.data(dataset.nodes)
.输入()
.append(“rect”)
.attr({“宽度”:boxWidth})
.attr({“height”:boxHeight})
//.style(“填充”,函数(d,i){返回颜色(i);})
.style(“填充”、“白色”)
.attr('笔划','黑色')
.呼叫(强制拖动);
var nodelabels=svg.selectAll(“.nodelabel”)
.data(dataset.nodes)
.输入()
.append(“文本”)
艾特先生({
//“x”:函数(d){返回d.x;},
//“y”:函数(d){返回d.y;},
“类”:“nodelabel”,
“笔划”:“黑色”
});
var edgepaths=svg.selectAll(“.edgepath”)
.data(数据集.edges)
.输入()
.append('路径')
艾特先生({
//“d”:函数(d){return'M'+d.source.x+''+d.source.y+'L'+d.target.x+''+d.target.y;},
“类”:“edgepath”,
“填充不透明度”:0,
“笔划不透明度”:0,
'填充':'蓝色',
“笔划”:“红色”,
'id':函数(d,i){return'edgepath'+i;}
})
.style(“指针事件”、“无”);
var edgelabels=svg.selectAll(“.edgelabel”)
.data(数据集.edges)
.输入()
.append('文本')
.style(“指针事件”、“无”)
艾特先生({
“类”:“edgelabel”,
'id':函数(d,i){return'edgelabel'+i;},
“dx”:80,
“dy”:0,
“字体大小”:10,
“填充”:“aaa”
});
edgelabels.append('textPath')
.attr('xlink:href',函数(d,i){return'#edgepath'+i;})
.style(“指针事件”、“无”)
.text(函数(d,i){return'label'+i;});
svg.append('defs').append('marker'))
艾特先生({
“id”:“箭头”,
'视图框':'-0-5100',
参考文献:25,
“refY”:0,
//'markerUnits':'strokeWidth',
“方向”:“自动”,
“markerWidth”:10,
“markerHeight”:10,
“xoverflow”:“可见”
})
.append('svg:path')
.attr('d','m0,-5l10,0l0,5')
.attr('fill','#ccc')
.attr('stroke','#ccc');
force.on(“勾号”,勾号).start();
函数约束X(点){
返回Math.max(r,Math.min(w-r,point));
}
函数约束(点){
返回Math.max(r,Math.min(h-r,point));
}
功能勾号(e){
//向上推源,向下推目标,形成弱树。
var k=60*e.alpha;
dataset.edges.forEach(函数(d,i){
d、 来源:y-=k;
d、 目标y+=k;
});
edges.attr({
“x1”:函数(d){return constraintx(d.source.x);},
“y1”:函数(d){返回约束(d.source.y);},
“x2”:函数(d){return constraintx(d.target.x);},
“y2”:函数(d){返回约束(d.target.y);}
});
nodes.attr({
“x”:函数(d){return constraintx(d.x)-boxWidth/2;},
“y”:函数(d){返回约束(d.y)-boxHeight/2;}
});
//附加boxWidth/2以确保标签位于框内
nodelabels.attr(“x”,函数(d){return constraintx(d.x)-boxWidth/2;})
.attr(“y”,函数(d){返回约束(d.y);});
EdgePath.attr('d',函数(d){
变量路径='M'+constraintx(d.source.x)+''+constrainty(d.source.y)+'L'+constraintx(d.target.x)+''+constrainty(d.target.y);
//控制台日志(d)
返回路径;
});
edgelabels.attr('transform',函数(d,i){
if(d.target.x0)
tspan.attr('x',0);
tspan.attr('dy','15');
tspan=el.append('tspan').text('what');
tspan.attr('x','0');
tspan.attr('d
// **********start configuration***************
var dataset = {
    "Nodes": [
        {
            "ID": 0,
            "Identity": "af12689c-de83-4a0d-a63d-1f548fd02e26",
            "RoleInstance": "RoleInstance",
            "LogonID": "LogonID1",
            "WeightedScore": 120,
            "AccountInstance": "AccountInstance1",
            "MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
        },
        {
            "ID": 1,
            "Identity": "a5bd36db-00e6-4492-92d7-49278f0046a7",
            "RoleInstance": "RoleInstance2",
            "LogonID": "LogonID2",
            "WeightedScore": 100,
            "AccountInstance": "AccountInstance2",
            "MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
        },
        {
            "ID": 2,
            "Identity": "a5bd36db-00e6-4492-92d7-49278f0046a7",
            "RoleInstance": "RoleInstance2",
            "LogonID": "LogonID2",
            "WeightedScore": 100,
            "AccountInstance": "AccountInstance2",
            "MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
        },
        {
            "ID": 3,
            "Identity": "a5bd36db-00e6-4492-92d7-49278f0046a7",
            "RoleInstance": "RoleInstance2",
            "LogonID": "LogonID2",
            "WeightedScore": 100,
            "AccountInstance": "AccountInstance2",
            "MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
        }
    ],
    "Edges": [
        {
            "source": 0,
            "target": 1
        }, {
            "source": 3,
            "target": 2
        }
    ]
};

// select all the features from the node that you want in the output
var nodelabelslist = ["ID", "Identity", "RoleInstance", "LogonID", "WeightedScore", "AccountInstance", "MinTimestampPst"];
var nodelabelslistlength = nodelabelslist.length;
// this is the rectangle height and width
var boxHeight = nodelabelslistlength * 20;
var boxWidth = 400;

var nodelabelverticaloffset = 15;
var nodelabelhorizontaloffset = 5;
var labelStartPos = -(nodelabelslistlength/2 - 1);

// setting up d3js parameters to be use through rest of the code
var w = 2000;
var h = 800;
var r = 30;
var linkDistance = 200;
var colors = d3.scale.category10();

// This is what how we should be setting gravity, theta and charge ideally: http://stackoverflow.com/questions/9901565/charge-based-on-size-d3-force-layout
var charge = -6000;
var gravity = 0.10;
var theta = 0.01;

// **********end configuration***************

var jsonNodes = dataset.Nodes;
var jsonEdges = dataset.Edges;

var svg = d3.select("body").append("svg").attr({ "width": w, "height": h });

var force = d3.layout.force()
    .nodes(jsonNodes)
    .links(jsonEdges)
    .size([w, h])
    .linkDistance([linkDistance])
    .charge(charge)
    .theta(theta)
    .gravity(gravity);

var edges = svg.selectAll("line")
    .data(jsonEdges)
    .enter()
    .append("line")
    .attr("id", function (d, i) { return 'edge' + i; })
    .attr('marker-end', 'url(#arrowhead)')
    .style("stroke", "#000")
    .style("pointer-events", "none");

var nodes = svg.selectAll("rect")
    .data(jsonNodes)
    .enter()
    .append("rect")
    //.style("fill", function (d, i) { return colors(i); })
    .style("fill", 'none')
    .attr('stroke', 'black')
    .call(force.drag);

var nodelabelsarr = [];
for (var index = 0; index < nodelabelslist.length; ++index) {
    var nodelabels = svg.selectAll(".nodelabel" + index)
        .data(jsonNodes)
        .enter()
        .append("text")
        .attr({
            "class": "nodelabel",
            "stroke": "black"
        })
        .text(function (d) { return nodelabelslist[index] + ": " + d[nodelabelslist[index]]; });
    nodelabelsarr[index] = nodelabels;
}

var edgepaths = svg.selectAll(".edgepath")
    .data(jsonEdges)
    .enter()
    .append('path')
    .attr({
        'class': 'edgepath',
        'fill-opacity': 0,
        'stroke-opacity': 0,
        'fill': 'blue',
        'stroke': 'red',
        'id': function (d, i) { return 'edgepath' + i; }
    })
    .style("pointer-events", "none");

var edgelabels = svg.selectAll(".edgelabel")
    .data(jsonEdges)
    .enter()
    .append('text')
    .style("pointer-events", "none")
    .attr({
        'class': 'edgelabel',
        'id': function (d, i) { return 'edgelabel' + i; },
        'dx': 80,
        'dy': 0,
        'font-size': 10,
        'fill': '#000'
    });

edgelabels.append('textPath')
    .attr('xlink:href', function (d, i) { return '#edgepath' + i; })
    .style("pointer-events", "none")
    .text(function (d, i) { return 'label ' + i; });


svg.append('defs').append('marker')
    .attr({
        'id': 'arrowhead',
        'viewBox': '-0 -5 10 10',
        'refX': 25,
        'refY': 0,
        //'markerUnits':'strokeWidth',
        'orient': 'auto',
        'markerWidth': 10,
        'markerHeight': 10,
        'xoverflow': 'visible'
    })
    .append('svg:path')
    .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
    .attr('fill', '#000')
    .attr('stroke', '#000');


force.on("tick", tick).start();

function ConstrainX(point) {
    return Math.max(r, Math.min(w - r, point));
}

function ConstrainY(point) {
    return Math.max(r, Math.min(h - r, point));
}

function edgeArc(d) {
    var path = 'M ' + ConstrainX(d.source.x) + ' ' + ConstrainY(d.source.y) + ' L ' + ConstrainX(d.target.x) + ' ' + ConstrainY(d.target.y);
    //console.log(d)
    return path;
}

function edgelabeltransform(d) {
    if (d.target.x < d.source.x) {
        var bbox = this.getBBox();
        var rx = bbox.x + bbox.width / 2;
        var ry = bbox.y + bbox.height / 2;
        return 'rotate(180 ' + rx + ' ' + ry + ')';
    }
    else {
        return 'rotate(0)';
    }
}

function tick(e) {
    // Push sources up and targets down to form a weak tree.
    var k = 60 * e.alpha;
    jsonEdges.forEach(function (d) {
        d.source.y -= k;
        d.target.y += k;
    });

    edges.attr({
        "x1": function (d) { return ConstrainX(d.source.x); },
        "y1": function (d) { return ConstrainY(d.source.y); },
        "x2": function (d) { return ConstrainX(d.target.x); },
        "y2": function (d) { return ConstrainY(d.target.y); }
    });

    nodes.attr({
            "x": function (d) { return ConstrainX(d.x) - boxWidth / 2; },
            "y": function (d) { return ConstrainY(d.y) - boxHeight / 2; }
        })
        .attr({ "width": boxWidth })
        .attr({ "height": boxHeight });

    // appending boxWidth/2 to make sure the labels are within the box
    var startOffset = labelStartPos;
    for (var index = 0; index < nodelabelsarr.length; index++) {
        nodelabelsarr[index].attr("x", function (d) { return ConstrainX(d.x) - boxWidth / 2 + nodelabelhorizontaloffset; })
            .attr("y", function (d) { return ConstrainY(d.y) + (nodelabelverticaloffset * startOffset); });
        startOffset++;
    }

    edgepaths.attr('d', edgeArc);

    edgelabels.attr('transform', edgelabeltransform);
}