多个强制布局图,d3在单独的svg / div中

时间:2013-08-13 08:37:54

标签: javascript html svg d3.js force-layout

我在使用d3创建多个强制布局图并从json文件中读取数据时遇到问题。我使用for循环遍历图形,创建一个单独的div,每个div包含一个svg。问题是,力布局仅应用于最后创建的布局,因此基本上其他布局只在左上角显示一个点。我可以通过在每次迭代结束时放置一个for循环来解决它,但我仍然失去了单独数字的交互功能。

请提前感谢以下代码。

干杯,迈克尔

var color = d3.scale.category20();

var force = new Array();
var div = new Array();
var svg = new Array();
var graph = new Array();
var link;
var node;
var width = 360;
var height = 360;
var brush = new Array();
var shiftKey;
var count = 0;

//loop through the different subsystems in the json-file
for(name_subsystem in graphs) {
    //add a div for each subsystem
    div[count] = document.createElement("div");
    div[count].style.width = "360px";
    div[count].style.height = "360px";
    div[count].style.cssFloat="left";
    div[count].id = name_subsystem;

    document.body.appendChild(div[count]);


    //force is called. all attributes with default values are noted. see API reference on github.
    force[count] = d3.layout.force()
        .size([width, height])
        .linkDistance(20)
        .linkStrength(1)
        .friction(0.9)
        .charge(-30)
        .theta(0.8)
        .gravity(0.1);

    div[count].appendChild(document.createTextNode(name_subsystem));

    //create the svg rectangle in which other elements can be visualised
    svg[count] = d3.select("#"+name_subsystem)
        .on("keydown.brush", keydown)
        .on("keyup.brush", keyup)
      .append("svg")
        .attr("width", width)
        .attr("height", height)
        .attr("id",name_subsystem);

    brush[count] = svg[count].append("g")
        .datum(function() { return {selected: false, previouslySelected: false}; })
        .attr("class", "brush");

    //force is started
    force[count]
        .nodes(graphs[name_subsystem].nodes)
        .links(graphs[name_subsystem].links)
        .start();

    //link elements are called, joined with the data, and links are created for each link object in links
    link = svg[count].selectAll(".link")
        .data(graphs[name_subsystem].links)
        .enter().append("line")
        .attr("class", "link")
        .style("stroke-width", function(d) { return Math.sqrt(d.thickness); })
        .style("stroke", function(d){
            if (d.linktype === 'reactant'){
                return "black";
            } else {
                return "red";
            }
        });

    //node elements are called, joined with the data, and circles are created for each node object in nodes
    node = svg[count].selectAll(".node")
        .data(graphs[name_subsystem].nodes)
        .enter().append("circle")
        .attr("class", "node")
        //radius
        .attr("r", 5)
        //fill
        .attr("fill", function(d) {
            if (d.type === 'metabolite') {
                return "blue";
            } else {
                return "red";
            }
        })
        .on("mousedown", function(d) {
            if (!d.selected) { // Don't deselect on shift-drag.
                if (!shiftKey) node.classed("selected", function(p) { return p.selected = d === p; });
            else d3.select(this).classed("selected", d.selected = true);
            }
        })
        .on("mouseup", function(d) {
            if (d.selected && shiftKey) d3.select(this).classed("selected", d.selected = false);
        })
        .call(force[count].drag()
            .on("dragstart",function dragstart(d){
                d.fixed=true;
                d3.select(this).classed("fixed",true);
            })
        );


    //gives titles to nodes. i do not know why this is separated from the first node calling.
    node.append("title")
        .text(function(d) { return d.name; });

    //enable brushing of the network
    brush[count].call(d3.svg.brush()
        .x(d3.scale.identity().domain([0, width]))
        .y(d3.scale.identity().domain([0, height]))
        .on("brushstart", function(d) {
            node.each(function(d) { d.previouslySelected = shiftKey && d.selected; });
        })
        .on("brush", function() {
            var extent = d3.event.target.extent();
            node.classed("selected", function(d) {
                return d.selected = d.previouslySelected ^
                (extent[0][0] <= d.x && d.x < extent[1][0]
                && extent[0][1] <= d.y && d.y < extent[1][1]);
            });
        })
        .on("brushend", function() {
            d3.event.target.clear();
            d3.select(this).call(d3.event.target);
        })
    );

    //applies force per step or 'tick'. 
    force[count].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("cx", function(d) { return d.x; })
            .attr("cy", function(d) { return d.y; });
    });
    //with this it works partly
    //for (var i = 0; i < 5000; ++i)force[count].tick();
    count++;
};

function keydown() {
  if (!d3.event.metaKey) switch (d3.event.keyCode) {
    case 38: nudge( 0, -1); break; // UP
    case 40: nudge( 0, +1); break; // DOWN
    case 37: nudge(-1,  0); break; // LEFT
    case 39: nudge(+1,  0); break; // RIGHT
  }
  shiftKey = d3.event.shiftKey || d3.event.metaKey;
}

function keyup() {
  shiftKey = d3.event.shiftKey || d3.event.metaKey;
}

编辑:在评论后更新代码,仍然是同样的问题。

2 个答案:

答案 0 :(得分:4)

我正在处理强制布局,同时有许多图表。

1您不需要为每个图形都有一个计数变量。

2不要将这些变量(force,svg,graph)作为数组。没有必要。只需将它们声明为(var svg;)并继续进行。当您调用该函数时,它会自动使其不同的副本和DOM分开维护它们。因此,您在图表中使用的每个变量都会使其在函数顶部声明。

3您正在同时绘制所有图形,因此在调用新图形时,前一个图形停止在svg上生成,这就是为什么只有最后一个图形成功构建的原因。所以在很短的时间间隔之后绘制它们。

<html>
<script>
function draw_graphs(graphs){

var color = d3.scale.category20();

var force;
var div;
var svg;
var graph;
var link;
var node;
var width = 360;
var height = 360;
var brush = new Array();
var shiftKey;


//loop through the different subsystems in the json-file
for(name_subsystem in graphs) {
//add a div for each subsystem
div = document.createElement("div");
div.style.width = "360px";
div.style.height = "360px";
div.style.cssFloat="left";
div.id = name_subsystem;

document.body.appendChild(div); 


//force is called. all attributes with default values are noted. see API reference on github.
force = d3.layout.force()
    .size([width, height])
    .linkDistance(20)
    .linkStrength(1)
    .friction(0.9)
    .charge(-30)
    .theta(0.8)
    .gravity(0.1);

div.appendChild(document.createTextNode(name_subsystem));

//create the svg rectangle in which other elements can be visualised
svg = d3.select("#"+name_subsystem)
    .on("keydown.brush", keydown)
    .on("keyup.brush", keyup)
  .append("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("id",name_subsystem); 

brush = svg.append("g")
    .datum(function() { return {selected: false, previouslySelected: false}; })
    .attr("class", "brush"); 

//force is started
force
    .nodes(graphs[name_subsystem].nodes)
    .links(graphs[name_subsystem].links)
    .start();

//link elements are called, joined with the data, and links are created for each link object in links
link = svg.selectAll(".link")
    .data(graphs[name_subsystem].links)
    .enter().append("line")
    .attr("class", "link")
    .style("stroke-width", function(d) { return Math.sqrt(d.thickness); })
    .style("stroke", function(d){
        if (d.linktype === 'reactant'){
            return "black";
        } else {
            return "red";
        }
    });

//node elements are called, joined with the data, and circles are created for each node object in nodes
node = svg.selectAll(".node")
    .data(graphs[name_subsystem].nodes)
    .enter().append("circle")
    .attr("class", "node")
    //radius
    .attr("r", 5)
    //fill
    .attr("fill", function(d) {
        if (d.type === 'metabolite') {
            return "blue";
        } else {
            return "red";
        }
    })
    .on("mousedown", function(d) {
        if (!d.selected) { // Don't deselect on shift-drag.
            if (!shiftKey) node.classed("selected", function(p) { return p.selected = d === p; });
        else d3.select(this).classed("selected", d.selected = true);
        }
    })
    .on("mouseup", function(d) {
        if (d.selected && shiftKey) d3.select(this).classed("selected", d.selected = false);
    })
    .call(force.drag()
        .on("dragstart",function dragstart(d){
            d.fixed=true;
            d3.select(this).classed("fixed",true);
        })
    );


//gives titles to nodes. i do not know why this is separated from the first node calling.
node.append("title")
    .text(function(d) { return d.name; });

//enable brushing of the network
brush.call(d3.svg.brush()
    .x(d3.scale.identity().domain([0, width]))
    .y(d3.scale.identity().domain([0, height]))
    .on("brushstart", function(d) {
        node.each(function(d) { d.previouslySelected = shiftKey && d.selected; });
    })
    .on("brush", function() {
        var extent = d3.event.target.extent();
        node.classed("selected", function(d) {
            return d.selected = d.previouslySelected ^
            (extent[0][0] <= d.x && d.x < extent[1][0]
            && extent[0][1] <= d.y && d.y < extent[1][1]);
        });
    })
    .on("brushend", function() {
        d3.event.target.clear();
        d3.select(this).call(d3.event.target);
    })
);

//applies force per step or 'tick'. 
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("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; });
});
//with this it works partly
//for (var i = 0; i < 5000; ++i)force[count].tick();
};

function keydown() {
if (!d3.event.metaKey) switch (d3.event.keyCode) {
case 38: nudge( 0, -1); break; // UP
case 40: nudge( 0, +1); break; // DOWN
case 37: nudge(-1,  0); break; // LEFT
case 39: nudge(+1,  0); break; // RIGHT
}
shiftKey = d3.event.shiftKey || d3.event.metaKey;
}

function keyup() {
shiftKey = d3.event.shiftKey || d3.event.metaKey;
}

}
</script>


<script>
$(document).ready(function() {
draw_graphs("pass here the json file");


// this will drawn 2nd graph after 1 second.              
var t = setTimeout(function(){
draw_graphs("pass here json file");
}, 1000)

});

答案 1 :(得分:2)

这里我在上述评论的帮助下最终使用的代码也可能对其他人有用:

<script type="text/javascript" src="d3_splitted_var.json"></script>
<script>
function draw_graphs(name_subsystem){   

    var force;
    var div;
    var svg;
    var link;
    var node;
    var width = 360;
    var height = 360;
    var r=5;
    var brush = new Array();
    var shiftKey;

    //add a div for each subsystem
    div = document.createElement("div");
    div.style.width = "360px";
    div.style.height = "360px";
    div.style.cssFloat="left";
    div.id = name_subsystem;

    document.body.appendChild(div);

    force = d3.layout.force()
        .size([width, height])
        .linkDistance(20)
        .linkStrength(1)
        .friction(0.9)
        .charge(-50)
        .theta(0.8)
        .gravity(0.1);

    div.appendChild(document.createTextNode(name_subsystem));

    //create the svg rectangle in which other elements can be visualised
    svg = d3.select("#"+name_subsystem)
      .append("svg")
        .attr("width", width)
        .attr("height", height)
        .attr("id",name_subsystem);

    //force is started
    force
            .nodes(graphs[name_subsystem].nodes)
        .links(graphs[name_subsystem].links)
        .start();

    //link elements are called, joined with the data, and links are created for each link object in links
    link = svg.selectAll(".link")
        .data(graphs[name_subsystem].links)
        .enter().append("line")
        .attr("class", "link")
        .style("stroke-width", function(d) { return Math.sqrt(d.thickness); })
        .style("stroke", function(d){
            if (d.linktype === 'reactant'){
                return "black";
            } else {
                return "red";
            }
        });

    //node elements are called, joined with the data, and circles are created for each node object in nodes
    node = svg.selectAll(".node")
        .data(graphs[name_subsystem].nodes)
        .enter().append("circle")
        .attr("class", "node")
        //radius
        .attr("r", r)
        //fill
        .attr("fill", function(d) {
            if (d.type === 'metabolite') {
                return "blue";
            } else {
                return "red";
            }
        })
        .call(force.drag()
            .on("dragstart",function dragstart(d){
                d.fixed=true;
                d3.select(this).classed("fixed",true);
            })
        );

    //gives titles to nodes. i do not know why this is separated from the first node calling.
    node.append("title")
        .text(function(d) { return d.name; });


    //applies force per step or 'tick'.
    force.on("tick", function() {
        node.attr("cx", function(d) { return d.x = Math.max(r, Math.min(width - r, d.x)); })
            .attr("cy", function(d) { return d.y = Math.max(r, Math.min(height - r, d.y)); });

        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; });
    });
};
for(name_subsystem in graphs) {
    draw_graphs(name_subsystem);
}
</script>

注意:graph是我的json文件中变量的名称。您需要包含d3库。