4

我在使用 d3 创建多个力布局图并从 json 文件中读取数据时遇到问题。我使用 for 循环遍历图形,为每个图形创建一个包含 svg 的单独 div。问题是,强制布局只应用于最后一个创建的布局,所以基本上其他布局只在左上角显示一个点。我可以通过在每次迭代结束时放置一个 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;
}

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

4

2 回答 2

4

i am working on force layout only, with many graphs at same time.

1 You don't need to have a count variable for each graph.

2 Don't make these variable(force, svg, graph) as array. There is no need for it. just declare them above as (var svg;) and further on. As you call the function, it automatically makes its different copy and DOM maintain them separately. So every variable you are using in graph, make it declare on top of function.

3 You are drawing all the graphs at same time, so as the new one is called, the previous one stops from being making on svg, that's why only last graph built successfully. So draw them after small time intervals.

<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)

});

于 2013-08-13T09:08:52.660 回答
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>

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

于 2013-08-14T14:14:26.360 回答