我正在尝试创建多行文本作为有向图的节点:
<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>
见:http://jsfiddle.net/nikosdim/S4eaL/1/
我目前有这个:
// 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
}
但这会弄乱节点中的新行。插入新行后,文本将在屏幕开头左对齐显示。这不是我想要的。我希望文本在节点中对齐,如第一张图片所示。
这是使用上述代码的输出:
答案 0 :(得分:0)
// **********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);
}