d3:节点+链接以生成多代族树;如何解析数据以绘制线条?

时间:2014-11-12 06:03:29

标签: javascript d3.js

我正在努力在d3.js中创建一个三代或四代家谱。你可以在这里看到早期版本:

http://jsfiddle.net/Asparagirl/uenh4j92/8/

代码:

// People
var nodes = [
    { id: 1, name: "Aaron", x: 50, y: 100, gender: "male", dob: "1900", hasParent: false, hasSpouse: true, spouse1_id: 2 },
    { id: 2, name: "Brina" , x: 400, y: 100, gender: "female", dob: "1900", hasParent: false, hasSpouse: true, spouse1_id: 1 },
    { id: 3, name: "Caden", x: 100, y: 260, gender: "female", dob: "1925", hasParent: true, parent1_id: 1, parent2_id: 2, hasSpouse: false },
    { id: 4, name: "David", x: 200, y: 260, gender: "male", dob: "1930", hasParent: true, parent1_id: 1, parent2_id: 2, hasSpouse: false }, 
    { id: 5, name: "Ewa", x: 320, y: 260, gender: "female", dob: "1935", hasParent: true, parent1_id: 1, parent2_id: 2, hasSpouse: true, spouse_id: 6 },
    { id: 6, name: "Feivel", x: 450, y: 260, gender: "male", dob: "1935", hasParent: false, hasSpouse: true, spouse_id: 5 },
    { id: 7, name: "Gershon", x: 390, y: 370, gender: "male", dob: "1955", hasParent: true, parent1_id: 5, parent2_id: 6, hasSpouse: false }
];
var links = [
    { source: 0, target: 1 }
];

// Make the viewport automatically adjust to max X and Y values for nodes
var max_x = 0;
var max_y = 0;
for (var i=0; i<nodes.length; i++) {
    var temp_x, temp_y;
    var temp_x = nodes[i].x + 200;
    var temp_y = nodes[i].y + 40;
    if ( temp_x >= max_x ) { max_x = temp_x; }
    if ( temp_y >= max_y ) { max_y = temp_y; }
}

// Variables
var width = max_x,
    height = max_y,
    margin = {top: 10, right: 10, bottom: 10, left: 10},
    circleRadius = 20,
    circleStrokeWidth = 3;

// Basic setup
var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .attr("id", "visualization")
    .attr("xmlns", "http://www.w3.org/2000/svg");

var elem = svg.selectAll("g")
    .data(nodes)

var elemEnter = elem.enter()
    .append("g")
    .attr("data-name", function(d){ return d.name })
    .attr("data-gender", function(d){ return d.gender })
    .attr("data-dob", function(d){ return d.dob })

// Draw one circle per node
var circle = elemEnter.append("circle")
    .attr("cx", function(d){ return d.x })
    .attr("cy", function(d){ return d.y })
    .attr("r", circleRadius)
    .attr("stroke-width", circleStrokeWidth)
    .attr("class", function(d) {
        var returnGender;
        if (d.gender === "female") { returnGender = "circle female"; } 
        else if (d.gender === "male") { returnGender = "circle male"; }
        else { returnGender = "circle"; }
        return returnGender;
    });

// Add text to the nodes
elemEnter.append("text")
    .attr("dx", function(d){ return (d.x + 28) })
    .attr("dy", function(d){ return d.y - 5 })
    .text(function(d){return d.name})
    .attr("class", "text");

// Add text to the nodes
elemEnter.append("text")
    .attr("dx", function(d){ return (d.x + 28) })
    .attr("dy", function(d){ return d.y + 16 })
    .text(function(d){return "b. " + d.dob})
    .attr("class", "text");


// Add links between nodes
var linksEls = svg.selectAll(".link")
    .data(links)
    .enter()
    // Draw the first line (between the primary couple, nodes 0 and 1)
    .append("line")
    .attr("x1",function(d){ return nodes[d.source].x + circleRadius + circleStrokeWidth; })
    .attr("y1",function(d){ return nodes[d.source].y; })
    .attr("x2",function(d){ return nodes[d.target].x - circleRadius - circleStrokeWidth; })
    .attr("y2",function(d){ return nodes[d.target].y; })
    .attr("class","line");
    // Draw subsequent lines (from each of the children to the couple line's midpoint)
    function drawLines(d){
        var x1 = nodes[d.source].x;
        var y1 = nodes[d.source].y;
        var x2 = nodes[d.target].x;
        var y2 = nodes[d.target].y;
        var childNodes = nodes.filter(function(d){ return ( (d.hasParent===true) && (d.id!=7) ) });
        childNodes.forEach(function(childNode){
             svg.append("line")
                 // This draws from the node *up* to the couple line's midpoint
                .attr("x1",function(d){ return childNode.x; })
                .attr("y1",function(d){ return childNode.y - circleRadius - circleStrokeWidth + 1; })
                .attr("x2",function(d){ return (x1+x2)/2; })
                .attr("y2",function(d){ return (y1+y2)/2; })
                .attr("class","line2");
        })
    }
    linksEls.each(drawLines);

所以,对于一代人来说,这是有道理的。问题在于,当下一代(Ewa与Feivel结婚,孩子是Gershom)的时候,我们必须弄清楚如何复制一个在合作伙伴之间有一条直线的结构,以及从中间向下流动的孩子的线条。父母夫妇的观点。一个相关的问题是,现在,第一对被认为是一对(不同的线型),只是因为它们是我的节点列表中的前两个数据,而不是通过读取数据真正被识别为(即hasSpouse,spouse1_id等)。

非常感谢让这项工作变得更好的想法和想法!

1 个答案:

答案 0 :(得分:1)

让hasSpouse属性值为true的所有人都拥有spouse_id(而不是spouse1_id或spouse_id),并从节点数组生成链接数组,如下所示。 couple对象用于防止链接的冗余,例如0-&gt; 1和1-&gt; 0的链接。

var couple = {},
    links = [];
nodes.forEach(function(d, i) {
    if (d.hasSpouse) {
        var link = {};
        link["source"] = i;
        var targetIdx;
        nodes.forEach(function(s, sIdx) {
            if (s.id == d.spouse_id) targetIdx = sIdx;
        });
        link["target"] = targetIdx;
        if (!couple[i + "->" + targetIdx] && !couple[targetIdx + "->" + i]) {
            couple[i + "->" + targetIdx] = true;
            links.push(link);
        }
    }
});

现在,您需要对代码进行一些小改动,以便在drawLines方法中查找子节点。通过匹配它的父ID来查找子节点。

function drawLines(d) {
    var src = nodes[d.source];
    var tgt = nodes[d.target];
    var x1 = src.x, y1 = src.y, x2 = tgt.x, y2 = tgt.y;
    var childNodes = nodes.filter(function(d) {
        //Code change
        return ((d.hasParent === true) && (d.parent1_id == src.id && d.parent2_id == tgt.id))
    });
    ......................
}

以下是更新后的fiddle