Related
I have looked for answer to this but none of the similar questions help me in my situation. I have a D3 tree that creates new nodes at runtime. I would like to add HTML (so I can format) to a node when I mouseover that particular node. Right now I can add HTML but its unformatted. Please help!
JSFiddle: http://jsfiddle.net/Srx7z/
JS Code:
var width = 960,
height = 500;
var tree = d3.layout.tree()
.size([width - 20, height - 60]);
var root = {},
nodes = tree(root);
root.parent = root;
root.px = root.x;
root.py = root.y;
var diagonal = d3.svg.diagonal();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(-30,40)");
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
var duration = 750;
$("#submit_button").click(function() {
update();
});
function update() {
if (nodes.length >= 500) return clearInterval(timer);
// Add a new node to a random parent.
var n = {id: nodes.length},
p = nodes[Math.random() * nodes.length | 0];
if (p.children) p.children.push(n); else p.children = [n];
nodes.push(n);
// Recompute the layout and data join.
node = node.data(tree.nodes(root), function (d) {
return d.id;
});
link = link.data(tree.links(nodes), function (d) {
return d.source.id + "-" + d.target.id;
});
// Add entering nodes in the parent’s old position.
var gelement = node.enter().append("g");
gelement.append("circle")
.attr("class", "node")
.attr("r", 20)
.attr("cx", function (d) {
return d.parent.px;
})
.attr("cy", function (d) {
return d.parent.py;
});
// Add entering links in the parent’s old position.
link.enter().insert("path", ".g.node")
.attr("class", "link")
.attr("d", function (d) {
var o = {x: d.source.px, y: d.source.py};
return diagonal({source: o, target: o});
})
.attr('pointer-events', 'none');
node.on("mouseover", function (d) {
var g = d3.select(this);
g.append("text").html('First Line <br> Second Line')
.classed('info', true)
.attr("x", function (d) {
return (d.x+20);
})
.attr("y", function (d) {
return (d.y);
})
.attr('pointer-events', 'none');
});
node.on("mouseout", function (d) {
d3.select(this).select('text.info').remove();
});
// Transition nodes and links to their new positions.
var t = svg.transition()
.duration(duration);
t.selectAll(".link")
.attr("d", diagonal);
t.selectAll(".node")
.attr("cx", function (d) {
return d.px = d.x;
})
.attr("cy", function (d) {
return d.py = d.y;
});
}
Using Lars Kotthoff's excellent direction, I got it working so I decided to post it for others and my own reference:
http://jsfiddle.net/FV4rL/2/
with the following code appended:
node.on("mouseover", function (d) {
var g = d3.select(this); // The node
var div = d3.select("body").append("div")
.attr('pointer-events', 'none')
.attr("class", "tooltip")
.style("opacity", 1)
.html("FIRST LINE <br> SECOND LINE")
.style("left", (d.x + 50 + "px"))
.style("top", (d.y +"px"));
});
I know that similar questions have been asked before here on stackoverflow, but I have fairly specific requirements. I'm trying to generate a pulsing dot for a D3 chart.
I modified some code on codepen.io and came up with this.
How would I do the same thing using a D3 transition() rather than the (cheesy) CSS classes?
Something more along the lines of:
circle = circle.transition()
.duration(2000)
.attr("stroke-width", 20)
.attr("r", 10)
.transition()
.duration(2000)
.attr('stroke-width', 0.5)
.attr("r", 200)
.ease('sine')
.each("end", repeat);
Feel free to fork my sample pen.
Thanks!
Have a look at the example on GitHub by whityiu
Note that this is using d3 version 3.
I have adapted that code to produce something like you are after in the fiddle below.
var width = 500,
height = 450,
radius = 2.5,
dotFill = "#700f44",
outlineColor = "#700f44",
pulseLineColor = "#e61b8a",
bgColor = "#000",
pulseAnimationIntervalId;
var nodesArray = [{
"x": 100,
"y": 100
}];
// Set up the SVG display area
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.attr("fill", bgColor)
.classed('visual-area', true);
var bgRect = d3.select("svg").append("rect")
.attr("width", width)
.attr("height", height);
var linkSet = null,
nodeSet = null;
// Create data object sets
nodeSet = svg.selectAll(".node").data(nodesArray)
.enter().append("g")
.attr("class", "node")
.on('click', function() {
// Clear the pulse animation
clearInterval(pulseAnimationIntervalId);
});
// Draw outlines
nodeSet.append("circle")
.classed("outline pulse", true)
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("fill", 'none')
.attr("stroke", pulseLineColor)
.attr("r", radius);
// Draw nodes on top of outlines
nodeSet.append("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("fill", dotFill)
.attr("stroke", outlineColor)
.attr("r", radius);
// Set pulse animation on interval
pulseAnimationIntervalId = setInterval(function() {
var times = 100,
distance = 8,
duration = 1000;
var outlines = svg.selectAll(".pulse");
// Function to handle one pulse animation
function repeat(iteration) {
if (iteration < times) {
outlines.transition()
.duration(duration)
.each("start", function() {
d3.select(".outline").attr("r", radius).attr("stroke", pulseLineColor);
})
.attrTween("r", function() {
return d3.interpolate(radius, radius + distance);
})
.styleTween("stroke", function() {
return d3.interpolate(pulseLineColor, bgColor);
})
.each("end", function() {
repeat(iteration + 1);
});
}
}
if (!outlines.empty()) {
repeat(0);
}
}, 6000);
Fiddle
I am trying to incorporate this example for adding/removing nodes from a Force Directed Graph. I am able to add nodes just fine, and for some reason I am also able to remove the nodes I have added without issues. However when I try to remove any of the original nodes, the node is not removed from the display, and it also breaks many other nodes' links. However, it and it's links are properly removed from the JSON based on the logs.
This example uses seemingly the same method as I do for removing nodes with splice.
Here also seems to use splice method though I don't fully follow the rest of what it is doing with filtering.
There is also this question that asks a somewhat similar thing though the answers only address adding.
I have tried exit/removing after the append which did not work. I have also tried using .insert instead of .append. The console.log during the update() function prints the JSON being used and it shows that nodes and links are all properly removed, but the display doesn't reflect that.
Relevant code is below.
Initializing/updating graph
//Get the SVG element
var svg = d3.select("svg");
var width = 960, height = 600;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var link = svg.append("g").selectAll(".link");
var node = svg.append("g").selectAll(".node");
var label = svg.append("g").selectAll(".label");
//Begin the force simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id; }).distance(50).strength(0.3))
.force("charge", d3.forceManyBody().strength(-15))
.force("center", d3.forceCenter(width / 2, height / 2));
//Highlight variables
var highlight_color = "blue";
var tHighlight = 0.05;
var config;
var linkedByIndex = {};
//Get the data
d3.json("/../../data.json", function (data) {
//if (!localStorage.graph)
//{
localStorage.graph = JSON.stringify(data);
//}
update();
forms();
});
function update() {
config = JSON.parse(localStorage.graph);
console.log(JSON.stringify(config));
linkedByIndex = {};
//Create an array of source,target containing all links
config.links.forEach(function (d) {
linkedByIndex[d.source + "," + d.target] = true;
linkedByIndex[d.target + "," + d.source] = true;
});
//Draw links
link = link.data(config.links);
link.exit().remove();
link = link.enter().append("line")
.attr("class", "link")
.attr("stroke-width", 2)
.attr("stroke", "#888")
//.attr("opacity", function (d) { if (d.target.radius > 7) { return 1 }; return 0; })
.merge(link);
node = node.data(config.nodes);
node.exit().remove();
node = node.enter().append("circle")
.attr("class", "node")
.attr("r", function(d) { return d.radius; })
.attr("fill", function (d) { return color(d.id); })
.attr("stroke", "black")
// .attr("pointer-events", function (d) { if (d.radius <= 7) { return "none"; } return "visibleAll"; })
// .attr("opacity", function (d) { if (d.radius <= 7) { return 0; } return 1; })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("mouseover", mouseOver)
.on("mouseout", mouseOut)
.merge(node);
label = label.data(config.nodes);
label.exit().remove();
label = label.enter().append("text")
.attr("class", "label")
.attr("dx", function (d) { return d.radius * 1.25; })
.attr("dy", ".35em")
.attr("opacity", function (d) { if (d.radius <= 7) { return 0; } return 1; })
.attr("font-weight", "normal")
.style("font-size", 10)
.text(function (d) { return d.id; })
.merge(label);
//Add nodes to simulation
simulation
.nodes(config.nodes)
.on("tick", ticked);
//Add links to simulation
simulation.force("link")
.links(config.links);
simulation.alphaTarget(0.3).restart();
}
//Animating by ticks function
function ticked() {
node
.attr("cx", function (d) { return d.x = Math.max(d.radius, Math.min(width - d.radius, d.x)); })
.attr("cy", function (d) { return d.y = Math.max(d.radius, Math.min(height - d.radius, 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; });
label
.attr("x", function (d) { return d.x = Math.max(d.radius, Math.min(width - d.radius, d.x)); })
.attr("y", function (d) { return d.y = Math.max(d.radius, Math.min(height - d.radius, d.y)); });
}
//Using above array, check if two nodes are linked
function isConnected(node1, node2) {
return linkedByIndex[node1.id + "," + node2.id] || node1.index == node2.index;
}
Adding/removing nodes and links.
ADDING works perfectly fine for both nodes and links this way.
function newNode(name, rad)
{
if (name == "")
{
alert("Must specify name");
return;
}
console.log("Adding node with name: " + name + " and radius: " + rad);
var temp = JSON.parse(localStorage.graph);
temp.nodes.push({ "id": name, "radius": Number(rad) });
localStorage.graph = JSON.stringify(temp);
update();
}
function newLink(source, target)
{
var foundSource = false;
var foundTarget = false;
if (source == "" && target == "")
{
alert("Must specify source and target");
return;
}
else if(source == "")
{
alert("Must specify source");
return;
}
else if (target == "")
{
alert("Must specify target")
return;
}
var temp = JSON.parse(localStorage.graph);
for (var i=0; i < temp.nodes.length; i++)
{
if(temp.nodes[i]['id'] === source)
{
foundSource = true;
}
if(temp.nodes[i]['id'] === target)
{
foundTarget = true;
}
}
if (foundSource && foundTarget) {
temp.links.push({ "source": source, "target": target });
localStorage.graph = JSON.stringify(temp);
update();
}
else {
alert("Invalid source or target");
return;
}
return;
}
function removeLink(linkSource, linkTarget)
{
}
function removeNode(nodeName)
{
var temp = JSON.parse(localStorage.graph);
var found = false;
if (nodeName == "")
{
alert("Must specify node name");
return;
}
for(var i=0; i<temp.nodes.length; i++)
{
if(temp.nodes[i]['id'] === nodeName)
{
console.log("Removing node: " + nodeName);
found = true;
temp.nodes.splice(i, 1);
temp.links = temp.links.filter(function (d) { return d.source != nodeName && d.target != nodeName; });
}
}
if(!found)
{
alert("Node does not exist");
return;
}
localStorage.graph = JSON.stringify(temp);
update();
}
JSON Data is in the format
{
"nodes":[
{
"id": "id1",
"radius": 5},
{
"id: "id2",
"radius": 6}
],
"links":[{
"source": "id1",
"target": "id2"
]
}
By default d3 joins data by index, so after you remove node, it assigns data incorrectly. The solution is to pass second argument to .data function. You need to replace
node = node.data(config.nodes);
with
node = node.data(config.nodes, d => d.id);
You can also do this for links, but if their styles are equal (i.e. they only differ by x1, x2, y1 and y2) it will make no difference.
Update: you should do this for labels as well
label = label.data(config.nodes, d => d.id);
The idea is to make an object reacting on mouse moves in vicinity of the object.
That is how far I got on this by now:
//declaring a canvas
var canvas = d3.select("body")
.append("svg")
.attr("width", 100)
.attr("height", 100);
//create circle with attributes and listeners
var circles = canvas.selectAll("circle")
.data([1])
.enter()
.append("circle")
.attr("cy", 50).attr("cx", 50).attr("r", 20)
.style({"fill": "grey"})
.on("mousemove", handleMouseMove);
//listener on mouse move inside the canvas
function handleMouseMove(){
var coordinates = [0, 0];
coordinates = d3.mouse(this);
var x = coordinates[0];
var y = coordinates[1];
console.log(x,y);
console.log(this.attributes);
}
See example on codepen
I can get the object with it's properties only when hovering over it - see last console.log();. And I am stuck on it. Please share your ideas regarding the solution if any.
Maybe the cleanest way, if you can, is by putting another circle with bigger radius just under your existing circle with a fill of transparent:
var g = canvas.selectAll("g")
.data([1])
.enter()
.append("g")
.on("mouseover", handleMouseOver) // event handlers here are applied
.on("mouseout", handleMouseOut) // to both 'circle'
g.append('circle').classed('invisible', true) // this goes before the
.attr("cy", 50) // visible circle
.attr("cx", 50)
.attr("r", 40)
.style({"fill": "transparent"});
g.append('circle').classed('visible', true)
.attr("cy", 50)
.attr("cx", 50)
.attr("r", 20)
.style({"fill": "grey"});
function handleMouseOver(d,i){
d3.select(this).select('.visible').transition()
.style({"fill": "red"});
};
function handleMouseOut(d,i){
d3.select(this).select('.visible').transition()
.style({"fill": "green"});
};
Or if you want to use mouse positions:
var circles = canvas.selectAll("circle")
.data([1])
.enter()
.append("circle")
.attr("cy", 50)
.attr("cx", 50)
.attr("r", 20)
.style({"fill": "grey"})
.on("mousemove", handleMouseMove);
function handleMouseMove(){
var coordinates = [];
coordinates = d3.mouse(this);
var x = coordinates[0];
var y = coordinates[1];
if (x>10 && x<90 && y>10 && y<90) { // example values; this is a rectangle but you can use more complex shapes
d3.select('.visible').style({"fill": "red"});
}
else {
d3.select('.visible').style({"fill": "green"});
}
Here is the FIDDLE where you can see both versions.
Hope it helps...
If you want to detect that the mouse is near the circle you need to set up your event handler on the object that contains the circle, in this case the svg contained in your canvas variable. Then to determine if the mouse is close, I'd use the point distance formula.
function handleMouseMove(){
var coordinates = d3.mouse(this),
x = coordinates[0],
y = coordinates[1];
var dist = Math.sqrt(Math.pow(circPos.x - x, 2) + Math.pow(circPos.y - y, 2));
console.log("distance to circle center is " + dist);
}
UPDATE FOR COMMENTS
var canvas = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500)
.on("mousemove", handleMouseMove);
var data = [];
for (var i = 0; i < 10; i++) {
data.push({
x: Math.random() * 500,
y: Math.random() * 500
});
}
var circles = canvas.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cy", function(d) {
return d.y;
})
.attr("cx", function(d) {
return d.x;
})
.attr("r", 10)
.style({
"fill": "grey"
});
function handleMouseMove() {
var coordinates = d3.mouse(this),
x = coordinates[0],
y = coordinates[1];
circles.style("fill", "grey");
var closestCircle = {
obj: null,
dist: 1e100
};
circles.each(function(d) {
var dist = Math.sqrt(Math.pow(d.x - x, 2) + Math.pow(d.y - y, 2));
if (dist < closestCircle.dist) {
closestCircle = {
obj: this,
dist: dist
};
}
});
d3.select(closestCircle.obj).style("fill", "green");
}
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
Voroni Example
var canvas = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
var data = [];
for (var i = 0; i < 10; i++) {
data.push({
x: Math.random() * 500,
y: Math.random() * 500,
id: i
});
}
var circles = canvas.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cy", function(d) {
return d.y;
})
.attr("cx", function(d) {
return d.x;
})
.attr("id", function(d) {
return "circle" + d.id;
})
.attr("r", 10)
.style("fill", "grey");
var voronoi = d3.voronoi()
.extent([
[-1, -1],
[500 + 1, 500 + 1]
])
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
})
var voronoiGroup = canvas.append("g")
.attr("class", "voronoi");
voronoiGroup.selectAll("path")
.data(voronoi.polygons(data))
.enter().append("path")
.attr("d", function(d) {
return d ? "M" + d.join("L") + "Z" : null;
})
.style("pointer-events", "all")
.style("fill", "none")
.style("stroke", "steelblue")
.style("opacity", "0.5")
.on("mouseover", mouseover);
function mouseover(d) {
circles.style("fill", "grey");
d3.select("#circle" + d.data.id).style("fill", "green");
}
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
I've a problem with creating multiple force layout graphs using d3 and reading data from a json file. I use a for loop to iterate over the graphs, create a separate div containing a svg for each. The problem is, that the force layout is only applied to the last one created, so basically the others just show a dot in the upper left corner. I could solve it partly by putting a for loop at the end of each iteration, but I still lose the interaction capabilities of the separate figures.
Find the code below, thanks in advance.
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;
}
edit: updated the code after the comments, still the same problem.
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)
});
Here the code I finally used with the help of the comments above, maybe helpful for others as well:
<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>
Note: graphs is the name of the variable in my json file. You need to include the d3-library.