I am using a force directed graph to draw entity relationships for a company. What I would like to do is use my built in index value for the "index" of the node instead of the array index. How do I override the d3 index value that gets set? Fiddle - http://jsfiddle.net/thielcole/ed9noqw1/
var forceLinks = [];
var forceData = [];
d3.csv('AmazonExampleforiCharts.csv', function(d) {
return { // This is the index I would like to use
index: d.Node,
parent: d.ParentNode,
color: d.NodeColor,
level: d.Hierarchy_code,
business: d.Business_Name,
power: d.Decisionpower,
hover: d.HoverOverValue,
link: d.LinkVALUE
};
}, function(error, rows) {
forceData = rows;
$.each(forceData, function(i, d) {
// console.log(d.parent);
if (d.parent != "" && d.parent !== undefined) { // generating link information here, I have to subtract one initially to match the array index
forceLinks.push({source: parseInt(d.parent , 10) - 1 , target: parseInt(d.index , 10) - 1, value: parseInt(d.level , 10)});
}
console.log(d);
});
$.each(forceLinks, function(i, d) {
// console.log(d);
});
initiateChart();
});
function initiateChart() {
var height = 1000,
width = 1400;
var graphData = [];
var graphLinks = [];
graphData = forceData;
graphLinks = forceLinks;
var color = d3.scale.category20();
var svg = d3.select('.svg')
.attr('width', width)
.attr('height', height);
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
// this is where it gets set
force.nodes(graphData)
.links(graphLinks)
.start();
var link = svg.selectAll('.link')
.data(graphLinks)
.enter().append('line')
.attr('class', 'link')
.style('stroke-width', function (d) {
return Math.sqrt(d.value);
});
var node = svg.selectAll('.node')
.data(graphData)
.enter().append('circle')
.attr('class', 'node')
.attr('r', 8)
.style('fill', function (d) {
return color(d.level);
})
.on('mouseover', function(d , i) {
d3.select(this).attr('r', 12);
var tooltip = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('position','absolute')
.style('top', (d3.event.pageY - 10) + 'px')
.style('left' , (d3.event.pageX) + 'px' )
.style('z-index' , '10')
.text(d.hover);
console.log(d)
})
.on('click', function(d , i) {
showChildren(d);
})
.on('mouseout', function(d, i) {
d3.select(this).attr('r', 8);
d3.select('body').selectAll('.tooltip')
.remove();
})
.call(force.drag);
//Now we are giving the SVGs co-ordinates - the force layout is generating the co-ordinates which this code is using to update the attributes of the SVG elements
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;
})
});
}
var node = svg.selectAll('.node')
.data(graphData)
.enter().append('circle')
.attr('class', 'node')
.attr('r', 8)
.attr('id', function(d){return(d.index);})// use the data 'index' property as node 'id' attribute
.style('fill', function (d) {
return color(d.level);
})
.call(force.drag);
The 'index'-es will be stored in circles 'id' attributes as strings (not numbers):
You can add as much attributes as you want and recall them later. Actually, the id tag is not the best choice. The much better alternatives are: data-index, data-id, data-company etc.
PS:
The notation .attr('class', 'node') is not a mistake, but d3.js have special selection.classed(name[, value]) method for classes. I recommend you for attributes use native js arrays:
var styles = {
node: {stroke: "white", "stroke-width": 1.5, r: 8},
link: {stroke: "#999", "stroke-width": 0.6}
};
...
var node = svg.selectAll('.node')
...
.attr(styles.node)
...
By the way, in SVG fill is attribute.
DEMO: http://jsfiddle.net/3gw8vxa3/
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 am using the force layout example here
I need to add labels to the nodes. All examples I have seen use something like this:
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
But this works when there is a function being called on local data such as:
d3.json("graph.json", function(error, json) {
But in my example the data is all client side and thus does not require d3.json to pass over it. How can I add labels to each node in this scenario? Below is the code I am using:
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
// set a width and height for our SVG
var width = 1000,
height = 800;
// setup links
var links = [
{ source: 'Baratheon', target:'Lannister' },
{ source: 'Baratheon', target:'Stark' },
{ source: 'Lannister', target:'Stark' },
{ source: 'Stark', target:'Bolton' },
];
// create empty nodes array
var nodes = {};
// compute nodes from links data
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source});
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target});
});
// add a SVG to the body for our viz
var svg=d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
// use the force
var force = d3.layout.force()
.size([width, height])
.nodes(d3.values(nodes))
.links(links)
.on("tick", tick)
.linkDistance(300)
.start();
// add links
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link');
// add nodes
var node = svg.selectAll('.node')
.data(force.nodes())
.enter().append('circle')
.attr('class', 'node')
.attr('r', width * 0.01);
// what to do
function tick(e) {
node.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.call(force.drag);
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; });
}
</script>
The example of labeling you cited works because each node is bound to an object by the .data() method. Each one of these objects has a name property that contains the label for the node.
In your code, you already have this set up! Your nodes are bound to the force.nodes() array of objects, whose children all have name properties. All you have to do is call node.text(function(d) { return d.name }).
By default, these labels will not be visible. See this question for ideas on how to display node labels.
I am trying to add a threshold to a force directed graph, where I will only include edges between vertices that that are above some threshold I store in a map. My slider is partly working, and the edges successfully get removed.
However, after the edges are removed, the graph stops animating, and there is an error in the console when calling force.start(). Do I really need to add unique ids? In the JSFiddle I linked, he does not do that, and his slider works with no problems.
Thanks!
There are multiple answers on StackOverflow to similar questions, and I have used them to fix my obvious errors (such as d3.js: "Cannot read property 'weight' of undefined" when manually defining both nodes and links for force layout), but I am down to this one. I am using this example: http://jsfiddle.net/simonraper/TdHgx/?utm_source=website&utm_medium=embed&utm_campaign=TdHgx from this website: http://www.coppelia.io/2014/07/an-a-to-z-of-extra-features-for-the-d3-force-layout/
I will send the relevant JS.
function draw(occurence) {
var ratio = window.devicePixelRatio || 1;
var width = Math.min(700,0.8*$(window).width()), height = 700;
var color = d3.scale.category20();
var svg = d3.select("#" + occurence).append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", "#" + occurence + "svg");
d3.json(occurence + ".json", function(error, graph) {
if (error) throw error;
var force = d3.layout.force()
.charge(-120)
.linkDistance(50)
.size([width, height]);
force
.nodes(graph.nodes)
.links(graph.links)
.start();
if (occurence == "occurrences3") {
console.log(graph.nodes);
console.log(graph.links);
}
var graphRec=JSON.parse(JSON.stringify(graph)); //Add this line
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
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; });
});
node.on("dblclick", function(d) {
$("#uncuratedGraphsModal").modal();
var d = d3.select(this).node().__data__;
var name = d.name;
$("#uncuratedGraphsModalHeader").text("Node " + name);
$("#uncuratedGraphsModalBody").empty();
var edge_by_person = edge_by_person_per_threshold[occurence + "edge_by_person"];
var edges = edge_by_person[name];
edges.sort(function(a,b) {
return a.dest - b.dest;
});
$.each(edges, function(edge) {
var new_edge = $("<li>");
new_edge.text("Neighbor: " + edges[edge].dest + ", Token: " + edges[edge].token);
$("#uncuratedGraphsModalBody").append(new_edge);
});
});
$("#" + occurence + "thresholdSlider").on('input', function(thresh) {
threshold($("#" + occurence + "thresholdSlider").val());
});
//adjust threshold
function threshold(thresh) {
var edge_by_person = edge_by_person_per_threshold_unweighted[occurence + "edge_by_person_unweighted"];
graph.links.splice(0, graph.links.length);
for (var i = 0; i < graphRec.links.length; i++) {
var source = graphRec.links[i].source.name;
var dest = graphRec.links[i].target.name;
var value = -1;
var edges = edge_by_person[source];
var found = false;
for (var edge = 0; !found && edge < edges.length; edge++) {
if (dest == edges[edge].dest) {
value = edges[edge].token;
found = true;
}
}
if (value >= thresh) {
graph.links.push({
source: graphRec.links[i].source.name,
target: graphRec.links[i].target.name,
value: graphRec.links[i].value
});
}
}
restart();
}
//Restart the visualisation after any node and link changes
function restart() {
link = link.data(graph.links);
link.exit().remove();
link.enter().insert("line", ".node").attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
node = node.data(graph.nodes);
node.enter().insert("circle", ".cursor").attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
force
.nodes(node)
.links(link)
.start();
}
});
}
This is the JS that he added from the JsFiddle I linked:
This is my understanding: If you have the same nodes, and you just remove a few edges from graph.links, can't you just do a force.start()? When I directly copy his restart() function, all links are broken, and none get redrawn, even though doing a console.log(graph.links) shows the correct links to-redraw.
I believe that it's talking to the wrong nodes at this point, maybe the wrong SVG. I have multiple SVGs on the same page.
I am having some issues with d3js and I can't figure out what is going on. The idea is to draw initial graph from some endpoint data (first img), that's fine works well. Each node is clickable, on click ajax call is made for that node and data is returned, based on some criteria at that point nodes.push(xx), links.push(xx) happens to add new nodes and restart() is called to draw new nodes and links. The issue is that the main graph is doing the correct thing (Not showed on screenshots as I had to put fake data on the first graph i.e. calling an endpoint /record/id/first doesn't return a data) but there are bunch of random nodes showing up in the right bottom corner.
You can also see on the example below, even if the data doesn't change after clicking on first/second/third something wrong goes with node.enter() after restart() with the same data passed in...
JS FIDDLE: http://jsfiddle.net/5754j86e/
var w = 1200,
h = 1200;
var nodes = [];
var links = [];
var node;
var link;
var texts;
var ids = [];
var circleWidth = 10;
var initialIdentifier = "marcin";
nodes = initialBuildNodes(initialIdentifier, sparql);
links = initialBuildLinks(sparql);
//Add SVG
var svg = d3.select('#chart').append('svg')
.attr('width', w)
.attr('height', h);
var linkGroup = svg.append("svg:g").attr("id", "link-group");
var nodeGroup = svg.append("svg:g").attr("id", "node-group");
var textGroup = svg.append("svg:g").attr("id", "text-group");
//Add Force Layout
var force = d3.layout.force()
.size([w, h])
.gravity(.05)
.charge(-1040);
force.linkDistance(120);
restart();
function restart() {
force.links(links)
console.log("LINKS ARE: ", links)
link = linkGroup.selectAll(".link").data (links);
link.enter().append('line')
.attr("class", "link");
link.exit().remove();
force.nodes(nodes)
console.log("NODES ARE: ", nodes)
node = nodeGroup.selectAll(".node").data (nodes);
node.enter().append("svg:g")
.attr("class", "node")
.call(force.drag);
node.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', circleWidth )
.attr('fill', function(d, i) {
if (i>0) { return palette.pink }
else { return palette.blue }
})
.on("click", function(d) {
nodeClicked (d);
})
.on('mouseenter', function(d){
nodeMouseEnter(d)
})
.on('mouseout', function(d){
nodeMouseOut(d)
});
node.exit().remove();
var annotation = textGroup.selectAll(".annotation").data (nodes);
annotation.enter().append("svg:g")
.attr("class", "annotation")
.append("text")
.attr("x", function(d) { return d.radius + 4 })
.attr("y", ".31em")
.attr("class", "label")
.text(function(d) { return d.name; });
annotation.exit().remove();
force.start();
}
function nodeClicked (d) {
// AJAX CALL happens here and bunch of nodes.push({name: "new name"}) happen
}
force.on('tick', function(e) {
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('transform', function(d, i) {
return 'translate('+ d.x +', '+ d.y +')';
})
svg.selectAll(".annotation").attr("transform", function(d) {
var labelx = d.x + 13;
return "translate(" + labelx + "," + d.y + ")";
})
});
Okay I got it, based on the docs (https://github.com/mbostock/d3/wiki/Selections#enter):
var update_sel = svg.selectAll("circle").data(data)
update_sel.attr(/* operate on old elements only */)
update_sel.enter().append("circle").attr(/* operate on new elements only */)
update_sel.attr(/* operate on old and new elements */)
update_sel.exit().remove() /* complete the enter-update-exit pattern */
From my code you can see I do enter() and then once again I add circle on node in a separate statement.
node = nodeGroup.selectAll(".node").data (nodes);
node.enter().append("svg:g")
.attr("class", "node")
.call(force.drag);
node.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', circleWidth )
.attr('fill', function(d, i) {
if (i>0) { return palette.pink }
else { return palette.blue }
});
Adding circle should be within the scope of enter() otherwise it happens to all nodes not only the new nodes therefore it should be :
node.enter().append("svg:g")
.attr("class", "node")
.call(force.drag)
.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', circleWidth )
.attr('fill', function(d, i) {
if (i>0) { return palette.pink }
else { return palette.blue }
});
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"));
});