I hava tried some methods, such as d3 Node Labeling proposed by mbostock. But it doesn't work, my text still doesn't move with the nodes, they are static.
(Coded by using CoffeeScript)
vis = d3.select(selection).append("svg")
.attr("width", width)
.attr("height", height)
linksG = vis.append("g").attr("class", "links")
nodesG = vis.append("g").attr("class", "nodes")
labelG = vis.append("g").attr("class", "labels")
nodeP = nodesG.selectAll("circle.node")
.data(curNodesDataP)
nodeP.enter().append("circle")
.attr("class", "node")
.attr("r", 15)
.style("fill", (d) -> nodeColors(d.id))
.style("stroke", (d) -> strokeFor(d))
.style("stroke-width", 1.0)
.call(force.drag)
text = labelG.selectAll("text")
.data(curNodesDataP)
text.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text((d) -> d.id)
I have troubled that several days, and how can i repair it? Thanks!
--zzcheng
You are appending the links, nodes and labels in separate g groups. So you should update the position of each group in tick function (Link group, Node group and label Group).
Here is a sample fiddle for the same. Note that I am updating position of label group also in the tick function.
var w = 900,
h = 400;
var circleWidth = 5;
var nodes = [{
"name": "Matteo"
}, {
"name": "Daniele"
}, {
"name": "Marco"
}, {
"name": "Lucio"
}, {
"name": "Davide"
}];
var links = [{
source: nodes[0],
target: nodes[1]
}, {
source: nodes[1],
target: nodes[2]
}, {
source: nodes[0],
target: nodes[3]
}, {
source: nodes[4],
target: nodes[2]
}, {
source: nodes[2],
target: nodes[3]
}];
var vis = d3.select("body")
.append("svg:svg")
.attr("class", "stage")
.attr("width", w)
.attr("height", h);
var force = d3.layout.force()
.nodes(nodes)
.links([])
.gravity(0.1)
.charge(-1000)
.size([w, h]);
var link = vis.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.attr("stroke", "#CCC")
.attr("fill", "none");
var node = vis.selectAll("circle.node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
var labels = vis.selectAll("text.label")
.data(nodes)
.enter().append("g")
.attr("class", "label");
//CIRCLE
node.append("svg:circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", circleWidth)
.attr("fill", "pink");
//TEXT
labels.append("text")
.text(function(d, i) {
return d.name;
})
.attr("x", function(d, i) {
return circleWidth + 5;
})
.attr("y", function(d, i) {
if (i > 0) {
return circleWidth + 0
} else {
return 8
}
})
.attr("font-family", "Bree Serif")
.attr("fill", "green")
.attr("font-size", "1em")
.attr("text-anchor", function(d, i) {
if (i > 0) {
return "beginning";
} else {
return "end"
}
})
force.on("tick", function(e) {
node.attr("transform", function(d, i) {
return "translate(" + d.x + "," + 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;
});
labels.attr("transform", function(d, i) {
return "translate(" + d.x + "," + d.y + ")";
});
});
force.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Other possible and more efficient way would be to group nodes and labels together. Here is a sample fiddle in which nodes and labels are grouped and updated position of the node group and link group only in tick function.
var w = 900,
h = 400;
var circleWidth = 5;
var nodes = [{
"name": "Matteo"
}, {
"name": "Daniele"
}, {
"name": "Marco"
}, {
"name": "Lucio"
}, {
"name": "Davide"
}];
var links = [{
source: nodes[0],
target: nodes[1]
}, {
source: nodes[1],
target: nodes[2]
}, {
source: nodes[0],
target: nodes[3]
}, {
source: nodes[4],
target: nodes[2]
}, {
source: nodes[2],
target: nodes[3]
}];
var vis = d3.select("body")
.append("svg:svg")
.attr("class", "stage")
.attr("width", w)
.attr("height", h);
var force = d3.layout.force()
.nodes(nodes)
.links([])
.gravity(0.1)
.charge(-1000)
.size([w, h]);
var link = vis.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.attr("stroke", "#CCC")
.attr("fill", "none");
var node = vis.selectAll("circle.node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
//CIRCLE
node.append("svg:circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", circleWidth)
.attr("fill", "pink");
//TEXT
node.append("text")
.text(function(d, i) {
return d.name;
})
.attr("x", function(d, i) {
return circleWidth + 5;
})
.attr("y", function(d, i) {
if (i > 0) {
return circleWidth + 0
} else {
return 8
}
})
.attr("font-family", "Bree Serif")
.attr("fill", "green")
.attr("font-size", "1em")
.attr("text-anchor", function(d, i) {
if (i > 0) {
return "beginning";
} else {
return "end"
}
})
force.on("tick", function(e) {
node.attr("transform", function(d, i) {
return "translate(" + d.x + "," + 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;
})
});
force.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Related
What I'm running now is showing a root node and bunch of nodes connected to it via d3 line element. I'd like to add a caption to the line, since I have it defined and can freely grab it from d.label (in the nodes section in JSON). I'm using the following code:
var width = 500,
height = 500;
var force = d3.layout.force()
.size([width, height])
.charge(-800)
.linkDistance(d => d.distance)
.on("tick", tick);
var svg = d3.select("#target").append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "mainsvg");
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
d3.json("test.json", function(error, graph) {
if (error) throw error;
force
.nodes(graph.nodes)
.links(graph.links)
.start();
link = link.data(graph.links)
.enter().append("line")
.attr("class", "link");
node = node.data(graph.nodes)
.enter().append("a")
node.append("g")
.attr("class", "node");
node.append("circle")
.attr("class", "circle")
.attr("r", function(d) { return d.r })
.attr("fill", function(d) { return d.color })
.attr("stroke", function(d){ return d.color });
});
function tick() {
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; });
svg.selectAll(".circle").attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
svg.selectAll(".text").attr("x", function(d) { return d.x+d.xoffs; })
.attr("y", function(d) { return d.y+d.yoffs; });
}
Now I've tried using normal <text> elements appended to the edgepath in this fashion:
var edgepaths = svg.selectAll(".edgepath")
.data(graph.links)
.enter()
.append('path')
.attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y},
'class':'edgepath',
'fill-opacity':0,
'stroke-opacity':0,
'fill':'blue',
'stroke':'red',
'id':function(d,i) {return 'edgepath'+i}})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(graph.links)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({'class':'edgelabel',
'id':function(d,i){return 'edgelabel'+i},
'dx':80,
'dy':10,
'font-size':10,
'fill':'black'});
edgelabels.append('textPath')
.attr('xlink:href',function(d,i) {return '#edgepath'+i})
.style("pointer-events", "none")
.text(function(d){return d.label});
However, the results I get are weird to say at best. Some labels are too close together, some are missing, but all of them are in the wrong place. How would I move them where they should be? To keep something in mind, I'm looking for a simple way to add caption to the paths I already have drawn out, nothing else. So the method I tried may have been too much, I'm not sure.
The problem is the dx in your edgelabels, that's always the same:
'dx': 80,
Assuming that you're using the code with the link distance (as in your last question), the solution is simple:
'dx':function(d){return d.distance/2},
Check the demo:
var nodes = [{
name: "a"
}, {
name: "b"
}, {
name: "c"
}, {
name: "d"
}, {
name: "e"
}, {
name: "f"
}];
var links = [{
source: 1,
target: 0,
distance: 80,
label: "foo"
}, {
source: 2,
target: 0,
distance: 40,
label: "bar"
}, {
source: 3,
target: 0,
distance: 180,
label: "baz"
}, {
source: 4,
target: 0,
distance: 80,
label: "foobar"
}, {
source: 5,
target: 0,
distance: 90,
label: "foobaz"
}]
var width = 400
height = 300;
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(d => d.distance)
.charge(-1000)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("stroke", "black")
.attr("class", "link");
var edgepaths = svg.selectAll(".edgepath")
.data(force.links())
.enter()
.append('path')
.attr({
'd': function(d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y
},
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'fill': 'blue',
'stroke': 'red',
'id': function(d, i) {
return 'edgepath' + i
}
})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(force.links())
.enter()
.append('text')
.style("pointer-events", "none")
.attr({
'class': 'edgelabel',
'id': function(d, i) {
return 'edgelabel' + i
},
'dx': function(d) {
return d.distance / 2
},
'dy': 10,
'font-size': 12,
'fill': 'black'
});
edgelabels.append('textPath')
.attr('xlink:href', function(d, i) {
return '#edgepath' + i
})
.style("pointer-events", "none")
.text(function(d) {
return d.label
});
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", (d, i) => i ? 10 : 16)
.style("fill", (d, i) => i ? "teal" : "brown");
function tick() {
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) {
return "translate(" + d.x + "," + d.y + ")";
});
edgepaths.attr('d', function(d) {
var path = 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
return path
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
text cannot be a child of path see this question path is not a container... per SVG spec.
best practice would be to first append g elements to your SVG as containers for the bound data, then append the path and text elements to the g so that they are siblings. Then you have complete control over relative positioning, etc.
I have a d3 graph that I'm adding nodes to. These nodes originally had an image and text. Now I need to add a drop down to the mix.
Here is the relevant section of code that builds the node:
var node = svg.selectAll(".node")
.data(scope.nodes);
var nodeg = node.enter().append("g")
.attr("class", "node")
.call(force.drag);
nodeg.append("image")
.attr("xlink:href", function (d) {
return d.avatar || 'https://github.com/favicon.ico'
})
.attr("x", -56)
.attr("y", -8)
.attr("width", 64)
.attr("height", 64);
nodeg.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.attr('class', 'name')
.text(function (d) {
return d._id === scope.user.profile._id ? 'You' : d.firstName + ' ' + d.lastName
});
nodeg.append('select')
.attr('dx', 12)
.attr('dy', '1.35em')
.selectAll('option')
.data(options)
.enter()
.append('option')
.text(function(o) {
return o.text;
})
.attr('value', function(o) {
return o.value;
});
The options array is defined like this:
var options = [{value: 'M', text: 'Married'},
{value: 'R', text: 'In a Relationship'},
{value: 'C', text: 'Child'}];
This error is occurring in the d3 library because it's trying to access the length property of the options property of the select element:
Uncaught TypeError: Cannot read property 'length' of undefined
... for(R=0,M=N.options.length
What am I doing wrong here? Why is the options property undefined? This mimics, for the most part, every example I can find online thus far.
Complete Example
var scope = {};
scope.user = {
profile: {
_id: 1,
firstName: 'Billy',
lastName: 'Bob'
}
};
scope.nodes = [scope.user.profile];
scope.links = [];
var options = [{
value: 'M',
text: 'Married'
}, {
value: 'R',
text: 'In a Relationship'
}, {
value: 'C',
text: 'Child'
}];
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.nodes(scope.nodes)
.links(scope.links)
.charge(-800)
.linkDistance(200)
.size([width, height]);
function renderGraph() {
force.start();
var link = svg.selectAll(".link")
.data(scope.links);
link.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(scope.nodes);
var nodeg = node.enter().append("g")
.attr("class", "node")
.call(force.drag);
nodeg.append("image")
.attr("xlink:href", function(d) {
return d.avatar || 'https://github.com/favicon.ico'
})
.attr("x", -56)
.attr("y", -8)
.attr("width", 64)
.attr("height", 64);
nodeg.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.attr('class', 'name')
.text(function(d) {
return d._id === scope.user.profile._id ?
'You' : d.firstName + ' ' + d.lastName
});
nodeg.append('select')
.attr('dx', 12)
.attr('dy', '1.35em')
.selectAll('option')
.data(options)
.enter()
.append('option')
.text(function(o) {
return o.text;
})
.attr('value', function(o) {
return o.value;
});
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("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
}
renderGraph();
.node text {
pointer-events: none;
}
.node text.name {
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Thanks to Lars, I was able to get that select on there. Using the foreignObject, as expected, did the trick.
nodeg.append('foreignObject')
.append('xhtml:select')
.attr('style', 'margin: 1rem')
.selectAll('option')
.data(options)
.enter()
.append('option')
.text(function(o) {
return o.text;
})
.attr('value', function(o) {
return o.value;
});
I have a problem with link.exit().remove(); and node.exit().remove();. If I set it in the initializeGraph method then I get several errors with the tick function I think. So my question is how or why do I get those errors:
Uncaught TypeError: undefined is not a function graph-d3.js:156initializeGraph graph-d3.js:156updateForceUsingNewNodes graph-d3.js:108createGraph graph-d3.js:18$.ajax.success ajax-stuff.js:106j jquery-2.1.1.min.js:2k.fireWith jquery-2.1.1.min.js:2x jquery-2.1.1.min.js:4n.prop.on.c jquery-2.1.1.min.js:4n.event.dispatch jquery-2.1.1.min.js:3r.handle jquery-2.1.1.min.js:3
3Error: Invalid value for <line> attribute x1="NaN" d3.min.js:1
3Error: Invalid value for <line> attribute y1="NaN" d3.min.js:1
3Error: Invalid value for <line> attribute x2="NaN" d3.min.js:1
3Error: Invalid value for <line> attribute y2="NaN" d3.min.js:1
Uncaught TypeError: Cannot read property 'attr' of undefined
Here is an exerpt of the source code. Not important lines are removed:
var alreadyThere = false;
var nodeCircles = {};
var svg, link, node;
var force = d3.layout.force();
var width = 700, height = 200;
var boxIDName = "#main-rightinfo";
var currentJSON;
var container;
var zoom = d3.behavior.zoom()
.scaleExtent([0.4, 5]);
var drag = force.drag();
function createGraph(newJSON){
if (alreadyThere){
svg.remove();
nodeCircles = {};
}
updateForceUsingNewNodes(generateObjects(newJSON));
alreadyThere = true;
currentJSON = newJSON;
force.start();
}
function updateGraph(newJSON){
svg.remove();
findDuplicatesAndSetEmpty(newJSON);
deleteEmptyObjectsInJSON(newJSON);
currentJSON = currentJSON.concat(newJSON);
updateForceUsingNewNodes(generateObjects(currentJSON));
force.start();
}
//here are some methods forming the json and array...
function initializeGraph(){
zoom.on("zoom", zoomed);
drag.on("dragstart", dragstart);
force
.size([width, height])
.gravity(.1)
.charge(-400)
.friction(0.9)
.theta(0.9)
.linkStrength(0.9)
.distance(55)
.alpha(0.1)
.on("tick", tick);
svg = d3.select("#main-right")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom).on("dblclick.zoom", null);
svg
.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 32)
.attr("refY", -0.05)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.attr('fill', '#359AF4');
container = svg.append("g");
link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link")
.attr("marker-end", "url(#end)");
node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", function(d) { click(d); })
.on("dblclick", function(d) { dblclick(d); })
.on('contextmenu', function(data, index) {
d3.event.preventDefault();
updateGraphByRemoveElement(data, index);
})
.call(drag);
node
.append("circle")
.attr("r", 20)
.attr("cx", 0)
.attr("cy", 0)
.style("fill", '#eee')
.style("stroke", '#fff')
.style("stroke-width", '0.5px');
node
.append("image")
.attr("xlink:href", function(d) {
if (d.class == "Person") {
return "pics/node_person.svg";
} else {
return "pics/node_appln.svg";
}} )
.attr("x", -20)
.attr("y", -20)
.attr("width", 40)
.attr("height", 40)
.attr("color", "white");
node
.append("text")
.attr("x", 20)
.attr("y", 4)
.style("fill", "#bbb")
.text(function(d) { return d.name; });
node
.append("circle")
.attr("r", 7)
.attr("cx", 0)
.attr("cy", -16)
.style("fill", '#359AF4');
node
.append("text")
.attr("text-anchor", "center")
.attr("x", -3)
.attr("y", -13)
.style("fill", "white")
.text(function(d) { return d.linkCount; });
function tick() {
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) { return "translate(" + d.x + "," + d.y + ")"; });
}
}
//here are some functions tick, mousedown and so on...
Well as you can see the svg.remove(); from the code is not needed. But until the exit().remove() do not work, it's needed. So yeah how to handle that with the tickevent/ .exit().remove().
Thank you for any tips.
PS: I used this very basic one https://gist.github.com/mbostock/1095795
and another which is very close to mine D3.js - exit() section does not remove all data
and this working not working, too Why does d3.js v3 break my force graph when implementing zooming when v2 doesn't?
Whole code or let's say how I think it should run, but currently getting some errors (not identical to the code above, but just changed some lines)
var alreadyThere = false;
var nodeCircles = {};
var svg, link, node;
var force = d3.layout.force();
var width = 700, height = 400;
var boxIDName = "#main-rightinfo";
var currentJSON;
var container;
var zoom = d3.behavior.zoom()
.scaleExtent([0.4, 5]);
var drag = force.drag();
function createGraph(newJSON){
if (alreadyThere){
//svg.remove();
nodeCircles = {};
}
updateForceUsingNewNodes(generateObjects(newJSON));
alreadyThere = true;
currentJSON = newJSON;
force.start();
}
function updateGraph(newJSON){
//svg.remove();
findDuplicatesAndSetEmpty(newJSON);
deleteEmptyObjectsInJSON(newJSON);
currentJSON = currentJSON.concat(newJSON);
updateForceUsingNewNodes(generateObjects(currentJSON));
force.start();
}
function findDuplicatesAndSetEmpty(newJSON){
for (var i = 0; i < currentJSON.length; i++) {
for (var o = 0; o < newJSON.length; o++) {
if ((currentJSON[i].source.ID == newJSON[o].source) && (currentJSON[i].target.ID == newJSON[o].target)
|| (currentJSON[i].source.ID == newJSON[o].target) && (currentJSON[i].target.ID == newJSON[o].source)){
newJSON[o] = {};
}
}
}
}
function deleteEmptyObjectsInJSON(json){
for (var i = 0; i < json.length; i++) {
var y = json[i].source;
if (y==="null" || y===null || y==="" || typeof y === "undefined"){
json.splice(i,1);
i--;
}
}
}
function updateGraphByRemoveElement(clickedNode, index){
svg.remove();
var json4Splicing = currentJSON;
for (var i = 0; i < json4Splicing.length; i++) {
if (json4Splicing[i].source.ID == clickedNode.ID){
json4Splicing[i] = {};
} else if (json4Splicing[i].target.ID == clickedNode.ID){
json4Splicing[i] = {};
}
}
deleteEmptyObjectsInJSON(json4Splicing);
deleteNode(force.nodes(),clickedNode);
currentJSON = json4Splicing;
updateForceRemoveElement(generateObjects(currentJSON));
}
function deleteNode(allNodes, clickedNode){
allNodes.forEach(function(node) {
if (node == clickedNode){
force.links().forEach(function(link) {
if (node.ID == link.source.ID){
link.target.linkCount--;
}
if (node.ID == link.target.ID){
link.source.linkCount--;
}
});
node.linkCount = 0;
}
});
}
function generateObjects(json) {
json.forEach(function(link) {
if (typeof(link.source) == "string"){
link.source = nodeCircles[link.source] || (nodeCircles[link.source] = {name: link.sourceName, ID: link.source, class: link.sourceClass, linkCount:0});
link.source.linkCount++;
}
if (typeof(link.target) == "string"){
link.target = nodeCircles[link.target] || (nodeCircles[link.target] = {name: link.targetName, ID: link.target, class: link.targetClass, linkCount:0});
link.target.linkCount++;
}
});
return json;
}
function updateForceRemoveElement(links){
force.nodes(d3.values(nodeCircles).filter(function(d){ return d.linkCount; }) );
force.links(d3.values(links));
initializeGraph();
}
function updateForceUsingNewNodes(links){
force.nodes(d3.values(nodeCircles).filter(function(d){ return d.linkCount; }) );
force.links(d3.values(links));
initializeGraph();
}
function initializeGraph(){
zoom.on("zoom", zoomed);
drag.on("dragstart", dragstart);
force
.size([width, height])
.gravity(.1)
.charge(-400)
.friction(0.9)
.theta(0.9)
.linkStrength(0.9)
.distance(55)
.alpha(0.1)
.on("tick", tick);
svg = d3.select("#main-right")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom).on("dblclick.zoom", null);
svg
.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 32)
.attr("refY", -0.05)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.attr('fill', '#359AF4');
container = svg.append("g");
link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link")
.attr("marker-end", "url(#end)");
link.exit().remove();
node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", function(d) { click(d); })
.on("dblclick", function(d) { dblclick(d); })
.on('contextmenu', function(data, index) {
d3.event.preventDefault();
updateGraphByRemoveElement(data, index);
})
.call(drag);
node
.append("circle")
.attr("r", 20)
.attr("cx", 0)
.attr("cy", 0)
.style("fill", '#eee')
.style("stroke", '#fff')
.style("stroke-width", '0.5px');
node
.append("image")
.attr("xlink:href", function(d) {
if (d.class == "Person") {
return "pics/node_person.svg";
} else {
return "pics/node_appln.svg";
}} )
.attr("x", -20)
.attr("y", -20)
.attr("width", 40)
.attr("height", 40)
.attr("color", "white");
node
.append("text")
.attr("x", 20)
.attr("y", 4)
.style("fill", "#bbb")
.text(function(d) { return d.name; });
node
.append("circle")
.attr("r", 7)
.attr("cx", 0)
.attr("cy", -16)
.style("fill", '#359AF4');
node
.append("text")
.attr("text-anchor", "center")
.attr("x", -3)
.attr("y", -13)
.style("fill", "white")
.text(function(d) { return d.linkCount; });
node.exit().remove();
}
function tick() {
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) { return "translate(" + d.x + "," + d.y + ")"; });
}
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
function zoomed() {
d3.event.sourceEvent.stopPropagation();
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstart(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("fixed", d.fixed = true);
}
function mouseover() {
d3.select(this).select("text").transition()
.duration(750)
.style("font-size","15px")
.style("fill","black");
d3.select(this).moveToFront();
}
function mouseout() {
d3.select(this).select("text").transition()
.duration(750)
.style("font-size","10px")
.style("fill","#ccc");
}
function click(d) {
$(boxIDName).empty();
if (d.class == "Person"){
$(boxIDName).append("<h2>Person/Company</h2>");
getNodeInfoPerson(d.ID);
}
if (d.class == "Appln"){
$(boxIDName).append("<h2>Application</h2>");
getNodeInfoAppln(d.ID);
}
}
function dblclick(d) {
entryClicked(d.ID+"|"+d.class,false);
}
I want to display one <circle> and <text> for each node. My code looks like this, having added the suggested code from the answer below. Please note the different
var width = 960,
height = 500;
var color = d3.scale.category10();
var nodes = [],
links = [];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-250)
.linkDistance(25)
.size([width, height])
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var node = svg.selectAll(".node")
.data(force.nodes(), function(d) { return d.id;}),
link = svg.selectAll(".link");
var text=svg.selectAll("text") //simply add text to svg
.data(force.nodes())
.enter()
.append("text")
.attr("class", "nodeText")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
function start() {
link = link.data(force.links(), function(d) { return d.source.id + "-" + d.target.id; });
link.enter().insert("line", ".node")
.attr("class", function(d) { return "link " + d.edgeType; })
.attr("id", function(d) { return d.source.id + "-" + d.target.id; });
link.exit().remove();
v1: <line>s exist and are displayed, no <circle>s or <text> exist
var g = node.enter().append("g");
g.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick);
g.append("text")
.text(function(d) {return d.id; });
/v1
v2: <line>s and <circle>s exist and are displayed. <text>s exist within <circle>s but aren't displayed
node = node.data(force.nodes(), function(d) { return d.id;});
node.enter()
.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick);
node.append("text")
.text(function(d) {return d.id; });
/v2
node.exit().remove();
force.start();
}
function nodeClick() {
var node_id = event.target.id;
handleClick(node_id, "node");
}
function tick() {
text.attr("dx", function(d) { return d.x+5; })
.attr("dy", function(d) { return d.y+5; })
.text(function(d){return d.id});
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return 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; });
}
Simply add text element as following in your js
var text=svg.selectAll("text") //simply add text to svg
.data(Your_nodes_array)
.enter()
.append("text")
.attr("class", "nodeText")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
And inside tick do this :
text.attr("dx", function(d) { return d.x+5;}) //to keep it away from node
.attr("dy", function(d) { return d.y+5; })
.text(function(d){return d.id});
This will solve circle and text problems as this time we are adding text directly now
I am a newbie to d3 and trying to do a graph layout.
var w = 1000;
var h = 500;
var dataset = {
nodes: [{
name: 'Aden'
}, {
name: 'Bob'
}, {
name: 'Sue'
}],
edges: [{
source: 0,
target: 1
}, {
source: 1,
target: 2
}]
};
var svg = d3.select("body")
.append("svg")
.attr("height", h)
.attr("width", w);
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([50])
.start();
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r", 10)
.style("fill", "red")
.call(force.drag);
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
My fiddle is in : http://jsfiddle.net/abhimita/MnX23/
I don't see any graph but couldn't figure out what I am doing incorrectly. Any help will be really appreciated.
1.you neet to set cx and cy of circle to position the circle
2.you need to set x1 y1, x2 y2 of line to position line
3.if you need active you need to listen to tick event of force layout
var w = 300;
var h = 300;
var dataset = {
nodes: [{
name: 'Aden'
}, {
name: 'Bob'
}, {
name: 'Sue'
}],
edges: [{
source: 0,
target: 1
}, {
source: 1,
target: 2
}]
};
var svg = d3.select("body")
.append("svg")
.attr("height", h)
.attr("width", w);
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.on("tick", tick) // listener tick to listen position change
.linkDistance([50])
.start();
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r", 10)
// set cx cy of circle to position the circle
.attr("cx", function (d) {return d.x; })
.attr("cy", function (d) { return d.y; })
.style("fill", "red")
.call(force.drag);
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
// set x1, y1, x2, y2 to position the line
.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;
})
.style("stroke", "#ccc")
.style("stroke-width", 1);
// make it actively
function tick(e) {
edges.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; });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
demo update: http://jsfiddle.net/MnX23/3/
In circle you have to mention the cx and cy attributes and line x1,y1,x2,y2 attributes
The x1 attribute defines the start of the line on the x-axis
The y1 attribute defines the start of the line on the y-axis
The x2 attribute defines the end of the line on the x-axis
The y2 attribute defines the end of the line on the y-axis
Try this code:
DEMO
var w = 1000;
var h = 500;
var dataset = {
nodes: [{
name: 'Aden'
}, {
name: 'Bob'
}, {
name: 'Sue'
}],
edges: [{
source: 0,
target: 1
}, {
source: 1,
target: 2
}]
};
var svg = d3.select("body")
.append("svg")
.attr("height", h)
.attr("width", w);
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([50])
.start();
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r", 10)
.style("fill", "red")
.call(force.drag);
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
force.on("tick", function() {
edges.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; });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});