In my directional force layout codepen I am using icons rather than circles; currently, the links are overlapping with the image/icon. I want the links to end just before the image. However I see in d3noob's example the link starts/end after the circle, it does not overlap with the circle.
I tweaked the following return statement but not able to fix it properly.
function tick() {
link.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return ( "M" + d.source.x + "," + d.source.y +
"A" + dr + "," + dr + " 0 0,1 " +
d.target.x + "," + d.target.y );
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
The mathematical way to avoid this, without playing with the elements' order, is computing an offset, so the links start and end at the nodes' edges.
In your case, we first get the angle between the two nodes:
var angle = Math.atan2(dx, dy);
Then we compute the offset:
var offsetX = radius * Math.cos(angle);
var offsetY = radius * Math.sin(angle);
And finally use that value in the d attribute:
return ("M" + (d.source.x + offsetX) + "," + (d.source.y + offsetY) +
"A" + dr + "," + dr + " 0 0,1 " + (d.target.x - offsetX) +
"," + (d.target.y - offsetY)
);
Here is the code with that change:
var width = 500,
height = 400,
radius = 8;
var fill = d3.scale.category20();
var links = [{ source: "FH", target: "TP" }];
var nodes = [
{ id: "FH", x: 100, y: 110 },
{ id: "TP", x: 200, y: 110 },
{ id: "GW", x: 200, y: 110 },
{ id: "DB", x: 100, y: 110 }
]
var map = {}
nodes.forEach(function(d,i){
map[d.id] = i;
})
links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
var force = d3.layout
.force()
.size([width, height])
.nodes(nodes)
.links(links)
.linkDistance(150)
.charge(-500)
.on("tick", tick);
var svg = d3
.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var arrows = svg
.append("svg:defs")
.selectAll("marker")
.data(["arrow"])
.enter()
.append("marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
svg
.append("rect")
.attr("width", width)
.attr("height", height);
var nodes = force.nodes(),
links = force.links(),
node = svg.selectAll(".node"),
link = svg.selectAll(".link");
restart();
function tick() {
link.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y;
var angle = Math.atan2(dy, dx);
var offsetX = radius * Math.cos(angle);
var offsetY = radius * Math.sin(angle);
dr = Math.sqrt(dx * dx + dy * dy);
return (
"M" +
(d.source.x + offsetX) +
"," +
(d.source.y + offsetY) +
"A" +
dr +
"," +
dr +
" 0 0,1 " +
(d.target.x - offsetX) +
"," +
(d.target.y - offsetY)
);
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
function restart() {
link = link.data(links);
link
.enter()
.append("path")
.attr("class", "link")
.attr("marker-end", "url(#arrow)");
link.exit().remove();
node = node.data(nodes);
node
.enter()
.insert("g")
.attr("class", "node")
.call(force.drag);
node
.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);
node
.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.id;
});
node.exit().remove();
force.start();
}
#nodeConsole {
width: 80%;
height: 1px;
font-family: courier new;
padding: 1px;
border: 3px solid gray;
margin-top: 1px;
overflow: autao;
}
#linkedNodes {
width: 80%;
font-family: courier new;
padding: 10px;
}
#srcNodes {
width: 40%;
font-family: courier new;
padding: 8px;
}
#targetNodes {
width: 40%;
font-family: courier new;
padding: 8px;
}
rect {
fill: none;
pointer-events: all;
}
.node {
fill: #000;
}
.cursor {
fill: none;
stroke: brown;
pointer-events: none;
}
.link {
stroke: #999;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
In the restart() function draw the links before the nodes and use an image that has no internal transparency.
function restart() {
link = link.data(links);
link
.enter()
.append("path")
.attr("class", "link")
.attr("marker-end", "url(#arrow)");
link.exit().remove();
node = node.data(nodes);
node
.enter()
.insert("g")
.attr("class", "node")
.call(force.drag);
node
.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);
node
.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.id;
});
node.exit().remove();
force.start();
}
Related
I am trying to add a context menu to the static nodes in this jsfiddle as given in jakosz's example.
On right-clicking the nodes, it does show the menu but at the left top of the screen, I am not able to figure out how to pass the node co-ordinates so the context menu appears around the node rather than somewhere else.
copying the same jsfiddle in the post.
.node {
fill: #000;
}
.cursor {
fill: green;
stroke: brown;
pointer-events: none;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
circle {
fill: green;
stroke: red;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960, height = 500;
var links = [{source:"simulator",target:"monitor" ,type:"resolved"} , {source:"web",target:"monitor" ,type:"resolved"} ];
var nodes = [ {"id":"monitor", "grp":"system"}, {"id":"simulator", "grp":"system"}, {id:"web", grp:"client"}];
function reset() {
}
function contextMenu() {
var height,
width,
margin = 0.1, // fraction of width
items = [],
rescale = false,
style = {
'rect': {
'mouseout': {
'fill': 'rgb(244,244,244)',
'stroke': 'white',
'stroke-width': '1px'
},
'mouseover': {
'fill': 'rgb(200,200,200)'
}
},
'text': {
'fill': 'steelblue',
'font-size': '13'
}
};
function menu(x, y) {
d3.select('.context-menu').remove();
scaleItems();
// Draw the menu
d3.select('svg')
.append('g').attr('class', 'context-menu')
.selectAll('tmp')
.data(items).enter()
.append('g').attr('class', 'menu-entry')
.style({'cursor': 'pointer'})
.on('mouseover', function(){
d3.select(this).select('rect').style(style.rect.mouseover) })
.on('mouseout', function(){
d3.select(this).select('rect').style(style.rect.mouseout) });
d3.selectAll('.menu-entry')
.append('rect')
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('width', width)
.attr('height', height)
.style(style.rect.mouseout);
d3.selectAll('.menu-entry')
.append('text')
.text(function(d){ return d; })
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('dy', height - margin / 2)
.attr('dx', margin)
.style(style.text);
// Other interactions
d3.select('body')
.on('click', function() {
d3.select('.context-menu').remove();
});
}
menu.items = function(e) {
if (!arguments.length) return items;
for (i in arguments) items.push(arguments[i]);
rescale = true;
return menu;
}
// Automatically set width, height, and margin;
function scaleItems() {
if (rescale) {
d3.select('svg').selectAll('tmp')
.data(items).enter()
.append('text')
.text(function(d){ return d; })
.style(style.text)
.attr('x', -1000)
.attr('y', -1000)
.attr('class', 'tmp');
var z = d3.selectAll('.tmp')[0]
.map(function(x){ return x.getBBox(); });
width = d3.max(z.map(function(x){ return x.width; }));
margin = margin * width;
width = width + 2 * margin;
height = d3.max(z.map(function(x){ return x.height + margin / 2; }));
// cleanup
d3.selectAll('.tmp').remove();
rescale = false;
}
}
return menu;
}
var width = 900,
height = 600,
radius = 8;
var map = {}
nodes.forEach(function(d,i){
map[d.id] = i;
})
links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(50)
.charge(-200)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var menu = contextMenu().items('first item', 'second option', 'whatever, man');
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag)
.on('contextmenu', function(){
d3.event.preventDefault();
menu(d3.mouse(this)[0], d3.mouse(this)[1]);
});
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.id; });
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
function mousedownNode(d, i) {
nodes.splice(i, 1);
links = links.filter(function(l) {
return l.source !== d && l.target !== d;
});
d3.event.stopPropagation();
refresh();
}
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
</script>
You want to get the position of the parent group, not the circle's position:
menu(d3.mouse(this.parentNode)[0], d3.mouse(this.parentNode)[1]);
Or, the most idiomatic way, just use the SVG as the container:
menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
Here is your code with that change only:
.node {
fill: #000;
}
.cursor {
fill: green;
stroke: brown;
pointer-events: none;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
circle {
fill: green;
stroke: red;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960, height = 500;
var links = [{source:"simulator",target:"monitor" ,type:"resolved"} , {source:"web",target:"monitor" ,type:"resolved"} ];
var nodes = [ {"id":"monitor", "grp":"system"}, {"id":"simulator", "grp":"system"}, {id:"web", grp:"client"}];
function reset() {
}
function contextMenu() {
var height,
width,
margin = 0.1, // fraction of width
items = [],
rescale = false,
style = {
'rect': {
'mouseout': {
'fill': 'rgb(244,244,244)',
'stroke': 'white',
'stroke-width': '1px'
},
'mouseover': {
'fill': 'rgb(200,200,200)'
}
},
'text': {
'fill': 'steelblue',
'font-size': '13'
}
};
function menu(x, y) {
d3.select('.context-menu').remove();
scaleItems();
// Draw the menu
d3.select('svg')
.append('g').attr('class', 'context-menu')
.selectAll('tmp')
.data(items).enter()
.append('g').attr('class', 'menu-entry')
.style({'cursor': 'pointer'})
.on('mouseover', function(){
d3.select(this).select('rect').style(style.rect.mouseover) })
.on('mouseout', function(){
d3.select(this).select('rect').style(style.rect.mouseout) });
d3.selectAll('.menu-entry')
.append('rect')
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('width', width)
.attr('height', height)
.style(style.rect.mouseout);
d3.selectAll('.menu-entry')
.append('text')
.text(function(d){ return d; })
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('dy', height - margin / 2)
.attr('dx', margin)
.style(style.text);
// Other interactions
d3.select('body')
.on('click', function() {
d3.select('.context-menu').remove();
});
}
menu.items = function(e) {
if (!arguments.length) return items;
for (i in arguments) items.push(arguments[i]);
rescale = true;
return menu;
}
// Automatically set width, height, and margin;
function scaleItems() {
if (rescale) {
d3.select('svg').selectAll('tmp')
.data(items).enter()
.append('text')
.text(function(d){ return d; })
.style(style.text)
.attr('x', -1000)
.attr('y', -1000)
.attr('class', 'tmp');
var z = d3.selectAll('.tmp')[0]
.map(function(x){ return x.getBBox(); });
width = d3.max(z.map(function(x){ return x.width; }));
margin = margin * width;
width = width + 2 * margin;
height = d3.max(z.map(function(x){ return x.height + margin / 2; }));
// cleanup
d3.selectAll('.tmp').remove();
rescale = false;
}
}
return menu;
}
var width = 900,
height = 600,
radius = 8;
var map = {}
nodes.forEach(function(d,i){
map[d.id] = i;
})
links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(50)
.charge(-200)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var menu = contextMenu().items('first item', 'second option', 'whatever, man');
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag)
.on('contextmenu', function(){
d3.event.preventDefault();
menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
});
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.id; });
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
function mousedownNode(d, i) {
nodes.splice(i, 1);
links = links.filter(function(l) {
return l.source !== d && l.target !== d;
});
d3.event.stopPropagation();
refresh();
}
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
</script>
I am following this example: http://bl.ocks.org/mbostock/1153292
I have row upon row of JSON data that looks like this:
{"source":"Michael Scott","target":"Jim Halpert","type":"pro"},
{"source":"Jim Halpert","target":"Dwight Schrute","type":"pro"}
Current code to render this data looks like this:
var links = [
{"source":"Michael Scott","target":"Jim Halpert","type":"pro"},
{"source":"Jim Halpert","target":"Dwight Schrute","type":"pro"}
];
var nodes = {};
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});
});
var width = 2000,
height = 1000;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("defs").selectAll("marker")
.data(["pro"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag);
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
</script>
How do I call the data from an external data.json file? I've looked at all the other related SO questions and other D3 examples, but I haven't been able to get anything to work.
I've tried (and then changing all references to links to data):
d3.json("data.json", function(error, data) {
});
Here is that full code:
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
d3.json("data.json", function(error, data) {
});
var nodes = {};
data.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});
});
var width = 2000,
height = 1000;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.data(data)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("defs").selectAll("marker")
.data(["pro"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.data())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag);
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
</script>
This results in the error Uncaught Reference Error: "data" is not defined for this line data.forEach(function(link) {. data.json is located in the same directory as index.html
I've tried various other implementations as well that I picked up from other D3 examples on blocks.org. Any and all insight would be greatly appreciated. Please let me know if there's anything I can do to improve my question!
Here is a working example with your data. You need to do a couple of things to make it work with an external json file.
Make sure you include all your code inside your d3.json call.
Since we're using the variable links to hold all our data, you need to set that equal to data which is returned from the d3.json call.
Check working code below:
var data_url = "https://api.myjson.com/bins/10kk91";
d3.json(data_url, function(error, data){
var links = data; //set links equal to data which is returned from d3.json
var nodes = {};
// Compute the distinct nodes from the links.
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});
});
var width = 960,
height = 500;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag);
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
}); //end d3.json call
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
#licensing {
fill: green;
}
.link.licensing {
stroke: green;
}
.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I am trying to create a chord diagram and have the value of each ribbons source and target value as seen be accessible when they are hovered over. I also want to be able to access the index of that value to refer to use the colour.
At the moment I am getting an error of d not being defined whenever I hover over - I'm not sure how to access the values I need.. I don't want to add any other .js libraries if possible.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.group-tick line {
stroke: #000;
}
.ribbons {
fill-opacity: 0.67;
}
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 14px;
text-align: center;
}
</style>
<svg width="960" height="960"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
outerRadius = Math.min(width, height) * 0.5 - 40,
innerRadius = outerRadius - 30;
var formatValue = d3.formatPrefix(",.0", 1e3);
var chord = d3.chord()
.padAngle(0.05)
.sortSubgroups(d3.descending);
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var ribbon = d3.ribbon()
.radius(innerRadius);
var color = d3.scaleOrdinal()
.domain(d3.range(4))
.range(["#000000", "#FFDD89", "#957244", "#F26223"]);
var g = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.datum(chord(matrix));
var group = g.append("g")
.attr("class", "groups")
.selectAll("g")
.data(function(chords) { return chords.groups; })
.enter().append("g");
group.append("path")
.style("fill", function(d) { return color(d.index); })
.style("stroke", function(d) { return d3.rgb(color(d.index)).darker(); })
.attr("d", arc);
var groupTick = group.selectAll(".group-tick")
.data(function(d) { return groupTicks(d, 1e3); })
.enter().append("g")
.attr("class", "group-tick")
.attr("transform", function(d) { return "rotate(" + (d.angle * 180 / Math.PI - 90) + ") translate(" + outerRadius + ",0)"; });
groupTick.append("line")
.attr("x2", 6);
groupTick
.filter(function(d) { return d.value % 5e3 === 0; })
.append("text")
.attr("x", 8)
.attr("dy", ".35em")
.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180) translate(-16)" : null; })
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return formatValue(d.value); });
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
g.append("g")
.attr("class", "ribbons")
.selectAll("path")
.data(function(chords) { return chords; })
.enter().append("path")
.attr("d", ribbon)
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); })
.on("mouseover", function(d) {
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html(function(d) {
return (d.target.index) + "Hello";
});
})
.on("mouseout", function(d){
tooltip
.style("display", "none")
})
;
// Returns an array of tick angles and values for a given group and step.
function groupTicks(d, step) {
var k = (d.endAngle - d.startAngle) / d.value;
return d3.range(0, d.value, step).map(function(value) {
return {value: value, angle: value * k + d.startAngle};
});
}
</script>
I'm using the tutorial here: https://bl.ocks.org/mbostock/4062006
The issue is that there is no datum associated with your tooltip:
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
So when you say tooltip.html(function(d) { ... there is no datum associated with that element to use.
Instead, try to use the datum associated with the selected chord:
.on("mouseover", function(d) { // the datum you want
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html(d.target.index + "Hello");
})
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.group-tick line {
stroke: #000;
}
.ribbons {
fill-opacity: 0.67;
}
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 14px;
text-align: center;
}
</style>
<svg width="960" height="960"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
outerRadius = Math.min(width, height) * 0.5 - 40,
innerRadius = outerRadius - 30;
var formatValue = d3.formatPrefix(",.0", 1e3);
var chord = d3.chord()
.padAngle(0.05)
.sortSubgroups(d3.descending);
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var ribbon = d3.ribbon()
.radius(innerRadius);
var color = d3.scaleOrdinal()
.domain(d3.range(4))
.range(["#000000", "#FFDD89", "#957244", "#F26223"]);
var g = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.datum(chord(matrix));
var group = g.append("g")
.attr("class", "groups")
.selectAll("g")
.data(function(chords) { return chords.groups; })
.enter().append("g");
group.append("path")
.style("fill", function(d) { return color(d.index); })
.style("stroke", function(d) { return d3.rgb(color(d.index)).darker(); })
.attr("d", arc);
var groupTick = group.selectAll(".group-tick")
.data(function(d) { return groupTicks(d, 1e3); })
.enter().append("g")
.attr("class", "group-tick")
.attr("transform", function(d) { return "rotate(" + (d.angle * 180 / Math.PI - 90) + ") translate(" + outerRadius + ",0)"; });
groupTick.append("line")
.attr("x2", 6);
groupTick
.filter(function(d) { return d.value % 5e3 === 0; })
.append("text")
.attr("x", 8)
.attr("dy", ".35em")
.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180) translate(-16)" : null; })
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return formatValue(d.value); });
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
g.append("g")
.attr("class", "ribbons")
.selectAll("path")
.data(function(chords) { return chords; })
.enter().append("path")
.attr("d", ribbon)
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); })
.on("mouseover", function(d) {
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html(d.target.index + "Hello");
})
.on("mouseout", function(d){
tooltip
.style("display", "none")
})
;
// Returns an array of tick angles and values for a given group and step.
function groupTicks(d, step) {
var k = (d.endAngle - d.startAngle) / d.value;
return d3.range(0, d.value, step).map(function(value) {
return {value: value, angle: value * k + d.startAngle};
});
}
</script>
I'm trying to use this example http://bl.ocks.org/eesur/be2abfb3155a38be4de4 in my own project. I'm trying to generalize the code block to use. I changed the code a little but now it's not generating similar graph in example. You can see my attempt in jsfiddle https://jsfiddle.net/wykbbvzf/1/
var SuperHeroes = function(selector, w, h) {
this.w = w;
this.h = h;
d3.select(selector).selectAll("svg").remove();
this.svg = d3.select(selector).append("svg:svg")
.attr('width', w)
.attr('height', h);
this.svg.append("svg:rect")
.style("stroke", "#999")
.style("fill", "#fff")
.attr('width', w)
.attr('height', h);
this.force = d3.layout.force()
.charge(function(d) { return d._children ? -d.size / 100 : -40; })
.linkDistance(function(d) { return d.target._children ? 80 : 25; })
.size([h, w]);
};
// some colour variables
var tcBlack = "#130C0E";
// rest of vars
var maxNodeSize = 50,
x_browser = 20,
y_browser = 25;
/*
d3.json("marvel.json", function(json) {
// Build the path
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.update();
});
*/
/**
*
*/
SuperHeroes.prototype.update = function(json) {
this.root = json;
this.root.fixed = true;
this.root.x = w / 2;
this.root.y = h / 4;
var nodes = this.flatten(this.root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
this.force.nodes(nodes)
.links(links)
.gravity(0.05)
.charge(-1500)
.linkDistance(100)
.friction(0.5)
.linkStrength(function(l, i) {return 1; })
.size([w, h])
.on("tick", tick)
.start();
var path = this.svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
path.enter().insert("svg:path")
.attr("class", "link")
// .attr("marker-end", "url(#end)")
.style("stroke", "#eee");
// Exit any old paths.
path.exit().remove();
// Update the nodes…
var node = this.svg.selectAll("g.node")
.data(nodes, function(d) { return d.id; });
// Enter any new nodes.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", this.click)
.call(this.force.drag);
// Append a circle
nodeEnter.append("svg:circle")
.attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
.style("fill", "#eee");
// Append images
var images = nodeEnter.append("svg:image")
.attr("xlink:href", function(d) { return d.img;})
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
// make the image grow a little on mouse over and add the text details on click
var setEvents = images
// Append hero text
.on( 'click', function (d) {
d3.select("h1").html(d.hero);
d3.select("h2").html(d.name);
d3.select("h3").html ("Take me to " + "<a href='" + d.link + "' >" + d.hero + " web page ⇢"+ "</a>" );
})
.on( 'mouseenter', function() {
// select element in current context
d3.select( this )
.transition()
.attr("x", function(d) { return -60;})
.attr("y", function(d) { return -60;})
.attr("height", 100)
.attr("width", 100);
})
// set back
.on( 'mouseleave', function() {
d3.select( this )
.transition()
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
});
// Append hero name on roll over next to the node as well
nodeEnter.append("text")
.attr("class", "nodetext")
.attr("x", x_browser)
.attr("y", y_browser +15)
.attr("fill", tcBlack)
.text(function(d) { return d.hero; });
// Exit any old nodes.
node.exit().remove();
// Re-select for update.
path = this.svg.selectAll("path.link");
node = this.svg.selectAll("g.node");
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + ","
+ d.source.y
+ "A" + dr + ","
+ dr + " 0 0,1 "
+ d.target.x + ","
+ d.target.y;
});
node.attr("transform", this.nodeTransform);
}
}
/**
* Gives the coordinates of the border for keeping the nodes inside a frame
* http://bl.ocks.org/mbostock/1129492
*/
SuperHeroes.prototype.nodeTransform = function(d) {
d.x = Math.max(maxNodeSize, Math.min(w - (d.imgwidth/2 || 16), d.x));
d.y = Math.max(maxNodeSize, Math.min(h - (d.imgheight/2 || 16), d.y));
return "translate(" + d.x + "," + d.y + ")";
}
/**
* Toggle children on click.
*/
SuperHeroes.prototype.click = function(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
this.update();
}
/**
* Returns a list of all nodes under the root.
*/
SuperHeroes.prototype.flatten = function(root) {
var nodes = [];
var i = 0;
function recurse(node) {
if (node.children)
node.children.forEach(recurse);
if (!node.id)
node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
SuperHeroes.prototype.cleanup = function() {
this.update([]);
this.force.stop();
};
var currentSuperHereos;
var createSuperHeroes = function(json) {
// remove previous flower to save memory
if (currentSuperHereos) currentSuperHereos.cleanup();
// adapt layout size to the total number of elements
var total = 5;
w = parseInt(Math.sqrt(total) * 30, 10);
h = parseInt(Math.sqrt(total) * 30, 10);
if (h < 300) h = 300;
if (w < 300) w = 300;
// create a new SuperHeroes
currentSuperHereos = new SuperHeroes("#visualization", w, h).update(json);
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
};
createSuperHeroes(JSON.parse('{"name":"MAlkara","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","children":[{"hero":"Kesan","name":"Keşan","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","additionalProperties":{}}],"additionalProperties":{}}'));
Do you have any idea about my mistakes?
You will just need to update node transform attribute inside tick function. this.nodeTransform is not seems to be defined in your code. So the tick function should be as shown below.
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + ","
+ d.source.y
+ "A" + dr + ","
+ dr + " 0 0,1 "
+ d.target.x + ","
+ d.target.y;
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
EDIT: Adding link labels
path.enter().insert("svg:path")
.attr("class", "link")
.style("stroke", "#eee")
.attr("id",function(d,i){ return "linkId_"+i; });
path.enter().append("g").attr("class", "linklabelholder")
.append("text")
.attr("class", "linklabel")
.style("font-size", "13px")
.attr("text-anchor", "middle")
.style("fill","#000")
.append("textPath")
.style('text-anchor', 'middle')
.attr('startOffset', '50%')
.attr("xlink:href",function(d,i) { return "#linkId_" + i;})
.text(function(d) {
return "my text"; //Can be dynamic via d object
});
var SuperHeroes = function(selector, w, h) {
this.w = w;
this.h = h;
d3.select(selector).selectAll("svg").remove();
this.svg = d3.select(selector).append("svg:svg")
.attr('width', w)
.attr('height', h);
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.svg.append("svg:rect")
.style("stroke", "#999")
.style("fill", "#fff")
.attr('width', w)
.attr('height', h);
this.force = d3.layout.force()
.charge(function(d) {
return d._children ? -d.size / 100 : -40;
})
.linkDistance(function(d) {
return d.target._children ? 80 : 25;
})
.size([h, w]);
};
// some colour variables
var tcBlack = "#130C0E";
// rest of vars
var maxNodeSize = 50,
x_browser = 20,
y_browser = 25;
/*
d3.json("marvel.json", function(json) {
// Build the path
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.update();
});
*/
/**
*
*/
SuperHeroes.prototype.update = function(json) {
this.root = json;
this.root.fixed = true;
this.root.x = w / 2;
this.root.y = h / 4;
var nodes = this.flatten(this.root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
this.force.nodes(nodes)
.links(links)
.gravity(0.05)
.charge(-1500)
.linkDistance(100)
.friction(0.5)
.linkStrength(function(l, i) {
return 1;
})
.size([w, h])
.on("tick", tick)
.start();
var path = this.svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
path.enter().insert("svg:path")
.attr("class", "link")
.style("stroke", "#eee")
.attr("id",function(d,i){ return "linkId_"+i; });
path.enter().append("g").attr("class", "linklabelholder")
.append("text")
.attr("class", "linklabel")
.style("font-size", "13px")
.attr("text-anchor", "middle")
.style("fill","#000")
.append("textPath")
.style('text-anchor', 'middle')
.attr('startOffset', '50%')
.attr("xlink:href",function(d,i) { return "#linkId_" + i;})
.text(function(d) {
return "my text"; //Can be dynamic via d object
});
// Exit any old paths.
path.exit().remove();
// Update the nodes…
var node = this.svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id;
});
// Enter any new nodes.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.on("click", this.click)
.call(this.force.drag);
// Append a circle
nodeEnter.append("svg:circle")
.attr("r", function(d) {
return Math.sqrt(d.size) / 10 || 4.5;
})
.style("fill", "#eee");
// Append images
var images = nodeEnter.append("svg:image")
.attr("xlink:href", function(d) {
return d.img;
})
.attr("x", function(d) {
return -25;
})
.attr("y", function(d) {
return -25;
})
.attr("height", 50)
.attr("width", 50);
// make the image grow a little on mouse over and add the text details on click
var setEvents = images
// Append hero text
.on('click', function(d) {
d3.select("h1").html(d.hero);
d3.select("h2").html(d.name);
d3.select("h3").html("Take me to " + "<a href='" + d.link + "' >" + d.hero + " web page ⇢" + "</a>");
})
.on('mouseenter', function() {
// select element in current context
d3.select(this)
.transition()
.attr("x", function(d) {
return -60;
})
.attr("y", function(d) {
return -60;
})
.attr("height", 100)
.attr("width", 100);
})
// set back
.on('mouseleave', function() {
d3.select(this)
.transition()
.attr("x", function(d) {
return -25;
})
.attr("y", function(d) {
return -25;
})
.attr("height", 50)
.attr("width", 50);
});
// Append hero name on roll over next to the node as well
nodeEnter.append("text")
.attr("class", "nodetext")
.attr("x", x_browser)
.attr("y", y_browser + 15)
.attr("fill", tcBlack)
.text(function(d) {
return d.hero;
});
// Exit any old nodes.
node.exit().remove();
// Re-select for update.
path = this.svg.selectAll("path.link");
node = this.svg.selectAll("g.node");
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
}
/**
* Gives the coordinates of the border for keeping the nodes inside a frame
* http://bl.ocks.org/mbostock/1129492
*/
SuperHeroes.prototype.nodeTransform = function(d) {
d.x = Math.max(maxNodeSize, Math.min(w - (d.imgwidth / 2 || 16), d.x));
d.y = Math.max(maxNodeSize, Math.min(h - (d.imgheight / 2 || 16), d.y));
return "translate(" + d.x + "," + d.y + ")";
}
/**
* Toggle children on click.
*/
SuperHeroes.prototype.click = function(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
/**
* Returns a list of all nodes under the root.
*/
SuperHeroes.prototype.flatten = function(root) {
var nodes = [];
var i = 0;
function recurse(node) {
if (node.children)
node.children.forEach(recurse);
if (!node.id)
node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
SuperHeroes.prototype.cleanup = function() {
this.update([]);
this.force.stop();
};
var currentSuperHereos;
var createSuperHeroes = function(json) {
// remove previous flower to save memory
if (currentSuperHereos) currentSuperHereos.cleanup();
// adapt layout size to the total number of elements
var total = 5;
w = parseInt(Math.sqrt(total) * 30, 10);
h = parseInt(Math.sqrt(total) * 30, 10);
if (h < 300) h = 300;
if (w < 300) w = 300;
// create a new SuperHeroes
currentSuperHereos = new SuperHeroes("#visualization", w, h).update(json);
};
createSuperHeroes(JSON.parse('{"name":"MAlkara","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","children":[{"hero":"Kesan","name":"Keşan","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","additionalProperties":{}}],"additionalProperties":{}}'));
body {
font-family: "Source Code Pro", Consolas, monaco, monospace;
line-height: 160%;
font-size: 16px;
margin: 0;
}
path.link {
fill: none;
stroke-width: 2px;
}
.node:not(:hover) .nodetext {
display: none;
}
h1 {
font-size: 36px;
margin: 10px 0;
text-transform: uppercase;
font-weight: normal;
}
h2,
h3 {
font-size: 18px;
margin: 5px 0;
font-weight: normal;
}
header {
padding: 20px;
position: absolute;
top: 0;
left: 0;
}
a:link {
color: #EE3124;
text-decoration: none;
}
a:visited {
color: #EE3124;
}
a:hover {
color: #A4CD39;
text-decoration: underline;
}
a:active {
color: #EE3124;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="visualization"></div>
I am having trouble getting a two-ring donut chart's labels to line up correctly. I think it has something to do with appending the labels to the gs var instead of the path var but if I make that switch, the labels are not visible at all. In the end, I would like each label to be centered on the angle and radius of each slice.
Code is here:
<html>
<body>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 960px;
}
text {
font: 10px sans-serif;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
</style>
<svg class="chart"></svg>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var dataset = {
bip: [.2, .1, .3, .05, .05, .2],
position: [0, 25, 35, 25, 15, 0]
};
var width = 450,
height = 450,
cwidth = 250;
var color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null)
.startAngle(Math.PI * -.25)
.endAngle(Math.PI * .25)
;
var arc = d3.svg.arc();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black")
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height + ")")
;
var gs = svg.selectAll("g")
.data(d3.values(dataset))
.enter()
.append("g");
var path = gs.selectAll("path")
.data(function(d) { return pie(d); })
.enter()
.append("path")
.attr("fill", function(d,i,j) {
return "rgb(" + 255*(1-j) + "," + (166 + d3.round(89*d.value,0))*(1-j) + "," + d3.round(255*d.value,0)*(1-j) + ")" ;
})
.attr("d", function(d, i, j) {
return arc.innerRadius(cwidth*j).outerRadius(cwidth-5+50*j) (d);
})
.style('stroke', 'white')
.style('stroke-width', 5)
;
//Labels
gs.append("svg:text")
.attr("transform", function(d, i, j) {
d.startAngle = (Math.PI * -.75 + Math.PI*i/6);
d.endAngle = (Math.PI * .25 + Math.PI*i/6);
d.innerRadius = cwidth*j;
d.outerRadius = cwidth-5+50*j;
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.style("fill", "white")
.style("font", "bold 12px Arial")
.text(function(d, i, j) { return d; });
</script>
</body>
</html>
Example is here:
http://jsfiddle.net/sgrossman/kzh7c8mn/
Appreciate the help.
I think that you are right about the problem being attaching to the gs vs. the path var.
I have a fiddle here that is working a bit like you want. It creates text and textPaths per arc element and links them via an ID. Centering is not perfect but could be tuned through trial and error.
//Add an id to the path
.attr("id", function(d, i, j){return 'path_' + i + '_'+ j;})
Add a svg:text and textPath per data point:
gs.selectAll("g").data(function (d, i, j) {
return d;
})
.enter().append('svg:text')
.attr("dx", function(d,i,j){
if(j==1) {return (d * 2) + 8;}
else {return (d * 250) - 15;}
})
.attr("dy", 25)
.append('textPath')
.attr("stroke","white")
//Link via xlink:href
.attr("xlink:href",function(d,i,j){
return '#path_' + i + '_' + j;
})
.text(function (d, i, j) {
return d;
});
JSFiddle