The basic example of my question builds on this chart. The goal is to fill only half the circle with the group color.
This SO question explains how to make half circles.
Here's a snippet of the original code
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
Here`s adding a half circle
var grad = svg.append("defs").append("linearGradient").attr("id", "grad")
.attr("x1", "0%").attr("x2", "0%").attr("y1", "100%").attr("y2", "0%");
grad.append("stop").attr("offset", "50%").style("stop-color", "lightblue");
grad.append("stop").attr("offset", "50%").style("stop-color", "white");
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
enter code here
How could I make this grad dependent on the d.group?
I tried
A get_grad() function and have it return the grad
A set_grad() function and have it set the fill attribute
However, I didn't manage to get either working. Who can help me?
If you want to have different elements with different gradients, you have to use the same data binding process to create the gradients themselves:
var defs = svg.append("defs")
.selectAll("foo")
.data(data)
.enter()
.append("linearGradient")
//etc...
Have in mind that IDs have to be unique. In the following demo I'm doing:
.attr("id", function(d) {
return "grad" + d
})
... to create unique IDs.
In the demo, this is the part that you probably are interested in:
defs.append("stop")
.attr("offset", "50%")
.style("stop-color", function(d) {
return colours(d)
})
As you can see, I'm applying the stop colours based on data.
Have a look at the demo (which is not a force directed chart, but simply a demo with elements using different gradients):
var svg = d3.select("svg");
var colours = d3.scaleOrdinal(d3.schemeCategory10);
var defs = svg.append("defs")
.selectAll("foo")
.data(d3.range(5))
.enter()
.append("linearGradient")
.attr("id", function(d) {
return "grad" + d
})
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "100%")
.attr("y2", "0%");
defs.append("stop")
.attr("offset", "50%")
.style("stop-color", function(d) {
return colours(d)
})
defs.append("stop")
.attr("offset", "50%")
.style("stop-color", "white");
var circles = svg.selectAll("foo")
.data(d3.range(5))
.enter()
.append("circle")
.attr("cy", 50)
.attr("cx", function(d) {
return 25 + d * 62
})
.attr("r", 25)
.attr("stroke", "dimgray")
.attr("fill", function(d) {
return "url(#grad" + d + ")"
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
You can also play with the offsets:
var svg = d3.select("svg");
var colours = d3.scaleOrdinal(d3.schemeCategory10);
var defs = svg.append("defs")
.selectAll("foo")
.data(d3.range(5))
.enter()
.append("linearGradient")
.attr("id", function(d) {
return "grad" + d
})
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "100%")
.attr("y2", "0%");
defs.append("stop")
.attr("offset", function(d) {
return 20 + d * 15 + "%"
})
.style("stop-color", function(d) {
return colours(d)
})
defs.append("stop")
.attr("offset", function(d) {
return 20 + d * 15 + "%"
})
.style("stop-color", "white");
var circles = svg.selectAll("foo")
.data(d3.range(5))
.enter()
.append("circle")
.attr("cy", 50)
.attr("cx", function(d) {
return 25 + d * 62
})
.attr("r", 25)
.attr("stroke", "dimgray")
.attr("fill", function(d) {
return "url(#grad" + d + ")"
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Related
I have done a d3 widget which works fine, now needs to show the size as tooltip.
I have tried attaching mouseover event to the node, that does not seems to work.
here is my code:
var width = 460,
height = 300;
var color = d3.scale.category20();
var radius = d3.scale.sqrt()
.range([0, 3]);
var svg = d3.select("#mdView").append("svg")
.attr("viewBox", "0 0 460 300 ")
.attr("width", '100%')
.attr("height", '100%');
var force = d3.layout.force()
.size([width, height])
.charge(-300)
.linkDistance(function(d) {
return radius(d.source.size) + radius(d.target.size) + 20;
});
d3.json("views/graph.json", function(error, graph) {
if (error) throw error;
force
.nodes(graph.nodes)
.links(graph.links)
.on("tick", tick)
.start();
var tooltip = d3.select("#chart")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
tooltip.append("div")
.attr("id", "tt-name")
.text("simple");
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("g")
.attr("class", "link");
link.append("line")
.style("stroke-width", function(d) {
return (d.bond * 2 - 1) * 2 + "px";
});
link.filter(function(d) {
return d.bond > 1;
}).append("line")
.attr("class", "separator");
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", function(d) {
return radius(d.size);
})
.style("fill", function(d) {
return color(d.skill);
});
node.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "margin-left")
.text(function(d) {
return d.skill;
});
function tick() {
link.selectAll("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;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
});
Working JSFIDDLE
I tried to give you some starting point by editing your script,.. please take a look
// NOTE : please add position:relative to #mdView html element.
var tooltip = d3.select("#mdView") // changed the selector
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("padding", "10px")
.style("background-color", "#333")
.style("border-radius", "5px")
.style("color", "#fff")
.style("opacity", "0");
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag)
/* Added these two events */
.on('mousemove',function(d,i){
console.log(d);
tooltip
.style('top',d.y+'px')
.style('left',d.x+'px')
.style('opacity',1)
.html('size : '+d.size);
}).on('mouseleave',function(){
tooltip
.style('opacity',0)
.html('');
});
Please keep in mind this is just a starter point for you , If you need more help please just ask.
Hope it helps.
I'm able to get images and labels to show for the nodes, but they show at the top left of the screen.
Nodes show up in the correct position when I use this
.enter().append("circle")
Labels and node images show at the top left (incorrect) when I use this:
.enter().append("g")
This works with append "circle" (commented out in the code below):
When I comment out append circle and use append "g" (in order to use node images and labels) the images and labels all show up near (0,0) instead of near the node:
Also, what exactly is append "g"? Where is the documentation to find out what's possible with append "g"?
Here is all the code:
<script>
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size(\[width, height\]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var graph = getData();
var nodeMap = {};
graph.nodes.forEach(function(d) { nodeMap\[d.name\] = d; });
graph.links.forEach(function(l) {
l.source = nodeMap\[l.source\];
l.target = nodeMap\[l.target\];
})
force.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke", function(d) {
return d.line_color;
})
.style("stroke-width", function(d) {
return Math.sqrt(d.value)+1;
});
var node = svg.selectAll(".node")
.data(graph.nodes)
// .enter().append("circle")
// .attr("class", "node")
// .attr("r", 10)
// .style("fill", function(d) { return d.fill_color; })
// .call(force.drag);
.enter().append("g")
.attr("class", "node")
.attr("r", 15)
.style("fill", function(d) { return d.fill_color; })
.on("click", function(d){
alert("You clicked on node " + d.name);
})
.call(force.drag);
node.append("title")
.text(function(d) { return d.label; });
node.append("image")
.attr("xlink:href", function(d) { return d.image_url })
.attr("x", -8)
.attr("y", -8)
.attr("width", 26)
.attr("height", 26);
node.append("text")
.attr("dx", function(d) {
if (d.image_url == "/profile.png"){
return 100;
}
else{
return 16;
}
})
.attr("dy", function(d) {
if (d.image_url == "/profile.png"){
return 100;
}
else{
return ".35em";
}
})
// .attr("dy", ".35em")
.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; });
});
function getData() {
return {
"nodes":\[
{"name":"user1","image_url":"http://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png","fill_color":"blue","text_color":"black"},
{"name":"user2","image_url":"http://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png","fill_color":"blue","text_color":"black"},
{"name":"user3","image_url":"http://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png","fill_color":"blue","text_color":"black"},
{"name":"tag1","image_url":"","fill_color":"blue","text_color":"black"},
{"name":"tag2","image_url":"","fill_color":"blue","text_color":"black"},
{"name":"tag3","image_url":"","fill_color":"blue","text_color":"black"}
\],
"links":\[
{"source":"tag1","target":"user1","value":1,"line_color":"green"},
{"source":"tag2","target":"user1","value":1,"line_color":"green"},
{"source":"tag3","target":"user1","value":1,"line_color":"green"},
{"source":"tag1","target":"user2","value":1,"line_color":"green"},
{"source":"tag2","target":"user2","value":1,"line_color":"green"}
\]
};
}
</script>
With .append("g") you insert a SVG Group Element.
The problem is, that you try to apply attributes that are for circles, like the radius with .attr("r",15), to the group element.
You have to use circles if you want to draw a circle. Group elements do not have any shape. They are used to group elements like circles.
A solution would be to append the g element and transform it to the location of the node. I updated your code in the following snippet. I used the group elements and added the circle, image and text inside the group elements.
Moreover I removed the backslashes before each angular bracket and set the title to the field name instead of label.
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var graph = getData();
var nodeMap = {};
graph.nodes.forEach(function(d) { nodeMap[d.name] = d; });
graph.links.forEach(function(l) {
l.source = nodeMap[l.source];
l.target = nodeMap[l.target];
})
force.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter()
.append("line")
.attr("class", "link")
.style("stroke", function(d) {
return d.line_color;
})
.style("stroke-width", function(d) {
return Math.sqrt(d.value)+1;
});
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter()
.append("g")
.attr("transform", function(d){return "translate("+d.x+","+d.y+")"})
.call(force.drag);
node.append("circle")
.attr("class", "node")
.attr("r", 15)
.style("fill", function(d) { return d.fill_color; })
.on("click", function(d){
alert("You clicked on node " + d.name);
});
node.append("title")
.text(function(d) { return d.name; });
node.append("image")
.attr("xlink:href", function(d) { return d.image_url })
.attr("x", -8)
.attr("y", -8)
.attr("width", 26)
.attr("height", 26);
node.append("text")
.attr("dx", function(d) {
if (d.image_url == "/profile.png"){
return 100;
}
else{
return 16;
}
})
.attr("dy", function(d) {
if (d.image_url == "/profile.png"){
return 100;
}
else{
return ".35em";
}
})
// .attr("dy", ".35em")
.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("transform", function(d){return "translate("+d.x+","+d.y+")"});
});
function getData() {
return {
"nodes":[
{"name":"user1","image_url":"http://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png","fill_color":"blue","text_color":"black"},
{"name":"user2","image_url":"http://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png","fill_color":"blue","text_color":"black"},
{"name":"user3","image_url":"http://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png","fill_color":"blue","text_color":"black"},
{"name":"tag1","image_url":"","fill_color":"blue","text_color":"black"},
{"name":"tag2","image_url":"","fill_color":"blue","text_color":"black"},
{"name":"tag3","image_url":"","fill_color":"blue","text_color":"black"}
],
"links":[
{"source":"tag1","target":"user1","value":1,"line_color":"green"},
{"source":"tag2","target":"user1","value":1,"line_color":"green"},
{"source":"tag3","target":"user1","value":1,"line_color":"green"},
{"source":"tag1","target":"user2","value":1,"line_color":"green"},
{"source":"tag2","target":"user2","value":1,"line_color":"green"}
]
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I have a simple force layout that calculates nodes and links when certain buttons are clicked. The first time the nodes are calculated and displayed everything is correctly positioned. However, when nodes are recalculated after another click, the position of the circles I have appended to the nodes are way off yet the text I added remains in the right place. Here's my JS:
//Compute Nodes and Links
var data = this.computePreviewNodes($(e.currentTarget).data("id"), $(e.currentTarget).data("type"));
var canvas = d3.select(".body").append("svg")
.attr("width", width)
.attr("height", screen.height/2)
.append("g");
canvas.append("text")
.text(compObj.name)
.attr("text-anchor", "middle")
.attr("font-size", "2em")
.attr("x", width/2)
.attr("y", 40);
var force = d3.layout.force()
.nodes(data.nodes)
.links(data.links)
.gravity(.05)
.distance(100)
.charge(-10)
.size([width, screen.height/2]);
var links = canvas.selectAll(".links")
.data(data.links)
.enter().append("line")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke", "blue");
var nodes = canvas.selectAll(".nodes")
.data(data.nodes)
.enter()
.append("g")
.attr("class", "nodes")
.call(force.drag);
nodes.append("circle")
.attr("cx", function(d) {return d.x;})
.attr("cy", function(d) {return d.y;})
.attr("r", 10)
.attr("fill", "green");
nodes.append("text")
.text(function(d) {return d.name})
.attr("text-anchor", "right")
.attr("font-size", "1.8em")
.attr("y", 5);
force.on("tick", function(e) {
nodes
.attr("transform", function(d, i){
return "translate(" + d.x + ", " + d.y + ")";
});
links
.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();
My computePreviewNodes() function simply comes up with what nodes need to be displayed based on which button is clicked. My thoughts are that maybe I'm not updating my node positions correctly after the second rendering of my nodes. Any ideas?
Here's my canvas at the first click:
And here it is when I click/calculate my nodes once again:
I am able to add text to my sketch, but I would like it if I could make my text attached directly to the circle. This means that if a circle gets over-written by another circle, the text will as well. On a higher level not, I am finding the d3 model hard for constructing objects in a way that makes them composable with different shapes, etc. The code seems very procedural to mean so any tips would be greatly appeciated :)
JSFiddle link
var link = "https://api.github.com/orgs/csci-4830-002-2014/repos"
d3.json(link, function(error, data) {
var w = 10000;
var h = 1000;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("line")
.data(data)
.enter()
.append("line")
.attr("x1", 5)
.attr("y1", 5)
.attr("x2", function (d,i){
return 30*d.forks_count;
})
.attr("y2", function (d,i){
return 30*d.open_issues_count;
})
.attr("stroke-width", 2)
.attr("stroke", "black");
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", 40)
.attr("cx", function(d){ return 30*d.forks_count; })
.attr("cy", function(d){ return 30*d.open_issues_count; })
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("fill", "white")
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("dx", function(d,i){ return 30*d.forks_count; })
.attr("dy", function(d,i){ return 30*d.open_issues_count; })
.text(function(d){
if (d.name.indexOf("challenge") != -1)
return "C";
else
return "H";
});
});
With the way your code written right now, all the lines will be added first, then all the circles, and finally the texts. SVG will always put elements added later on top. So to achieve what you want, you will need to group them up. To do this, you will need to add a g element for each element of your data
var element = svg.selectAll(".element")
.data(data)
.enter()
.append("g")
.attr("class","element");
Now you can add the line, circle, and text to it
element.append("line")
.attr("x1", 5)
.attr("y1", 5)
.attr("x2", function (d, i) {
return 30 * d.forks_count;
})
.attr("y2", function (d, i) {
return 30 * d.open_issues_count;
})
.attr("stroke-width", 2)
.attr("stroke", "black");
element.append("circle")
.attr("r", 30)
.attr("cx", function (d) {
return 30 * d.forks_count;
})
.attr("cy", function (d) {
return 30 * d.open_issues_count;
})
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("fill", "white")
element
.append("text")
.attr("dx", function (d, i) {
return 30 * d.forks_count;
})
.attr("dy", function (d, i) {
return 30 * d.open_issues_count+6;
})
.style("text-anchor", "middle")
.text(function (d) {
if (d.name.indexOf("challenge") != -1) return "C";
else return "H";
});
You can check the updated JSFiddle at http://jsfiddle.net/9tp1yun7/2/
Here is jsfiddle of D3 cluster force layout:
How to achieve 3D look of nodes similar to this picture: (don't pay attention on diagram itself, this is just illustration of "look" of circles)
Here is jsfiddle of the solution. It is based on SVG radial gradients.
For each node, a gradient is defined:
var grads = svg.append("defs").selectAll("radialGradient")
.data(nodes)
.enter()
.append("radialGradient")
.attr("gradientUnits", "objectBoundingBox")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", "100%")
.attr("id", function(d, i) { return "grad" + i; });
grads.append("stop")
.attr("offset", "0%")
.style("stop-color", "white");
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d) { return color(d.cluster); });
Then, instead of line:
.style("fill", function(d) { return color(d.cluster); })
this line is added in the code that creates circles:
.attr("fill", function(d, i) {
return "url(#grad" + i + ")";
})
This produces this effect:(animated gif that I used has some limitations for number of colors, so gradients are not smooth as in real example)
Create linear or radial gradient based on your requirement using different colors. Set fill attribute as gradient.
var gradient = svg.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
gradient.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", "#0c0")
.attr("stop-opacity", 1);
gradient.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", "#c00")
.attr("stop-opacity", 1);
var node = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.style("fill", "url(#gradient)")
.call(force.drag);