2013-08-13 3 views
4

У меня возникла проблема с созданием нескольких графиков компоновки сил с использованием d3 и считыванием данных из json-файла. Я использую цикл for для итерации по графам, создавая отдельный div, содержащий svg для каждого. Проблема в том, что макет силы применяется только к последнему созданному, поэтому в основном остальные просто показывают точку в верхнем левом углу. Я мог бы решить это частично, поставив цикл for в конце каждой итерации, но я все еще теряю возможности взаимодействия отдельных фигур.Множество графиков компоновки силы с d3 в отдельном svg/div's

Найдите код ниже, заблаговременно.

Cheers, Michael

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

Вот код, который я, наконец, использовать с помощью комментариев выше, может быть полезным для других, а также:

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

Примечание: графы это имя переменной в мой json-файл. Вам нужно включить d3-библиотеку.

4

Я работаю только с раскладкой силы, с множеством графиков в одно и то же время.

1 Вам не нужно иметь переменную count для каждого графика.

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) 

}); 

+0

Переменная count была в основном для различения контейнеров-контейнеров. Массивы были в основном попыткой, потому что я думал, что это может помочь, если переменные известны вне функции, но теперь я избавился от этого. –

+0

Для третьего пункта это главный вопрос. Можно ли сохранить svg «активным» при вызове нового. Для генерации небольшой интервал времени прекрасен, но я хотел бы иметь возможность перетаскивать узлы впоследствии. Возможно ли это вообще? –

+0

вы можете добавлять графики в один и тот же div, или вы можете передать id div в функцию, на которую вы хотите нарисовать график. –

Смежные вопросы